💤 本章介绍
可能有伙伴就要问了,咋一来就进阶指针!
不要慌问题不大,bug郭之前就写个一篇博客,介绍指针基础知识!
有兴趣的伙伴可以点击查看C语言指针,楼下大爷都能学会的小细节(和bug郭一起学C系列),建议收藏!
大家都复习完了指针基础吧,那我们就开始指针进阶的学习吧!
指针基础的一些概念:
指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
指针的大小是固定的4/8个字节(32位平台/64位平台)。
指针是有类型,指针的类型决定了指针的+-
整数的步长,指针解引用操作的时候的权限。
指针的运算。
本章重点
6. 字符指针
7. 数组指针
8. 指针数组
9. 数组传参和指针传参
10. 函数指针
11. 函数指针数组
12. 指向函数指针数组的指针
13. 回调函数
14. 指针和数组面试题的解析
字符指针
字符指针顾名思义就是一个指针变量,指针指向的空间存放的是一个字符!
char* 字符指针
//基本用法 int main() { char ch = 'c'; char* pc = &ch; *pc = 'w'; return 0; }
这种基本的用法,bug就不介绍了,相信大家都会!
//进阶 int main() { char* pstr = "abcdef"; //pstr字符指针存了字符串,第一个字符(a)的地址 printf("%s",pstr); return 0; }
代码char* pstr = "abcdef";特别容易让我们以为是把字符串abcedf放到字符指针pstr里了,但是本质是把字符串abcdef 首字符的地址放到了pstr中。
我们可以知道通过字符指针pstr我们可以找到字符串abedef。
为啥我们不直接创建一个字符串变量,而要用这种方式,有何不同呢?
//测试 #include <stdio.h> int main() { char str1[] = "hello world."; char str2[] = "hello world."; char *str3 = "hello world."; char *str4 = "hello world."; 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; }
输出结果
可以看到,字符数组str1和str2不相同,因为str1和str2是数组名,而数组名就是第一个数组的地址。str1和str2分别开辟了两个数组空间,只不过它们存放的内容一样而已!
而str3和str4它们都是指向的同一块空间,因为它们指向的是字符串常量hello world.
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,
当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。
数组指针
数组指针,我们首先要明确的就是,数组指针是指针而不是数组,它是指向数组的指针!
//判断数组指针 int* arr1[3]; //指针数组 int (*arr2)[3]; //数组指针
我们之前学过C语言操作符,建议收藏,我们知道操作符[]的优先级高于*。
所以arr2是数组指针,而arr1是指针数组。
int (arr2*)[3] : arr2是一个指针,指向的对象是整型数组,数组的元素个数为3
数组指针的使用
#include<stdio.h> int main() { int arr[4] = { 1,2,3,4 }; int(*parr)[4] = &arr; //数组指针存放数组arr的地址! return 0; }
大家肯定很少见代码这么写吧,数组指针很少这样使用!
我们已经知道了数组名就是,数组的首元素地址,而取地址数组名是数组的地址 。
那&arr和arr有啥区别呢?
#include<stdio.h> int main() { int arr[4] = { 1,2,3,4 }; int(*parr)[4] = &arr; printf("arr :%p\n",arr); printf("&arr:%p\n", &arr); return 0; }
居然都是第一个元素的地址!
但是我们知道,指针的类型决定了指针加减的步长!
#include<stdio.h> int main() { int arr[4] = { 1,2,3,4 }; int(*parr)[4] = &arr; printf("arr :%p\n",arr); printf("&arr:%p\n", &arr); printf("arr+1 :%p\n", arr+1); //整型指针加1,加一个整型类型大小 printf("&arr+1:%p\n", &arr+1);//数组指针加1,加一个数组类型大小 return 0; }
可以看到,数组指针和首元素地址,指针的类型不同
数组名arr:指针类型是整型 指针加减1,步长为整型大小(4bit)
&数组名:指针类型是数组 指针加减1,步长为数组大小(16bit)
数组指针正确使用
#include <stdio.h> void print_arr1(int arr[3][5], int row, int col) { int i = 0; for(i=0; i<row; i++) { for(j=0; j<col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void print_arr2(int (*arr)[5], int row, int col) { int i = 0; for(i=0; i<row; i++) { for(j=0; j<col; j++) { 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; }
学了数组指针,是不是发现有点懵了!
//捋一捋 int *arr1[3]; //指针数组 //数组个数是3,元素是int指针类型的数据 int (*arr2)[3];//数组指针 //指针,指向数组,且数组的类型是int类型,且元素个数为3 int* (*arr3)[3]; //数组指针 //指针,指向数组,数组元素是int*类型,且元素个数为3 int (*arr4[3])[3]; //数组指针指针 //指针,指向一个数组指针,数组指针的类型是int(*) [3] 指向数组且为为int类型,元素个数为3 ......
就捋到吧,再捋下去就更懵了,兄弟们慢慢学,你可以了的!
数组参数、指针参数
在写代码的时候难免要把数组或者指针传给函数,那函数的参数该如何设计呢?
一维数组传参
#include <stdio.h> void test(int arr[])//ok? {} //int arr[] 接收就是以int * arr形式接收,因为*arr等价与 arr[] void test(int arr[10])//ok? {} //int arr[10] 同上在形参中都是一个整型指针,形参中的数组长度无意义 void test(int* arr)//ok? {} //整型指针接收数组名就是首元素地址也就是整型指针 void test2(int* arr[20])//ok? {} //int* arr[20]等价于 int* arr[]等价于 int**arr 即二级指针 //而实参就是一个指向整型指针的指针也就是二级指针 void test2(int** arr)//ok? {} //二级指针接收 int main() { int arr[10] = { 0 }; int* arr2[20] = { 0 }; test(arr); test2(arr2); }
二维数组传参
void test(int arr[3][5])//ok? {} //二维数组传参二维数组接收 void test(int arr[][])//ok? {} //error 不知道二维数组中一维数组中元素个数 void test(int arr[][5])//ok? {} //可以省略行不能省略列 //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 void test(int *arr)//ok? {} //error 二维数组的数组名就是首元素地址,即一维数组的地址, // 也就是数组指针,应该用数组指针接收 void test(int* arr[5])//ok? {} //error,指针数组,实参是数组指针 void test(int (*arr)[5])//ok? {} //实参为数组指针与形参类型相同 void test(int **arr)//ok? {} //error int main() { int arr[3][5] = {0}; test(arr); }
我们来总结一下!
二维数组的数组名就是首元素地址,而二维数组的元素就是一维数组,所以数组名的类型就是数组指针。
当二维数组数组名传参,形参接收时,数组的行可以省略,列不能省略,如果省略了列,我们就无法知道当指针加减跳过几个字节。
一级指针传参
#include <stdio.h> void print(int *p, int sz) //一级指针传参,一级指针接收 { int i = 0; for(i=0; i<sz; i++) { printf("%d\n", *(p+i)); } } //void print(int p[],int sz) //数组接收,也即一级指针接收,不提倡这样写 int main() { 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; }
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
//以int型指针为例 void test(int* p) { } int main() { int x=0; int* px=&x; int arr[10]; test(&x);//整型地址 test(px);//一级指针 test(arr);//一维数组名,即首元素地址,int* return 0; }
二级指针传参
void test(char** p ) { } int main() { char ch = 'c'; char* pc = &ch; char* *ppc = &pc; char* arr[3]; test(&pc); //一级指针的地址,即二级指针 test(ppc); //二级指针 test(arr); //数组名,首元素地址,首元素为一级指针,所以为二级指针 return 0; }
思考:
当函数的参数为二级指针的时候,可以接收什么参数?