本文共 6595 字,大约阅读时间需要 21 分钟。
入门学习计算机第十四天—结构体,CC语言实用调试技巧
编译器:Microsoft Visual Studio 2010
前言 记录第十三天学习C语言的博客。
结构体
- 结构体类型的声明
- 结构体的初始化
- 结构体成员访问
- 结构体传参
结构体的声明
结构的基础知识
结构是一项值的集合,这些值称为成员变量。结构的每一个成员可以是不同类型的变量
struct tag//结构体标签{ member-list;//成员列表}variable-list;//变量列表
例如:
struct stu//struct-结构体关键字 stu-结构体标签{ char name[10]; short age; char tele[15]; //定义一个结构体类型,不占空间,相当于建房子的图纸 char sex[5];}s1,s2,s3;//是三个全局的结构体变量int main(){ struct stu s; //创建结构体变量,占有实际空间,相当于房子, s是局部变量 return 0;}
当在struct stu 前面加一个typedef,可以对整个类型,重新起名字,在之后的创建结构体变量时可以简化。比如:
typedef struct stu{ char name[10]; short age; char tele[15]; char sex[5];}Stu;//类型int main(){ Stu s ; return 0;}
结构成员的类型
结构的成员可以是标量,数组,指针,甚至是其他结构体。结构体变量的定义和初始化
typedef struct stu{ char name[10]; short age; char tele[15]; char sex[5];}Stu;typedef struct resume{ Stu s; char homeadd[20]; char *pc;}Res;int main(){ char arr[]="白云校区"; Res s ={ { "张三",20 , "18576854961", "保密"},"广师大",arr} ;//初始化 printf("%s\n",s.s.name); printf("%s\n",s.homeadd); printf("%s\n",s.pc); return 0;}
结构体传参
typedef struct stu{ char name[10]; short age; char tele[15]; char sex[7];}Stu;void Print(Stu tmp){ printf("name: %s\n",tmp.name); printf("age: %d\n",tmp.age); printf("tele: %s\n",tmp.tele); printf("sex: %s\n",tmp.sex);}void Print2(Stu* ps){ printf("name: %s\n",ps->name); printf("age: %d\n",ps->age); printf("tele: %s\n",ps->tele); printf("sex: %s\n",ps->sex); }int main{ Stu s = { "张三",20 , "18576854961", "保密"}; Print(s); Print2(&s); return 0;}
Print与Print2函数对比:
Print函数在传参s时,需要再开辟一块相同的空间,拷贝s的值,空间浪费特别严重,拷贝也需要一定的时间。 而Print2,传的是地址,函数只需创建一个指针变量接收地址,一个指针变量只是4/8字节,不会开辟新的空间,也不要时间。 函数在传参的时候,参数是需要压栈的。如果传递是一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能下降。C语言实用调试技巧
调试的基本步骤:
- 发现程序错误的存在
- 以隔离、消除等方式对错误进行定位
- 确定错误产生的原因
- 提出纠正错误的解决方法
- 对程序错误予改正、重新测试
Debug和Release的介绍
Debug 通常称为调试版本,包含调试信息,而且不作任何优化,便于程序员调试程序。
Release 通常称为发布版本,它往往是进行了优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用
在主页面输入以下函数,确认环境为Debug,运行代码:
运行代码后会产生一个Debug的可执行程序:双击第一个文件,窗口会一闪而过。需要对代码进行修改。
int main(){ int i = 0; for(i=0; i<100; i++) { printf("%d ",i); } system("puase"); return 0;}
再次双击
修改环境为Release,允许代码后,文件夹会产生Release可应用文件。
两者文件最直接的区别就是两个文件的大小不一样,Debug的版本稍大,因为里面有调试信息。两者在功能上也有区别:
int main(){ int i = 0 ; int arr[10]= { 1,2,3,4,5,6,7,8,9,10}; for(i =0; i<=12; i++) { printf("hehe\n"); arr[i] = 0; } system("pause"); return 0;}
在Debug环境下是死循环:
在Release环境下,输出的了13个hehe快捷键
常用快捷键 | 功能 |
---|---|
F5 | 启动调试,经常用来直接调到下一个执行逻辑断点处,通常与F9配合使用 |
F9 | 创建断点和取消断点 |
F10 | 逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句 |
F11 | 逐语句,就是每次都执行一条语句,但是这个快捷键可以进行函数内部(最常用) |
ctrl+F5 | 开始执行不测试,如果想让程序直接运行起来而不调试可以直接使用 |
F5,F9试用
F9设置断点,按下F5
代码如下: F5可以快速跳到断点,并执行断点前的代码。再按下F5,会跳到下一个执行逻辑断点,而不是物理上的断点。F10试用:
按下多次F10,调试箭头并没有进入函数,而是直接到下一个语句,说明F10关注是结果。F11:当按下F11时,箭头会进入函数内部
调试的时候查看程序当前信息
查看临时变量的值
1、自动窗口
在调试的状态下,调试——窗口——自动窗口 当程序调试过程中,自动窗口会自动将运行到的代码处的上下代码变量添加到窗口,不需手动添加,但缺点是,代码运行到其他地方时,某个变量会自动消失在窗口,所以无法一直观察某个变量的变化。2、局部变量
在调试的状态下,调试——窗口——局部变量 一旦出了局部变量的范围,局部变量窗口就自动将变量移出。无法手动添加。3,监视窗口
在调试的状态下,调试——窗口——监视 需要手动添加观察的值,可以一直观察。4,内存
在调试的状态下,调试——窗口——内存 也可以手动添加。调用堆栈
当有这么一个代码:
void test2(){ printf("hehe\n");}void test1(){ test2();}void test(){ test1();}int main(){ test(); return 0;}
调出调用堆栈窗口
函数每次被调用的时候,都是在顶上加入信息。当返回上一级函数时,顶上的信息又是先被移出的。调试实例:
int main(){ int i = 0; int arr[10] = { 1,2,3,4,5,6,7,8,9,10}; for(i = 0; i<=12; i++) { printf("hehe\n"); arr[i] = 0; } return 0;}
输出的结果是死循环的hehe
为什么呢? 对代码进行调试: F10进入调试状态。当i = 10时,此时已经超出数组范围,非法访问:
当i=12时: 发现arr[12]与i的值相等,并且也同时为0了 发现i的地址与arr[12]的地址相等,所以i的值为0,重新进入循环,造成了死循环。为什么i的地址和arr[12]的地址相等呢?
arr[i]非法访问其他空间时,遇到了i的地址,所以当两者相遇时,两者的值也就相等了。
在不同编译器下,i的地址的布局是不相同的。 vs2010 是<=12 vs6.0是<=10 gcc编译器是<=11 如果在vs2010下,将值改为i<=11,运行结果会报错。在Release环境下,代码会实现优化
任何写出好(易于调试)的代码
优秀的代码:
1、代码运行正常
2、bug很少 3、效率高 4、可读性高 5、可维护性高 6、注释清晰 7、文档齐全
常见的coding技巧:
1、使用assert
2、尽量使用const 3、养好良好的编码风格 4、添加必要的注释 5、避免编码的陷阱
示范:
模拟实现库函数:strcpy
初步写:
viod my_strcpy(char* dest , char* src){ while(*src != '\0') { *dest = *src; src++; dest++; } *dest = *src; //打印\0 }int main(){ //strcpy 字符串拷贝 char arr1[] = "#################"; char arr2[] = "bit"; my_strcpy(arr1,arr2); printf("%s\n",arr1); return 0;}
这种代码,面试官看到了只会给6\10分,还可以继续优化
第一次优化:
void my_strcpy(char* dest , char* src){ while( *dest++ = *src++)//简化了++,而且也考虑到了\0 { ; }}int main(){ char arr1[] = "###"; char arr2[] = "bit"; my_strcpy(arr1,arr2); printf("%s\n",arr1); return 0;}
这种代码,面试官看到了只会给7\10分,还可以继续优化
万一别人在使用代码的时候,将参数传错成了NULLvoid my_strcpy(char* dest , char* src){ while( *dest++ = *src++) { ; }}int main(){ char arr1[] = "###"; char arr2[] = "bit"; my_strcpy(arr1,NULL);//在这个地方传成了空指针 printf("%s\n",arr1); return 0;}程序什么也不会输出,说明在传参数的时候是有问题的。 想在运行代码时,可以直接把问题呈现出来,方便对参数进行修改。 所以需要在拷贝字符串之前,进行判断
所以需要用到assert( exp)函数——断言函数,表达式如果为真,代码会继续运行,如果为假,会直接报错。
在使用断言函数时,需要引头文件<assert.h> 第三次优化:void my_strcpy(char* dest , char* src){ assert(dest != NULL); assert(src != NULL); while( *dest++ = *src++) { ; }}int main(){ char arr1[] = "###"; char arr2[] = "bit"; my_strcpy(arr1,NULL);//错误传参 printf("%s\n",arr1); return 0;}
当有空指针传参到函数时,输出的结果如下:
可以很明显的看到 src 的值为空指针,方便对参数修改。 面试官会说:“小伙子不错,给你8分”还有地方可以继续优化
结果发现还是传参的问题,如果有人在使用代码的时候,将目标地址和原地址位置弄反了。// 将while( *dest++ = *src++)错误写成了while( *src++ = *dest++)代码可以顺利运行,但就无法正确打印出来想要的效果,而且也无法直接将错误地方体现出来,可以需要调试进行排除错误,耗时耗力。所以有优化的地方。
第四次优化:
void my_strcpy(char* dest ,const char* src)//在这个地方写const,将* src规定为不可变的量{ assert(dest != NULL); assert(src != NULL); while( *src++ = *dest++) { ; }}int main(){ char arr1[] = "###"; char arr2[] = "bit"; my_strcpy(arr1,NULL);//错误传参 printf("%s\n",arr1); return 0;}
如果写错了,代码就无法直接运行了,问题也是一目了然
现在是9分了!!!!!! 对const 进行补充 const 修饰指针时有两个位置:第一种:const char* p = &a,const 放在 * 左边时,修饰的是p, 也就说不能通过p来改变p(a)的值。
*第二种:char * const p = &a: , const放在 * 的右边,修饰是p本身,p不能被改变了还可以再优化一次,在使用函数的时候,想直接能够看出这个函数是对字符串进行的操作。
将目的地的地址返回:char* my_strcpy(char* dest ,const char* src){ char* ret = dest;//将原来首元素的地址保存起来 assert(dest != NULL); assert(src != NULL); while( *dest++ = *src++)//把src指向的字符串拷贝到dest指向的空间,包含'\0' { ; } return ret;//返回目的地首元素的地址}int main(){ char arr1[] = "###"; char arr2[] = "bit"; printf("%s\n",my_strcpy(arr1,arr2);); return 0;}
加上一些注释,这就是满分答案。
编程常见的错误
编译型错误(语法错误):
直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。 比如:少个{ },或者; 链接型错误: 看错误提示信息,主要是代码中找到了错误信息中的标识符,然后定位问题所在。 比如:未应用头文件,或者函数名写错 运行时错误: 借助调试,逐步定位问题,最困难。计算机知识/代码知识(零碎)
设置断点后,右键断点,设置条件,可以快速停在符合条件的断点处:
转载地址:https://blog.csdn.net/xiaotangyu7dong/article/details/115869579 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!