在学习指针的时候,我们经常会看到下面这段代码:
- int main()
- {
- int a = 10;
- int* pa = &a;
- *pa = 20;
- }
之前并没有接触过指针的朋友们看到后可能是一头雾水,根本不知道从哪里去理解;下面我们就通过一些场景慢慢的去理解:
试想一下:如果你要给你的好朋友寄过去一些好吃的,然而你并不知道地址,这时你也许就会很懊恼;但如果你知道了你的好朋友的地址,你就可以通过快递把这些好吃的送到你朋友的身边。
把我们日常所说的地址类比到计算机,其实那些地址对应的就是计算机内存中的内存单元编号,也就是我们所说的指针。
比如说一栋宿舍楼,你在303号房间,你的朋友在605号房间,那么你就需要通过楼层和门牌号来找到你朋友所在的位置。
内存单元编号 = 地址 = 指针(指针变量,习惯于称为指针)。
指针就是存放地址的变量;下面通过一幅图来更好的理解上面的代码:
* :解引用操作符,也叫间接访问操作符。
*pa就是找到0x0012ff40这片地址空间存储的数据,进而可以进行打印和修改。
这里还涉及到一个大小端存储的问题,我们所用到的vs2019是小端字节序存储(低位数据存储在低地址),把存储的地址倒着读取就是a的地址。
- int main()
- {
- printf("%d\n", sizeof(int*));
- printf("%d\n", sizeof(char*));
- printf("%d\n", sizeof(long*));
- printf("%d\n", sizeof(long long*));
- printf("%d\n", sizeof(float*));
- printf("%d\n", sizeof(double*));
- return 0;
- }
运行结果:
这里有很多人会有疑问:为什么结果是一样的,并且大小都是4?
那么接着往下看:
对于32位机器,假设有32根地址线,那么32根地址线产生的地址就会是:
1个字节 = 8个比特位;所以把这32个字节存储起来需要4个比特位;sizeof(指针变量)在32位机器下的结果就是4。
在64位机器下,地址是由64根地址线组成的,和32位机器下存储的道理是一样的——指针的大小是8个字节;
1.在32位机器下,指针的大小是一定的,都是4个字节。
2.在64位机器下,指针的大小也是一定的,都是8个字节。
指针也是变量,也是有类型的:
- int a = 10;
- int* pa = &a;//pa指向的空间时int类型,pa是一个整型指针
- char ch = 'a';
- char* pc = &ch;//pc指向的空间时char类型,pc是一个字符指针
不同类型的指针变量解引用访问权限不同,光说不练假把式,下面通过两段程序来进一步了解:
程序1:
- int main()
- {
- int arr[10] = { 0 };
- char* pc = (char*)arr;
- for (int i = 0; i < 40; i++)
- {
- *pc = 'x';
- pc++;
- }
- return 0;
- }
程序2:
- int main()
- {
- int arr[10] = { 0 };
- int* pa = arr;
- for (int i = 0; i < 10; i++)
- {
- *pa = 0x11223344;
- pa++;
- }
- return 0;
- }
通过vs的调试窗口可以发现,int类型的指针解引用时可以访问四个字节,char类型的指针解引用时可以访问一个字节;
指针类型决定了解引用时的访问权限。
不同的指针类型不仅解引用时访问权限有所不同,步长也是不同的:
- int main()
- {
- int a = 10;
- int* pa = &a;
- printf("%p\n", pa);
- printf("%p\n", pa + 1);
- char* pc = (char*)&a;
- printf("%p\n", pc);
- printf("%p\n", pc + 1);
- return 0;
- }
指针的类型决定了指针向前或者向后一步所走的步长;
整型指针一步的步长为4个字节,字符型为1个字节。
野指针:指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
1.指针未初始化
- int main()
- {
- int* p;//局部变量指针未初始化,默认值为随机值
- *p = 20;
- return 0;
- }
2.指针越界访问
- int main()
- {
- int arr[10] = { 0 };
- int* p = arr;
- for (int i = 0; i <= 11; i++)
- {
- //当指针指向的范围超出数组arr的范围时,p就是野指针
- *(p++) = i;
- }
- return 0;
- }
3.已经销毁的栈帧
- int* test()
- {
- int a = 10;
- return &a;
- }
- int main()
- {
- int* pa = test();//出了test函数,栈帧销毁
- printf("%d\n", *pa);
- return 0;
- }
1.指针初始化
2.小心指针越界
3.指针指向空间释放即使置NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性
- int main()
- {
- int* p = NULL;
- int a = 10;
- p = &a;
- if (p != NULL)
- {
- *p = 20;
- }
- return 0;
- }
指针+整数:
- int main()
- {
- int arr[10] = { 0 };
- int* pa = arr;
- for (int i = 0; i < 10; i++)
- {
- *(pa+i) = 10;//指针+整数
- }
- return 0;
- }
指针-整数和指针+整数是相似的,这里就不过多解释了。
指针减指针是有前提的,相减的两个指针必须是连续的,一般常用语数组。
- int main()
- {
- int arr[10] = { 0 };
- printf("%d\n", &arr[9] - &arr[0]);
- return 0;
- }
指针-指针得到的是指针之间元素的个数;
指针减指针实现库函数strlen:
- int my_strlen(char* str)
- {
- char* ch = str;
- while (*str != '\0')
- {
- str++;
- }
- return str - ch;
- }
- int main()
- {
- char arr[] = "abcdef";
- int ret = my_strlen(arr);
- return 0;
- }
首先,我们来了解一个概念——数组名;
数组名:首元素地址;下面两种情况除外;
1.sizeof(arr),arr表示整个数组,sizeof(arr)求出的是整个数组的大小。
2.&arr,此时arr表示整个数组,&arr取出的是整个数组的地址。
&arr和arr打印出的地址是相同的,那我们怎么证明&arr取出的是整个数组的地址呢?
- int main()
- {
- int arr[10] = { 0 };
- printf("%p\n", &arr);
- printf("%p\n", &arr + 1);
-
- printf("%p\n", arr);
- printf("%p\n", arr + 1);
- return 0;
- }
&arr+1所走的步长是40个字节,刚好是数组的大小,说明&arr取出的确实是整个数组的地址。
- int main()
- {
- int a = 10;
- int* pa = &a;
-
- int** ppa = &pa;
-
- return 0;
- }
初阶指针的使用到这里就结束了,对以上内容存在疑问的可以直接私信博主,Fighting! Fighting! Fighting!