优快云话题挑战赛第2期
参赛话题:学习笔记
本人0基础开始学编程,我能学会的,你也一定可以,学会多少写多少。
下载安装请从官网入手,社区版本即可,这里主要使用的软件是VS2019,图标如下。
上一篇:从0开始学c语言-22-结构体声明和初始化、结构体大小、结构体成员访问、结构体传参_阿秋的阿秋不是阿秋的博客-优快云博客
目录
1·如何写出好(易于调试)的代码
优秀代码
常见的coding技巧:
模拟实现库函数:strcpy
查看功能

简单来说,就是把source位置的字符串拷贝到destination中。
效果就像这样。
可以看到,会把结束标志的 \0 也拷贝进去。
模拟实现
首先模仿strcpy,起个自己的函数名,然后确定要传递的函数参数。
my_strcpy(arr1, arr2);
然后考虑函数的返回类型和函数参数的类型。
因为我们的arr1和arr2都是字符数组,数组的名字是首元素的地址,所以我们在传参的时候传递的是首元素的地址,那么函数参数的类型就要用指针进行接收。(因为指针变量是用来存地址的啊!)
那么函数就会长这样。
void my_strcpy(char* dest, char* source)
{
//dest表示目的地(copy的目的地)
//source表示源(需要copy的字符串)
}
这就是传过来的dest和source指针。
为了实现copy这个功能,就需要让
*dest=*source;
这样便可以实现copy,接下来就需要指针向后挪动指向下一个内容进行copy。
dest++;
source++;
那么为了实现所有字符的copy,我们需要循环,循环便需要确定什么时候不用循环?
当然是遇到 \0 便停止循环了。那代码就是这样。
while (*source != '\0')
{
*dest = *source;
dest++;
source++;
}
可这还没完,因为我们没有把arr2中的结束字符 \0 拷贝过去,就不算完全copy了arr2字符数组。
想想走出循环的时候,dest和source指针都指向什么位置呢?
source指针正好指向 \0。
那么这就是我们设想的代码了。
void my_strcpy(char* dest, char* source)
{
while (*source != '\0')
{
*dest = *source;
dest++;
source++;
}
*dest = *source;
}
现在可以实现我们想要的功能了,但它不是一个优秀的代码。
优化代码
*dest++ = *source++;
这还不够,出了循环后又让*dest = *source显得不是很简洁,我们需要想办法避免这个语句。
我们知道的是出了循环之后,可以直接让指向的内容相等。
那么有没有什么办法能够让这个语句在循环内就实现交换,并且copy后就跳出循环呢?
有!放到循环条件中
while (*dest++ = *source++)
{
;
}
*dest++ = *source++这个表达式的结果是什么呢?
*source是谁,这个表达式的结果就是谁,比如第一个*source是字符a,那么这个表达式的结果就是字符a,如果*source指向\0,那么这个表达式的结果就是0,此时判断条件为假,跳出循环,但是后置++的操作是必须执行的,因此是判断为假后,进行++,然后跳出循环。
断言assert
如果我们的arr2传过来是一个空值,那么接收的指针应该是个空指针,空指针不能被解引用实现字符串的copy,所以我们要避免这种情况,那就需要assert来判断是否是空值。
assert(source!=NULL);
这代表我们不允许source指针是空指针,如果是空指针那便会程序崩溃并提示你。
可以把assert理解为一个条件判断语句,条件为真,照常执行。条件为假,它会告诉你有问题,还会告诉你它在第几行,你就可以根据这个找到程序的问题在哪。
如果你没有assert的情况下传过来了空指针,那你连问题在哪都不知道。
这东西真是个好玩意,帮助我们快速找到问题。
同样的道理,我们还需要判断一下arr1是不是空值。
那代码就是这样
void my_strcpy(char* dest, char* source)
{
assert(dest != NULL);
assert(source != NULL);
while (*dest++ = *source++)
{
;
}
}
const修饰
但还不够好,我们看看strcpy在库函数中的定义。
char *strcpy( char *strDestination, const char *strSource );
再放上我们自己定义的
void my_strcpy(char* dest, char* source)
1·可以看到,source当中有个const来修饰。
它是指souce字符串不能被修改的意思,也就是说用来防止把dest和source写反了的情况,
2·函数的返回类型是char*
我们通过学习网址
https://cplusplus.com/reference/
找到strcpy,看到它返回的是目标空间的起始地址(也就是dest的起始地址)
之前我们的代码是这样
void my_strcpy(char* dest,const char* source)
{
assert(dest != NULL);
assert(source != NULL);
while (*dest++ = *source++)
{
;
}
}
我们先把函数返回类型改为 char*
然后写一个返回值
return dest;
你是不是觉得完美了?
错!
dest指针早就被你不知道向后挪了多少步,所以为了能够返回起始地址,我们需要用一个变量来存储dest最开始的地址,就像这样。
char* my_strcpy(char* dest,const char* source)
{
assert(dest != NULL);
assert(source != NULL);
char*const ret = dest; //加了这个
while (*dest++ = *source++)
{
;
}
return ret; //加了这个
}
可以看到我加了个const修饰,后面我们深入讲一下const。
上面的代码还可以继续优化成这样
char * strcpy(char * dst, const char * src) {
char * cp = dst;
assert(dst && src);
while( *cp++ = *src++ )
; /* Copy src over dst */
return( dst );
}
2·const修饰指针
const 修饰变量,这个变量就被称为常变量,不能被修改,本质上还是变量。
const int num=10;
num=20; //err
就像这样,num没办法被修改。
现在我们把num地址存放到指针p中
const int num=10;
int *p=#
*p=20;
此时,num的值可以被修改。
你可以理解为每次修改都需要访问内存,num是直接从内存门口走进去修改值的,而指针直接翻窗户进来修改,所以这时候的num可以被修改。
那么这个const就对num不是那么有作用了,我们就需要在指针前加一个const
const int *p=#
*p=20;//err
这时候*p没办法改变num
const修饰指针变量的时候,
放在*左边,修饰的是*p,*p表示指针指向的内容,也就是说不能通过*p来改变指针指向的内容了,但是指针变量p可以改变。
*p——指针指向的内容
p——指针变量,存放地址
上面这段话,在代码中就会体现成这样
const int *p=#
*p=20;//err
p=&n;//ok
现在我们把const放在*右边,那代码就是这样
int * const p=#
*p=20;//ok
p=&n;//err
const修饰指针变量的时候,
放在*右边,修饰的是指针变量p,指针变量存储的地址不能被改变,但是*p(指针指向的内容)可以改变。
总结
const修饰指针变量的时候,
放在*左边,修饰的是*p,*p表示指针指向的内容,也就是说不能通过*p来改变指针指向的内容了,但是指针变量p可以改变。
放在*右边,修饰的是指针变量p,指针变量存储的地址不能被改变,但是*p(指针指向的内容)可以改变。
可以都放const
//*左边
const int *p=#
*p=20;//err
p=&n;//ok
//*右边
int * const p=#
*p=20;//ok
p=&n;//err
3·模拟实现一个strlen函数
功能
int main()
{
char arr[] = "asdfg";
int len = strlen(arr);
printf("%d\n", len);
return 0;
}
我们是这样使用strlen函数的,求字符串长度。
现在写一个我们自己的strlen函数。
函数返回类型和参数类型确定
my_strlen(arr);
然后进行函数返回类型和函数参数的类型确定。
1·函数参数的类型确定
因为我们传过去的是arr字符数组的名字,而数组名字又是首元素的地址,所以我们的接收参数的函数参数类型应该是char*类型的指针。
2·函数返回类型
因为我们计算的是字符串的长度,所以返回的应该是一个整数,int类型。
函数现在长这样。
int my_strlen(char* start)
{
}
确定实现函数功能的过程
本来一开始打算设置个变量来储存长度,当做函数的返回值。
后来我突然想起来,咱们接收的是char*指针,然后我就想起来了 指针 - 指针 是 指针之间的元素个数。
那么现在只有一个指针变量,所以我们需要再来一个指针,才能进行 指针 - 指针 的操作。
char* end = start;
然后我们需要让指针向后移动,一直移动到 \0
那就需要循环来让指针向后移动,循环就需要考虑循环条件,本来我想写成这样
while (*end)
{
end++;
}
后来我想起来strcpy函数的优化过程,就改良成了这样。
while (end++);
然后返回两个指针相减的值
int my_strlen(char* start)
{
char* end = start;
while (*end++);
return (end - start);
}
但是不对,我画图示意一下
每次先进行判断后进行+1,同一颜色的是判断后+1后的指针。
虽然判断为假,但是指针依旧向后跳了一步,只不过不会进行下一次判断了而已。
那么我们 返回的值 就比 本来的字符串长度 多1
代码应该是这
int my_strlen(char* start)
{
char* end = start;
while (*end++);
return (end - start-1);
}
最后进行assert和const的点睛之笔
int my_strlen(const char* start)
{
assert(start);
const char* end = start;
while (*end++);
return (end - start-1);
}
4·编程常见的错误
.1 编译型错误
直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单。
.2 链接型错误
.3 运行时错误
借助调试,逐步定位问题。最难搞。
下一篇:从0开始学c语言-24- 剖析数据在内存中的存储(整型在内存中的储存和练习)、char的取值范围、大端小端储存_阿秋的阿秋不是阿秋的博客-优快云博客