C语言——实用调试技巧——第2篇——(第23篇)

坚持就是胜利

文章目录

  • 一、实例
  • 二、如何写出好(易于调试)的代码
    • 1、优秀的代码
    • 2、示范
      • (1)模拟 strcpy 函数
        • 方法一:
        • 方法二:
        • 方法三:有弊端
        • 方法四:对方法三进行优化
          • assert 的使用
        • 方法五:对方法三、方法四进行优化
          • 1、解决char*
            • 问题一:怎么返回 起始地址?
            • 解决办法
          • 2、解决const,因为 const char* source
            • 可能出现的问题
            • 修饰指针 的作用
        • 方法六:最终的正确结果
      • (2)模拟 strlen 函数
  • 三、编程常见的错误
    • 1、编译型错误(语法错误)
    • 2、链接型错误
    • 3、运行时错误
  • 四、做一个有心人,积累排错经验。


一、实例

在这里插入图片描述

在这里插入图片描述

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

这段程序非常依赖当前所在的编译环境的,编译环境不同,出现的效果也是不同的。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
类似的题目:
在这里插入图片描述
上一篇文章介绍了,在Debug版本下,上述代码会死循环。
但是,在Release版本下,上述代码不会死循环。
原因如下:
在这里插入图片描述

在这里插入图片描述

二、如何写出好(易于调试)的代码

1、优秀的代码

1、代码运行正常
2、BUG很少
3、效率很高
4、可读性高
5、可维护性高
6、注释清晰
7、文档齐全

常见的coding技巧:
1、使用 assert
2、尽量使用 const
3、养成良好的编码风格
4、添加必要的注释
5、避免编码的陷阱

2、示范

(1)模拟 strcpy 函数

char * strcpy ( char * destination, const char * source );

Copies the C string pointed by source into the array pointed by destination,
including the terminating null character (and stopping at that point).
(第二句英文:包含结束字符 ‘\0’)
将’h’,‘e’,‘l’,‘l’,‘o’,‘\0’ 传给arr2数组
在这里插入图片描述

方法一:
#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "hello";  //将'h','e','l','l','o','\0' 传给arr2[]数组char arr2[20] = { 0 };    //别忘了  结束标志: '\0'strcpy(arr2, arr1);printf("%s\n", arr2);return 0;
}
方法二:
#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "hello";char arr2[20] = { 0 };printf("%s\n", strcpy(arr2, arr1));return 0;
}
方法三:有弊端
#include <stdio.h>void my_strcpy(char* dest, char* src)
{while (*src != '\0')  //或者简洁点写成: while(*src){*dest = *src;dest++;src++;}
}int main()
{char arr1[] = "hello";char arr2[20] = { 0 };my_strcpy(arr2, arr1);printf("%s\n", arr2);return 0;
}
#include <stdio.h>void my_strcpy(char* dest, char* src)
{while (*dest = *src){dest++;src++;}
}int main()
{char arr1[] = "hello";char arr2[20] = { 0 };char* ps = NULL;my_strcpy(arr2, arr1);printf("%s\n", arr2);return 0;
}
#include <stdio.h>void my_strcpy(char* dest, char* src)
{while (*dest++ = *src++)  //先执行后置 ++,再 解引用 *{;    //空语句     //什么都不做}
}int main()
{char arr1[] = "hello";char arr2[20] = { 0 };char* ps = NULL;my_strcpy(arr2, arr1);printf("%s\n", arr2);return 0;
}

虽然可以正常输出,但是遇到 空指针 NULL ,就会出错

#include <stdio.h>void my_strcpy(char* dest, char* src)
{while (*src != '\0'){*dest = *src;dest++;src++;}
}int main()
{char arr1[] = "hello";char arr2[20] = { 0 };char* ps = NULL;      //此时 ps 是 空指针,空指针 是 不能直接使用的my_strcpy(ps, arr1);  //这样子,整个代码是什么都输出不了的,空指针 是 不能直接使用的printf("%s\n", *(ps));  //什么都输出不了,程序报错return 0;
}
方法四:对方法三进行优化
assert 的使用

头文件:#include <assert.h>
assert 的作用:会清晰的告诉你,哪一行的代码出现了错误!

#include <stdio.h>#include <assert.h>  //assert 的头文件void my_strcpy(char* dest, char* src)
{//断言 assertassert(dest != NULL);   //dest 不允许为 空指针assert(src != NULL);    //src  不允许为 空指针while (*src != '\0'){*dest = *src;dest++;src++;}
}int main()
{char arr1[] = "hello";char arr2[20] = { 0 };char* ps = NULL;      my_strcpy(ps, arr1);  printf("%s\n", *(ps));  return 0;
}

在这里插入图片描述

方法五:对方法三、方法四进行优化

从方法一 ~ 方法四,my_strcpy函数的返回值都是 void .
因为并没有 return ,所以都是 void。
然而,根据 strcpy函数的定义: char * strcpy ( char * destination, const char * source );
返回值的类型,应该是:char *
const char* source,要有 const

1、解决char*
问题一:怎么返回 起始地址?
#include <stdio.h>char* my_strcpy(char* dest, char* src)
{//断言assert(dest != NULL);assert(src != NULL);while (*dest++ = *src++){;   }return dest;  //此时的 dest 已经指向了数组的最后了,返回之后,无法输出想要的字符串
}                 //我们需要的是:目标函数的 起始地址int main()
{char arr1[] = "hello";char arr2[20] = { 0 };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++){;}return ret;           //就是这么简单
}int main()
{char arr1[] = "hello";char arr2[20] = { 0 };printf("%s\n", my_strcpy(arr2, arr1));return 0;
}
2、解决const,因为 const char* source
可能出现的问题
#include <stdio.h>
#include <assert.h>char* my_strcpy(char* dest, char* src)
{char* ret = dest;      //问题得到解决//断言assert(dest != NULL);assert(src != NULL);while (*src++ = *dest++)     //本来应该是:while (*dest++ = *src++),{                            //但是写成了:while (*src++ = *dest++)。;}return ret;           //就是这么简单
}int main()
{char arr1[] = "hello";char arr2[20] = "xxxxxxxxxxxxx";printf("%s\n", my_strcpy(arr2, arr1));return 0;
}

在这里插入图片描述

#include <stdio.h>
#include <assert.h>char* my_strcpy(char* dest,const char* src)   //添加 const
{char* ret = dest;      //问题得到解决//断言assert(dest != NULL);assert(src != NULL);while (*src++ = *dest++)     //本来应该是:while (*dest++ = *src++),{                            //但是写成了:while (*src++ = *dest++)。;}return ret;           //就是这么简单
}int main()
{char arr1[] = "hello";char arr2[20] = "xxxxxxxxxxxxx";printf("%s\n", my_strcpy(arr2, arr1));return 0;
}

在这里插入图片描述

修饰指针 的作用

结论:

1、const 如果放在 * 的 左边,修饰的是 指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。

2、const 如果放在 * 的 右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

在这里插入图片描述

#include <stdio.h>int main()
{const int num = 100;   //下面的截图中,忘记添加 const 了,应该是 const int num = 100;int a = 90;const int* ps = &num;//*ps = 200;  //不能这样改变ps = &a;printf("%d\n", *(ps));return 0;
}

在这里插入图片描述

#include <stdio.h>int main()
{const int num = 10;//int abc = 200;int* const ps = &num;//ps = &abc;   //错误*(ps) = 200;printf("%d\n", num);return 0;
}

在这里插入图片描述

方法六:最终的正确结果
#include <stdio.h>#include <assert.h>char* my_strcpy(char* dest, const char* src)  //const 在 * 的左边
{                                             //保证 指针指向的内容不会发生改变char* ret = dest;//断言assert(dest != NULL);assert(src != NULL);while (*dest++ = *src++){;   //空语句,什么都不做}return ret;
}int main()
{char arr1[] = "hello";char arr2[20] = { 0 };char* ps = NULL;printf("%s\n", my_strcpy(arr2, arr1));return 0;
}

(2)模拟 strlen 函数

size_t strlen ( const char * str );
在这里插入图片描述
%u 或者 %zd 来打印 无符号整型(unsigned int)。

The length of a C string is determined by the terminating null-character: A C string is as long as the number of characters between the beginning of the string and the terminating null character
(without including the terminating null character itself).
最后一句话:字符串的长度 不包含 结束字符 ‘\0’。

在这里插入图片描述

#include <stdio.h>
#include <assert.h>size_t my_strlen(const char* src)
{int count = 0;//断言assert(src != NULL);while (*src++){count++;}return count;
}int main()
{char arr1[] = "hello";const char* ps = arr1;size_t len = my_strlen(ps);printf("%zd\n",len);    //%zd  或者  %u  打印 无符号整型return 0;
}

三、编程常见的错误

1、编译型错误(语法错误)

在编译期间,产生的错误,都是:语法问题。

直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单。
在这里插入图片描述

2、链接型错误

在链接期间,产生的错误。

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。
一般是 标识符名不存在 或者 拼写错误。

在这里插入图片描述

3、运行时错误

程序运行起来了,但是结果不是我们想要的,逻辑上出现问题。

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

四、做一个有心人,积累排错经验。

做一个错题本,将 调试的错误 都积累起来!

微软雅黑字体
黑体
3号字
4号字
红色
绿色
蓝色

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2806520.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【C++STL】STL容器详解

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

VMware虚拟机从一台电脑复制到另一台电脑

1 概述 在一台电脑上利用虚拟机安装了OS系统&#xff0c;特别是如果虚拟机中的系统进行了各种繁琐的配置&#xff0c;因为换电脑或者需要在其他电脑上配置&#xff0c;这个时候就可以将虚拟机中的系统复制拷贝一份到新电脑上&#xff0c;省时省力。 2 操作步骤 2.1 vmx文件 …

Python学习 --- 面向对象

1.什么是对象 1.Python中创建类的关键字是 class 2.类的成员方法 1.函数是写在类外面的,方法则是写在类里面的 1.上面这一段代码中就展示了如何在方法中访问类的成员变量: self.成员变量名 3.魔术方法 魔术方法其实就是python中的类中的内置方法,下面这几个只是我们比较常…

高级语言期末2011级B卷

1.编写函数&#xff0c;实现按照如下公式计算的功能。其中n为自然数。 #include <stdio.h>int fac(int n) {if(n0)return 1;elsereturn n*fac(n-1); }double func(int n) {if(n0)return 0;return 1.0*n/((n1)*fac(n2))func(n-1); } 2.编写函数&#xff0c;对给定的整数数…

Flutter Engine 编译

本地环境 Flutter 开发基本环境配置&#xff0c;SDK【】 MAC. M2芯片 git工具 python环境[MAC自带] xcode Chromium depot_tools depot_tools 是调试 Flutter 引擎的必备工具包&#xff0c;包含了 gclient、gn 和 ninja 等工具&#xff0c;这些在下面会用到&#xff01;…

线段树学习笔记 下

可持久化线段树 上面两篇是几年前写的&#xff0c;笔者今日才加以整理&#xff0c;如有错误请见谅。 线段树加上版本就是可持久化线段树。 Problem Intro 给定一个数组&#xff0c;只需要单点修改和单点查询&#xff0c;但要维护版本。 具体说&#xff0c;每一次操作可能从…

中兴助力低空经济发展,携山东移动完成5G-A通感一体商用验证

日前&#xff0c;中兴通讯在5G-A通感一体化技术研究和商用落地领域实现新突破。具体来说&#xff0c;中兴通讯联手山东移动&#xff0c;率先完成了5G-A&#xff08;5G-Advanced&#xff09;通感一体化技术试点&#xff0c;完成对低空无人机的通信感知融合测试。据悉&#xff0c…

Linux使用C语言获取进程信息

Linux使用C语言获取进程信息 Author: OnceDay Date: 2024年2月22日 漫漫长路&#xff0c;才刚刚开始… 全系列文章可查看专栏: Linux实践记录_Once_day的博客-CSDN博客 参考文档: Linux proc目录详解_/proc/mounts-CSDN博客Linux下/proc目录介绍 - 知乎 (zhihu.com)Linux内…

普中51单片机学习(AD转换)

AD转换 分辨率 ADC的分辨率是指使输出数字量变化一个相邻数码所需输入模拟电压的变化量。常用二进制的位数表示。例如12位ADC的分辨率就是12位&#xff0c;或者说分辨率为满刻度的1/(2^12)。 一个10V满刻度的12位ADC能分辨输入电压变化最小值是10V1/(2^12 )2.4mV。 量化误差 …

基于协同过滤算法的体育商品推荐系统

摘要 本文深入探讨了基于协同过滤算法的体育商品推荐系统的构建方法及其在电子商务中的重要性。首先&#xff0c;介绍了协同过滤算法的基本原理&#xff0c;包括用户-商品矩阵、相似度度量和推荐生成。其次&#xff0c;探讨了协同过滤算法在体育商品推荐中的两种主要应用方式&a…

Java知识点一

hello&#xff0c;大家好&#xff01;我们今天开启Java语言的学习之路&#xff0c;与C语言的学习内容有些许异同&#xff0c;今天我们来简单了解一下Java的基础知识。 一、数据类型 分两种&#xff1a;基本数据类型 引用数据类型 &#xff08;1&#xff09;整型 八种基本数…

Qt Android sdk配置报错解决

使用的jdk8总是失败&#xff0c;报错command tools run以及platform sdk等问题。后来主要是设置jdk版本为17&#xff0c;就配置生效了。Android sdk路径可以选用Android Studio自带的&#xff0c;但是也要在Qt中点击“设置SDK”按钮做必要的下载更新等。 编译器这里会自动检测到…

28-k8s集群中-StatefulSets控制器(进阶知识)

一、statefullsets控制器概述 1&#xff0c;举例 假如&#xff0c;我们有一个deployment资源&#xff0c;创建了3个nginx的副本&#xff0c;对于nginx来讲&#xff0c;它是不区分启动或者关闭的先后顺序的&#xff0c;也就是“没有特殊状态”的一个服务&#xff0c;也成“无状…

使用yolo-seg模型实现自定义自动动态抠图

yolov8导航 如果大家想要了解关于yolov8的其他任务和相关内容可以点击这个链接&#xff0c;我这边整理了许多其他任务的说明博文&#xff0c;后续也会持续更新&#xff0c;包括yolov8模型优化、sam等等的相关内容。 YOLOv8&#xff08;附带各种任务详细说明链接&#xff09; …

java——特殊文件日志技术

目录 特殊文件Properties文件XML文件XML文件有如下的特点XML的作用和应用场景解析XML文件 日志技术概述日志技术的体系结构Logback日志框架概述快速入门核心配置文件logback.xml日志级别项目中使用日志框架 特殊文件 Properties文件 后缀为.properties的文件&#xff0c;称之…

Qt MDI应用方法:QMdiArea和QMdiSubWindows类

重点&#xff1a; 1.使用MDI应用程序&#xff0c;需要在主窗口的工作区放置一个QMdiArea组件。 并将QMdiArea组件设置成中心窗口 2.MDI有两个显示模式&#xff1a;Tab多页显示模式和子窗口显示模式 子窗口显示模式有两种显示方法&#xff1a;窗口级联展开和平铺展开 窗口级联…

Vue+SpringBoot打造超市自助付款系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 商品类型模块2.2 商品模块2.3 超市账单模块 三、界面展示3.1 登录注册模块3.2 超市商品类型模块3.3 超市商品模块3.4 商品购买模块3.5 超市账单模块 四、部分源码展示4.1 实体类定义4.2 控制器接口 五、配套文档展示六、…

(蓝桥杯软件赛Java研究生组/A组)第一章语言基础-第二节:Java基础

文章目录 一&#xff1a;标识符、修饰符和注释&#xff08;1&#xff09;标识符&#xff08;2&#xff09;修饰符&#xff08;3&#xff09;注释 二&#xff1a;数据类型&#xff08;1&#xff09;分类&#xff08;1&#xff09;基本数据类型&#xff08;2&#xff09;引用数据…

大数据之Flink优化

文章目录 导言&#xff1a;Flink调优概览第1章 资源配置调优1.1 内存设置1.1.1 TaskManager 内存模型1.1.2 生产资源配置示例 1.2 合理利用 cpu 资源1.2.1 使用 DefaultResourceCalculator 策略1.2.2 使用 DominantResourceCalculator 策略1.2.3 使用DominantResourceCalculato…

在 where子句中使用子查询(一)

目录 子查询返回单行单列 查询公司工资最低的员工信息 查找公司雇佣最早的员工信息 子查询返回单行多列 查询与 ALLEN 工资相同&#xff0c;职位相同的所有员工信息 子查询返回多行单列 IN 操作 查询职位是“MANAGER”的所有员工的薪水 Oracle从入门到总裁:https://bl…