前言
我们在C语言基础中,已经了解并认识到了指针的概念:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。接下来我们来认识不一样的指针,更有深度的指针。
一、字符指针
字符指针,顾名思义是接收字符类型的指针,书写方式为:char *
一般使用:
//字符指针 int main() { char a = 'w'; char* p = &a; printf("%c\n", *p); return 0; }
还有一种使用方法:
int main() { //char a = "abcde";//字符类型不能接受字符串 char* p = &a; //字符类型char是 无法直接接收字符串的,而已用字符数组的形式接收,但是字符指针可以直接接收字符串,效果和字符数组类似 char* p = "abcdef";//将字符串这个常量值,给字符指针p 得到的是a的地址 就是字符串首元素的地址 printf("%s\n", p);//输出的时候p是字符串的地址 就相当于字符数组的形式进行输出 return 0; }
第二种使用方法的数据存放方式:
且使用的是常量字符串的时候,最好在 char* 前面加上const,因为本身常量是无法改变的,所以加上const,更加符合编程要求。
面试题(字符指针):
二、指针数组
顾名思义,指针数组,就是存放指针的数组,这样的数组是要有统一的存储指针的类型的。
如:
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5]; //二级字符指针的数组
//指针数组 int main() { //存放字符指针的数组 const char* arr[3] = { "hello","bit","why" }; for (int i = 0; i < 3; i++) { printf("%s\n", arr[i]); } return 0; } int main() { int arr1[5] = { 1,2,3,4,5 }; int arr2[5] = { 2,3,4,5,6 }; int* arr[4] = { arr1,arr2 }; //模拟二维数组 for (int i = 0; i < 2; i++) { for (int j = 0; j < 5; j++) { printf("%d", arr[i][j]); }// 或者是 *(arr[i]+j) printf("\n"); } return 0; }
三、数组指针
3.1数组指针定义
数组指针,其实是一个指针。
整型指针:int * pi 能够指向整形数据的指针。
字符指针:char *pc 能够指向字符数据的指针。
那么:
数组指针: int (*pi) [10] 能够指向数组的指针
int* pa [10]; //这个是指针数组 int (*pa)[10]; //这个才是数组指针 //因为 [] 的优先级比 * 大,所以要加括号
对 int (*pa)[10] 进行解释:
3.2.&数组名和数组名
&数组名和数组名,有什么区别:
我们知道arr是数组名,传参的时候,是表示首元素的地址。
int main() { int a = 10; int* pa = &a; //这个时候的去掉(*pa)得到的是a的类型 int int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; //&arr取出的是数组的地址,只有数组的地址才需要数组来接收 int(*p)[10] = &arr;//【】优先级比 * 高 所以要加上括号 这个去掉(*p)得到的是 int [10] //数组名 - 数组首元素的地址 //&数组名 - 是数组的地址 //数组首元素的地址和数组的地址从值的角度来看是一样的,但是意义不一样 printf("%p\n", arr);//表示首元素的地址 printf("%p\n", arr+1);//加4/8 在32/64的平台下 printf("%p\n", &arr[0]);//表示首元素的地址 printf("%p\n", &arr[0]+1);//加4/8 在32/64的平台下 printf("%p\n", &arr);//表示整个数组的地址 printf("%p\n", &arr+1);//&arr表示的是整个数组的地址 +40/80 在32/64位的平台下 return 0; }
所以,&arr和arr虽然数值是一样的,但是意义是不一样的。
实际上,&arr表示的是数组的地址,arr表示的是数组的首元素的地址
arr == arr[0] == &arr (数值相同,都是数组首元素的地址)
arr+1 == arr[0]+1 != &arr+1 (arr+1、arr[0]+1是4/8个字节,&arr+1是跨越整个数组大小,+数组每个元素的字节 * 数组元素个数)
3.3数组指针的使用
可以使用数组指针访问二维数组,这里只是演示如何使用,并非只能如此。
//传统方法: void print1(int arr[3][4], int r, int c) { for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { printf("%d ", arr[i][j]); } } } //使用数组指针 void print2(int(*p)[4], int r, int c) { //去掉(*p)得到的是存储的数据的类型 int [4] 因为我们存储的是二维数组,。那么二维数组的每一行都可以看作是一个一维数组 即 int [4] for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { printf("%d ", (*(p + i))[j]); //printf("%d ", p[i][j]);// 实际上 【】相当于 *() } printf("\n"); } } int main() { int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10 }; //print1(arr, 3, 4); //对于二维数组的数组名表示的是首元素的地址 print2(arr, 3, 4); return 0; }
以上咱学习了,指针数组和数组指针,下面进行分析,区分指针数组和数组指针
int arr[5]; int *parr1[10]; int (*parr2)[10]; int (*parr3[10])[5];
1.第一个明显的是一个整型数组,可以存放5个整型元素
2.这是一个指针数组,归根结底是一个数组,可以存放10个整型指针的数组
3.这是一个数组指针,归根结底是一个指针,存放的是有十个整型元素的数组
4.这是一个数组指针,首先这个是一个数组有十个元素,取出(*parr3【10】)剩下为 int [5] 对应的是接收五个元素的数组的地址。
四、数组参数、指针参数
4.1一维数组传参
使用一维数组传参的方式有哪些,什么样的形参可以接收一维数组?
void test1(int arr[]) { //使用 int arr[] 数组的形式是可以接收的 } void test1(int arr[10]) { //当然也可以使用这样的形式接收 10 可带可不带 } void test1(int* arr) { //使用整型指针的形式是可以接收整型一维数组的 } void test2(int* arr[10]) { //指针数组 可以用指针数组的形式接收 } void test2(int** arr) { //实参为指针数组,传递的是 int* arr[10] ,加上 传递数组名表示,传递的是数组的地址,所以用二级指针接收是可以的 } int main() { int arr1[10] = { 0 }; int* arr2[10] = { 0 }; test1(arr1); test2(arr2); return 0; }
4.2二维数组传参
//二维数组传参 void test(int arr[][4]) { //可以的 arr[] 相当于 (*arr) 也就是int(*arr)【4】 //对应的只需要知道有几列就可以 第一个[]可以不写 第二个必须写 } void test(int arr[][]) { //是不对的,第二个[]省略了 } void test(int arr[3][4]) { //是可以的 } void test(int* arr) { //不可以,传递的是二维数组,也就是第一行数组的地址,不能用一级指针接收一个数组的内容 //一级指针可以接收一维数组的地址,不能接收二维数组的地址 } void test(int* arr[5]) { //是指针数组,这样也不能接收,二维数组 } void test(int(*arr)[5]) { //二维数组看来指针方面 ,只能由数组指针可以接收了,【5】为列的个数 } void test(int** arr) { //二级指针同理,传递的是二维数组,实际上是二维数组的第一行数组的地址,只能由数组指针来接收,二级指针不行 } int main() { int arr[3][4]; test(arr); return 0; }
4.3一级指针传参
4.4二级指针传参
对于以上类型传参有两个我认为比较容易混淆。
1.二维数组的传参,可以使用数组指针来接收 int(*pa)[10]
2.对于二维指针的接收,可以传参指针数组 int*arr[10]
3.二维数组的传参的时候,需要用指针接收的时候只能用,数组指针 int(*pa)[10]
五、函数指针
根据上面各种指针所学,我们大概也知道函数指针应该是什么了,接下来,我来带大家看看到底什么是函数指针!
函数指针:存放函数地址的指针
如: int test (int a) {};
函数指针为: int(*pa)(int) = &test;
使用函数指针: int num = pa(10);
说明&函数名和函数名的地址是一样的
下面是函数指针的使用方法,以及表示方法
//函数指针 - 函数指针是存放函数地址的指针 void add(int x, int y) { printf("%d \n", x + y); } int main() { int (*pf1)(int, int) = &add; int (*pf2)(int, int) = add; // //&函数名和函数名都是函数的地址 printf("%p\n", pf1); printf("%p\n", pf2); //函数指针的调用 (*pf1)(2, 3); //(*pf1)解引用得到函数,然后括号传参,调用函数 add(2, 3);//我们知道这种形式是一般调用函数的方式 //int (*pf2)(int, int) = add;// add的地址给了pf2 说明pf2实际上就可以是add //add(2, 3)的时候也是add表示的是函数的地址 //所以可以这样写 pf2(2, 3);//得到结果一样 //那么(*pf2)这个星号到底有没有用呢,答案是用不到的 //(*******pf1)(2, 3);//写多少星号最后都是要得到add的地址,所以星号没有意义,但是只要加星号就要括号括起来,避免优先级问题 return 0; }
下面是《C陷阱和缺陷》中的两行代码
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
总结
以上是本文对于指针进阶的一系列指针相关内容的描述,接下来是下半部分对于指针进阶的内容的讲解,大家多多支持,您的每一次点赞、评论,都是我前进的动力!!!