一、指针
1、什么是指针
指针是一个变量,是用来存放某个数据或元素在内存(存储空间)中的地址的。通过这个指针可以间接的访问指针指向的数据或元素。什么类型的数据就用什么类型的指针,如:int 类型的数据,就用【int* 指针变量名】去存储。
2、指针所指向的空间的大小是多大?
在内存中指针指向的地址的大小是一个字节,因为系统为每个地址分配的大小就都是一个字节,char类型占1个字节,int类型占4个字节,double类型占8个字节,等等。
3、32位/64位指针所占内存的大小是多少?
32位机器,有32根地址线,每条地址线可以传送0/1的电信号,每个电信号转变成内存地址时所占的空间就是1bit,所以它能传送的二进制序列就有2的32次方中可能。
每种结果都是32个比特位的空间,1字节(byte) = 8比特位(bit),所以32个比特位就等于4个字节。所以:
32位操作系统中指针所占内存的大小是4个byte。
64位操作系统中指针所占内存的大小是8个byte。
4、指针和指针类型
不同类型的元素或变量在取地址后要有有一个指针变量来存放这个地址,而这个指针变量的类型就是看所指空间的元素或变量的类型来决定的。
如:【char*】类型的指针是为了存放 char 类型变量的地址。 【short*】 类型的指针是为了存放 short 类型变量的地址。【int*】类型的指针是为了存放 int 类型变量的地址。
(1)、指针 +- 整数
指针+-一个整数,得到的结果是不一样的,【char*】指针+1,是跳过一个字节指向下一个字节。
因为char类型只占1个字节,所以它只移动一位。
【int*】指针+1,是跳过4个字节,指向第五个字节。
因为int类型占4个字节,所以pa+1,移动4个字节。
总结:指针类型决定了,指针+-一个整数,移动的距离有多大。
int* +1 —> +1*sizeof(int) == +4
char* +1 —> +1*sizeof(char) == +1
int* +n —> +n*sizeof(int) == +n*4
char* +n —> +n*sizeof(char) == +n*1
(2)、指针的解引用
指针通过对取地址&操作得到元素或变量的地址,而通过解引用操作符*可以间接访问指针所指地址的元素或变量。
不同类型的指针的变量所能访问的内存空间是不同的。
这里的a是int类型,取出a的地址是用int接收,对pa这个指针变量进行修改时,int*指针可以修改内存中的4个字节。
而这里是将a取地址后存放在char*指针中,对*pa这个指针变量进行修改时,char*指针可以修改内存中的1个字节。
总结: 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
二、野指针
1、什么是野指针?
野指针是指,指针指向的位置不可知、指向的位置随机、指向的位置不正确,或没有明确限制的指针,就叫野指针。
2、野指针的成因
a.指针未初始化
因为这里的指针变量p没有初始化,也就是它没有一个指定的,唯一的地址,所以他有可能选择任意一个地址,但是那个任意的地址又不属于这个指针变量,所以会出现报错。
b.指针越界访问
因为创建的数组只有5个元素即{1,2,3,4,5},而for循环中指针parr要访问到数组的第10个元素。所以出现了5个随机值,这随机值不是我们能料到的,所以这就是指针越界访问的后果。
c.指针指向的空间被释放
这里在函数中创建了一个局部变量a,返回变量的地址给主函数中的指针变量pa,函数test结束,栈区中局部变量a创建的空间也被释放了,空间还给操作系统了,这时返回的a的地址不再指向a这个局部变量了,当还用*pa修改这个空间的值,就有可能会出现问题。
3、如何避免野指针?
(1)对指针进行初始化
(2)小心/防止指针越界
(3)指针指向的空间被释放后,即使将指针置为NULL(空指针)
(4)避免返回局部变量的地址
避免2.(c)的情况。
(5)指针在使用前要检查指针的有效性
三、指针运算
1、指针 +/- 整数
指针 +/- 一个整数,实现的是对这个指针位置的移动。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* parr = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(parr + i));
}
return 0;
}
开始指针(parr+0)指向数组中的1,当i+1之后,parr的指向也向后移动了一位指向了数组中的2。所以,对指针+/-一个整数实现的是对指针位置的移动。
2、指针 - 指针
指针-指针得到的是两个指针之间元素的个数,前提是:两个指针必须指向同一块空间。
int test(char* arr)
{
char* start = arr;
while (*arr)
{
arr++;
}
return arr - start;
}
int main()
{
char arr[] = "abcdefg";
int ret = test(arr);
printf("ret = %d\n", ret);
}
这里实现的是使用指针的方法实现strlen函数,求一个字符串中字符的个数。test中,将arr数组的首地址送给了指针start,然后通过while循环找到\0前的元素,用这个元素的地址 - start的首地址得到的就是两个地址之间元素的个数。
3、指针的关系运算
C语言标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
允许和数组最后一个元素后面的位置地址进行比较,不允许和数组第一个元素前面的位置地址进行比较。
四、指针和数组
int main()
{
int arr[5] = { 1,2,3,4,5 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
由此程序可以得出结论:数组名是首元素地址。
但是有两个例外:
(1)sizeof(数组名) ——> 得出的是整个数组的大小。
(2)&数组名 ——> 取出的是整个数组的地址。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr);
printf("%p\n", &arr+1);
printf("%d\n", sizeof(arr));
return 0;
}
由此图可以得出,数组名是数组首元素的地址,+1跳过的是一个元素,4个字节。
&数组首元素,+1跳过的也是一个元素,4个字节。
&数组名,取出的是整个数组的地址,+1跳过的是整个数组,从884到8AC,中间差了28(十六进制),转化为10进制得到的是40,每个元素占4个字节,40个字节就是10个元素,所以是跳过了一个数组。
sizeof数组名,求得出的值是整个数组的大小——40字节。
五、通过指针可以定位到数组中的任何一个元素
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
指针变量p被赋值为数组首元素的地址,再利用p+i可以找到数组中任何一个元素的地址,在对其解引用即可获得该元素的值。
六、二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里呢?
指针变量的地址也要存放在指针中,这个存放指针变量地址的指针就叫:二级指针。
二级指针如何理解?
一级指针int* pa = &a; 可以理解为:int *pa = &a;*pa结合说明pa是个指针,而pa指向的地址中元素的类型是int类型的元素。
二级指针int** ppa = &pa; 可以理解为:int* *ppa = &pa; 结合说明ppa是个指针,而int* 代表的是这个指针指向的地址中元素的类型是int*的。
所以对ppa解引用(*ppa)得到的就是pa这个指针,再对pa进行解引用(*pa)得到的就是变量a。所以**ppa可以直接得到变量a。
七、指针数组
整形数组中存放的是整形变量,字符数组中存放的是字符变量,那指针数组中存放的就是指针变量。
如:
指针数组中存放的都是指针变量。
而指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int* arr[] = { &a,&b,&c,&d };
return 0;
}
如图,将a,b,c,d四个变量取地址后存放在数组arr中,这个arr的类型是int*的,所以这个数组arr就是指针数组。
结尾:感谢你能看到这里,如果有写的不对的地方请给位不吝赐教,谢谢啦!