目录
指针运算
笔试题17
思考:++*px、*px++和(*px)++的区别!
笔试题18
补充命令8:“cd ..”退回到上一级目录
补充命令9:“man 3 函数名”可以查看库函数的原型
const 修饰指针是什么意思?(笔试重点)
空指针和野指针
指针的初始化
补充:调试段错误
补充命令10:”gcc 文件名.c -o文件名 -g”回车”gdb 文件名”回车”run”回车查看段错误
上一篇复习了指针的定义相关内容,这一篇我们继续来复习指针使用时的注意事项。
说明:我们学过单片机的一般都是有C语言基础的了,网上关于C语言的资料有很多,大家如果对C语言不熟悉的话可以先去详细学一下,再以这篇博文作为复习资料学习。
这篇博文的目的是复习C语言,我们会陆续以30多个编程题作为复习要点,这30多个编程题基本涵盖了C语言所有的内容了,只要你掌握了这30多个编程题,那么你的C语言基本就没什么问题了。
注意:由于本专栏是嵌入式全栈开发专栏,为了我们能熟悉以后实际工作中的开发环境,我们写C语言全部在Linux中的vim编辑器中写,这么做事为了我们能够熟练掌握Linux系统的常用命令以及Linux上的vim编辑器的常用工作命令,以达到对口训练的目的!
vim编辑器的一些工作命令在上一篇博文中已经详细介绍过了,如果不了解可以先去看看。
我们正式开始:
指针运算
指针可以赋值运算
比如定义一个int型的变量a、int*型的pa、in*型的pb:
int a, *pa = &a, *pb ; pb = pa;//pa指向a,pa赋值给pb,则pb也指向a
注意:以上就是一种连续定义变量和指针的一种形式,记住连续定义指针时,每一个指针变量前面都不能漏掉“*”号。连续定义指针的方式如:int* p1, *p2,不可以写成int* p1, p2,如果这样赋值的话p2就是一个int型,而不是int*型
笔试题17
问:y最终等于多少?
int x=3, y=0 , *px = &x;
y = *px +5;
y = ++*px;
y = *px++;
答案是:y最终等于4
解析:
int x=3, y=0 , *px = &x; //px指向x
y = *px +5; //等价于x+5,y=8
y = ++*px; //等价于x+1=4,y=4
y = *px++; //等价于y=x, x+1,即y等于4, x=4+1=5
思考:++*px、*px++和(*px)++的区别!
(1)++*px是*px先加1(也就是x先加1)再赋值;
(2)*px++是*px先赋值(也就是x先赋值),然后px加1,px加1就相等于是x的地址在加1,px加1的话就跳过4个字节,指向x空间的外面了;
(3)(*px)++是*px先赋值(也就是x先赋值),然后*px加1(也就是x加1)。
区分这些有什么用呢?我们现在来感受一下它们的作用......
笔试的时候特别喜欢考让我们写代码实现我前面讲过的一些字符串处理函数。
我们就做一道笔试题:
笔试题18
实现库函数:strcpy
strcpy函数的原型如下:
char *mystrcpy(char *dest, const char *src)
{
}
注:strcpy我的前几篇博文中也讲过这个函数是字符处理函数,它的参数是指针函数(后面会具体复习,先不管),它的作用就是将后一个参数的内容拷贝到前一个参数中,也就是将src中的内容拷贝到dest中。
我们有两种写法:
一种是先判断再赋值,
参考代码:
#include <stdio.h>
void mystrcpy(char*dest,char* src)
{
while(*src!='\0')
{
*dest=*src;
dest++;
src++;
}
*dest='\0';
}
int main()
{
char s1[]="helloworld";
char s2[]="helloworld12345";
mystrcpy(s2,s1);
printf("%s\n",s2);
return 0;
}
运行结果:
一种是先赋值再判断:
参考代码:
#include <stdio.h>
void mystrcpy(char*dest,char* src)
{
while((*dest++ =*src++)!='\0');
}
int main()
{
char s1[]="helloworld";
char s2[]="helloworld12345";
mystrcpy(s2,s1);
printf("%s\n",s2);
return 0;
}
运行结果:
注:传数组的时候有两种写法:
比如
一种是void mystrcpy(char s1[ ])
一种是void mystrcpy(char *s1)
以上这道笔试题最好是记下来,因为它比较经典。
补充命令8:“cd ..”退回到上一级目录
补充命令9:“man 3 函数名”可以查看库函数的原型
比如要查看strcpy的函数原型,可以输入:man 3 strcpy
回车可以看到函数原型
注:想要退出查看函数的界面可以直接按Q
我们看到strcpy的原型,它的参数加了一个const,它的作用是防止我们传过来的s1这个字符数组在函数体中被修改。
这个const一旦加上,那*src就不能被修改了,但是src是可以修改的,因为const修饰的是*src。
现在我们来具体分析这个问题:
const 修饰指针是什么意思?(笔试重点)
const有“就近原则”,也就是靠谁近就是修饰谁;
const char *s1;//修饰*s1的“*”号,那么*s1就不能被修改(即s1指向的内容不能被修改),而这种情况下,s1可以被修改的,比如s1++可以运行,(*s1)++就不能运行
char *const s1;//修饰s1,那么s1就不能被修改,s1++不可以运行,而(*s1)++可以运行
const char *const s1;//“*”号被修饰,s1也const修饰了,因此,s1++和(*s1)++都不能运行
空指针和野指针
1、定义指针的时候,养成初始化的习惯。
比如char *s = NULL; //NULL是空指针,其实就是0,这种指针也不能用
既然空指针不能用,那为什么我们有时候写代码也会定义空指针呢?是因为我们有时候要做判断,比如int *p=NULL; *P=100; if(P!=NULL) 或者if (p==NULL)。
虽然空指针不能用,但是它至少比野指针可控,我们知道它里面是什么东西。而野指针我们不知道它里面是什么东西,野指针所指向的区域到底存不存在我们不知道,指向的区域里面的内容是什么我们也不知道。
2、如果定义指针没有初始化,则变成野指针。
int *p; //如果p是局部变量,则为垃圾值,称为野指针
*p = 100; //访问不能访问的内存(野地址),程序奔溃
注:我们也不能自己去指针的内存地址, 否则它也是野指针,比如int *p=(int*)0x10000; *p=100;像这种写法,也是野指针。
3、正确的使用指针的步骤:
a、定义指针;
b、初始化指针;
c、使用指针。
指针的初始化
1.初始化为已知变量的地址。
int a;
int *p = &a;
2.初始化为字符串常量的地址;
char *s = “helloworld”; //把字符串的地址赋值给指针变量
3.malloc动态申请内存
malloc这个函数可以用来申请堆内存,返回的是一个void*型的地址,我们可以自己强制类型转换成(char*)型的地址 (char *) malloc (),还可以强制类型转换成别的类型。
比如:
char *s = (char *) malloc (sizeof(char) * 128);
注意:申请的内存使用完后要释放掉,否则申请的越来越多就会占据很多内存。
free(s);//释放s指向的那块内存,即释放的是malloc申请的那块内存
补充:调试段错误
如果程序出现“段错误”可以在附近的每一行代码随便打印个什么东西来进行调试,如果运行后某一行的printf没有被打印出来,则问题就可以出现在这行的前面。
比如:
运行
说明段错误就在这附近
或者我们还可以用指令来查看:
补充命令10:”gcc 文件名.c -o文件名 -g”回车”gdb 文件名”回车”run”回车查看段错误
即编译的时候在后面加上-g,然后回车输入”gdb 文件名”回车,然后我们在输入”run”就是让运行起来,再回车,这样我们就能看到编译器给我们报的段错误出现在哪里了。
比如:
运行
查看
以上就是这篇内容,如想了解更多,欢迎订阅本专栏!
如有问题可评论区或者私信留言,如果想要进交流群请私信!