C语言初阶⑨(调试)(如何写出好的代码)(模拟实现strcpy和strlen)

简介: C语言初阶⑨(调试)(如何写出好的代码)(模拟实现strcpy和strlen)

一、调试(Debug)

定义:调试,又称除错,是发现和减少计算机程序电子仪器设备中程序错误的一个过程;

基本步骤:

① 发现程序错误的存在;


能够发现错误的人:


⑴ 程序员,自己发现;


⑵ 软件测试人员,测试软件;


⑶ 用户,代价严重;


要善于承认自己的错误,不能掩盖错误;


② 以隔离、消除等方式对错误进行定位;能知道大概在什么位置,再确定错误产生的原因是什么;


③ 提出纠正错误的解决方案;


④ 对程序错误订正,重新调试;

Debug和Release的介绍

Debug 通常称为调试版本,它包含调试信息,并且不做任何优化,便于程序员调试程序;

Release 称为发布版本,他往往是进行了各种优化,使得程序在代码大小和运行速度上是最优的,

以便用户更好的使用;Release 版本是不能调试的;

Windows环境调试介绍

下面我们来介绍VS中的调试

开始调试(F5)

作用:启动调试,经常用来直接调到下一个断点处;

注意事项:

① 如果直接按 F5 ,如果没有阻挡的话程序一口气就结束了;

② 使用 F5 之前要先使用 F9 ,设置断点;

断点(F9)

作用:创建断点和取消断点,断电的重要作用可以在程序的任意位置设置断点;这样就可以使得程序在想要的位置随意停止执行,继而可以一步步执行下去;

逐过程(F10)

作用:通常用来处理一个过程,一个过程可以是一次函数的调用,或者是一条语句;

逐语句(F11)

作用:每次都执行一条语句,观察的细腻度比 F10 还要高,可以进入到函数内部;

注意事项:F10F11 大部分情况是一样的,区别在于 F11 遇到函数时可以进到函数内部去,函数的内部也可以一步步观察,而 F10 遇到函数调用完之后就跳出去了;

开始执行不调试(Ctrl + F5)

作用:开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用;

调试时查看程序当前信息

查看方法:调试(D) → 窗口(W) → 选择相应的选项;

注意事项:只有调试之后才会显示调试窗口里的选项;

监视

作用:在调试开始之后,便于观察变量的值;

自动窗口

作用:编辑器自行监视,随着代码自动给出值;

注意事项:

① 自动窗口和监视是一样的效果,但是自动窗口里的表达式会自动发生变化;

② 自由度低,自动窗口是编辑器自己监视,你管不了;

条件断点

假设某个循环要循环1000次,我怀疑第500次循环时程序会出问题,

那么我要打上断点然后再按500次 F10 吗?

方法:使用条件断点;

在断点设置好之后右键鼠标,选中条件:


(以下内容不懂的先了解,自己很少用到)

查看内存信息

作用:在调试开始之后,用于观察内存信息;

看内存信息:


红色框起来的是地址,绿色的是内存数据,蓝色的是翻译文本

查看调用堆栈

作用:通过调用堆栈,可以清晰地反应函数的调用关系和所处的位置;

查看调用堆栈:

查看汇编信息

调试开始后,有两种方式转到汇编:

① 第一种方式:右击鼠标,选择 " 转到反汇编 "

② 第二种方式:调试 → 窗口 → 反汇编

查看寄存器信息

作用:可以查看当前运行环境的寄存器的实用信息;


调试总结

① 多多动手,尝试调试,才能有进步;

② 一定要熟练掌握调试的技巧;

③ 初学者可能80%的时间在写代码,20%的时间在调试。

但是一个程序员可能20%的时间在写程序,但是80%的时间在调试;

④ 我们所讲的都是一些简单的调试。

以后可能会出现很复杂的调试场景:多线程程序的调试等;

⑤ 多多使用快捷键,提升效率;

优秀的代码:

1. 代码运行正常

2. bug很少

3. 效率高

4. 可读性高

5. 可维护性高

6. 注释清晰

7. 文档齐全

常见的coding技巧:

1. 使用assert

2. 尽量使用const

3. 养成良好的编码风格

4. 添加必要的注释

5. 避免编码的陷阱。

(下面的模拟实现strcpy和strlen将会逐步引导写出好的代码)

二、练习:

const

语言中哪一种形式声明了一个指向char类型变量的指针p,p的值不可修改,但p指向的变量值可修改?( )

A.const char *p

B.char const *p

C.char*const p

D.const char *const p

答案解析:

A:错误,const修饰*p,表示p指向的内容不能修改

B:错误,同上

C:正确,const修饰p本身,表示p的指向不能修改,p指向的空间中内容可以修改

D:错误,第一个const表示p指向的内容不能修改,第二个const表示p不能指向其他变量

因此,选择C

Debug和Release

关于Debug和Release的区别说法错误的是:( )

A.Debug被称为调试版本,程序调试找bug的版本

B.Release被称为发布版本,测试人员测试的就是Release版本

C.Debug版本包含调试信息,不做优化。

D.Release版本也可以调试,只是往往会优化,程序大小和运行速度上效果最优

答案解析:

A:正确,Debug为调试版本,一般在开发完成后发布工程前,调试代码都是在Debug模式下进行的

B:正确,Release版本最终是要发送给用户的,发给用户的版本必须要没有问题,测试人员就是最后一个把关的

C:正确,Debug版本是调试版本,编译器编译时会增加一些调试信息,编译器基本不会对其进行优化

D:错误,Release版本是不能调试的,一般都是在Debug版本下调试的,Release版本一般编译器会进行大量的优化,删除无用的代码,指令的次序调整等,让其速度更快

因此:选择D

程序死循环解释

VS开发环境调试下面的代码,画图解释下面代码的问题

注:Debug下就会死循环,Release下就不会死循环(在上一题区分两者区别)

 
#include <stdio.h>
int main()
{
    int i = 0;
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    for(i=0; i<=12; i++)
    {
        arr[i] = 0;
        printf("hello world\n");
    }
    return 0;
}

答案解析:

以下代码有两个问题:1. 数组访问越界 2. 死循环

以下代码再vs2013下会造成死循环,原因:

栈内存:

|CC CC CC CC|

arr[0]|01 00 00 00|\

arr[1]|02 00 00 00| \

arr[2]|03 00 00 00| \

arr[3]|04 00 00 00| \

arr[4]|05 00 00 00| \

arr[5]|06 00 00 00| / arr的空间

arr[6]|07 00 00 00| /

arr[7]|08 00 00 00| /

arr[8]|09 00 00 00| /

arr[9]|0A 00 00 00|/

|CC CC CC CC|

|CC CC CC CC|

|00 00 00 00| i的空间

|CC CC CC CC|

for循环中,i的内容是从0,一直增加到12,而数组只有10个空间,因此会越界

每次访问arr数组i号位置时,都会将该位置内容设置为0,当访问到arr[12]时,也会将该位置内容设置为0,而位 置恰好为i的位置,即a[12]恰巧将i设置为0,因此造成死循环。


调整奇数偶数顺序

调整数组使奇数全部都位于偶数前面。

输入一个整数数组,实现一个函数,

来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分,

所有偶数位于数组的后半部分。

 
#include<stdio.h>
void swap_arr(int arr[], int sz)
{
    int left = 0, right = sz - 1;
    while (left < right)
    {
        // 从前往后,找到一个偶数,找到后停止
        while ((left < right) && arr[left] % 2 == 1)
        {
            left++;
        }
        // 从后往前找,找一个奇数,找到后停止
        while ((left < right) && arr[right] % 2 == 0)
        {
            right--;
        }
        // 如果偶数和奇数都找到,交换这两个数据的位置
        // 然后继续找,直到两个指针相遇
        if (left < right)
        {
            int tmp = arr[left];
            arr[left] = arr[right];
            arr[right] = tmp;
        }
    }
}
int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    swap_arr(arr, sz);
    for (int i = 0;i < sz;i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

模拟实现strcpy

1.正常思维

 
#include<stdio.h>
#include<string.h>
void my_strcpy(char* dest,char* src)//destination dest 目的   source src 源头
{
    while (*src != '\0')
    {
        *dest++ = *src++;
    }
    *dest++ = *src++;//拷贝反斜杠0
}
int main()
{
    char arr1[30] = "******************";
    char arr2[] = "hello world!";
    //strcpy(arr1, arr2);
    my_strcpy(arr1, arr2);
    printf("%s\n", arr1);
    return 0;
}

2.代码简化

 
#include<stdio.h>
#include<string.h>
void my_strcpy(char* dest,char* src)//destination dest 目的   source src 源头
{
    while (*dest++ = *src++)//'\0'的ASCII码为0拷贝后跳出循环
    {
        ;
    }
}
int main()
{
    char arr1[30] = "******************";
    char arr2[] = "hello world!";
    //strcpy(arr1, arr2);
    my_strcpy(arr1, arr2);
    printf("%s\n", arr1);
    return 0;
}

3.代码优化

 
#include<stdio.h>
#include<string.h>
#include<assert.h>
//destination dest 目的   source src 源头
char* my_strcpy(char* dest, const char* src)
{                     //const修饰变量,这个变量成为常变量,不能被修改,本质上还是变量
    assert(dest != NULL);//断言 表达式为真,无事发生 表达式为假,报错
    assert(src != NULL);
    char* ret = dest;
    while (*dest++ = *src++)
    {
        ;
    }
    return ret;
}
int main()
{
    char arr1[30] = "******************";
    char arr2[] = "hello world!";
    //strcpy(arr1, arr2);
    //my_strcpy(arr1, arr2);
    printf("%s\n", my_strcpy(arr1, arr2));
    return 0;
}

模拟实现strlen

 
#include<stdio.h>
#include<string.h>
#include<assert.h>
int my_strlen(const char* p)
{
    assert(p != NULL);
    int n = 0;
    while (*p++)
    {
        n++;
    }
    return n;
}
int main()
{
    char arr[] = "hello world!";
    //int sz = strlen(arr);
    int sz = my_strlen(arr);
    printf("%d", sz);
    return 0;
}

strlen的一种原码(指针-指针):

size_t 就是 unsinged int,还有__cdecl是一种函数调用约定,不用管

本篇完。C语言初阶结束咯。


目录
相关文章
|
18天前
|
C语言
C语言指针带代码
C语言指针带代码
23 3
|
12天前
|
算法 C语言
数据结构和算法——归并排序(有序子列的归并、递归算法、非递归算法、思路图解、C语言代码)
数据结构和算法——归并排序(有序子列的归并、递归算法、非递归算法、思路图解、C语言代码)
10 0
|
12天前
|
C语言
C语言学习记录——模拟字符串相关函数(strcpy、strlen、strcat)相关知识-const、typedef
C语言学习记录——模拟字符串相关函数(strcpy、strlen、strcat)相关知识-const、typedef
10 1
|
12天前
|
存储 编译器 C语言
C语言学习记录——调试技巧(VS2019环境下)
C语言学习记录——调试技巧(VS2019环境下)
18 2
|
12天前
|
C语言
C语言——oj刷题——模拟实现库函数strlen
C语言——oj刷题——模拟实现库函数strlen
|
12天前
|
编译器 C语言
C语言尾递归知识及代码示例
C语言尾递归知识及代码示例
|
15天前
|
C语言
【初始C语言8】详细讲解初阶结构体的知识
【初始C语言8】详细讲解初阶结构体的知识
|
15天前
|
C语言
C 语言关于sizeof() 和 strlen()区别?
C 语言关于sizeof() 和 strlen()区别?
11 2
|
20天前
|
C语言
C语言循环结构程序设计详解及代码实例
C语言循环结构程序设计详解及代码实例
19 0
|
20天前
|
存储 C语言 索引
C语言一维数组与二维数组详解及代码实例
C语言一维数组与二维数组详解及代码实例
22 0