- 指针是什么
- 指针和指针类型
- 野指针
- 指针运算
- 指针和数组
- 二级指针
- 指针数组
1. 指针是什么?
学习总结
指针就是地址,是内存中一个最小单元的编号。通常人们所说的指针,指的是指针变量,即用来存放地址的变量。
在32位的机器上,地址是由32个0或者1组成的二进制序列,那地址就得用4个字节的空间来存储,所以一个指针的大小就应该是4个字节。同理,在64位机器上,一个指针的大小就应该是8个字节。
间歇疑问
1)为什么一个指针在32位机器上占4个字节,在64位机器上占8个字节?
首先,对于这个问题,应该先从最基础的计算机读取数据说起。对于计算机有64位、32位、16位,这里的位指的就是计算机CPU中通用寄存器一次性处理、处理、暂时存储的信息的最大长度。即CPU在单位时间内(同一时间)能一次处理的二进制数的位数。对于硬盘数据,CPU是无法直接读取的,CPU通过地址总线、数据总线和控制总线三条线对内存中的数据进行传输和操作。其中,地址总线负责找到该条数据;控制总线负责判断信息的处理方式,是读操作还是写操作;数据总线负责将该数据读取到CPU还是从CPU写到内存中。
举个例子,一个计算机的地址总线是32位,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(0和1)。那么32跟地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
......
11111111 11111111 11111111 11111111
这里一共有2^32个地址。如果我们想找到内存中的所有地址,就需要32个0或1的组合,而每一个这样的32个0或1的组合,就是32个位,而每一位本质上就是二进制的0或1。在数据存储中,是以“字节”(Byte)为单位,数据传输大多是以“位”(bite,又名“比特”)为单位,1个字节(Byte)= 8位(bit)=8比特。因此,作为能够找到所有地址的指针,为了找到所有的地址,每一个指针都需要4个字节大小,故一个指针在32位机器上占4个字节,同理,在64位机器上占8个字节。
2. 指针和指针类型
我们知道变量有不同的类型,整形,浮点型等,对于指针也有相关的类型。常见的指针类型有:
char *a = NULL
int *b = NULL
short *c = NULL
long *d = NULL
float *e = NULL
double *f = NULL
指针的定义方式是:type + * 。比如char*类型的指针表示的是为了存放char类型的地址。因此每个指针都有相应的类型,不同的指针类型决定了该指针解引用的权限有多大。举例:
//char类型
int a = 0x11223344;
char* pc = &a;//这里取地址a,将其赋给指针pc
*pc = 0;//这里将0赋值给指针pc
//int类型
int b = 0x55667788;
char* pa = &b;//这里取地址b,将其赋给指针pa
*pa = 0;//这里将0赋值给指针pa
下面来看一下内存位置:
除此之外,指针类型决定了指针走一步能走多远(步长)。举例:
从上图可以看出,指针p为int类型,指针pc为char类型,p的初始值为0x00aff744,加了1之后,变为0x00aff748,步长为4,pc为char类型,初始值为0x00aff744,加了1之后,变为0x00aff745,步长为1。
3. 野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针的成因
1)指针未初始化。举例:
//这里的p就是一个野指针
int* p;//p是一个局部的指针变量,局部变量不初始化的话,默认是随机值
*p = 20;//非法访问内存
2)指针越界访问。举例:
//2.越界访问
int arr[10] = { 0 };
int* p = arr;//将数组arr的首地址赋给指针p
int i = 0;
for (i = 0; i <= 10; i++)//数组arr有10为,因此其下标范围为0,..,9
{
*p = i;
p++;
}
3)指针指向的空间释放。举例:
void func(int* s)
{
printf("%d\n", *s);
free(s);//这里指针s的空间被释放掉了
}
int main()
{
int a = 10;
int* p = &a;
func(p);
printf("%d\n", *p);
return 0;
}
int* test()
{
int a = 10;
return &a;//这里返回的是一个地址,所以返回类型为指针类型int*
}
//当你一旦return返回后,出了这个函数范围后,这个a的变量的生命周期就结束掉了,就会被销毁了
//所占据的内存也还给操作系统了
int main()
{
int*p = test();//返回的是一个指针类型,接收也用这种类型
*p = 20;//将变量20放置在指针p这个位置上,但此时这个位置已经还给内存l
//就会造成非法访问内存
return 0;
}
如何规避野指针
1)指针初始化
//当前不知道p应该初始化为什么地址的时候,直接初始化为NULL
int* p = NULL;
//明确知道初始化的值
int a = 10;
int* ptr = &a;
2)小心指针越界,
C语言本身是不会检查数据的越界行为的
3)指针指向空间释放即使其置NULL
这里要注意一点,就是NULL是空指针,空指针是不能被用户拿来使用的,故不能放置变量。
int* p = NULL;
*p = 10;
//空指针不能放置变量,报错,应该先判断一下,看看这个指针是否有效
//空指针无效
if(p != NULL)
*p = 10;
//1. 当一个指针你不知道指向哪里的时候,你把它指向空指针
//2. 当这个指针指向的空间不用的时候,你在把这个指针指向空指针
//3. 当它指向一个有效的空间的时候,你在给它一个有效的地址
//4. 这样就存在两种结果,一个是有效的空间+有效的地址,另一个是NULL
4)避免返回局部变量的地址
5)指针使用之前检查有效性
4. 指针运算
指针 + - 整数
#define N_VALUES 5 //定义N_VALUES 是数值5
float values[N_VALUES];//数组values
float* vp;//一个float的指针地址
for(vp = &values[0]; vp < &values[N_VALUES]; )//这里是指针的关系运算,比较大小
{
*vp++;//这里是指针的+ - 整数
}
//当指针带*号,表示的是变量值,当指针不带*号,表示的是指针的地址
指针 - 指针
两个指针相减,得到的是指针和指针之间元素的个数。并且指针和指针相减的前提是两个指针指向同一块空间。举例:
两个指针相减,属于同一块空间
知识点补充:
关于strlen()的函数过程用法:1.计数器;2.递归;3.指针-指针......
指针-指针
(指针+指针,两个地址相加,没有意义。类似于两个日期相加,已没有意义)
int my_strlen(char* str)
{
char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;//指针相减等于指针与指针之间元素的个数
}
int main()
{
//strlen(); - 求字符串长度
int len = my_strlen("abc");
printf("%d\n", len);
return 0;
}
指针的关系运算
指针的关系运算就是比较大小
for (vp = &values[5]; vp >= &values[0]; vp--)
{
*vp = 0;
}
//values[]是一个数组
//这个是通过for循环的方式,从位数高的逐渐过渡到位数低的
谨记一点:当按照从高位到低位这样逐渐递减,但是根据C语言标准规定允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5. 指针和数组
除了两种情况以外,数组名表示的都是数组首元素的地址
两种特殊情况:
1. sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
2. &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
为此,我们可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为了可能。举例:
通过上图,可以发现,指针p+i实际上就是计算数组arr下标为i的地址,因此,可以直接用指针来访问数组。举例:
知识点补充:
由上图,数组名表示数组的首地址(排除两种情况),指针p+i表示数组arr下标为i的地址,则当将arr首地址传给指针p后,会出现如下的情况:
arr[2] <==> *(arr+2) <==> *(p+2) <==> *(2+p) <==> *(2+arr) <==> 2[arr] <==> 2[p]
arr[2]表示数组第3个元素的值,数组名表示首地址,根据指针,首地址+2,就等价了。同理,此时首地址传给指针p了,则p+2也是如此,操作符+号,允许交换,故2+p也可以,同理2+arr也等价,则同理,[]也是操作符,2和arr作为两个操作数,也可以交换,则等价于2[arr]和2[p]。
请看代码:
6. 二级指针
原理:指针变量也是变量,是变量就会有地址,那指针变量的地址存放在哪里呢?请看代码:
int a = 10;
int *pa = &a;
int *ppa = &pa;
此时,a的地址存放在pa中,pa的地址存放在ppa中,pa是一级指针,而ppa是二级指针。
对于二级指针的运算有:
*ppa通过对ppa中的地址解引用,这样找到的是pa,*ppa其实访问的就是pa。
int b = 20;
*ppa = &b;//等价于 pa = &b
**ppa先通过*ppa找到pa,然后对pa进行解引用操作;*pa,那找到的是a。
**ppa = 30;
//等价于*pa = 30
//等价于a = 30
此外还可以写三级指针,四级指针......但都很少用到。
7. 指针数组
指针数组是数组,是存放指针的数组。
数组有整形数组、字符数组,还有指针数组,比如整形指针数组。书写方式:
int* arr[5];
//arr是一个数组,有5个元素,每个元素是一个整形指针
//还有字符指针数组,等等
char* arr1[5];