深入理解指针(3)
1.字符指针变量
接下来给一串代码来看
在指针的类型中我们知道有一种指针类型为字符指针char* ;
我们这里定义了ch变量,里面存了个字符 w
然后我将这个变量的地址取出来放到pc里,它的类型是char*,pc就是字符指针变量
现在我们来拓展一下
这里的”hello world”我们可以想象成数组 但是是有区别的
数组是可以改变的
其实这里的”hello world” 是常量字符串 常量字符串是不能修改的
既然它是不能被修改的 我们可以在它的前面加上const修饰
从上面我们已知常量字符串将第一个元素的地址赋给了字符指针变量p
那么我们是不是可以对待数组那样对待p呢 我们试一试
结果告诉我们 当然可行的
《剑指offer》中有一道与字符串相关的笔试题 我们一起学习一下
其实 我刚开始看这个题目是 给出的答案与之相反 如果大家做对了 说明大家学习c语言还是很强的
接下来 我们来解释解释这个题目
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。
但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
2.数组指针变量
同样 我们举例
字符指针 char* p 指向字符的指针 存放的是字符的地址
整形指针 int* p 指向整型的指针 存放的是整型的数组
数组指针 应该就是指向数组的指针 存放的是数组的地址
所以数组指针是一种指针变量 是存放数组地址的指针变量
这里的数组地址应该是 &arr(数组arr的地址)
解释:p先和*结合 说明p是一个指针变量 然后指针指向的是一个大小为10个整型的数组 所以p是一个指针 指向一个数组 叫数组指针
这里要注意:[]的优先级要高于*号 所以必须加上( )来保证p先和*结合
我们举例说明 如:char* p; 在这里去掉p 剩下的就是p的类型 char*
现在我们来回答之前为什么&arr+1跳过了40个字节
int arr[10]={0};
arr;
arr+1; 跳过4字节
&arr[0];
&arr[0]+1; 跳过4字节
&arr;
&arr+1; 跳过40个字节
int (*p)[10]=&arr;------类型int (*)[10]
该指针指向一个数组(数组内有10个int型的元素)---40个字节
+1是跳过了一个数组
注意方括号不能省略
因为 int arr1[5] 和int arr2[8]两者的类型也不相同 前者类型为int [5] 后者类型为int [8]
只要元素个数不一样 类型就不会一样
当我们写一个指针指向一个数组 我们必须写的规范 不能省略方括号和元素个数
数组指针变量怎么初始化?
数组指针变量是用来存放数组地址的 那怎么获得数组的地址呢 那当然是用我们学习过的&数组名
我们调试 也能看到&arr和p的类型是完全一致的
数组指针类型解析:
数组指针有什么用呢?
我们在之前学习了指针变量 可以对指针变量进行解引用 然后输出数组的元素
如图
那我们是不是也可以通过数组指针来处理呢
我来解析一下 数组指针p 存放的是整个数组的地址 p=&arr;
当我们对其进行解引用 *p=*&arr; 这里的* 和&抵消 就剩下一个arr(数组首元素地址)
这里的arr我们在下一知识点会进行重点讲解
但其实这里还是比较别扭的 不建议强行使用 数组指针我们一般用在二维数组中 在后面我们将会见到如何使用
二维数组传参的本质
我们知道数组名是数组首元素的地址
那么二维数组的首元素是什么呢?
在之前我们学习二维数组时 我们知道一个二维数组可以由几个一维数组构成
二维数组其实可以看作是每个元素是一维数组的数组 也就是二维数组的每个元素是一个一维数组 那么二维数组的首元素就是第一行(一维数组)
二维数组的数组名就是第一行的地址
int arr[3][5];
int (*p)[5]=arr; //p就是数组指针 是指向一维数组的指针
二维数组传参的本质上也是传递了地址 传递的是第一行这个一维数组的地址
那么之前的代码也可以改成下列代码
总结:二维数组传参 形参的部分可以写成数组
函数指针变量
什么是函数指针变量?
类比
字符指针---只想字符--存放字符的地址
整型指针---指向整型--存放整型的地址
那么函数指针---指向函数--存放的是函数的地址 未来可以通过地址调用函数
当然我们要存放函数的地址 便用到了函数指针变量
函数指针变量的写法与数组指针变量的写法非常类似
函数指针变量的创建
下面我们再练一个
函数指针变量的使用
现在我们再拓展一下
当然在调用函数时 那个*号其实是个摆设 无论有无 或者有多个 都不重要
那我们想到可以直接用Add调用函数 为什么还要用指针呢?
指针是提供另一种处理问题的方式
函数指针类型解析:
两段有趣的代码
代码1:
我们知道对函数有两种 一种是函数声明 一种是函数调用
代码2:
signal的第二个参数的类型是void (*)(int) 的函数指针类型 该指针可以指向一个函数 指向的函数参数是int 返回类型是void
typedef关键字
typedef是用来对类型重命名的 可以将复杂的类型 简单化
比如 你觉得unsigned int 写起来不方便 如果能写成uint就方便多了 那么我们可以使用
typedef对指针类型重命名
如果是指针类型,能否重命名呢?其实也是可以的,比如,将int*重命名为pint,这样写:
但是对于数组指针和函数指针稍微有点区别
比如我们有数组指针类型int (*)[5] 需要重命名为parr_t 那就可以这样写:
函数指针类型的重命名也是一样的,比如,将void (*)(char*) 类型重命名为pf_t ,就可以这样写:
那我们对下列代码进行重命名
得到的应该是
当然我们知道define也可以进行重命名
但define和typedef是有区别的
所以类型的重命名 咱们尽量不要用define
用typedef更好
函数指针数组
我们还像之前那样进行类比:
整型数组:是存放整型的数组
字符数组:是存放字符的数组
指针数组:是存放指针的数组
char* arr[5]; 字符指针数组---是存放字符指针的数组
int* arr2[10]; 整型指针数组
那么函数指针数组就应该是存放函数指针的数组
那函数指针的数组如何定义呢?
pfArr 先和[] 结合,说明 pfArr是数组,数组的内容是什么呢?
是int (*)() 类型的函数指针。
结论:如果要把多个相同类型的函数指针存放在一个数组中 这个数组就是函数指针数组
拓展
这里可以试着想想数组指针来理解
那我们怎么去调用呢
那函数指针数组有啥子用呢 我们依旧会在下一知识点里告知大家
转移表
函数指针数组的用途:转移表
想写一个计算器:完成2个整数的运算
1.加法
2.减法
3.乘法
4.除法
.
这代码特别冗余 如果扩展功能 代码也会大量增加
那么我们现在优化一下该代码
这里我们就用到了函数指针数组 是不是就把那么长的switch语句给替换掉了呢
当然我们还有另一种优化方式
通过将函数的指针作为参数传递给另一个函数 然后调用函数
这里看不懂没关系 我们之后会着重讲解的
在学了函数指针之后 我们知道调用函数不仅仅可以用函数名调用 也可以通过函数指针调用