C语言入门笔记 第十一讲【如何写出好(易于调试)的代码】

一、前言

优秀的代码:

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函数,但你自己没有定义,或者名字写错。 

运行时错误

借助调试,逐步定位问题。最难搞。

做一个有心人,积累排错经验!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值