第六周:程序设计与C语言
C语言学习笔记
本系列笔记是学习复盘慕课上浙江大学 翁恺老师《程序设计入门-C语言》课程的笔记和一些自己的总结。(文章的部分截图来自课程视频截图)
课程链接:https://www.icourse163.org/learn/ZJU-199001?tid=1206771253#/learn/content
6.1数组
数据可以存放在变量里,每一个变量有一个名字,有一个类型,还有它的生存空间。如果我们需要保存一些相同类型、相似含义、相同生存空间的数据,我们可以用数组来保存这些数据,而不是用很多个独立的变量。数组是长度固定的数据结构,用来存放指定的类型的数据。一个数组里可以有很多个数据,所有的数据的类型都是相同的。
6.1.1初试数组
EG1:如何写一个程序计算用户输入的平均数?输入-1代表输入结束。
不需要记录输入的每一个数;计算sum然后除法
变相:
EG2:且输出所有大于平均数的数?
需要记录读入的所有数。
使用数组的常见步骤:
定义数组---赋值数组---遍历数组(使用数组中的元素)
问题:使用的元素个数会不会超过数组的长度呢?
6.1.2定义数组
<类型>变量名称[元素个数]
- 元素数量必须是整数
- C99之前:元素数量必须是编译时刻确定的字面量(二级考试),不能是变量,不能是动态的程序运行过程中产生的数字。
- 数组是一种容器(放东西的东西),所有的元素具有相同的数据类型;一旦创建,不能改变大小;数组中的元素在内存中是连续依次排列的。
- 数组中的单元可以出现在复制号的左边或者右边;在赋值号左边的叫做左值。
有效的下标范围:
- 编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读还是写
- 一旦程序运行,越界的数组访问可能导致问题,导致程序崩溃。
Segmentation fault
Int a【10】,其实最大的下标是9,不可以对a【10】=2进行复制。
6.1.1节中我们的代码是危险的,因为输入的数据可能超过100个。
解决方案一:读满100个就不可以再读
解决方案二:C99特性,先让用户输入它要输入的个数,然后再创建数组
- Int a[0] :长度为0的数组,编译可以通过,可以存在,但是无用。因为即使是0也是越界了
- 字符可以做下标吗?可以
6.1.3数组例子:统计个数
EG:写一个程序,输入数量不确定的【0,9】的范围内的整数,统计每一种数字出现的次数,输入-1表示结束。
不需要记录输入的数值,而是要记录输入的这个数曾经输入过多少次,设置的变量是记录次数的。
几个注意点:
- 初始化数组是必要的,并且需要通过循环才能完成
- Const int number 通过用这种方式来设定数组大小。
- Magic number:是指在程序中经常使用的数字,比如数组的大小,在for的条件里常常用到,我们使用const int 来声明,能够让其他人更加理解和明白代码中数字的含义。
6.2函数的定义与使用
6.2.1初见函数
EG: 求素数的和。
可以将判断一个数是不是素数的那部分代码放入函数中。
EG: 求出1到10,20到30,35到45的三个和。
用一个函数去求三段的和。
6.2.2函数定义和调用
函数:是一块代码,接收0个或者多个参数,做一件事情,并返回0个或者1个值。
函数结构:函数头(返回类型 函数名 参数)+函数体
- ()的含义是起到了表示函数调用的重要作用,即使没有参数也需要()。讲函数指针的时候我们再来讲调用函数时一定要加上()的原因。
DEV C++编译器中我们通常在调试的时候是使用下一步(主程序),但是如果我们要进入一个函数,一步一步的执行一个函数中的步骤,我们使用单步进入的按钮。
6.2.3 从函数中返回值
Return的作用:停止函数的执行,并送回一个值。
Return有两种写法: 1.return; 2.return 表达式;
- 一个函数中是可以出现多个return语句的。而且return不一定要放在最后。但是这不好,不符合我们单一出口的理念。有很多个出口可以传递值到外面。
- 可以赋值给变量,有时也可以丢弃(有时候我们要的是副作用)
- 用void做返回类型的函数,可以没有return,但一定不能使用带值的return。
- 6.3函数的参数与变量
- 6.3.1函数原型
函数先后关系:
1.如果我们不在主函数中声明函数,那么函数应该写在主函数的上面,这是因为C 的编译器是自上而下的顺序分析的你的代码的。
2.如果我们不是将函数写在上面,那么我们就需要在主函数中先声明这个需要使用 的函数。
(通常我们使用第二种顺序)LLVM是一个严格的编译器。
函数的原型声明:声明不是函数,只有函数头,可以在int main之外去定义
函数的定义:具体的代码(函数头+函数体);
原型声明在写主函数里是旧版本的C;现在一般习惯于函数的原型声明在写主函数的外面。
6.3.2参数传递
表达式包括:字面量,变量,返回值,计算结果。
类型不匹配?
- 调用函数时的值与参数的类型不匹配是C语言传统上最大的漏洞。
- 编译器总是悄悄的替你把类型转换好,但这很可能不是你所期望的。
- 后续的语言,C++/java在这方面很严格。
EG:
不可以改变主函数里面a和b的值,当传递参数给函数的时候,我们永远传递的是值,是把5和6传递过去,虽然函数参数中的a和b与主函数中的a和b同名,但是实际上他们是没有任何关系的,不等同的。
形式参数与实际参数:
传值:每一个函数是有它自己的变量空间,参数也位于这个独立的空间中,和其他 的函数没有关系。对于函数参数表中的参数,叫做形式参数,调用函数时给的值,叫做实际参数。
正是因为我们这种古老的方式来称呼实际参数,导致我们容易误会这个实际参数就是进入到函数内部实际参加预算的参数,不是这样,我们调用函数的时候不是传递进去变量,而是传递进去值了。
C语言中都是传值。正确的理解方式:是参数与值的关系。
- 6.3.3本地变量(局部变量、自动变量)
本地变量是指在函数每次运行中产生的变量空间中的变量,而不是主函数里面所声明的变量。
- 函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,成为本地变量。
- 定义在函数内部的变量就是本地变量。
- 参数也是本地变量。和本地变量具有一样的生存期和作用域。
自动:生存期是自动的。
生存期&作用域
生存期:开始?消亡?
作用域:在代码的什么范围内可以访问这个变量?
对于本地变量,这两个问题的答案是同一的:大括号内—块。
Main里面声明的变量的作用域不包括自己定义的函数大括号之中。
***特别注意的一点:main函数有自己的变量空间;我们定义的函数也有自己的变量空间。
本地变量的规则:
- 本地变量是定义在块内的(就是大括号),可以使函数,也可以是语句。甚至可以直接随便拉一对大括号来定义变量。
EG:
If大括号之后,这个i就不存在了。
- 程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了。
- 在块外面定义的变量在块里面仍然有效。
- 块里面定义了和外面同名的变量时,则里面的变量掩盖了外面的变量。(Java不是这样)
- 本地变量不会被默认初始化。
- 参数在进入函数的时候被初始化了。
6.3.4函数细节
Void f(void)&void f():
在传统C上,第一种表示没有参数;第二种表示参数未知,但并不表示没有参数
C99中编译不出错,但是传递的值可能会出错。
12行编译器认为要两个int的传递;但是25行我们要两个double,这两个整数传递到double那里去,就出错了。
可以在一个函数里定义另一个函数吗?不予许,C语言不允许函数的嵌套定义。
Return (i): 圆括号没有任何意义;其实仍然是返回i的值。
6.4二维数组
6.4.1二维数组
定义:a【3】【5】3行5列的矩阵
初始化:
- 列数必须给出,行数可以由编译器来数
- 每一行一个{},逗号分隔
- 最后的逗号可以存在,有古老的传统
- 如果省略,表示补零
- 也可以用定位(* C99 ONLY)
二维数组在内存中的排列是连续依次的,里面也可以不带大括号。
怎么对一行遍历?
怎么对一列遍历?
怎么判断对角线?
这是二维数组里经常做的事情。