作者:阿辉不一般
你说呢:不服输的你,他们拿什么赢
专栏:爱上C语言
作图工具:draw.io(免费开源的作图网站)
如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持博主,如有不足还请指点,博主及时改正,感谢大家支持!!!
大家好啊!今天阿辉将为大家介绍C语言的指针部分,包括认识指针、指针变量、指针运算、const关键字、assert断言以及二级指针,关注阿辉不迷路哦 ,内容干货满满,接下来就跟着阿辉一起学习吧
在讲指针前,我们先了解一下内存
我们在购买电脑前都会很在意电脑的内存大小
内存是计算机存储数据的一种方式,它可以存储正在运行的程序和数据
如果内存容量太小,会导致计算机不能同时运行多个程序或运行大型应用程序时速度变慢。因此,在一定程度上,内存越大可以提高计算机的性能和运行速度
现在电脑普遍内存大小为4GB、8GB和16GB几种,这么大的内存,我们怎么样精确的找到我们想要的数据呢,这时智慧的科学家就把每个字节(byte也就是8个bit位)大小的空间都编上号,通过编号来精确定位,而这个编号就是指针也就是地址
针指变量又是什么?别慌我们接着看
既然是变量,那它就也有类型,只不过指针变量存的是地址罢了
type * p;
p是指针变量的名字
这里的type指的是所指向的数据类型是type
type可以是int、char、float、double、int[] ....
而这颗 * 表示p是指针
type*这俩一起表示p的类型
不管什么类型的指针,指针都是用来存放地址的,而指针变量开辟空间的大小也取决于地址
那么指针的类型有什么用呢,别急我们接着看
栗子
int main()
{
int a = 10;
&a;
return 0 ;
}
取地址操作符(&) 取出的是数据存储的在低地址位置的地址,因为很多数据并非一个字节,如short、int、doubt等
并不需要取出每个字节的地址
上述代码中&a
的地址就是0x00BFFC44
我们知道指针是用来存储地址的,那指针到底有什么用呢?
其实指针可以通过解引用操作,由其存储的地址找到所指向的内存空间,从而改变该块空间的内容
上述例子中取出
a
的地址赋给指针p
,然后通过对p
解引用操作找到a
,*p
就等价于a
,*p = 1
也就是a = 1
所以打印出来的a的值为1
解引用操作与取地址操作互为反操作
跟字面意思一样,就是指针变量加上或者减去一个整数
ptr + n;
ptr - n;
ptr是指针变量,n为整数
那它有什么用呢?这里就与指针的类型有关了,指针的类型决定了指针加减整数跳过的空间大小
栗子
int main()
{
int a = 0;
char c = 0;
double d = 0.0;
int* pa = &a;
char* pc = &c;
double* pd = &d;
printf("pa = %p\n", pa);
printf("pa + 1= %p\n", pa + 1);
printf("pc = %p\n", pc);
printf("pc + 1= %p\n", pc + 1);
printf("pd = %p\n", pd);
printf("pd + 1= %p\n", pd + 1);
return 0;
}
上述图中我们可以看到,
int*
类型指针跳过4个字节,char*
类型指针跳过一个字节而double*
类型指针跳过8个字节(加减整数就是跳过该整数倍类型的空间大小,比如int*
就是跳过8个字节)
结论:指针的类型决定了指针向前或向后移动一步的距离
指针减指针得到的是两个指针之间的元素个数
栗子
切记指针与指针相减俩指针必须是同一块空间
//指针的关系运算
#include
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;
}
我们创建变量都会去给变量初始化,否则变量里面存的就是随机值,而如果指针创建没有初始化会造成野指针,野指针会导致程序崩溃或者产生难以调试的bug
导致野指针的几种情况
栗子
#include
int main()
{
//指针未初始化
int* p1;//指针未初始化
*p1 = 5;//在不知道指针指向的空间在哪时,强行改变指向空间的地址
//指针越界访问
int arr[5] = { 1,2,3,4,5 };
int i = 0;
int *p2 = arr;//p指向数组的首地址
for (i = 0; i <= 10; i++)
{
printf("%d\n", *(p2++));
}
//当p2指向的空间超过数组的最后一个元素时,指针就非法访问,即越界访问了未知空间
//指向的空间被释放了
int j = 0;
for (j = 0; j < 5; j++)
{
int a = 5;
a += 1;
}
int* p3 = &a;//当循环结束之后,局部变量a向内存申请的空间就被释放了。
//此时p3指针指向的空间就是已经被释放掉的空间
return 0;
}
我们可以通过以下几种方式来规避野指针
NULL
)当变量被const
修饰后,该变量就具有了常属性
,本质依然是变量但是不能被修改
栗子
如上图,在vs2022编译器上变量被const
修饰后,如果变量在赋值操作符左边会直接报错
我们来看看一种神奇的操作
a
虽然被const
修饰了,但是照样被改了
我们发现我们不能直接改a
的值,但是我们可以通过指针间接改掉a
的值
我们也可以给指针也用const
修饰,但是const
修饰指针复杂了一丢丢,我直接上代码
int main()
{
const int a = 0,b = 0,c = 0;
下面这两种const都在*左边表示不能通过解引用操作更改所指向空间的内容
但是可以把p1和p2内存的地址给改了
const int* p1 = &a;
int const* p2 = &b;
下面这种表示const在*右边表示不能更改p3的值,就是不能把p3内存的地址给改了
但是可以通过解引用操作更改c的值
int* const p3 = &c;
return 0;
}
结论:const修饰指针变量的时候
*
的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变*
的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变assert.h 头文件件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”
例如:
assert(p != NULL);
上面代码在程序运行到这一行语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示
assert()的好处:
例如:
#define NDEBUG
#include
assert() 是一种在程序中插入的断言,用于在运行时检查特定条件是否为真。其缺点之一是引入额外的检查,可能会增加程序的运行时间。在开发过程中,可以在调试版本中使用 assert() 来帮助程序员排查问题,而在发布版本中禁用 assert() 来提高程序的效率。在一些集成开发环境中,如Visual Studio,发布版本中通常会自动优化掉 assert()。因此,使用 assert() 需要在调试和发布版本之间进行适当的管理,以兼顾程序员的开发需求和最终用户的使用效率。
二级指针就是存储指针变量地址的指针
int a = 1![请添加图片描述](https://img-blog.csdnimg.cn/d52ded127def45478ddafeabedd09966.png)
0;
int * p = &a;
int* * pp = &p;
两颗*指的是pp是一个二级指针
左边的int*是pp所指向的变量的类型是int*
int* *是pp的类型
*(pp)等价于p
*(*(pp))等价于a
图解
不仅有二级指针还有三级、四级、五级指针等等,不过一般最多用到三级指针,更高的用不上
到这里,阿辉今天关于指针的基础知识分享就结束了,不过这一篇仅仅是深入指针的基础,希望这篇博客能让大家有所收获, 如果觉得阿辉写得不错的话,记得给个赞呗,你们的支持是我创作的最大动力