一、指针是什么?
总结 二、指针和指针类型三、野指针
1、野指针的成因:
(1)指针未初始化(2)指针越界访问(3)指针指向的空间释放 2、如何避免野指针?
(1)指针初始化
case1:当前不知道指针应该初始化成什么地址的时候,直接初始化为空指针NULLcase2:明确知道初始化的值 (2)小心指针越界(3)指针指向空间被释放后,及时将指针置空为NULL(4)指针使用之前检查有效性 四、指针运算
1、指针 `+-` 整数2、指针 `-` 指针3、指针的关系运算(比较大小)
case1:case2:(不提倡) 五、指针和数组六、二级指针七、指针数组
一、指针是什么? 在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)电脑存储器中另一个地方的值。由于通过地址可以直接找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
我们把内存划分为一个一个的内存单元,取一个字节为一个基础的内存单元。同时为了能更好的管理这些内存单元,我们设置了编号,也称为内存单元的“地址”,如:0x00000001。因为地址指向这些内存单元,所以也把这些地址称为“指针”。
1.指针是用来存放地址的,地址是唯一标识一块地址空间的。
2.指针的大小在32位平台是4个字节,在64位平台是8个字节。
我们发现不论指针是什么类型的,它都是占4个字节。但是我们不能建立一个通用指针,用一种指针类型表示所有的指针类型。那么指针的类型有什么用呢?
指针类型的意义:
1、指针类型决定了:指针解引用的权限有多大(能操作几个字节)。
如:int类型指针解引用能访问4个字节,而char类型指针解引用只能访问1个字节,double类型指针解引用能访问8个字节。当你要从某一个地址向后访问一个整型的时候,我们最好拿一个整型指针int*来解应用,因为整型指针刚好能访问4个字节。
将数组arr的地址放到p和pc中,p是int*指针,pc是char*指针。arr是整型数组,放到p中是没有问题的,pc虽然是字符型指针,但是如果强行把arr的地址放进去也能存的下。所以不管写成什么指针,它里面都能存放地址。
我们看到因为p和pc放的都是数组名,所以两者的地址是一样的。整型指针+1相当于跳过一个整型,p+1和p的地址会相差4个字节,所以p的地址会+4;而字符型指针+1相当于跳过一个字符,pc+1和pc相差1个字节,所以pc的地址会+1。
2、指针类型决定了:指针走一步能走多远,步长会是多长。
如:double型的指针+1,地址会+8。p+1到底加几取决于p的类型,跟p指向的对象没有关系,跟p被赋予的值也没有关系。
概念:野指针就是指针指向的位置是不可知的(随机的、不确定的、没有明确限制的)。
1、野指针的成因: (1)指针未初始化int main()
{
//这里的p就是一个野指针
int* p;//p是一个局部的指针变量,局部变量不初始化的话,默认随机值
*p = 20;//非法访问内存了
return 0;
}
p是一个局部的指针变量,放的是一个随机的地址。*p是对p进行解引用操作,访问p指向的地址,但是这个地址不一定是属于我们当前的程序。所以造成了非法访问内存。
这部分代码是无法正常运行的。
(2)指针越界访问int main()
{
//2、越界访问
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
*p = i;
p++;
}
return 0;
}
从 i = 0 时开始,只要 i <= 10,for循环就会继续下去,总共会循环11次。
当循环第十次后,*p = 9,指针p已经访问到数组边界了,i = 10。因为10满足循环条件,所以进入第十一次for循环,*p = 10,指针p已经越界,它所访问的4个字节不属于数组arr[] ,p就是一个野指针了。
int* p = NULL;case2:明确知道初始化的值
int a = 10; int* ptr =&a;(2)小心指针越界
C语言本身是不会检查数据的越界行为,只能依靠我们自己在编写代码的时候尽可能得保证代码的准确。
(3)指针指向空间被释放后,及时将指针置空为NULL (4)指针使用之前检查有效性 指针初始化时若不知道指向哪里,可以设置为空指针NULL,但是这并不是万无一失的。
如:
int* p = NULL; *P = 10;//此时依然会出问题,程序会卡死
任何一个指针在使用之前都可以检查其有效性。
int* p = NULL; if(p != NULL)//当p不是空指针的时候才会使用它 *P = 10;四、指针运算 1、指针 +- 整数
#define MaxSize 5
int main()
{
float values[MaxSize];
float* vp;
printf("values[0]的地址:%pn", &values[0]);
printf("values[1]的地址:%pn", &values[1]);
printf("values[2]的地址:%pn", &values[2]);
printf("values[3]的地址:%pn", &values[3]);
printf("values[4]的地址:%pn", &values[4]);
printf("values[5]的地址:%pn", &values[5]);
for (vp = &values[0]; vp < &values[MaxSize];)//指针vp在for循环中初始化
{
*vp++ = 0;
}
return 0;
}
当vp中的地址小于values[5]的时候就会进入for循环中。在*vp++ = 0;中,*vp++的结果是vp当前指向的地址,我们把0放进去,然后*vp再+1。*vp没有访问values[5]就不算越界。
for循环的调整部分(第二个逗号后面的内容)可以不写,直接继续进行循环判断。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%dn", &arr[9] - &arr[0]);
return 0;
}
运行结果:
&arr[0]指向数组arr的第一个元素,&arr[9]指向数组第10个元素。除了10在指针后面,其余共9个元素都在这两个指针之间。
指针-指针得到的是两个指针之间的元素个数。
若改成两种不同类型的指针,则:
虽然会算出一个值,但是系统会警告从“char *”到“int *”的类型不兼容,是错误写法。因为两个指针相减得到的是中间元素的个数,但是得到的究竟是字符型的个数还是整型的个数不好说。所以指针和指针相间的前提是:两个指针指向同一块空间。
for (vp = &values[MaxSize]; vp > &values[0];)
{
*--vp = 0;
}
vp被赋值为values[5]的地址,这是数组values后面那个元素的地址。如果vp > values[0]的地址的话,就进入for循环。
然后vp先自减1,指针前移一个位置,在解引用并赋值0。
紧接着,循环再上来,但是调整部分什么都没写,所以进行循环判断,发现vp仍大于values[0]的地址,所以–vp,然后解引用并赋值。以此类推,直至
当vp指向values[1]的地址的时候,仍然大于values[0]的地址,进入循环体。先–vp,使得vp指向values[0]的位置,然后解引用把0放进去。此时数组values[]的5个元素全为0了。
这时,vp不大于values[0]的地址,所以停止for循环
for (vp = &values[MaxSize-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
开始的时候vp指向values[4]的位置,然后*vp解引用并赋值。循环上来vp–,vp指针前移。
以此类推,当vp指向values[0]的地址时,解引用并赋值。然后循环上来,vp–,vp指针前移。
如果前面有元素的话,vp指针应该指向下列位置。此时vp要小于values[0]的地址,跳出循环。
case2的代码实际上在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不能与指向第一个元素之前的那个内存位置的指针进行比较。
什么是数组名?
int main()
{
int arr[10] = { 0 };
printf("%pn", arr);//数组名是数组首元素的地址
printf("%pn", &arr[0]);//数组首元素的地址
return 0;
}
我们可以看到二者的地址是完全一样的。
arr[2] <==> *(arr+2) <==> *(p + 2) <==> *(2 + p) <==> *(2 + arr) <==> 2[arr] //这几个是一样的六、二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里呢?这就是二级指针
int main()
{
int a = 10;
int* pa = &a;//pa是指针变量,一级指针。表明pa指向了a
//ppa就是一个二级指针变量
int* *ppa = &pa;//pa也是个变量,&pa取出pa在内存中的起始地址
return 0;
}
pa是指针变量,一级指针;ppa是二级指针。
int* pa = &a;中*说明pa是指针,int说明pa指向的a是int类型。而且pa也是变量,也要在内存中开辟空间。我们使用&pa获得pa在内存中的地址。
int* *ppa = &pa;中,靠近ppa的*说明ppa是指针且ppa指向的是pa,而pa的类型是int*。在这行代码中,我们可以写成int* *ppa = &pa;,也可以写成int** ppa = &pa;,还可以写成int **ppa = &pa;或者int ** ppa = &pa;它们只是书写上的区别,代码的意义是一样的,采用哪种写法全凭个人喜好,习惯上采取后两种。
我们把a的地址取出来放到pa中,把pa的地址取出来放到ppa里。(有点像俄罗斯套娃)
问题:如何用ppa找到a?
答:我们对ppa解引用*ppa,找到pa。所以*ppa 等价于 pa。同理*pa等价于a。因此,使用* *ppa就能找到a。
而且我们也可以对指针变量ppa取地址,存放到pppa中int** *pppa = &ppa;这样pppa就是三级指针,以此类推下去。但在实际应用中连三级指针都很少用。像pa这样的一级指针最常用。
int main()
{
int arr[10];//arr中有10个元素,每个元素都是int类型。所以数组arr是整型数组——存放整型的数组就是整型数组
char ch[5];//字符数组——存放的是字符
int* parr[5];//parr是一个数组,也是数组名,里面有5个元素,每个元素的类型都是int*。所以parr是一个整型指针的数组。
return 0;
}



