目录
首先,在学习指针之前我们需要明白指针的作用和含义。我在学习指针的时候,一直在想指针到底有什么用处。在理解指针之前我们需要搞懂,指针和指针变量是两回事。
- 指针就是地址。
- 指针变量用来存放地址。
这两句话很重要,在之后我会详细讲解。
对象和地址
变量是“保存数值的盒子”,有序地排列在内存空间当中。“变量”拥有多个属性。在之前,我们学习了数据类型长度。例如int类型变量和double类型变量就有不同的长度。可以利用sizeof求得。
//示例一
#include <stdio.h>
int main()
{int i = 10;
double x = 20;
printf("%d",sizeof(i));
printf("%d",sizeof(x));
return 0;}
在内存空间上,想要能够准确地找到各个变量的“位置”,我们需要地址。
那么就有了取址运算符“&”。取址运算符的功能是取得对象的地址,也就是生成一个指向地址的指针。
int main()
{int i = 10;
double x = 20;
printf("%p",&i);
printf("%p",&x);
return 0;}
指针
只显示对象的地址并没有太大的用处,要想明白指针的用处还是看如下代码。
看这段代码,我们可以大致明确“int型变量”和“指向int型变量的指针变量”的区别也可以明白指针的赋值形式。请看下图。
指针变量大小
结论:
- 32位平台下地址是32个比特位,指针变量大小是四个字节
- 64位平台下地址是64个比特位,指针变量大小是八个字节
- 指针变量的大小和类型是无关的,只要是指针类型的变量,在相同的平台下,大小是相同的。
指针变量类型的意义
结论
指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。
指针+-整数
结论
指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。
const修饰指针
结论:
- const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。
- const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变
指针运算
指针类型运算分为三种
- 指针+-整数
- 指针-指针
- 指针的关系运算
指针+-整数
指针-指针
指针的关系运算
野指针
概念:
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
成因:
- 指针未能初始化。
- 指针越界访问。
- 指针指向的空间释放。
如何规避野指针:
- 如果不知道指针应该指向哪⾥,可以给指针赋值NULL.
- 指针变量不再使用时,即时置NULL,指针使用之前检查有效性。
assert断言
assert.h头文件定义了宏asser()用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行,这个宏常被称为“断言”。
使用指针访问数组
数组名的理解
例外情况:
- sizeof(数组名),sizeof中单独放数组名这里表示整个数组,计算的是整个数组的大小,单位是字节。
- &数组名,这里数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
结论:
除了上述两种特例,任何地方使用数组名,数组名就是首元素的地址。
使用指针访问数组
数组传参的本质
示例代码:
由上图代码可以发现,在函数内部是没有正确的获得数组的元素个数。由上个部分学习了:数组名是首元素的地址,那么数组传参的时候,传递的是数组名,也就是本质上数组传参本质上传递的是数组首元素的地址。
所以函数形参的部分,理论上应该使用指针变量来接受首元素的地址。那么在函数内部我们写sizeof(arr)计算一个地址的大小(单位字节)而不是数组的大小(单位字节)。正是因为函数的参数部分是指针,所以在函数内部是没办法求得数组元素个数的。
总结:
一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
二级指针
前面已经学过,指针变量里面存放的是地址,那么指针变量的地址存放在哪里?答案是二级指针里面。
指针数组
指针数组是存放指针的数组。
字符指针变量
代码constant char*pstr="hello world.",本质是把字符串"hello world."首字符的地址放到了pstr中。
数组指针变量
概念:
存放的是数组的地址,能够指向数组的指针变量。
int (*p)[10];
p和*先结合,说明p是一个指针变量,然后指向的一个是大小为10个整形的数组。所以p是一个指针指向一个数组,叫做数组指针。
初始化:
int (*p) [10] = &arr;
| | |
| | |
| | p指向数组的元素个数
| p是数组指针变量名
p指向的数组的类型
函数指针变量
概念:
函数指针变量是用来存放函数地址的,未来通过地址调用函数的。
创建函数指针变量:
函数名就是函数的地址,当然也可以通过&函数名的方式获得函数的地址。
void test()
{
printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x,int y)
{
return x+y;
}
int (*pf3)(int,int) = Add;
int (*pf3)(int x, int y) = &Add;
解析:
int (*pf3) (int x, int y)
| | ---------------
| | |
| | pf3指向函数的参数类型和个数的交代
| 函数指针变量名
pf3指向函数的类型
使用:
int Add(int x, int y)
{
return x+y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));
return 0;
}运行结果:
58
typedef关键字
typedef是用来类型重命名,可以将复杂的类型,简单化
例如:你觉得 unsigned int写起来不方便,就可以重命名为unit
typedef unsigned int unit
对于数组指针和函数指针稍微有一些区别
例如:我们由数组指针类型int(*)[5],需要重命名parr_t,可以这样写:
typedef int(*parr_t)[5]
函数指针也是如此,比如将void(*)(int)类型重命名pf_t
typedef void(*pfun_t)(int) //新类型必须在*的右边
函数指针数组
概念:
把函数的地址存到一个数组中,那这个数组就叫函数指针数组
定义
int (*parr1[3])();
parr1先和[ ]结合,说明parr1是数组,那么数组的内容就是int(*)()类型的函数指针。
回调函数
概念:
回调函数就是一个通过函数指针调用的函数。
总结
磨蹭了两天,笔力实在有限,对于这类介绍文章实在写不出新东西新感悟。说是详解,肯定还有缺漏之处,还请各位多多指教。