C 语言学习 第12次作业总结

时间:2021-12-11 16:55:48

作业总结

本次课堂的内容为字符串相关的几个函数还有结构体。

字符串相关函数

在此之前的课程中,输入主要都是使用scanf这个函数。而在这节课上,冯老师讲解了字符串获取函数gets。在不需要控制符的情况下,gets函数都可以取代scanf

  • strcpy函数:实现一个字符串到另一个字符串的拷贝
  • strcat函数:实现两个字符串连接为一个新的字符串
  • strcmp函数:实现两个字符串大小的比较(基于 ASCII 码)
  • strlen函数:获得一个字符串的长度数据(需要知道的是,字符串的实际长度还需要加一个尾缀\0

结构体

冯老师在课堂上讲了一些结构体的知识,但是结构体中有一些细节的东西没有涉及,我在这里稍作整理,部分同学后期的课程会遇到(主要针对学习目标为嵌入式的同学)。

位域的概念

之前在课堂上,冯老师会告诉大家,结构体的内容是基本数据类型或者是结构数据类型,这里其实还有一种类型就是位域化的基本数据类型:

struct {
int a;
int b;
}

先考察上面的这个结构体的内容,它包含有两个基本数据类型的变量ab。在内存中,他们将会占用2 * sizeof(int)个内存空间。在某些情况下,可能仅想要用一位来表示一个数据是否被使用(即真或者假),那么对于上面的结构体,空间就浪费的过于厉害。这时可以修改以上的结构体:

struct {
int a :1;
int b :1;
}

这时候,这个结构体的内存占用就变成了一个sizeof(int),虽然还是2int类型的变量,但是每一个变量都仅占用一个位的内存。

对齐和补齐

虽然结构体和数组都是一种“固实”的数据类型(表现在内存中为连续存储)。但是稍有不同是的,数组的确是一种真的“固实”数据——arr[1]arr[2]之间插不进去一个任意的数据类型。但是结构体就不同了。

这里先看一个例子:

struct {
int a; // 假设 int 是 32 位的数据
char b; // 假设 char 是 8 位的数据
int c;
}

对这个结构体进行sizeof操作就会发现结果是12。这时你会好奇的发现,两个 int 加一个 char 不应该是 9 吗,这里为什么变成 12 了呢。其实在结构体中,有这样的一个要求:结构体内部的数据,总是按照最大的那个数据类型进行对齐(但是当最大的数据类型已经超过 4 个字节,则始终按照 4 个字节对齐)。这里,可以看到数据类型最大的是 int ,它占用 4 个字节,那么第二个类型 char 只能按照它进行对齐(见下图,每一个方块都是一个 sizeof(char))。这里,第二行存在 3 个位置的空洞,这些空洞是理论上可以存储数据,但是被浪费掉的

C 语言学习 第12次作业总结

也许你会好奇这有什么用处呢。考虑以下的结构体:

struct{
int a;
char b;
int c;
char d;
int e;
char f;
int g;
char h;
}

在对以上的知识有了解后,你一定就可以知道这样的结构体的设计是不合理的,最好是将几个char类型都归并到一起。

C99 中的结构体初始化的方式

同学们现在学习的 C 语言语法大多数都是按照 C89 标准来的,C99 中稍微增加了一点其他的东西,和结构体相关的有以下这个方法:

在 C89 标准中,如果想要对结构体中的成员变量进行初始化,你可能会那么做:

struct ABCD abcd;
abcd.a = 10;
abcd.b = 20;
abcd.c = 30;
abcd.d = 40;

而在 C99 标准中,提供了一种新的初始化方法:

struct ABCD abcd = {
.a = 10;
.b = 20;
.c = 30;
.d = 40;
}

作业中的问题

通过查看同学们此次作业的内容,主要发现以下的 3 个问题。

没有初始化就使用指针变量

虽然同学们在之前的总结中都一再的强调说指针变量需要先明确的指向一个对象,然后才能使用,但是在实际的代码中,的确是多次发现有同学没有初始化指针变量就使用它(或者是指针变量赋值为 null 后使用它)。对于指向 null 的指针变量,虽然使用一般不会出错,但是还是不建议使用,毕竟它本身没有什么意义。况且假如将多个功能不同的指针,都指向同一个地址,稍微用几次,也都会出现问题。

尝试将局部变量返回给形参

这个错误很典型:在同学们的成绩管理系统中,添加新成员的时候,很多同学首先在函数中定义一个结构体对象,然后对对象中的成员变量赋值,在最后将这个结构体对象的地址直接赋值给形参。

出现这个错误,不怪大家,因为大家都还没有学习过计算机原理相关的知识,不知道在内存中有堆内存和栈内存的概念。这里先跟大家简单的介绍下:

在计算机系统中,程序使用的内存,分为很多种,其中和变量相关的一般是以下两种:堆内存(heap)和栈内存(stack)。现在,将内存想象为一个可乐的瓶子,堆内存就是堆在瓶底的内存,它会一点点的向上生长;栈内存就是在瓶顶的内存,它会一点点的向下生长。

  • 堆内存(heap):堆在地上的内存,向上生长。堆内存有一个特点就是它一般都是由程序员分配和释放的,当然,程序如果结束运行了,那么所有的内存都会由系统进行收回。
  • 栈内存(stack):栈内存,向下生长。栈内的内存有一个特点就是它一般都是由编译器自动分配和释放,而不需要你人工的干预。

在脑海中存在这样的两个简单的概念后,我们返回到程序代码中去。这里先理解这个概念“程序员分配和释放”,它是什么意思呢。一般来说在不特殊指明的情况下,我们都可以将程序员分配假想为函数malloc或者是 C++中过的操作符 new。而将释放假想为free函数。到这里,大家就可以发现了,至今自己都还没有学习过与此相关的函数,所以到目前为止所有的变量都是分配在栈上的。栈上的内存有什么特点呢——它是由编译器自动分配和释放。

先将这个概念稍微放置下,回忆我之前总结中提及的子函数的特点:

在 C 语言中,一切的传参都是值传递。

假想存在一片广袤的土地。子函数就是在需要调用它是时候,在这片土地上圈了一块地,在其中挖坑也罢,种树也好,但是一旦这个函数结束,将会有一辆看不见的推土机轰隆而来,将这圈起来的土地重新归于广袤。

以上,就是子函数的特点。现在,开始看代码:

#include <stdio.h>

void return_func(int *arr){
int a = 10;
arr = &a;
} int main(){
int aa = 0;
return_func(&aa);
printf("%d\n",aa);
return 0;
}

在这个代码中存在一个名为return_func的函数,这个 函数的目标就是将传入的变量的值改为 10。这个函数,和同学们这次作业要做的内容极其相似,只是我这里将它简化成上面的简单版本。

按照同学们的理解,这里最终应该能够实现输出 10 这一结果。但是实际上,最终的输出依旧是 0。为什么函数不能生效呢?!现在,用上面形容子函数的方法来看这个题目。

到底什么是值传递

首先,请同学们不要将我归纳的“在 C 语言中,一切的传参都是值传递”奉为金圭玉臬。因为到现在都不曾在一本 C 语言书中看到类似的话。

// 从主函数开始看起
int main(){
int aa; // 假设变量 aa 的地址是 0x0000 0001
return_func(&aa); // 开始调用子函数,因为需要传递参数
// 且参数为一个地址,所以通过 & 获得 aa 的地址 0x0000 0001 后传入函数 // 调用函数后进入子函数体,因为传入的参数是一个指针,所以先需要构造一个变量存放 aa 的地址
// 于是,子函数相当于变成:
// void return_func(){
// int *arr = 0x0000 0001;
// int a = 10;
// arr = &a;
// }
printf("%d\n",aa);
return 0;
}

清空学说

继续以上的子函数,子函数已经变形为上面的样子了,下面开始学说二:清空学说:

void return_func(){
int *arr = 0x0000 0001;
int a = 10; // 假设 a 的地址是 0x0000 0002
arr = &a; // 那么这里的 arr 存储的内容就变成为 0x0000 0002
}

可以看到这里的 arr 是一个局部的变量,在这个函数中,它后来被赋值为 0x0000 0002。看到这里应该就明白了吧——明明是想要将地址 0x0000 0001 对应的内容修改为 10,但是最后却将这个地址指向了 0x0000 0002。最最最关键的是,按照清空学说,最后这个变量的地址还没有了。到这里,错在哪里就很显然了。

成绩统计

本次成绩统计说明:本次成绩,一旦发现作业抄袭情况,则代码部分直接清零(冯老师之前和同学有约定),抄袭情况暂时还没有整理完毕,作业的完整总结明天给出。

编号 学号 Cnblogs昵称 代码 总结 加权得分 备注
1 160809401 付胤 0 60 24 代码没有按照题目要求编写,没有第一题和第二题,第三题没有实现需求
2 160809402 张博洋 0 59 23.6 没有写代码,总结敷衍
3 160809403 董宇豪 75 59 68.6 变量名不能够见名知意
4 160809404 朱念齐 35 59 44.6 使用指针数组解决问题,代码2是有问题的代码,无法通过编译
5 160809405 芦彦儒 0 80 32 代码提交时间:2016-12-15 16:42,同11号
- ------ ------ --- --- --- -------------------------------------
7 160809407 Leonardo#* -100 -100 -100
8 160809408 iL.linker 90 85 88
9 160809409 gdcs16_409 -100 -100 -100
10 160809410 无声的梦 80 85 82 代码提交时间:12-12 12:57
11 160809411 刘悦 0 60 24 代码提交时间:2016-12-15 13:59,同10号
12 160809412 张磊 85 75 81
13 160809413 王洪烨 0 85 34 代码提交时间:2016-12-12 11:47,同26号
14 160809414 纪柏如 85 80 83
15 160809415 闫墨杰 70 90 78 作业3实现不完整
16 160809416 史航 60 75 66 作业3没有写,作业1编写正确
17 160809417 狂欢 -100 -100 -100
18 160809418 水母Jam -100 -100 -100
19 160809419 朱钰铖 -100 -100 -100
20 160809420 虞小生 40 70 52 代码3没有写,代码1实现错误,使用strcpy无法实现长度判断
21 160809421 饮冰少年1 60 60 60
---- ---------- --------- --- --- --- -----------------------------------------
23 160809423 李筱 -100 -100 -100
24 160809424 Xzy! 30 65 44 作业3没有写,作业1实现错误
25 160809425 刹那神华 70 70 70 作业3仅实现增加同学的函数
26 160809426 zlt.Santorini'Ly 60 80 68 代码提交时间:2016-12-11 21:28
27 160809427 江超民 0 90 36 代码提交时间:2016-12-14 22:14,同26号
28 160809428 zxkai 90 -100 14 没有总结
29 160809429 王鑫沐 70 85 76
30 160809430 茉妍 65 85 73
31 160809431 茉莉雨 95 85 91