众所周知,指针是c语言的灵魂,学会指针的运用,将会非常轻松的理解一些难以理解的问题,下面进入正题。
一、什么是指针?
说起指针,我们不妨先从日常生活说起,我们去一个陌生的地方,这个地方有许多房间,我们要去其中一个房间,假设什么也不告诉你,你能找到我们要去的房间是哪一个吗?显然不能。所以我们通常要给每个房间编号,我们可以通过这个编号找到具体的房间。计算机中有内存,内存有许多内存单元,每个内存单元大小为1个字节,如果我们定义一个char类型的变量ch,会在内存中申请1字节的空间,但当我们想要找到ch时,该怎么找到它呢?我们可以想象每个内存单元相当于一个房间,我们要找到这个房间,就要给房间编号,同样的我们想要找到具体的内存单元,要给每个内存单元编号,通过这个编号,CPU就可以快速的找到对应的内存空间,我们通常不叫编号,称为地址,我们也给这个地址取了个新名字:指针。所以我们可以理解为:内存单元的编号 == 地址 == 指针 通过指针就可以找到ch。
二、指针的基础
我们了解了指针的概念后,下面说一说指针该怎么使用?C语言中创建变量就是向内存中申请空间。
我们知道a其实是占4个字节的空间,那么我们就会产生一个疑惑,每一个字节都有一个地址,那a的地址该怎么算?其实第一个字节的地址就是a的地址(较小的地址),我们知道了a的地址,该怎么得到它呢?有一种操作符&,它的全称是取地址操作符,我们想要拿出a的地址,只需要&a即可。
#include <stdio.h>
int main()
{
int a = 0;
printf("%p\n",&a);//这里就能取出a的地址
}
怎么得到地址我们应该明白了,但怎么把它存储起来呢?char类型可以吗?显然不行。那用short?int? long? float? double?显然统统不行。这时候指针就站出来了。
#include <stdio.h>
int main()
{
int a = 1;
int* pa = &a;
return 0;
}
这里的pa就是指针变量。指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。至于pa前面的*就是指出它是指针,int就是pa这个指针变量指向的类型是int类型对象。
这就是两者的联系。
这时候相信大家又会产生出来一个疑问,我们把地址存起来,怎么用呢?在现实⽣活中,我们使⽤地址要找到⼀个房间,在房间⾥可以拿去或者存放物品。
C语⾔中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。
#include <stdio.h>
int main()
{
int a = 1;
int* pa = &a;
*pa = 3;
return 0;
}
*pa 的意思就是通过pa中存放的地址,找到指向的空间,*pa其实就是a变量了;所以*pa=3,这个操作符是把a改成了3。解引用操作符未来有很大用处,逐渐的我们会体会到的。
接着,指针变量的大小是多少?我们知道指针是用来存放地址的,那么指针变量的大小就是地址大小,在32位机器下,地址是32bit位的,所以指针变量的大小就是4字节,在64位机器下,指针大小就是8字节。注意指针变量的大小和类型是⽆关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
三、指针变量类型的意义
我们又学习到了指针的大小和平台有关,那么char* ,short* ,int*等,它们有什么区别吗?下面请看两端代码:
代码一中pa是int*,它解引用会访问4个字节,通过调试我们可以看到将44 33 22 11改成00 00 00 00
代码二中pa是char*,它解引用会访问1个字节,通过调试我们可以看到将44 33 22 11改成00 33 22 11
这就是它们的区别。
结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。
其次,指针也可以加减整数。我们看下面代码:
pa是int*,pa+1地址变化了4字节。
pc是char*,pc+1地址变化了1字节。
结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。
四、指针的运用
我们知道数组名是首元素地址,数组中的元素是顺序存储的,那么我们就可以用指针来访问整个数组。请看下面代码:
#include <stdio.h>
int main()
{
int arr[]={0,1,2,3,4,5,6,7,8,9};
int sz=sizeof(arr)/sizeof(arr[0]);
for(int i=0;i<sz;i++)
{
printf("%d ",*(arr+i));
}
printf("\n");
return 0;
}
这段代码就可以看出指针的"妙用",arr是数组名即数组首元素地址,arr+0就是数组中第一个元素的地址,arr+1就是指向数组中第二个元素的地址,以此类推可以找到数组中每个元素的地址,再通过解引用操作符找到数组所有的元素。这段代码也反应出了其实arr[i]与*(arr+i)是等价的,在这个数组中,arr+5是元素5的地址,arr是元素0的地址,arr+5-arr就是这两个指针之间元素的个数,但前提是在同一个数组中,我们可以利用这点,处理一些问题。
两个指针也可以进行比较。请看代码:
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
while(p<arr+sz) //指针的⼤⼩⽐较
{
printf("%d ", *p);
p++;
}
return 0;
}
在while循环中,我们可以利用指针的大小比较来控制打印数组中的元素。
五、使用指针会遇到的问题
下面介绍一个特殊的指针---野指针,所谓野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
我们知道使用指针可以用来指向一片内存空间,在生活中,我们去酒店开房间会得到一个房间号,假设是101,我们通过房间号找到属于我们居住的房间,但如果你去了102,这肯定不行,你会被赶出来的,甚至会骂你是“流氓”,所以你要去到属于你自己的房间,指针也一样,你可以指向一片内存空间,但你不可以指向不属于你的内存空间,这就会“违法”,如果指向不属于你的内存空间,就称其为“野指针”。
野指针有以下几种:
1.指针未初始化就解引用,具体代码如下:
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
我们并不知道p指针指向的位置就冒然解引用操作,此时p就是野指针。
2.指针越界访问,代码如下:
#include <stdio.h>
int main()
{
int arr[5]={1,2,3,4,5};
int* p=&arr[0];
for(int i=0;i<=5;i++)
{
printf("%d ",*(p+i));
}
printf("\n");
return 0;
}
想必大家对这段代码已经熟悉了,当p指针为p+5时已经发生越界,解引用访问时就会拿不属于我们的“东西”,此时p+5就是野指针。
3.指针指向的内存空间释放,代码如下:
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
当我们调用完test函数时,n的那块空间已经不属于我们了,我们只是记住的它的地址,解引用时就是“违法”的。比如说,你在酒店订了一个房间101,你对你朋友说这个房间很好让他也来,第二天你去退房,过一会你朋友来了,直接进了101房间,这显然是不可以的。你也会被赶出来的。
六、如何解决指针遇到的问题
1.指针初始化
如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL.NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。
2.小心指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性。当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL。
4.避免返回局部变量的地址。
5.assert断言。assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写⼊⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。
在使用指针之前,我们可以把指针变量作为参数给assert,如果指针为空就直接报错。防止我们解引用时,出现的“违法”行为。
七、总结
指针在c语言中是非常重要的,以上的内容只是一个“引子”,帮助大家熟悉指针,大家在未来写代码时还会进一步了解指针。大家也不要“害怕”指针,慢慢熟悉,试着使用,逐渐加深,最终拿捏它。
祝大家天天开心!