一、指针基础(※以32位机器为例※)
指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中某个地方的值。
int a = 10; //在内存中开辟一块空间给a,存储a的值
int *p = &a; //利用&操作符,取出a的地址,存放在p中,p就是一个指针变量
总结:指针就是变量,是用来存放地址的变量。(存放在指针中的值都当成地址处理)
为每一个地址分配一个对应的字节。针对32位机器,有32根地址线,因而可以有2^32个地址。那么编址的空间大小为2^32/1024/1024/1024GB == 4GB。 同理,指针在64位机器上自然就是8个字节。
int main()
{
printf("%d\n",sizeof(char*)); //4
printf("%d\n",sizeof(int*)); //4
printf("%d\n",sizeof(short*)); //4
printf("%d\n",sizeof(double*)); //4
return 0;
}
此时不禁发出疑问,既然指针类型的大小都是4个字节,那么不同指针类型之间有什么区别呢?
① 指针类型如果对应错误,可能在部分编译器内无法通过。
② 指针类型决定了在进行解引用操作时能够访问空间的大小。
int *p; *p能够访问4个字节
char *p; *p能够访问1个字节
double *p; *p能够访问8个字节
所以对于一个int型变量,如果用char*等类型进行访问修改,因为不能完全访问int的所有储存,所以修改不能覆盖到所有空间。
③ 指针的类型决定了指针向前或者向后走一步有多大。
指针+(-)整数 的时候,由于空间大小不同,所以运算所访问的空间也就不同。
int *pa = &a;
char * pc = &a;
pa与pc是相同的,但是pa+1和pc+1就不一样了,前者是int型所以加了四个字节,后者是char型所以加了一个字节。
二、野指针
野指针是指指针指向的位置不可知(随机的、不正确的、没有明确限定的)
野指针形成原因
1.指针未初始化
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i = 0;i<11;i++)
{
//当指针指向的范围值超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3.指针指向的空间释放
int* test()
{
int a = 10;
return &a;
}
int main()
{
int *p = test();
*p = 20;
return 0;
}
当地址返回后,变量被注销,地址对应的位置不再是该变量,所以成为了野指针。
在编写代码的过程中一定要避免野指针的出现,当需要一个指针变量但却暂时不需要赋值时,可以置为空:
int *pa = NULL;
三、指针运算
1.指针加减整数
指针加减整数根据类型而定访问下一个地址,在上文已经介绍。
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
int* p = arr;
for(i = 0;i<sz;i++)
{
printf("%d ",*p);
p++;
}
return 0;
}
2.指针减指针
指针之间的相减的结果是两个位置之间所相隔的元素个数。多用于单个数组内部元素的计算,因为数组的存储是连续的,如果让两个数组之间作差,那么因为不连续所以结果不可知、无意义。
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
char ch[5] = {0};
printf("d\n",&arr[9] - &arr[0]); //9
printf("d\n",&arr[0] - &arr[9]); //-9
printf("d\n",&arr[9] - &ch[0]);//error
return 0;
}
通过指针减指针的方法模拟strlen函数
int my_strlen(char* str)
{
char* start = str;
char* end = str;
while(*end != '\0')
{
end++;
}
return end - start;
}
int main()
{
char arr[] = "hello";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
3.指针的关系运算
指针之间可以进行关系运算(即大小比较),判断地址的大小。
四、指针和数组
一般情况下,数组名实际上是数组首元素地址。arr <=> &arr[0]
※有两个例外:
1)&数组名:数组名表示的是整个数组,取出的是整个数组的地址。
2)sizeof(数组名):数组名表示的是整个数组,计算的是整个数组的大小。
int main()
{
int arr[10] = {0};
printf("%p\n",arr); //00EFF8E0
printf("%p\n",arr+1); //00EFF8E4
printf("%p\n",&arr[0]); //00EFF8E0
printf("%p\n",&arr[0]+1); //00EFF8E4
printf("%p\n",&arr); //00EFF8E0
printf("%p\n",&arr+1); //00EFF908
return 0;
}
通过代码运算结果,可以发现对arr与&arr[0]进行+1运算,地址变大了4(即一个int空间),而&arr进行+1运算,地址变大了40(arr数组的空间)。
五、二级指针
一个指针变量可以存一个变量的地址,那么创建这个指针变量也需要向系统申请一块空间,所以指针变量也应该具有对应的地址,那么把这个指针变量的地址存在另一个指针变量内,这个指针变量就应该是二级指针。同理可以得到三级等的指针。
int a = 10;
int* pa = &a;
int** ppa = &pa; //二级指针
int*** pppa = &ppa; //三级指针
六、指针数组
顾名思义,就是由指针组成的数组。
int a = 0,b = 0,c = 0;
int* pa = &a,pb = &b,pc = &c;
int* arr[3] = {pa,pb,pc};
七、const修饰指针变量
const修饰指针有两种情况:
1)const 放在指针变量的*左边: const int* p = #
修饰的是*p,使得*p成为只读变量,不能通过p来改变*p(num)的值。
2)const 放在指针变量的*右边: int *const p = #
修饰的是指针变量p本身,使得p成为只读变量,不能再改变p。
#include <stdio.h>
#include <assert.h>
int my_strlen(const char* str)
//const 修饰*str,使得不会在函数内通过指针的方式改变原指向对象变量赋值
{
int count = 0;
assert(str != NULL); //断言,以保证指针的有效性,当条件为假时会报错
while(*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
本文为学习C语言心得与笔记记录,部分举例来源于B站C语言教学up主鹏哥