对指针解析(1)总结
上节我们介绍了创建指针变量和指针变量类型及其意义,指针加减整数和指针减指针的内容,还有一个小小的应用。
指针变量是存储地址的变量,需要用到&符号,名叫取地址操作符,创建指针变量时需要给定变量类型变量名,和赋初值,如果不赋初值会导致一个野指针的问题,关于野指针,本节会介绍到。
void fun(int a, int b)
{
return a - b;
}
int main()
{
int a = 1;
int* i = &a; //整型指针变量
char* c = NULL; //字符指针变量,不知道赋值什么时,可以给一个NULL指针
int arr[5] = { 0 };
int(*arr)[5] = &arr; //数组指针,
void (*pf)(int,int) = fun; //函数指针
return 0;
}
对于上面代码,列举了一些简单的指针变量,最后两个指针类型后面会介绍到。
指针变量的类型的意义是:
1,决定指针进行解引用操作访问的字节数目。
2,决定指针进行加减操作跳过的字节数目。
举个例子
int main()
{
int a = 0x11223344;
int* p = &a; //p是一个整型指针变量,那么解引用会访问四个字节
char* p1 = (char*)&a; //p1是一个字符指针变量,解引用会访问一个字节
printf("%x\n%x", *p, *p1); //%x是打印十六进制的数字 打印结果应该是11223344和44
return 0;
}
这个代码可以说明意义一,如果有的小伙伴打印结果是11223344和11的话,说明你的电脑在进行数据的存储时是按照大端字节序的方式存储,关于大小端的介绍大家可以看这一篇[关于大小端的介绍],里面对大小端进行了介绍以及如何测试大小端都有介绍。(https://blog.csdn.net/qq_74245477/article/details/134908862?spm=1001.2014.3001.5501)
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p = arr; //这里没有加&的原因是:数组名是首元素的地址
for (int i = 0; i < 5; i++)
{
printf("%d ", *(p + i)); //p是一个整型指针,加1会跳过四个字节,也就是一个整型
//那么p+i,会跳过i*4个字节,而arr是一个存储整型数据的数组
//那么每个数据就是四个字节,指针加整数可以跳过数据,在使用循环就可以打印数组内容了
}
return 0;
}
对意义二也进行了举例
对于指针减指针,这里要纠正一下指针解析(一)里的一个错误,指针相减的结果的绝对值是指针之间元素的个数,因为元素个数总不能有负的吧。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr; //首元素的地址
int* p1 = &arr[9]; //尾元素的地址
printf("%Id\n", p1-p); //打印9
printf("%Id ", p-p1); //打印-9
return 0; //指针减指针的结果的绝对值是两个指针相差的元素个数
}
对上节内容就复习到这里了,接下来介绍新的内容
一,二级指针
对于变量都有地址,可以创建指针变量存储它们的地址,那么指针变量也是变量,存储指针变量的地址的指针叫什么呢?哎,就是二级指针了
int main()
{
int a = 1;
int* p = &a; //p存储的是整型变量的地址,属于一级指针
int** pp = &p; //pp存储的是一级指针变量的地址,属于二级指针
return 0;
}
同样,我们解释一下代码的含义,int** pp=&p这句代码,第二个*的含义是说明pp是一个指针变量,第一个*和int组合在一起成为int*,说明pp这个指针变量存储的是一级指针变量的地址。
像这样的,存储一级指针变量地址的指针变量称为二级指针。那么存储二级指针变量地址的指针变量就称为三级指针,二级以上的指针称为多级指针。
不过三级及以上的指针用的很少,大家了解下就可以啦。
二,assert断言
assert断言是一种判断错误的一种方式,但是比较暴力,当出现某些不确定或者我们不可掌握的错误,会选择使用这种方式。
函数原型是
void assert (int expression);
括号内部是一个整型表达式,表达式的值为真,则无事发生,如果为假,则会报出错误并中止程序。使用时需要包含一个头文件assert.h
#include<assert.h>
int main()
{
int* a = NULL;
assert(a != NULL);
return 0;
}
上面代码就会报出错误,NULL!=NULL显然为假。
也可以是如下的形式使用
#include<assert.h>
int main()
{
int* a = NULL;
assert(a == NULL); //也可以是下面的方法
if (a == NULL)
{
//.....
exit(); //代表终止程序
}
int i = 5;
int b = 10;
assert(i < b);
printf("%d", i);
return 0;
}
assert断言和if语句判断效果类似,但是断言会报出详细的错误出处,而if语句判断只会终止程序,上面代码是不会打印i的值的。
三,野指针
野指针是指指向不确定的空间的指针。
野指针的成因:
1.指针没有初始化
2.指针越界访问
3.返回局部变量的地址
4.指向已经释放的空间
接下来对这几种情况进行举例。
int* fun()
{
int a = 10;
return &a;
}
int main()
{
int* a;
int arr[10] = { 0 };
int* p = arr;
for (int i = 0; i <= 10; i++)
{
printf("%d ", *p);
p++;
}
int* pf = fun();
printf("\n"); //作用是覆盖上一层栈帧
printf("%d", *pf); //这里并不会打印10,打印的是随机值
}
a是一个未初始化的指针变量,如果打印a指向的值,vs就会报错,因为不知道a是指向哪一块空间的。
p是一个指向数组的指针,打印后进行p++操作,依次指向数组的每一个元素,依次打印,但是数组只有十个元素,p++了十一次,会访问数组后面的那四个字节的内容,这一块内容是不属于数组的,如果还去访问就会造成越界。
最后一个返回了fun函数内的a的地址,在通过pf接收,打印,这样pf就是一个野指针。局部变量出了作用域就销毁了,内存就还给操作系统了,这样再去访问就构成非法访问。
关于最后的指向已经释放的空间,后期解释动态内存开辟时会介绍。
那么如何规避野指针呢?
首先一定要初始化,如果不知道初始化内容,则可以给NULL指针。二,小心指针越界。三,避免返回局部变量的地址。四,在使用指针之前进行检查,方法就是上面的assert断言和if语句判断了。
本小节就介绍这些了,下节见。