C 语言学习 第12次作业总结
作业总结
本次课堂的内容为字符串相关的几个函数还有结构体。
字符串相关函数
在此之前的课程中,输入主要都是使用scanf
这个函数。而在这节课上,冯老师讲解了字符串获取函数gets
。在不需要控制符的情况下,gets
函数都可以取代scanf
。
strcpy
函数:实现一个字符串到另一个字符串的拷贝strcat
函数:实现两个字符串连接为一个新的字符串strcmp
函数:实现两个字符串大小的比较(基于 ASCII 码)strlen
函数:获得一个字符串的长度数据(需要知道的是,字符串的实际长度还需要加一个尾缀\0
)
结构体
冯老师在课堂上讲了一些结构体的知识,但是结构体中有一些细节的东西没有涉及,我在这里稍作整理,部分同学后期的课程会遇到(主要针对学习目标为嵌入式的同学)。
位域的概念
之前在课堂上,冯老师会告诉大家,结构体的内容是基本数据类型或者是结构数据类型,这里其实还有一种类型就是位域化的基本数据类型:
struct {
int a;
int b;
}
先考察上面的这个结构体的内容,它包含有两个基本数据类型的变量a
和b
。在内存中,他们将会占用2 * sizeof(int)
个内存空间。在某些情况下,可能仅想要用一位来表示一个数据是否被使用(即真或者假),那么对于上面的结构体,空间就浪费的过于厉害。这时可以修改以上的结构体:
struct {
int a :1;
int b :1;
}
这时候,这个结构体的内存占用就变成了一个sizeof(int)
,虽然还是2
个int
类型的变量,但是每一个变量都仅占用一个位的内存。
对齐和补齐
虽然结构体和数组都是一种“固实”的数据类型(表现在内存中为连续存储)。但是稍有不同是的,数组的确是一种真的“固实”数据——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 个位置的空洞,这些空洞是理论上可以存储数据,但是被浪费掉的
也许你会好奇这有什么用处呢。考虑以下的结构体:
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 |
C 语言学习 第12次作业总结的更多相关文章
- C语言学习 第七次作业总结
C语言学习 第七次作业总结 数组可以分为数组和多下标数组(在传统的国内C语言书本中,将其称为二/多维数组). 数组名称 在之前的课程中,大家应该都有印象,对于int a这样的定义,会为变量 a 声明一 ...
- C语言学习 第八次作业总结
本次作业其实没有新的内容,主要就是复习上一次的一维数组的相关内容.冯老师布置了5道题目,其中涉及到一些比较简单的排序或者是查找的方法.因为数据很少,所以直接使用for循环遍历就可以了. 关于本次作业, ...
- C语言学习 第十一次作业总结
作业总结 两次的作业,都是和指针有关.从第一次的作业开始,我就多次让同学们思考这个问题:为什么要用指针,为什么在函数的形参中要使用指针.如果能够想明白这2个问题,那么同学们应该会指针的了解就差不多足够 ...
- C 语言学习 第六次作业总结
本次作业,同学们开始学习函数.通过之前和同学们的沟通,似乎同学们在这里遇到的问题比较多.下面,我先帮同学们整理下函数的相关知识点. 什么是函数 首先,需要明确的是,什么是函数.所谓函数,也就是一段有名 ...
- C语言学习 第四次作业总结
本次作业主要为了复习分支语句,同时复习之前学习过的判断语句,printf和scanf函数的使用. 学习到这里,同学们应该已经基本掌握了基本的数据类型,分支结构,循环结构,条件判断语句.应该可以利用这些 ...
- C 语言学习 第三次作业总结
本次作业内容: For循环的使用 If判断语句的使用 常用数学运算表达式的使用 数学函数库中几个常见函数的使用及自我实现 将操作代码提交到coding 作业总结: For循环是C语言中一种基本的循环语 ...
- C语言程序设计第12次作业
一.本次课主要内容: 本章主要介绍指针相关的基础知识,本节课的主要如下 (1)通过示例"电码加密"引入字符指针与字符串处理.首先重点介绍字符指针和字符串的关联和区别,然后对常用字符 ...
- C 语言学习 第五次作业总结
第五次作业,主要学习和复习的是几种循环结构的使用. 在前一次的课堂上,同学们已经学习了分支语句的使用.分支语句和循环语句配合使用,就可以写出更多的,逻辑功能丰富的代码了. 逻辑功能的丰富,也意味着学习 ...
- Go语言学习之12 etcd、contex、kafka消费实例、logagent
本节内容: 1. etcd介绍与使用 2. ElastcSearch介绍与使用 1. etcd介绍与使用 概念:高可用的分布式key-value存储,可以使用配置共享和服务发现 ...
随机推荐
- (转)从P1到P7——我在淘宝这7年
(一) 2011-12-08 [原文链接] 今天有同事恭喜我,我才知道自己在淘宝已经七周年了.很多人第一句话就是七年痒不痒,老实说,也曾经痒过,但往往都是一痒而过,又投入到水深火热的工作中去.回家之后 ...
- Marshal.Copy将指针拷贝给数组
lpStatuss是一个UNITSTATUS*的指针类型实例,并包含SensorDust字段 //定义一个数组类型 byte[] SensorDust = new byte[30] //将指针类型拷贝 ...
- C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍
在前面一系列文章中,我们可以看到微信自定义菜单的重要性,可以说微信公众号账号中,菜单是用户的第一印象,我们要规划好这些菜单的内容,布局等信息.根据微信菜单的定义,我们可以看到,一般菜单主要分为两种,一 ...
- Java的内存分配
java内存分配 A:栈 存储局部变量 B:堆 存储所有new出来的 C:方法区(方法区的内存中) 类加载时 方法信息保存在一块称为方法区的内存中, 并不随你创建对象而随对象保存于堆中; D:本地方法 ...
- 移动Web触摸与运动解决方案AlloyTouch开源啦
传送门 Github地址:https://github.com/AlloyTeam/AlloyTouch 简介 AlloyTouch的本质是运动一个数字,把数字的物理变化映射到你想映射的任何属性上.所 ...
- app使用微信支付成功后,点击返回到该app却跳到另外一个app去了
刚接手了公司iOS的两个APP, 现在碰到了这样一个问题: 有一台iPhone在一个APP中使用了微信支付,支付成功后,点击返回到该APP,结果却跳到了另外一个APP去了. 这两个APP都是公司开发的 ...
- 9.PNG的制作
1.背景自适应且不失真问题的存在 制作自适应背景图片是UI开发的一个广泛问题,也是界面设计师渴望解决的问题,我相信我们彼此都深有体会. 比如: 1.列表的背景图一定,但是列表的高度随着列表 ...
- Spark运行模式与Standalone模式部署
上节中简单的介绍了Spark的一些概念还有Spark生态圈的一些情况,这里主要是介绍Spark运行模式与Spark Standalone模式的部署: Spark运行模式 在Spark中存在着多种运行模 ...
- [笔记]过期的UBUNTU怎么更新软件包
使用old-releases仓库替换main/security仓库,就像下面这样. sudo sed -i -r 's/([a-z]{2}\.)?archive.ubuntu.com/old-rele ...
- class.c 添加中文注释(3)
int class_device_register(struct class_device *class_dev) { /* [cgw]: 初始化一个struct class_device */ cl ...