坚持就是胜利
文章目录
- 一、实例
- 二、如何写出好(易于调试)的代码
-
- 1、优秀的代码
- 2、示范
-
- [(1)模拟 strcpy 函数](#(1)模拟 strcpy 函数)
-
- 方法一:
- 方法二:
- 方法三:有弊端
- 方法四:对方法三进行优化
-
- [assert 的使用](#assert 的使用)
- 方法五:对方法三、方法四进行优化
- 方法六:最终的正确结果
- [(2)模拟 strlen 函数](#(2)模拟 strlen 函数)
- 三、编程常见的错误
- 四、做一个有心人,积累排错经验。
一、实例
c
#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数组
方法一:
c
#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;
}
方法二:
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "hello";
char arr2[20] = { 0 };
printf("%s\n", strcpy(arr2, arr1));
return 0;
}
方法三:有弊端
c
#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;
}
c
#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;
}
c
#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 ,就会出错
c
#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 的作用:会清晰的告诉你,哪一行的代码出现了错误!
c
#include <stdio.h>
#include <assert.h> //assert 的头文件
void my_strcpy(char* dest, char* src)
{
//断言 assert
assert(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*
问题一:怎么返回 起始地址?
c
#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;
}
解决办法
c
#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
可能出现的问题
c
#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;
}
c
#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 如果放在 * 的 右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。
c
#include <stdio.h>
int main()
{
const int num = 100; //下面的截图中,忘记添加 const 了,应该是 const int num = 100;
int a = 90;
const int* ps = #
//*ps = 200; //不能这样改变
ps = &a;
printf("%d\n", *(ps));
return 0;
}
c
#include <stdio.h>
int main()
{
const int num = 10;
//int abc = 200;
int* const ps = #
//ps = &abc; //错误
*(ps) = 200;
printf("%d\n", num);
return 0;
}
方法六:最终的正确结果
c
#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'。
c
#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号字
红色
绿色
蓝色