一、前言
优秀的代码:
1. 代码运行正常
2. bug很少
3. 效率高
4. 可读性高
5. 可维护性高
6. 注释清晰
7. 文档齐全
常见的coding技巧:
1.使用assert
2.尽量使用const
3.养成良好的编码风格
4.添加必要的注释
5.避免编码的陷阱
二、示范之模拟实现库函数:strcpy
首先,arr1和arr2是两个char类型的数组。
strcpy(arr1,arr2)会把arr2的内容复制一份,然后放入arr1中,覆盖arr1的所有内容。
因为传参的是数组名,而数组名是首元素地址,是一个指针,
所以参数变量为:
char* dest, char* src//目的和源
void my_strcpy(char* dest, char* src)
{
while (*src != '\0')
{
*dest = *src;
src++;
dest++;
}
*dest = *src;//拷贝‘\0’
}
然后进行赋值,直到遇到'\0'。
遇到'\0'我们跳出循环,最后一次单独赋值即可。
这是我们的常规思路;
但是,这是一个优秀的代码吗?
第一处优化:
*dest = *src;
src++;
dest++;
我们可以改成后置加加的写法:
*dest++ = *src++;
第二处优化:
把整个while改掉。
while (*src != '\0')
{
*dest = *src;
src++;
dest++;
}
改成:
while (*dest++ = *src++)
{
;
}
表达式只要不为0,就会一直循环。
而字符0的ASCII码值是48,'\0'的ASCII码值是0,
所以只有当出现以下情况,才会跳出while循环:
*src中存放的是'\0',并把它赋给了*dest。
那么此时*dest的值的ASCII码是0,为假,
所以跳出循环。
也可以完成函数的任务;
第三处优化:
假如我们传一个空指针呢?
my_strcpy(arr1,NULL);
当我们在使用指针时,要先判断指针的有效性。
if (dest != NULL && src != NULL)
{
while (*dest++ = *src++)
{
;
}
}
这样无非是保持原字符串不变,增加了程序的健壮性。
但是,这样写把问题掩盖了,规避了,不容易发现传参的问题。
第三处接着优化:
使用assert(),断言。
引用头文件:#include<assert.h>
assert()里面的表达式为真,什么都不会发生;
assert()里面的表达式为假,会报错。
所以我们可以把if改成assert,
即:
assert(dest != NULL);
assert(src != NULL);
那么如果出现传入空指针,就会出现:
第四处优化:
原库函数的类型:
源字符串是const类型。
我们的目的是把源拷到目的地里,
但是假如程序员写反了,把目的地拷到源去了,
结果就会出现问题,但是能跑,
这时候就很难找到问题。
但是如果我们把源定义成const,也就是常变量,
源是不能被改变的,
写错了就很容易发现,编译器会报错。
举个小例子,以下写法有问题:
非法写法,因为num是const类型的。
指针p是破坏了规则。
我们可以将指针变成const对象,
也就是这样写:
这样指针也不能通过找到地址来改变num的值。
以下两种写法不同:
const int *p
放在左边时,修饰的是*p,也就是说不能通过p来改变*p的值;
*p = 20;//就会报错
int* const p
放在右边时,修饰的是p,也就是说,
假如我们这样写,
p里已经存放了num的地址,
如果再让它转而存放n的地址,就会报错。
因为p这个指针是常变量。
结论:
const修饰指针变量的时候:
1. const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。
2. const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。
第五处优化:
原库函数的返回值是char*。
我们需要返回目的地的起始地址:
char* ret = dest;
开头就存放目的地起始地址。
char * strcpy(char * dest, const char * src)
{
char * ret = dest;
assert(dest && src);
while( *dest++ = *src++ )
; /* Copy src over dst */
return ret;
}
这样函数可以作为另一个函数的参数,实现链式访问,更加灵活。
我们以同样的方式实现一下strlen():
int my_strlen(const char* str)//不期望他的内容被改变
{
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
总结一下代码优化:
1. 分析参数的设计(命名,类型),返回值类型的设计
2.野指针,空指针的危害
3. assert断言的使用
4. 参数部分 const 的使用,这里讲解const修饰指针的作用
5. 注释的添加
三、三种错误
编译型错误
直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单。
链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误。
如使用ADD函数,但你自己没有定义,或者名字写错。
运行时错误
借助调试,逐步定位问题。最难搞。
做一个有心人,积累排错经验!