一篇文章让你搞懂字符指针,数组指针,指针数组,数组传参和指针传参,函数指针

您所在的位置:网站首页 指针数组用法 一篇文章让你搞懂字符指针,数组指针,指针数组,数组传参和指针传参,函数指针

一篇文章让你搞懂字符指针,数组指针,指针数组,数组传参和指针传参,函数指针

#一篇文章让你搞懂字符指针,数组指针,指针数组,数组传参和指针传参,函数指针| 来源: 网络整理| 查看: 265

在这里插入图片描述

回顾

首先我们来大概回顾一下指针初阶的知识 内存会划分为一个个的内存单元,每个内存单元都有一个独立的编号—编号也被称为地址,地址在C语言中也被称为指针,指针(地址)需要存储起来—存储到变量中,这个变量就被称为指针变量 例如:

int a = 10; int* pa = &a;

指针的大小固定是4/8个字节(32位/64位平台) 地址是物理的电线上产生 电信号转化为数字信号 32位机器—32根地址线—1/0 32个0/1组成的二进制序列,把这个二进制序列就作为地址,32个bit位才能存储这个地址 也就是需要4个字节才能存储 所以指针变量的大小就是4个字节 同理在64位机器上,地址的大小是64个0/1组成的二进制序列,需要64个bit位存储,也就是8个字节,所以指针变量的大小就是8个字节

1 字符指针

在指针的类型中我们知道有一种指针类型为字符指针char* 一般使用

int main() { char ch = 'w'; //ch = 'a'//这里可以理解成办事的方法有很多种,这只是其中一种 char *pc = &ch;//pc就是字符指针 *pc = 'w'; return 0; }

现在有以下代码

int main() { char arr[] = "abcdef"; const char* p = "abcdef";//本质上是把字符串首字符的a地址放在p当中,相当于p指向了字符串首元素a的地址//这个字符串是一个常量字符串,常量字符串不能被修改,所以我们需要加上一个const防止被修改 return 0; }

这里本质上是把字符串首字符的a地址放在p当中,相当于p指向了字符串首元素a的地址//这个字符串是一个常量字符串,常量字符串不能被修改,所以我们需要加上一个const防止被修改,另外注意一下const的用法,const放在的左边限制的是p,const放在*右边限制的p 我们来将上面的代码打印出来看一看效果

int main() { char arr[] = "abcdef"; const char* p = "abcdef";//本质上是把字符串首字符的a地址放在p当中,相当于p指向了字符串首元素a的地址//这个字符串是一个常量字符串,常量字符串不能被修改,所以我们需要加上一个const防止被修改 printf("%s\n", p); printf("%c\n", *p); return 0; }

在这里插入图片描述 在这里插入图片描述 大家思考一个问题(注意结合上述字符指针的知识),看下面代码

int main() { const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗? printf("%s\n", pstr); return 0; }

代码 const char* pstr = “hello bit.”,本质是把字符串 hello bit. 首字符的地址放到了pstr中 在这里插入图片描述 上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量pstr中 掌握了字符指针的基本知识过后我们来看一道笔试题,看下面代码

#include int main() { char str1[] = "hello bit."; char str2[] = "hello bit."; const char *str3 = "hello bit."; const char *str4 = "hello bit."; if(str1 ==str2) printf("str1 and str2 are same\n"); else printf("str1 and str2 are not same\n"); if(str3 ==str4) printf("str3 and str4 are same\n"); else printf("str3 and str4 are not same\n"); return 0; }

在这里插入图片描述 在这里插入图片描述

2 指针数组

整型数组—存放整型的数组 字符数组—存放字符的数组 指针数组—存放指针的数组 这里我们再复习一下,下面的指针数组分别是什么意思

int* arr1[10]; //整形指针的数组 char *arr2[4]; //一级字符指针的数组 char **arr3[5];//二级字符指针的数组

我们来使用指针数组模拟实现一个二维数组

int main(int argc, char* argv[], char* envy[]) { int arr1[5] = { 1,2,3,4,5 }; int arr2[5] = { 2,3,4,5,6 }; int arr3[5] = { 3,4,5,6,7 }; int* arr[3] = { arr1,arr2,arr3 }; int i = 0; for (i = 0; i printf("%d ", *(*(arr + i) + j)); Sleep(1000); } printf("\n"); } system("pause"); return 0; }

这里的*(*(arr + i) + j)意思是,arr这个指针变量里面存放的是首元素的地址,也就是arr1数组名第一个元素的地址,然后+依次遍历二维数组,我这里加的一个Sleep(1000)的意思是停留1秒钟打印一个数组 这不是真正的二维数组,真正的二维数组在内存中是连续存放的

3 数组指针 3.1 数组指针的定义

数组指针 类比: 整型指针—指向整型变量的指针,存放整型变量的地址的指针变量 字符指针—指向字符变量的指针,存放字符变量的地址的指针变量 数组指针—指向数组的指针,存放的是数组的地址的指针变量 我们已经熟悉: 整形指针: int * pint; 能够指向整形数据的指针 浮点型指针: float * pf; 能够指向浮点型数据的指针 那数组指针应该是:能够指向数组的指针 下面代码哪个是数组指针?

int *p1[10]; int (*p2)[10]; //p1, p2分别是什么?

解释

int (*p)[10]; //解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指 向一个数组,叫数组指针。 //这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。 3.2 &数组名VS数组名

这里我们把数组名的理解讲一下 数组名是数组首元素的地址

int main() { int arr[10] = { 0 }; printf("%p\n", arr); printf("%p\n", &arr[0]); return 0; }

但是数组名表示首元素的地址有两个例外 1.sizeof(数组名),这里的数组名表示的不是数组首元素的地址,数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节 2.&数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址 除此之外,所有的其他地方的数组名都是数组的首元素的地址 这里有一个现象,大家看下面代码

int main() { int arr[10] = { 0 }; printf("%p\n", arr); printf("%p\n", &arr[0]); printf("%p\n", &arr); return 0; }

这里打印的地址都是一样的,但是本质上真的是一样的吗? 我们将代码调试起来看一看 在这里插入图片描述 这里虽然我们看到的地址是一样的,但是他们的类型有所差距 这里取地址arr的类型应该是int(*)【10】 正因为他们的类型不同 现在我们来看下面代码

int main() { int arr[10] = { 0 }; printf("%p\n", arr); printf("%p\n", arr + 1); printf("%p\n", &arr[0]); printf("%p\n", &arr[0] + 1); printf("%p\n", &arr); printf("%p\n", &arr + 1); return 0; }

你拷贝这串代码编译运行会发现各自加1的效果产生了不同的效果 在这里插入图片描述 原因是指针类型的不同带来的不同效果 对于数组名来说 &数组名得到的是数组的地址

3.3 数组指针的使用 int main() { int arr[10] = { 0 }; //数组的地址存放到数组指针变量 //int[10]*p=&arr;//error int(*p)[10] = &arr; //int*p1=&arr; return 0; }

这里的int*p1=&arr可能会报警告,因为指针类型不符合,上面我们给大家介绍了&arr的指针类型是int(*p1)[10] 那么数组指针怎么使用呢?一般放在二维数组上才方便

#include void print_arr1(int arr[3][5], int row, int col) { int i = 0; for(i=0; i printf("%d ", arr[i][j]); } printf("\n"); } } void print_arr2(int (*arr)[5], int row, int col) { int i = 0; for(i=0; i printf("%d ", arr[i][j]); } printf("\n"); } } int main() { int arr[3][5] = {1,2,3,4,5,6,7,8,9,10}; print_arr1(arr, 3, 5); //数组名arr,表示首元素的地址 //但是二维数组的首元素是二维数组的第一行 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址 //可以数组指针来接收 print_arr2(arr, 3, 5); return 0; }

看下面代码

//二维数组传参,形参是指针的形式 void Print(int (*p)[5], int r, int c) { int i = 0; for (i = 0; i printf("%d ", *(*(p + i) + j)); } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 }; //arr 是数组名,数组名表示数组首元素的地址 Print(arr, 3, 5); return 0; }

这里给大家解释一下*(*p+i)+j),加i相当于拿到某一行的地址,然后加j在进行遍历 二维数组的每一行可以理解为二维数组的一个元素,每一行又是一个一维数组,所以二维数组其实是一维数组的数组 二维数组的数组名也是数组名,数组名就是首元素的地址,arr—首元素的地址—第一行的地址—一维数组的地址—数组的地址 在这里插入图片描述 一维数组传参,形参的部分可以是数组,也可以是指针 我们看下面代码

//void test1(int arr[5], int sz) //{} //void test2(int* p, int sz) //{} // //int main() //{ // int arr[5] = { 0 }; // test1(arr, 5); // test2(arr, 5); // return 0; //}

形参部分可以写成数组,但是本质上是一个指针,所以建议用一个指针来接收 二维数组传参。形参部分可以是数组,也可以是指针

//void test3(char arr[3][5], int r, int c) //{} // //void test4(char (*p)[5], int r, int c) //{} //int main() //{ // char arr[3][5] = {0}; // test3(arr, 3, 5); // test4(arr, 3, 5); // // return 0; //} //

数组指针—是指针—是指向数组的指针 指针数组—是数组—是存放指针的数组 大家可以用好孩子这个例子去理解一下

4数组传参 指针传参

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

4.1 一维数组传参 #include void test(int arr[])//ok? {} void test(int arr[10])//ok? {} void test(int *arr)//ok? {} void test2(int *arr[20])//ok? {} void test2(int **arr)//ok? {} int main() { int arr[10] = {0}; int *arr2[20] = {0}; test(arr); test2(arr2); }

第一个OK,数组传参用一个数组接收没毛病 第二个OK,只是把元素个数写出来了 第三种OK,用一个指针接收 第四种OK,一个数组20个元素,每个元素为int*,用int*arr[20]接收没毛病 第五种OK,一级指针的地址用一个二级指针来接收,所以也没毛病

4.2 二维数组传参 void test(int arr[3][5])//ok? {} void test(int arr[][])//ok? {} void test(int arr[][5])//ok? {} //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 void test(int *arr)//ok? {} void test(int* arr[5])//ok? {} void test(int (*arr)[5])//ok? {} void test(int **arr)//ok? {} int main() { int arr[3][5] = {0}; test(arr); }

第一种OK 形参部分,行可以省略,但是列不能省略 总结:二维数组传参,函数形参的设计只能省略第一个[]的数字 因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素 这样才方便运算 所以第二种不OK,第三种OK 第四种,数组名是首元素的地址—二维数组是一行的地址,绝对不是某一个的地址,所以错误 第五种不行,因为我传过去的是数组名,这里的形参只是个指针数组,那肯定不行,要么写成数组指针或者二维数组 第六种OK,是一个数组指针,可以指向一行 第七种错误,数组名表示首元素的地址,第一行的地址怎么可能会用二级指针来接收呢,肯定不对!

4.3 一级指针传参 #include void print(int *p, int sz) { int i = 0; for(i=0; i int arr[10] = {1,2,3,4,5,6,7,8,9}; int *p = arr; int sz = sizeof(arr)/sizeof(arr[0]); //一级指针p,传给函数 print(p, sz); return 0; }

大家自行理解一下,比较简单 这里大家思考一个问题 当一个函数的参数部分为一级指针的时候,函数能接收什么参数? 比如

void test1(int *p) {} //test1函数能接收什么参数? void test2(char* p) {} //test2函数能接收什么参数?

在这里插入图片描述

4.4 二级指针传参 #include void test(int** ptr) { printf("num = %d\n", **ptr); } int main() { int n = 10; int*p = &n; int **pp = &p; test(pp); test(&p); return 0; }

思考: 当函数的参数为二级指针的时候,可以接收什么参数?

void test(char **p) { } int main() { char c = 'b'; char*pc = &c; char**ppc = &pc; char* arr[10]; test(&pc); test(ppc); test(arr);//Ok? return 0; }

在这里插入图片描述

5函数指针

函数指针—指向函数的指针 数组指针—指向数组的指针 举个例子,看代码

//int Add(int x, int y) //{ // return x + y; //} // //int main() //{ // int arr[10] = {0}; // int (*pa)[10] = &arr; // //printf("%p\n", &Add); // //printf("%p\n", Add);

结果是一模一样的,你们可以在自己的编译器上面去试一试 函数名是函数的地址 &函数名也是函数的地址

//int Add(int x, int y) //{ // return x + y; //} // int (*pf)(int, int) = &Add;//pf是函数指针变量 // //int (*)(int, int) 是函数指针类型 // return 0; //}

来看一段代码

#include void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }

输出的结果 在这里插入图片描述 看下面代码

//int Add(int x, int y) //{ // return x + y; //} // //int main() //{ // //int (*pf)(int, int) = &Add; // int (*pf)(int, int) = Add; // // int r = Add(3, 5); // printf("%d\n", r); // // int m = (*pf)(4, 5); // // printf("%d\n", m); // // return 0; //}

注意*要先和pf结合称为一个指针,再和参数类型结合组合称为函数指针 来看看下面两段有趣的代码 我们先看第一个

//代码1 (* (void (*)())0 )();

很多人看第一眼我去直接懵逼了,这个0是个什么东西,一个函数指针的相关知识搞这么复杂,其实这行代码是有他的特定意思的,我给大家解释一下 0可以默认为是整型 我可以把0认为是int类型,和x0012ff40没有区别各位 也可以0x0012ff40—是一个以16进制表示的整数 你也可以认为这个东西是地址—地址是编号—可以是整型的地址—Int 举个例子 //void(*p)()—p是函数指针

//void(*)()是函数指针类型 我在0前面加了有个括号 这里是把0强制类型转化为下面这种

void(*)()这种指针类型 所以这行代码的意思是调用0地址处的函数,将0强制类型转化为

void(*)()类型的函数指针 然后调用0地址处的这个函数 这里面就运用到了函数指针的知识,尝试着去理解一下,这行代码出自于C陷阱与缺陷 来看第二个代码

//代码2 void (*signal(int , void(*)(int)))(int);

signal是一个函数声明 signal函数有两个参数,第一个参数的类型是Int,第二个参数类型是void(*)(int)函数指针类型 该函数指针指向的函数有一个Int类型的参数,返回类型为void

signal函数的返回类型也是void(*)(int)函数指针类型,该函数指针指向的函数有一个int类型的参数,返回类型为void 这个代码有点复杂,我们稍作修改 我们需要了解一下typedef这个函数—类型重命名

// typedef void(*pf_t)(int); // pf_t signal(int, pf_t); // void (* signal(int, void(*)(int) ) )(int); //


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3