C语言_分支语句、循环语句、函数初步
bool类型
bool只有true 和false;在C语言中0是false,其他情况(非0)都为true。
在.c文件中需要引入头文件<stdbool.h>;在.cpp文件中直接使用。
注意:VS2012不完全支持C99标准,不能引入头文件stdbool.h 。但文件后缀为.cpp可以直接使用bool类型。
也可以自己构造bool 类型(但没必要,因为cpp文件下可以直接用)。
构造bool类型代码示例
1 | // test.c 文件 注意文件后缀是 C 文件。 |
1 |
|
1 |
|
关系表达式
关系表达式运算结果是bool值。关系运算符都是双目运算符,其结合性均为左结合。关系运算符的优先级低于算术运算符,高于赋值运算符。在六个关系运算符中,<
、<=
、>
、>=
的优先级相同,高于==
和!=
,==
和!=
的优先级相同。
需要特别注意:==
才表示等于比较,而 =
表示赋值,大家要注意区分,切勿混淆。
该死的=
号
1 |
|
此例中if圆括号内的表达式为"age=3"
,是一个常量赋值给变量的操作,一定会通过导致表达式结果值为1,所以无论在外输入什么此处判断都会为true。这是因为少打了一个=号造成等值判断误成为了赋值语句。为了规避这个错误,我们应该在使用等值判断语句时尽量把常量放在左边,把待比较的变量放在右边。这样的话,如果写成"3=age"
后,编译时期即会报错,而不是把错误延续给运行时期!
逻辑表达式
逻辑表达式运算结果是bool值。
与运算(&&
)
又称截断与、简洁与。参与运算的两个表达式都为真时,结果才为真,否则为假。
或运算(||
)
又称截断或、简洁或。参与运算的两个表达式只要有一个为真,结果就为真;两个表达式都为假时结果才为假。
非运算(!
)
参与运算的表达式为真时,结果为假;参与运算的表达式为假时,结果为真。
优先级
逻辑运算符和其它运算符优先级从低到高依次为:
赋值运算符(=
) <
&&
和||
<
关系运算符
<
算术运算符
<
非(!
)
&&
和||
低于关系运算符
,!
高于算术运算符
。
分支语句
双分支语句加几行代码变单分支
1 |
|
1 |
|
三目运算符替代简单的if语句
1 |
|
if 语句在某些情况下可以用条件运算符“?:”来简化表达。“ ? :”是一个三元运算符,其构成的表达式格式为:<表达式1> ? <表达式2> : <表达式3>;执行逻辑:先计算表达式1,若其值为真(或非0),则计算表达式2(不计算表达式3),并将该值作为整个表达式的值;反之,即表达式1 的值为假或为0,则计算表达式3(不计算表达式2),并将该值作为整个表达式的值。
if-else多分支语句
判断字符类别
判别键盘输入字符的类别,是否是数字字符,是否是小写字符,是否是大写字符,还有其它字符。
1 | //输入字符给变量有两种写法。 |
相应的头文件<ctype.h>
函数
字符分类
isalnum(char)
;判断一个字符是否是字母或数字isalpha(char)
;判断一个字符是否是字母islower(char);
判断一个字符是否是小写字母isupper(char);
判断一个字符是否是大写字母isdigit(char);
判断一个字符是否是数字isxdigit(char);
判断一个字符是否是十六进制数字字符(0123456789abcdefABCDEF
)iscntrl(char);
判断一个字符是否是控制字符isspace(char);
判断一个字符是否是空白字符isblank(char);
判断一个字符是否是空格字符(C99
)ispunct(char);
判断一个字符是否是一个标点符号
字符操作
tolower(char);
将字符转换成小写toupper(char);
将字符转换成大写
良好的代码风格
switch多分支结构
函数初步
在结构化程序设计中,函数是将任务进行模块划分的基本单位。通过函数,可以把一个复杂任务分解成为若干个易于解决的小任务。充分体现结构化程序设计由粗到精,逐步细化的设计思想。一个大的程序一般应分为若干个程序模块,每个模块实现一个特定的功能,这些模块称为子程序,在C语言中子程序用函数实现。
什么时候我们认为模块是足够小的:功能是单一的。
按是否系统预定义分两类
编译系统预定义
一类是编译系统预定义的,称为库函数或标准函数,如一些常用的数学计算函数、字符串处理函数、图形处理函数、标准输入输出函数等。这些库函数都按功能分类,集中说明在不同的头文件中。用户只需在自己的程序中包含某个头文件,就可直接使用该文件中定义的函数。
<asserst.h>
<ctype.h>
<math.h>
<stdio.h>
把函数名字、函数功能记下来。
用户自定义
另一类是用户自定义函数,用户可以根据需要将某个具有相对独立功能的程序定义为函数。
自定义函数有:函数返回类型
+ 函数名
+ 形参列表
+ 函数体构成
;
函数的命名要求
- 拿英文命名函数
- 第二个要求:见名知义,不要用汉语拼音,有歧义。
函数的声明、定义注意事项
原则
- 需要外部输入的(比如scanf)写到形参中;
- 需要打印、输出的,return返回。
该死的形参
形参变量类型名后的标识符要不要省
- 函数的声明中形参列表可以省去形参名(标识符),但不能省去类型名。因为虽然函数不识别名称,但必须识别类型;
- 而函数定义就要把形参名写全,因为函数体中要操作之;
- 函数的调用中,参数前不能加类型名。
形参变量的定义必须每个参数都有一个类型和一个名称
形参变量的定义与局部变量定义是有区别的。局部变量是可以int x,y;
这样定义的,但形参定义不可以,必须是一个类型匹配一个名称!
函数声明、定义后的分号
函数声明是一个语句,所以要加分号。但是定义函数完成后花括号后加分号也没影响,因为那是个空语句,但也没必要!
一定要在函数被调用前声明或定义
被调用函数要在调用者调用它之前的区域声明或定义,不然编译是不会通过的!
不允许函数的嵌套定义
C语言中不允许函数的嵌套定义,即在一个函数中定义另一个函数。
函数的调用是允许嵌套的
示例
定义函数时可能会涉及若干个变量,究竟哪些变量应当作为函数的参数?哪些应当定义在函数体内?这有一个原则:
作为一个相对独立的模块,函数在使用时完全可以被看成 “黑匣子”,除了输入输出外,其他部分可不必关心。从函数的定义看出,函数头正是用来反映函数的功能和使用接口,它所定义的是“做什么”,在这部分必须明确“黑匣子”的输入输出部分,输出就是函数的返回值,输入就是参数。因此,只有那些功能上起自变量作用的变量才必须作为参数定义在参数表中;函数体中具体描述“如何做”,因此除参数之外的为实现算法所需用的变量应当定义在函数体内。
形参和实参
形式参数(形参)
只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。
形式参数是指函数名后括号中定义的变量,形式参数只有在函数被调用的过程中给于赋值(分配存储空间)。函数执行完后形式参数变量就自动释放了,所以形式参数只在函数中可见(作用域)。
实参(实际参数)
调用函数时给出的参数包含了实实在在的数据,所以称为实际参数,简称实参。
实参可以是:常量、变量、表达式或函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
功能
形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。
形参实参的区别与联系
- 形参变量只有在函数被调用时才会分配内存(在stack 中),调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
- 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。
- 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。
- 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有关系,所以,在函数调用过程中,形参的值发生改变并不会影响实参的值。
函数调用中的内存分配
假如内存共有1M空间。我们会把它分解成若干个栈帧(下去查询VS如何设置栈的大小和栈帧的大小),主函数调用时,会把底层的栈帧分配给主函数,如果将要占用很多的空间,我们就得继续往上占用上层的栈帧。每当有一个函数调用,即分配一个栈帧。
1 | int a = 10; |
传地址交换值–间接改变值
问题1
int tmp = *ap;
通过指针指向取x的值并修改tmp值的底层实现是如何的?
mov eax,10;
mov ebx,0x00b3f9f0;
mov [ebx],100h;
直接访问、间接访问。任何一本讲微机原理的书都有讲解。
问题2
仔细观察,发现未曾开辟定义的存储空间中都是随机值"cccccccc
",而有两个地址很特殊,就是Swap_p
的函数域中ap
指针变量的地址之上的两个地址"0X00B3F9F8"
和"01351459"
。这两个值是什么值呢?
C语言的面试:指针、编译链接过程、函数调用过程中线程的保护、恢复是怎么实现的。调用函数、现场保护,调用完后要实现现场的恢复。这是区分学的好不好、自学能力强不强的标准。C语言全部讲完后,分模块讲时再说。
函数调用机制
C语言中,先把y入栈,再把x入栈,函数参数入栈的顺序是从右向左的!有些编程语言是从左向右的。
函数调用首先要进行参数传递,参数传递的方向是由实参传递给形参。传递过程是,先计算实参表达式的值,再将该值传递给对应的形参变量。一般情况下,实参和形参的个数和排列顺序应一一对应,并且对应参数应类型匹配(赋值兼容),即实参的类型可以转化为形参类型。而对应参数的参数名则不要求相同。
在示例中int MaxInt(int a,int b)
,a和b是形参,在main中 x, y 是实参。
查:被调用函数MaxInt return c给主函数中的max变量时,肯定不能直接赋值,而是用临时空间先存放,再取出送给这个max。这个临时空间谁来担当?
多文件结构
循环语句
while语句
示例–打印平方表
1 |
|
do-while循环
特点是先执行,后判断。要有一个条件使之退出while才行。
1 | //计算一个正整数的位数 |
但是do-while有一个漏洞,就是如果上例代码输入了一个"0"值的话,还是会执行一次,最后输出1位。但是实际上0是不占位数的。
for循环
- 表达式1只执行一次
- 表达式2判断为真才执行循环体
- 循环体执行完后才执行表达式3
特点是:编程的执行顺序和我们编写他的顺序不符合,所以有些人不习惯for语句。
VS和VC++编译器对于for语句中表达式1的区别
VS2012/2019中的for(int i=0;i<10;++i)
中i的作用域只在for块内
VC++中i的作用域在块外也有,因此不能重新声明i
VC++中.c
文件中的for语句中表达式1不能同时定义、初始化。只能在for外先定义i。
for循环的惯用法
对于向上加(变量自增)或向下减(变量自减)的循环来说,for语句通常是最好的选择。
1 | // 从0 向上加到n-1 |
1 |
|
循环语句圆括号中省略表达式
1 | int n=10; |
对于for省略:如果省略了表达式2,那么是死循环的效果
对于while,省略圆括号内表达式,不可行。
三种死循环
for
1 | for(;;) |
1 | //如何把死循环写法改为之前for(int i=0;i<n;++i)的效果 |
while
1 | while(1) |
do-while
1 | do |
0716重点:跳转语句
实际上,break/continue/return
都是goto
的变种。
break
语句只能用在switch语句和循环语句中,用来跳出switch语句或提前终止循环,转去执行switch语句或循环语句之后的语句。
需要注意的是:break语句只能跳出一层循环。
continue
语句只能用在循环语句中,用来终止本次循环。当程序执行到continue语句时,将跳过其后尚未执行的循环体语句,开始下一次循环。下一次循环是否执行仍然取决于循环条件的判断。continue语句与break语句的区别在于,continue语句结束的只是本次循环,而break结束的是整个循环。
但上面这段话没说明本质。
continue对于for语句
continue对于for语句跳到的是表达式3。如果处理不当就会出问题:
1 | //打印:1 3 5 7 9 |
continue对于while和do-while语句
continue对于while和do-while语句,continue跳到的是圆括号内的判断。如果处理不当,更会出问题。
如何将上述for的代码由for改为while循环?
1 |
|
1 |
|
goto
语句和标号语句一起使用,所谓标号语句是用标识符标识的语句,它控制程序从goto语句所在的地方转移到标号语句处。
goto语句会导致程序结构混乱,可读性降低,而且它所完成的功能完全可以用算法的三种基本结构实现,因此一般不提倡使用goto语句。
1 |
|
适用场合
在某些特定场合下goto语句可能会显出价值,比如在多层循环嵌套中,要从深层地方跳出所有循环,如果用break语句,不仅要使用多次,而且可读性较差,这时goto语句可以发挥作用。
1 |
|
注意
- goto最好只用它来从上到下跳。不要从下到上跳,因为可能会产生程序的二义性。
- 不能在函数间跳转,不能跨越两个函数。只能在本函数的作用域、可见性中跳转。
VS2019
return
语句用于结束函数的执行,返回调用者,如果是主函数,则返回至操作系统(终止程序的执行)。
利用一个return语句可以将一个数据返回给调用者。
return本质上就是goto连带一个数据返回。与goto的区别就是goto不能带一个数据,return可以。
主函数中的return与子函数中的return
- 主函数return后,程序结束。子函数return只是本函数结束。
- 主函数的return是返回给操作系统。
return与exit函数的区别
在主函数中,exit(1);
与return 0;
的效果是一样的。都是结束程序的执行;但在子函数中就和return语句不一样了,子函数中调用exit()
也会直接终止整个程序的执行。
调用exit()函数会直接终止程序的进行,需要引入头文件<stdlib.h>
。
传递给exit函数的实际参数和main函数的返回值具有相同的含义:两者都说明程序终止时的状态,为了表示正常
终止,传递0,即 exit(0);因为0 有点模糊,所以C语言允许用EXIT_SUCCESS
来替代(效果相同)。exit(0);等同于exit(EXIT_SUCCESS)
;,表示程序正常退出;exit(1);等同于exit(EXIT_FAILURE);,表示程序异常退出。
1 | exit(EXIT_SUCCESS); /* normal termination */ |
返回类型为void
通常,当函数的返回类型为void时, return语句可以省略,如果使用也仅作为函数或程序结束的标志。有些编译器可以写成return void;
,但在VS2019中不可以。
总结
都是goto的变种。
空语句
语句可以为空,也就是除了末尾处的分号以外什么符号也没有。
所带来的问题
圆括号后放置空语句
不小心在if、while 或 for 语句的圆括号后放置分号会创建空语句,从而造成if、 while 或 for 语句提前结束。if 语句中,如果在圆括号后放置分号,无论条件表达的值是什么,if 语句执行的动作都一样,都会执行if块内的代码:
if语句
1 | if(d == 0) ; |
while语句
while 语句中,如果在圆括号后放置分号,会产生无限循环:
1 | i = 10; |
另一种可能是循环终止,但是在循环终止后只执行一次循环体语句:
1 | i = 10; |
for语句
for 语句中,如果在圆括号后放置分号,会导致只执行一次循环体语句:
1 | for(i = 10; i > 0 ; --i) ; |
要注意的地方
该死的分号;
函数后的分号
函数声明语句
函数声明时加分号。
函数定义语句
在大括号后加了分号也没事,因为这是空语句,无大碍。
结构体定义语句
结构体定义结束时,在大括号后必须加分号。表示结束。
逗号表达式
逗号表达式只能写类型一致的声明,int i = 0, float = 2.0
是不对的。
1 |
|
上例中,x = (a+10, a=b, c += 10);
这句表达式如何运算呢?首先执行a+10
,但a的值不变;再执行a=b
,将b的值赋给,a的值变成20;再让c+=10
,c的值变为40。那么x的值会被赋为多少呢?答案是40,因为逗号表达式的值是取最后一条表达式的值。
按照逗号表达式的运行机制,我们可以优化一个事情,就是下面讲到的scanf的代码位置。
scanf_s
函数的机制
1 |
|
stdin标准输入文件流
stdout标准输出文件流
stderr错误流
标准输入/输入文件流他们都带有缓冲区。stdin从键盘上输入数据的时候,就先把数据放到标准输入文件流的缓冲区中了。stdin还有一个能力,会把缓冲区中的内容回显在屏幕上。如果没有回车,我们就认为这个输入没有结束。如果打了回车,就相当于通知scanf从缓冲区中取值。可以每输入一个数据回车一次后scanf读取此数,接下来输入后面的数据并回车时,就会把前面的缓冲区覆盖掉;也可以使多个数据空格隔开全输入完,再一次性回车,交给scanf依次读取。两种方式最大的不同就在于缓冲区存储的数据不一样多。
这种输入的形式,有个不好的地方,就是while外一个scanf,while内有个scanf。怎么样使之更为简洁呢?就用到了逗号表达式。
1 |
|
while最终要判断的是逗号表达式最后的一个表达式即n!=0
。所以这样做不但不影响while的正确判断还简化了代码编写。
0715作业
- 两个for循环(二维数组)打印一个乘法口诀表。
1 |
|
- 仔细观察如何把一维数组转化输出为一个二维平面?–>为N后做准备
- 选做题:仔细观察,n=5,从输出的角度如何打印成这样的效果:
(提示:第一行每个数只有1位,每一行都比上一行少一个数)
如果是整型数
如果是字符数组:滑动动态窗口方式。 - 输入任意顺序的三位数,都能正确找到其中间大小的数。
1 |
|
- 下去查询VS如何设置栈的大小和栈帧的大小
6. 下去查:每一个工程的入口函数默认是主函数,怎么设置其他函数为入口?
7. 查:直接访问、间接访问。讲微机原理的书都有讲解。
mov eax,10;
mov ebx,0x00b3f9f0;
mov [ebx],100h;
8. 被调用函数return一个数给主函数中的一个变量时,肯定不能直接赋值,而是用临时空间先存放,再取出送给这个变量。这个临时空间谁来担当?
9. EAX惯用于“累加器”(accumulator),它是很多加法乘法指令的缺省寄存器;还用来存放函数返回值;占用32个2进制位,4个字节。eax的后16位为ax,后16位中,前8位为ah,后8位为al,前16位的访问需要右移。有时EAX也用于程序数据的返回值。
1. EBX惯用于“基地址”(base)寄存器,在内存寻址时存放基地址。多与指针相关。
2. ECX惯用于“计数器”(counter),是重复(REP)前缀指令和LOOP指令的内定计数器。用于循环的计数。
3. EDX:I/O设备的地址编号大于255时,存放设备的端口号。
4. 在进行乘除法运算时,EAX用来存商,EDX用来存余数。
5. 临时量具有常性,只读不可写,Add(x,y)=100;是不可行的,是不能给函数的返回值赋值的。
0716作业
1 | 这里对程序中用到的产生随机数的函数进行解释。 |
- 做一个简单的计算器,弄一个.h和.cpp文件
1 |
|