本章重点
- 字符指针
- 数组指针
- 指针数组
- 数组传参和指针传参
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
- 回调函数
- 指针和数组面试题的解析
1. 字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char*
int main() { char ch = 'w'; char *pc = &ch; *pc = 'w'; return 0; }
还有一种方式是
int main() { const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗? printf("%s\n", pstr); return 0; }
这样就能完整打印整个字符串了。
但是我们不是把整个字符串放到pstr,而是放了字符串首元素的地址。
上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。
下面看一道笔试题。
#include <stdio.h> 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; }
我们运行起来的结果是什么呢,让我们看一下编译器给的结果吧。
这里原因是我们第一个是数组,数组创造会开辟空间的,而开辟的是两个不同的空间,这样他们的地址肯定也是不同的,而第二个为什么相同呢,首先我们的hello bit.
是常量字符串,只给常量字符串开辟了空间所以str3和str4是指向同一个空间的,这样的话地址是相同的,我们这道题其实就是比较他们的地址是不是相同的。我们也可以通过调试来看到我们想要的结果。
这样就很明显的可以看出我们给数组的时候是重新分配空间的。
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当
几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化
不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。
#include<stdio.h> int main() { int a = 10; int* p = &a; int* ps = &a; return 0; }
其实就是和上面这个代码一样的效果,这样大家可能就好理解了。
2. 指针数组
在我们学这个的时候,大家经常搞混,一下子字符数组,一下字指针数组,还有数组指针,其实我们只要抓住它是什么就可以解决好多问题,比如数组指针,他就是指向数组的指针,那相反的话,指针数组就是存放指针的数组。
在《指针》章节我们也学了指针数组,指针数组是一个存放指针的数组。
这里我们再复习一下,下面指针数组是什么意思?
int* arr1[10]; //整形指针的数组 char *arr2[4]; //一级字符指针的数组 char **arr3[5];//二级字符指针的数组
3. 数组指针
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
int *p1[10]; int (*p2)[10]; //p1, p2分别是什么?
首先p1先和括号联系,在和联系,那表示的意思就是指针数组,元素个数是10个。
p2就是先和联系,在和[]联系,那就是代表指向数组的指针。//这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合
3.2 &数组名VS数组名
这个内容其实我们在之前就讲过好几次了,数组名是首元素的地址,除了两个特例之外,一个是&,一个是sizeof,这两个代表整个数组。
#include <stdio.h> int main() { int arr[10] = {0}; printf("%p\n", arr); printf("%p\n", &arr); return 0; }
看上面这个例子。
我们可以看到他们的内容虽然是一样的,但是效果就是不同,让我们来看看他们+1的结果。
很明显不一样了,这是因为取出地址+1是跳过整个数组。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是(16进制是28)40,刚刚好整个数组。
3.3 数组指针的使用
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
#include <stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,0}; int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p //但是我们一般很少这样写代码 return 0; }
我们用数组指针来打印数组。
#include<stdio.h> void print_arr(int(*arr)[5], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { printf("%d ", arr[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} }; print_arr(arr, 3, 5); return 0; }
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
int arr[5]; int *parr1[10]; int (*parr2)[10]; int (*parr3[10])[5];
第一个就是存放int 类型的数组。
第二个就是存放int的数组,指针数组。
第三个就是我们数组指针。
第四个parr3先和[10]结合,然和在和结合,所以parr3它的意思就是指向数组的数组指针
画个图让大家更好的理解
4. 数组参数、指针参数
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1 一维数组传参
那我们看上面的传参那些结果是正确的吧
从上往下,第一个数组传参,以数组的形式接收,肯定对,其实它的本质就是int* arr,
下一个[]中的参数其实没有意义,甚至可以乱写,所以也对,
第三个就是我们说i的本质。
那我们再看看下面的指针数组传参是怎样的。
第四个也是对,我们以数组的形式来接收。
第五个也是对的,我们认为后一个*表示的是它是一个指针,指向的内容是是int 的,也可以认为arr是属于int*
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); }
那再让我们来看看这个二维数组传参。
我直接写代码里面了,这样看起来就能一一对应
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); }
我们上面讲过二维数组的传参,所以判断起来就快一点了
4.3 一级指针传参
其实对于这个问题,我们只要掌握一个诀窍就可以了,一级指针传用一级指针接收,二级指针传参用二级指针来接收,但是一级指针的地址得用二级指针来接收。
#include <stdio.h> void print(int *p, int sz) { int i = 0; for(i=0; i<sz; i++) { printf("%d\n", *(p+i)); } } 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; }
思考 当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test1(int *p) {} //test1函数能接收什么参数? void test2(char* p) {} //test2函数能接收什么参数?
void test1(int *p) {} //test1函数能接收什么参数? 可以是一维数组的数组名,可以是一个常量的地址,可以是一级指针 void test2(char* p) {} //test2函数能接收什么参数? 字符指针,字符数组的数组名,一个字符的地址
4.4 二级指针传参
#include <stdio.h> 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. 函数指针
我们先看一下下面的代码
#include <stdio.h> void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }
我们看看地址输出的是什么
其实地址是一样的,我们函数加取地址其实和没加一样,都是对的
输出的是两个地址,这两个地址是 test 函数的地址。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); void *pfun2();
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参
数,返回值类型为void。
其实函数的指针和我们数组指针特别相似,我们下面看两个特别有趣的代码,有点难度,但是有趣
//代码1 (*(void (*)())0)(); //代码2 void (*signal(int , void(*)(int)))(int);
那我们先看第一代码,先给结论,在0这个地址给了一个函数指针,没有返回类型,参数也没有
我们0之前有个大括号,这明显就是强制类型转换,在0这个地址放一个函数指针的类型,然后调用这个函数
下一个是signal这个指针。
我们拆分开来signal(int , void()(int)),先看这个,是一个函数,函数第一个参数是int 第二个参数是一个函数指针,类型就是void()(int),然后在看signal外面有一个*,代表就是一个指针,返回是void 参数是int 其实本质上就是一个函数指针。
typedef void(*pfun_t)(int); pfun_t signal(int, pfun_t);
也可以这样写。
今天的分享就到这里,我们下次见!!!!