前言:
指针其实就是地址,而凡是存储在内存中的值都会有属于自己的地址,指针指向地址,这样我们就能通过指针间接操作变量。我们在指针初阶中介绍了指针的基本概念:如指针大小、野指针问题、指针间的关系运算等,在我们的指针进阶中,将会对指针进行进一步剖析,见识更深的指针!
正文:
我们将在指针进阶中学习各种各样指针,比如字符指针、数组指针、函数指针等,这些指针种类虽多,但能力都很强大,作为进阶系列文章,涉及知识多多少少有点难度,但我们相信无论多么大的困难都无法阻挡我们的学习之路,因为每个人的潜力都是无限的,相信自己!
知不足而奋进,望远山而前行!!!
大家加油!!!
一、const修饰指针
首先,我们来讲一下const修饰指针的一些作用。
变量其实是可以修改的,如果把一个变量的地址交给一个指针变量,通过指针变量也可以修改这个变量的值,但是如果我们希望一个变量加上一些限制,不能被修改,怎么做呢?这就是const的作用。
#include<stdio.h>
int main()
{
int a=10;
a=20;
printf("%d\n",a);
}
这串代码带印出来就会是20,然后我们如果在int 的前面加上一个const,那a的值便无法被改变
因为const使a具有了常属性,常属性的意思就是不能被修改了。
下面我们来看一下const在指针中的应用。
如果我们非要去该下面a的值,那么可以这样做:
int main()
{
const int a=10;
int*p=&a;
*p=0;
printf("%d",a);
}
如果我们这样写,a的值就可以被修改了。
int main()
{
int a=10;
int const*p=&a;
int * const p=&a;
return 0;
}
再看这串代码,我们可以发现const可以放在*的前面和后面,我们来看一下这两种写法的区别:
当我们这样写的时候,我们可以发现,a的值可以被改变,但是p无法重新赋值,无法将b的地址赋给p。
我们来这样看一下:
我们可以看出,p作为一个指针,其可以存放一个地址,然后p可以指向a,然后p本身还有一个地址。
理解这一点之后,我们再看刚才那串代码,const限制的是p本身,p已经指向a地址,这时我我们想再让p指向b的地址是无法实行的,因为const会限制这一操作。但是我们可以改变a的值。
!!!!重点:const在修饰指针变量时,放在*的右边,限制的是指针变量本身,即p本身,指针变量不能在指向其他变量,但可以修改指针变量指向的内容。
再看另外一串代码:
int main()
{
int a=10;
int b=20;
int const *p=&a;
p=&b
return 0;
}
运行这串代码之后我们发现是没有错误的,那就说明此时指针变量本身是可以改变的,但是指向的内容不可以修改。
!!!!重点:const在修饰指针变量时,放在*的左边,限制的是指针变量指向的内容,指针变量可以再次指向其他变量,但不可以修改指针变量指向的内容。
二、指针运算
指针的基本运算有三种,分别是:
- 指针+-整数
- 指针-指针
- 指针的关系运算
我们再来想一下,指针的作用是什么,通俗的来讲就是访问内存的。
2.1指针+-整数
先来看一下指针+1
int a=10;
int*p=&a;
p+1 -->表面上是加1,但其实是跳过4个字节,
其实就是跳过1*sizeof(type)个字节。
不同的type跳过的字节不同。
如果是char类型,那便是跳过一个字节
接下来我们来考虑一下他有什么作用呢:
我们都知道,数组在内存中的存储是连续的,只要知道1第一个元素的地址,顺藤摸瓜就能找到后面的所有元素。
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int sz=sizeof(arr).sizeof(arr[0]);
int*p=&arr[0];
for(int i=0;i<sz;i++)
{
printf("%d ",*p);
p+=1;
}
return 0;
}
这样我们便可以通过指针的方式来打印这个数组,这便是指针的一个作用。
就像我们上面说的,每次p+1跳过4个字节,来到下一个数组元素,然后打印。
2.2指针-指针
我们再来看一下指针-指针的应用:
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%zd\n",&arr[9]-&arr[0]);
return 0;
}
这样我们便可以得到一个9,这便是指针-指针的应用,
int my_strlen(char*str)
{
int count=0;
while(*str!='\0')
{
count++;
str++;
}
}
int main()
{
char arr[]="abcdef";
//[a b c d e d \0]
int len=my_strlen(arr);
printf("%d\n",len);
return 0;
}
这也是指针-指针的一种应用,大家可以看一下。
2.3指针的关系运算
指针的关系运算其实就是指针和指针比较大小,即地址和地址比较大小。
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);
int*p=arr;//arr[0]
while(p<sz+arr)
{
printf("%d ",p);
p++;
}
}
这里的iwhile语句中,便是指针之间的关系运算。
三、assert断言
对于断言,相信大家都不陌生,大多数编程语言也都有断言这一特性。简单地讲,断言就是对某种假设条件进行检查。在 C 语言中,断言被定义为宏的形式(assert(expression)),而不是函数,其原型定义在<assert.h>文件中。其中,assert 将通过检查表达式 expression 的值来决定是否需要终止执行程序。也就是说,如果表达式 expression 的值为假(即为 0),那么它将首先向标准错误流 stderr 打印一条出错信息,然后再通过调用 abort 函数终止程序运行;否则,assert 无任何作用。
默认情况下,assert 宏只有在 Debug 版本(内部调试版本)中才能够起作用,而在 Release 版本(发行版本)中将被忽略。当然,也可以通过定义宏或设置编译器参数等形式来在任何时候启用或者禁用断言检查(不建议这么做)。同样,在程序投入运行后,最终用户在遇到问题时也可以重新起用断言。这样可以快速发现并定位软件问题,同时对系统错误进行自动报警。对于在系统中隐藏很深,用其他手段极难发现的问题也可以通过断言进行定位,从而缩短软件问题定位时间,提高系统的可测性。
assert(p!=NULL);
例如这行代码,便可以验证p是否等于NULL,如果不等于NULL,程序继续进行,如果为假,返回值则为0,assert()就会报错
assert()的使用对程序员十分友好,使用它有以下几个好处,
它不仅能自动标识文件和出现的问题的行号,还有一种无需更改代码就能开启或关闭assert()的机制,如果已经确认程序没问题,不需要再做断言,就在#include<assert.h>语句前面,定义一个宏NDEBUG就可以了
看一下下面两张图片:
这样我们便很容易的可以搞清楚assert()断言的作用。
assert 出现报错的时候,直接就会报错,指明什么文件,哪一行
并且关闭assert之后,在Release版本中,不会影响运行效率。
#include<stdio.h>
int my_strlen(char* str)
{
int count = 0;
assert(str != NULL);
while (*str != '\0') {
count++;
str++;
}
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d", len);
return 0;
}
这样写可以防止传参是=时传的是空指针。
四、指针的使用和传址调用
写一个函数,交换两个函数的内容
#include<stdio.h>
void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d b=%d", a, b);
swap(a, b);
printf("交换后:a=%d b=%d\n", a,b);
return 0;
}
首先我们看一下上面的代码,看似是可以的,我们运行一下:
我们发现a与b的值并未改变,这是为什么呢, 那是因为我们在调用swap函数时,值虽然能传过去,但是只有x与y的值发生互换,无法改变a与b的值,x与a的地址不同,y与b的值也不同,所以他们不会改变a与b的值,那该怎样去写呢?
上面我们其实就是在传值调用,即传过去a与b的值,下面我们将通过传址调用来改变a与b的值。
#include<stdio.h>
void swap(int *pa, int *pb) {
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d b=%d\n", a, b);
swap(&a, &b);
printf("交换后:a=%d b=%d\n", a,b);
return 0;
}
这样便可以实现a与b值的交换。
总结:
本次我们对指针进阶的一部分内容进行了阐述,如const修饰指针,assert断言部分都是比较重要的,希望对大家有所帮助,对哪里有疑惑的话可以在评论区留下你的疑惑。