6. 如何写出好(易于调试)的代码
6.1 优秀的代码:
- 代码运行正常
- bug很少
- 效率高
- 可读性高
- 可维护性高
- 注释清晰
- 文档齐全
常见的coding技巧:
- 使用assert
- 尽量使用const
- 养成良好的编码风格
- 添加必要的注释
- 避免编码的陷阱
6.2 示范:
模拟实现库函数:strcpy
我们先来看一下strcpy是如何使用的:
#include <stdio.h> #include <string.h> int main() { char arr1[] = "hello bit"; char arr2[20] = "xxxxxxxxxxxxx"; //strcpy(arr2, arr1); //printf("%s\n", arr2); printf("%s\n", strcpy(arr2, arr1)); return 0; }
接下来我们来实现它:
#include <stdio.h> void my_strcpy(char* dest, char* src) { while (*src != '\0') { *dest = *src; dest++; src++; } *dest = *src;// \0 的拷贝 } int main() { char arr1[] = "hello bit"; char arr2[20] = "xxxxxxxxxxxxx"; my_strcpy(arr2, arr1); printf("%s\n", arr2); return 0; }
我们还可以使用assert对它进行优化:
#include <stdio.h> #include <assert.h> void my_strcpy(char* dest, char* src) { //断言 assert(dest != NULL); assert(src != NULL); while (*src != '\0') { *dest = *src; dest++; src++; } *dest = *src;// \0 的拷贝 } int main() { char arr1[] = "hello bit"; char arr2[20] = "xxxxxxxxxxxxx"; char* p = NULL; //my_strcpy(p, arr1); my_strcpy(arr2, arr1); printf("%s\n", arr2); return 0; }
通过assert,我们可以确保某些事情不会发生,一旦发生,它就会报错,这样就能方便我们快速找到错误。
我们还可以将字符和\0的拷贝放到一起:
#include <stdio.h> #include <assert.h> void my_strcpy(char* dest, char* src) { //断言 assert(dest != NULL); assert(src != NULL); while (*dest = *src)//赋值表达式,比如把h赋给*dest,表达式的结果就是h的ASCII码值 { dest++; src++; } } int main() { char arr1[] = "hello bit"; char arr2[20] = "xxxxxxxxxxxxx"; char* p = NULL; //my_strcpy(p, arr1); my_strcpy(arr2, arr1); printf("%s\n", arr2); return 0; }
还可以这样写:
#include <stdio.h> #include <assert.h> void my_strcpy(char* dest, char* src) { //断言 assert(dest != NULL); assert(src != NULL); while (*dest++ = *src++)//赋值表达式,比如把h赋给*dest,表达式的结果就是h的ASCII码值 { ;//空语句 } } int main() { char arr1[] = "hello bit"; char arr2[20] = "xxxxxxxxxxxxx"; char* p = NULL; //my_strcpy(p, arr1); my_strcpy(arr2, arr1); printf("%s\n", arr2); return 0; }
此外,我们还可以对返回类型进行优化:
#include <stdio.h> #include <assert.h> //函数返回的是目标空间的起始地址 char* my_strcpy(char* dest, char* src) { char* ret = dest; //断言 assert(dest != NULL); assert(src != NULL); while (*dest++ = *src++)// 赋值表达式,比如把h赋给*dest,表达式的结果就是h的ASCII码值 { ;//空语句 } return ret; } int main() { char arr1[] = "hello bit"; char arr2[20] = "xxxxxxxxxxxxx"; char* p = NULL; //my_strcpy(p, arr1); //my_strcpy(arr2, arr1); //printf("%s\n", arr2); printf("%s\n", my_strcpy(arr2, arr1)); return 0; }
另外,为了保证传进去的arr1不被修改,我们还可以加上const进行修饰:
#include <stdio.h> #include <assert.h> //函数返回的是目标空间的起始地址 char* my_strcpy(char* dest, const char* src) { char* ret = dest; //断言 assert(dest != NULL); assert(src != NULL); while (*dest++ = *src++)// 赋值表达式,比如把h赋给*dest,表达式的结果就是h的ASCII码值 { ;//空语句 } return ret; } int main() { char arr1[] = "hello bit"; char arr2[20] = "xxxxxxxxxxxxx"; char* p = NULL; //my_strcpy(p, arr1); //my_strcpy(arr2, arr1); //printf("%s\n", arr2); printf("%s\n", my_strcpy(arr2, arr1)); return 0; }
6.3 const的作用
有以下两种方式可以修改num的值:
int main() { int num = 10; num = 20; int* p = # *p = 200; return 0; }
现在我们加上const:
#include <stdio.h> int main() { const int n = 100; //n = 200;//err int* p = &n; *p = 20; printf("%d\n", n); return 0; }
加上const是为了不让n的值发生变化,但是现在我们却可以通过地址的方式来改变它,于是我们可以进行以下操作:
int main() { const int n = 100; //n = 200;//err //int* p = &n; //*p = 20; //printf("%d\n", n); const int* p = &n; //*p = 20;//err return 0; }
通过以上代码我们可以发现const是可以修饰指针的:
//const 修饰指针的时候 //当const 放在*的左边的时候,限制的是指针指向的内容,不能通过指针变量改变指针指向的内容,但是指针变量本身是可以改变的 //当const 放在*的右边的时候,限制的是指针变量本身,指针变量本身是不能改变的,但是指针指向的内容是可以通过指针来改变的 #include <stdio.h> int main() { int m = 10; int n = 100; //const可以修饰指针 const int* p = &m; //*p = 0;//err p = &n;//ok printf("%d\n", m); return 0; }
#include <stdio.h> int main() { int m = 10; int n = 100; //const可以修饰指针 int* const p = &m; *p = 0;//ok //p = &n;//err printf("%d\n", m); return 0; }
#include <stdio.h> int main() { int m = 10; int n = 100; //const可以修饰指针 const int* const p = &m; //*p = 0;//err //p = &n;//err printf("%d\n", m); return 0; }
练习:
模拟实现一个strlen函数
//模拟实现一个strlen函数 //assert //const //size_t 是专门为sizeof设计的一个类型 //size_t --> unsigned int / unsigned long //>=0 #include <stdio.h> #include <assert.h> size_t my_strlen(const char* str) { assert(str != NULL); size_t count = 0; while (*str != '\0') { count++; str++; } return count; } int main() { char arr[] = "abc"; size_t len = my_strlen(arr); printf("%zd\n", len);//zd是专门用来打印size_t类型的值的 return 0; } //%u 无符号整数的 • 36
7. 编程常见的错误
7.1 编译型错误
直接看错误提示信息(双击),解决问题,或者凭借经验就可以搞定,相对来说简单。
int main() { int a = 10//编译期间找到的一般都是语法问题 return 0; }
7.2 链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在,一般是标识符名不
存在或者拼写错误。
//链接型错误是在链接期间发现的错误 int Add(int x, int y) { return x + y; } int main() { int ret = add(2, 3); return 0; }
7.3 运行时错误
借助调试,逐步定位问题,最难搞。
#include <stdio.h> int Add(int x, int y) { return x - y; } int main() { int ret = Add(2, 3); printf("%d\n", ret); return 0; }