《高级语言程序设计》
《高级语言程序设计》
2021/22第一学期期末总复习
课程的全部内容可分为五大模块:
- 基础知识
- 三种基本结构及流程控制
- 函数
- 数据类型
- 文件
下面按这五大模块分别作回顾
模块一:基础知识部分
需要掌握的知识要点:
(1)计算机的组成,重点是内存知识,内存容量及内存地址,每个存储单元为一个字节,按字节编址,一个字节由8个二进制位组成,内存容量取决于地址总线数量,2n
(2)C语言程序的组成:由一个或多个文件组成,每个文件由一个或多个函数组成,函数是C程序的基本单位,一个程序有且只有一个main( )函数,这是程序的入口函数
(3)从C的源程序到可执行的文件的全过程:编辑、编译、链接最后才能运行,各阶段生成的文件扩展名分别为:源文件(.c)、目标文件(.obj)、可执行文件(.exe),头文件的扩展名:.h
(4)函数的组成:函数首部和函数体,函数中可有注释行/* …*/
(5)编译预处理有三种:文件包含、宏定义、条件编译:预处理命令行都必须以“#”号开始,在程序中出现的位置任意
无参宏定义#define[阮1] 宏名 串
带参数的宏可以完成一些简单的功能,例如:
#define MUL(x,y) x*y
主函数中c= MUL(3+2,4+5),则变量c的值为45确进行宏替换(技巧:对实参表达式不事先进行计算,直接代替参数位置)
区分:#define MUL(x,y) (x)*(y)
主函数中c= MUL(3+2,4+5),则变量c的值为?
(6)C程序中的六种符号:关键字、标识符、运算符、分隔符、其它符号、数据(包括常量和变量)
(7)熟记所有常见关键字,关键字中最多的是流程控制语句中的关键字,如:for、while、do、if、else、switch、case、default、break、continue、goto、return等,还有一大类是变量类型及存储类别的,如:void 、int、short、long、signed、unsigned、char、double、float、enum [阮2] 、union、struct [阮3] 、typedef、static [阮4] 、register、auto、extern,还有一些如: sizeof 等;常见预定义标识符:printf,scanf,define,include,他们可以作为用户的标识符
(8)能正确定义标识符并会判断合法的标识符(在程序中,符号常量名、变量名、函数名、类型名都是用户自定义标识符),自定义标识符的正确与否可从以下几方面判断(也可以认为是判断的三个步骤):①是否以字母或下划线开头;②是否是只有字母、数字、下划线的序列③是否选用了关键字
练习:下列哪一个是合法的用户自定义标识符______。答案: [阮5] A
A、_do_while; B、begin?end; C、for; D、2nd_run;
B中存在“?”不合法;C中for为关键词不可自定义;D不是以字母或者下滑线开头,不符合自定义标识符的要求
(9)掌握所有的运算符及其优先级、结合方向及特殊的运算规则:
34种运算符的优先级关系大致为: [ ]、( )、->、.---à一元运算符---à乘除类算术运算符 [阮6] ---à加减类算术运算符---à移位运算符---à有大小关系的关系运算符---à等与不等的关系运算符---à位运算符---à逻辑与---à逻辑或---à条件运算符---à各种赋值运算符---à逗号运算符 [阮7] 。
结合方向:一元运算符、条件运算符、赋值运算符三种为自右向左结合,其它均为自左向右结合
能改变变量值的运算符:赋值 [阮8] 、++、- -,注意前后++、- -的区别
具有特殊运算规则的运算符:
①逻辑与&&(短路功能,第一运算对象为假则第二对象不运算);
②逻辑或||(短路功能 [阮9] ,第一运算对象为真则第二对象不运算);
③条件运算符(总是先运算第一运算对象,根据其真或假的值从第二或第三对象中取一个作为整个表达式的值),常常可以代替一些简单的双分支if,例如:if (x>y) z=x; else z=y; 可以用条件表达式写成:z=(x>y)?x:y;条件表达式牢记真前假后
④%运算符要求两个运算对象均为int型
⑤注意两个运算符的区别:=与= =
注意表达式的值和变量的值是两个概念,很多情况下不是一回事儿.
例如:int a=1,b; 表达式b=a++使得变量a的值最终为2,b的值最终为1,该赋值表达式的值为1,因为赋值表达式是以最后一个被赋值变量的值作为整个表达式的结果的;
但是如果以上表达式改为:b=++a则变量a、变量b以及赋值表达式的结果均为2,请大家一定要注意理解,做题时看清题目问你表达式的值还是某个变量的值,注意区分。
这一部分要多刷题,熟悉各种运算符的使用,慕课上的题目每题都要理解会做。
强制类型转换 [阮10] 的标志是类型上一定有括号,比如(int)x就是将x强制类型转换为int型,强制类型转换需要注意数据的丢失问题。执行运算操作并不会改变数据类型。
例如、double a;float b;int c;char d;执行语句c=(d+b)*c-a;之后变量c的数据类型仍然为int型
Float a=3.9,b=6.9;int c;执行语句c=a+(int)(a+b)%3/2;后,c的值为4
(10) 常量是其值不可变化的量,有两种表现形式:字面常量和符号常量,C是定义符号常量只有一种方法:#define 常量名 字符串
C中的字面常量有五种:整型常量、实型常量、字符常量、字符串常量、枚举常量 [阮11] (不作要求)
① 整型常量有三种合法制式:十、八(如012)、十六(如oxa3),另外还可以有 L(l)、U(u)作后缀,分别表示长整型及无符号常量,掌握十进制整型常量在源程序中的八进制及十六进制的等效表示,掌握进制转换的基本方法(十进制到N进制:整除、取余、逆置)例如:十进制的25在源程序中的八进制表示为031,十六进制表示为:0x19或0X19;c语言中没有二进制,只有八进制,十进制与十六进制,二进制用于机器语言,也就是电流传播信号以二进制01的方式存在;八进制以0开头,数字中不包含8;十六进制以0x开头,9后面的数字用abcdef表示
注意:068不是正确的整型数,0开头表示是用八进制表示,但是数字8不是8进制的合理数字
② 实型常量有两种表示形式:小数形式、指数形式,注意:小数形式要求一定要有小数点,小数点前面或后面的数字可以缺省;指数形式一定要有E(e) [阮12] ,并且在其左边一定是合法的整数或小数形式的实数,e右边必须是合法的整数。即:e前e后必有数,e后必定为整数
③ 字符常量 [阮13] 是以一对单‘’括起的单个字符,如果以双引号作为定界符则一定不是正确的字符常量。需要特别注意的是以右\开头的转义字符的三种形式:‘\n’、‘\121’、‘\xa5’ [阮14] ,记住’A’、’a’、‘0’、‘\n’的ASCII码分别为:65、97、48、10,字符常量的大小就是其ASCII码值的大小
小写转大写:ch=ch-32;大写转小写:ch=ch+32;字符常量可以进行计算例如‘A’+32=‘a’;
④ 字符串常量是以一对双“”括起的0个或多个字符,需要特别注意字符串的长度与字符串所占的字节数之间相差1,有一个’\0’作为字符串结尾的标志
(11)变量:在程序运行过程中其值可变的量,需要先定义后使用。
每一个变量都具有:变量名、变量的当前值、变量的地址、变量的存储属性.
指针变量中存的是地址,其余变量中存放的是值。
变量的输入和输出控制需要熟练掌握,在scanf的格式控制串中出现的所有内容都需要运行时从键盘读入,例如:对应于输入语句scanf(“x=%d”,&x); 从键盘必须输入:x=2回车才能让变量x获得值2
在所有函数之外定义的变量称为全局变量,其作用域为定义点开始到程序结束(去掉含同名局部量的作用区域);
在函数内部定义的变量称为局部变量(形式参数也是局部变量),局部变量分auto(默认)和static两种,前者是在函数被调用时分配空间,每次函数执行完毕后将不再存在,但是static型的局部变量在程序刚运行时便分配空间并且做初始化(如果未指定初值则默认初值为0),初始化只做一次,每次函数执行完毕后将继续存在,但是只在本函数中才起作用,因此这种变量的读程序题一定要注意!
练习:
#include <stdio.h>
int cal(int n)
{ static int f=20;
f+=n; //指f=f+n
return f;
}
int main ( )
{
int i;
for(i=1;i<7;i++)
if (i%2) printf("%d ",cal(i));
return 0;
}
答案:21 24 29
在做读程题时要区分各种变量,哪些是全局变量,哪些是局部变量,是自动局部变量还是静态局部变量,全局变量有没有同名局部量问题,确定好每一种变量的生命期与作用域,从而保证能得到正确结果。
模块二:三种基本结构及流程控制
C程序的三种基本结构是顺序结构、选择结构、循环结构。这三种结构分别需要借助于特定的语句控制实现。
(1)顺序结构:赋值语句和函数调用语句是控制该结构的主要语句。函数调用语句中最常用的是:scanf( )、printf( ) [阮15] 、getchar( )、putchar( )等用于控制输入输出的语句。注意用scanf( )输入变量值时要严格按照格式控制串的形式要求输入,用printf( )输出时要进行相应的格式控制,需要熟记几种格式转换说明符:%c、%s、%d、%f、%lf(当变量为double类型时,在scanf中必须用%lf,但输出时用%f就可以了)。
注意%s的特殊性,无论scanf还是printf中,与之对应的都是地址,其他格式控制串,如:%d、%c、%lf、%f等,在scanf中用地址对应,但是在printf中用值对应
(2)选择结构:
用if...else、switch语句配合复合语句、break语句控制实现。
if语句有三种形式:单分支if、双分支if、嵌套if,注意else应与其前最靠近的未匹配过的if相对应,而与对齐的形式无关,每一个else实际上都隐含了一个条件,要充分利用这些条件分析程序;if后的条件通常是关系或逻辑表达式,但是实际语法上可以是任何表达式,仅以该表达式是0或非0来判断条件的假或真。注意:if ( )及else后面都只能控制一条语句,经常需要用到复合语句。
注意:if (!x) … 此处!x相当于x= =0
if (x) … 此处x相当于x! =0
注意:if (x=y)和if (x==y)是完全不同的表达
例如:int x=1,y=2,z=3;
语句if (x=y) z=10; else z=20;执行后,z的值为10
而语句if (x==y) z=10; else z=20;执行后,z的值为20
if (x%2)就是 if (x%2!=0) 也就是 if (x%2==1) 代表x是一个奇数
if (!(x%2))就是 if (x%2==0) , 代表x是一个偶数
在阅读和书写程序时注意if后条件的表达,每个分支所控制的语句。
特别是注意理解嵌套的if,其中每一个else总是与其前最靠近的且未与其它else匹配过的if相对应,注意每个if或else分别对应于什么情况的处理。
switch语句中特别要注意的是:
①switch后面的表达式类型应当为整型、字符型、枚举型,而不可以是float或double型,因为表达式的取值必须是离散值。
②找到与表达式匹配的常量后开始执行,直到遇到break或switch语句结束才会停止。因此每一个分支后如果没有及时用break,则继续向下执行, 这是读程题考试的一个重要考点。
嵌套的switch在阅读的时候要注意每一层的起止位置。
练习:
1、已知变量tf为整型,下面的条件语句中,五种表达有一种与其它四个不等价,它是? 答案:E
- if (tf ) 语句1; else 语句2;
B、if ( tf==0 ) 语句2; else 语句1;
C、if (tf!=0 ) 语句1; else 语句2;
D、if (!tf ) 语句2; else 语句1;
E、if (tf==1 ) 语句1; else 语句2;
2、设以下所有变量均为整型,下列程序所实现的分段函数是?
if (x>0) y=1;
else y=0;
if (x<0) y=-1;
3、下列程序段执行后,a、b的值是? 答案:2、0
a=1; b=2; c=0;
if (a>b)
c=a;
a=b;
b=c;
4、设有声明int a=1, b=2,则下面程序段的输出结果是? 如果是内层的每个case后有break,而外层没有break,输出结果是?
switch (a)
{
case 1:
printf("#\n");
switch (b)
{
case 1: printf("*\n");
case 2: printf("**\n");
case 3: printf("***\n");
}break;
case 2: printf("##\n"); break;
}
结果为#*****##与#**##
(3)循环结构:常用while、do…while、for三种流程控制语句实现,注意while和for属于当型循环,do…while属于直到型循环,若while后的条件为非0则继续循环,若为0则结束,三种循环是可以相互转化的。这里特别强调的是:循环的退出第一种情况是循环控制条件为0;果表示条件的表达式值永远不可能为0, 则该循环为死循环,这是编程中需要避免的。
循环条件:for语句的表达式2,如果表达式2缺省,则表示永真条件;while或do ... while后的表达式表示条件。
循环退出的第二种情况是:循环体内满足某条件执行break语句退出本层循环。
在循环体中可以用continue表示忽略本次循环体中的后续语句,重新开始下一次循环条件的判断,以便决定是否要继续循环。
掌握循环体执行的次数的计算及循环之后相关变量的结果.
对于循环嵌套,理解每一层循环的循环体内容,理解两重循环的控制及执行方式。
循环体只有一条语句,注意复合语句的正确使用。
特别注意循环体是不是空语句,例如:for (i=0;i<3;i++); [阮16] s+=i; 这里循环体为空语句,语句s+=i;在循环终止后只执行一次,是普通的赋值语句。
某些情况下循环体就是什么也不需要做,这个时候就一定要使用空语句。
注意:程序中有关退出的几个操作:
①在循环结构中用break结束本层循环、在switch语句中用break结束本层switch中当前分支的执行;
②在函数体内的return语句结束本函数的调用,返回到调用点;
③库函数exit结束程序的执行,返回到操作系统,该函数在<stdlib.h>头文件中
练习:
1、若已有定义int i,则对于如下循环语句,循环体执行的次数分别为______。
①for( i=0 ; ; i++ )
printf("%d ",i); //次数:无穷
②for( ; i=0 ; i++ )
printf("%d ",i); //次数:零
③for( i=0 ; i<0 ; i++ )
printf("%d ",i); //次数:零
④ i=0;
while (i=1)
i++; //次数:无穷
2、下列程序运行后,x和y的值分别是?
#include<stdio.h>
int main()
{
int x=1,y=2;
for(;x<10;x++) [阮17]
{
x+= 2; //x=x+2
if ( x>7 )
{
printf("x=%d y=%d\n",x,y); 答案: x=9 y=6
break;
}
if ( x==6 ) continue;
y *= x; //y=y*x
}
printf("x=%d,y=%d\n",x,y); 答案:x=9,y=6
return 0;
}
这一部分请大家要多做第四章的主教材和补充习题,阅读程序写答案及选择题部分,多练!
这一模块中有很多经典算法,下面几个是同学们必须非常熟悉的:
- 质数问题(从 2 到 n – 1, 判断是否存在能被n整除的数,既( n % i = = 0 , 2 < = i < = n − 1 )如果有就不是质数,否则为质数。)
- 判断一个整数是否为质数(用一层循环可以解决)
教材中的例子请完全掌握,结合第5章也必须掌握定义函数来实现,函数原型:int prime(int n); 判断参数n是否为质数,如果是,返回1,如果不是,返回0。
Int prime (int n);
{
Int i,n;//n为输入数字
For(i=2;i<n-1;i++)
{
If(n%i==0)
Return 0; [阮18]
}
Return 1;
}
-
- 找出一定范围内所有的质数(用两层循环可以解决),结合实验题目看
- //找出一定范围内所有的质数,并且输出
- int pdzhishu(int n)
- {
- int i;
- for (i = 2; i < n - 1; i++)
- {
- if (n % i == 0)
- {
- return 0;
- }
- }
- printf("%d\n",n);
- return 1;
- }
- int main()
- {
- int x, y,m;//用于定义上下界
- printf("请输入下界x(x>=2)与上界y");
- scanf_s("%d%d",&x,&y);//输入上下界
- if (x < 2)
- {
- printf("错误输入");
- }
- else
- {
- for (m=x;m<=y;m++)//将m赋值为下界,直接从下界开始判断质数
- {
- pdzhishu(m);
- }
- }
- return 0;
- }
③能按一定的格式输出
(2)求和问题:
掌握求和问题中条件的控制方式、累加的方式,每一项的特点:
- 可以根据求和的项数要求来求解,例如:求100项之和;
- 可以根据每一项的精度进行求解,例如:求到这一项的绝对值小于1E-6为止,注意这时的循环控制为:
do
{
求当前项的语句;
将当前项累加到和里的语句;
根据求和要求改变分子、分母、符号位的语句;
}while(fabs(term)>=1E-6);。
- 每一项实际上都可以表达成:1.0*分子*符号位/分母,(假设分子分母都是int型)有的求和不存在正负项交替,则不需要考虑符号位;有的分子或分母是某一个常量,则上式更简化。求和需要设一个累加器首先清零,然后在循环体内加上表示项的那个变量。具体参考教材中的求和示例以及实验1中数列求和问题。
- 典型求和案例:
教材例4.8 正在上传…重新上传取消 i = 1, 2, ......
再举几个求和示例:重点分析当前项item的表达
例1:s=1-2/3+3/5-4/7+…+i/2*i-1 该数列与例4.8非常类似,区别在于分子,所以这题的item=sign*i/(2.0*i-1)
例2:s=1+2+3+…N 这是例4.6的求和问题,本题分母恒为1,无正负交替问题,只有分子,分子正好就是当前项,因此item=i,所以在编程时不需要定义item变量,直接用sum+=i表示加当前项
例3:s=1-3/5+5/7-7/9+…+2*i-1/2*i+1, 这个数列与本例接近,只是分子分母与i之间的函数关系有变化,item=sign*(2.0*i-1)/(2.0*i+1)
例4:s=1+1/2!+2/3!+3/4!+…, 这个数列没有正负项交替,所以不需要sign变量,设f存放阶乘,初始化为1.0,充分利用相邻阶乘之间的关系,因此f*=i;item=(i-1)/f,和值sum初值为1.0,将第一项1直接作为sum初值,循环从i=2开始
例5:s=1/2-2/3+3/5-5/8+…,这个数列很有意思,分子分母分别是一个斐波那契数列,有正负项交替。用a,b分别代表分子分母,初值分别是1,2,于是item=sign*1.0*a/b且b=a+b;a=b-a,sign=-sign
通过以上几个例子,对数列求和问题既要掌握通用方法,又要会根据实际的需要正确表达每一项的三个要素:正负号、分子、分母。
(3)符合一定特征的数据寻找问题,例如水仙花数和完数问题。水仙花数:一个三位数如果恰好等于它每一位数字的立方和,这个数就叫做“水仙花数”。例如,153=13+53+33,因此153是“水仙花数”。
求解思路:将三位数字分离出来:个位: a=n%10; 十位:b=n/10%10; 或:b=n%100/10; 百位:c=n/100; 然后符合a*a*a+b*b*b+c*c*c==n的数就是水仙花数
完数:一个数如果恰好等于它的因子(不包含自身)之和,这个数就叫做“完数”。例如,6的因子为1、2、3,而6=1+2+3,因此6是“完数”
(4)最大公约数和最小公倍数问题,第四章课后读程题有,理解辗转相处法。
(5)整数的逆置问题,例如将n=123转变成321.方法:设一个累加器变量存转变后的结果,初始化为0,然后用循环,每次首先将个位数求出,然后对n降阶,再将累加器*10+个位数,下面的代码供参考:
① int s = 0,r;
while (n)
{
r = n % 10;
n /= 10;
s = s * 10 + r;
}
循环终止时的s就是n的逆置整数
②int n=123;
while(n!=0)
{
printf(“%d”,n%10);
n=n/10;
}
(6)利用循环可以控制输入数据的范围。
例如:实现输入的a,b范围是:10<=a<=b<=1000
int a,b;
do
{ scanf(”%d%d”,&a,&b);
} while (a<10||a>b||b>1000);
这里while后的条件也可以表达成:
while (!(a>=10 && b<=1000 && a<=b));
(7)计算闰年问题
例如:判断那一年为闰年
int runnian(int year)
{
if((year%4==0&&year%100!=0)||year%400==0)
year=1;
else year=0;
return year;
}
模块三:函数
C语言的源程序由一个或多个函数组成,其中必须有且只能有一个主函数main( ),程序执行总是从main函数开始的,也是因main函数的结束而结束整个程序。
函数只可以嵌套调用,而不能嵌套定义(即:一个函数的函数体内不可以定义另一个函数)
C语言的库函数 [阮19]
这一部分需要搞清楚以下问题:
(1)函数定义:包括函数首部和函数体。
函数首部又包括了函数返回值类型 函数名(形式参数表),如果返回值类型缺省,则默认为int型;函数的形式参数名字无关紧要,重要的是形参的变量类型,每一个形参都是自动局部变量,在形参表中每个形参前必须有类型标识
若函数无需返回值,则将返回值类型可以定义为void。
函数的返回值由首部的函数返回值类型决定,return后的表达式类型若与其不一致,则自动转换,将return后表达式的类型转成函数返回值类型。
函数通过return只能返回一个值,如果一个函数需要返回多个信息,则可以考虑借助于指针形式参数实现。
例如:int f(int a[],int n,int *max); 由函数返回数组a的前n个元素之和,而元素的最大值通过设定max指针,在函数体内求*max实现,调用时,要用一个实参变量的地址对应于指针形式参数max。
函数体是由一对大括号括起的语句系列组成,如果函数返回值为void,则可以没有return语句
(2)函数原型:函数应当先定义后被调用,如果出现调用先于定义的情况,则必须对后定义的函数在调用点之前进行原型声明,函数原型就是函数定义的首部最后加一个分号,在原型声明中形式参数名字可以缺省。
(3)函数调用:形式为:函数名(实在参数表),实在参数与形式参数对应的要求是:个数相同、对应位置的类型最好一致;
如果函数的返回值类型为void,则只能作为独立的函数调用语句来调用;
如果函数的返回值不是void类型,则可以作为独立的函数调用语句,更多情况下是作为运算对象来调用的
(4)形式参数:有值形式参数和指针形式参数两种,值形式参数是实在参数将值赋值到值形式参数变量中;指针形式参数,这时将实参的地址赋值到指针形式参数中,虽然都是单向传递,但是可以通过在被调函数中修改指针所指向的内容达到修改实参变量的目的。如:swap(int *x,int *y),在该函数体内交换的是*x和*y的值;对swap的几种正确与错误实现需要能正确理解。如果函数原型为:swap(int *x,int y),在函数体内交换*x 和y的值,则主函数中的调用形式应该为:swap(&a,b);只有实参a可能会得到改变,无法完成a、b互换的目的。
传数值:形参的变化不会改变实参的变化
由于自定义函数exch并没有返回值,所以当子函数exch运行完毕后,临时变量内存直接消失,ab的值不变
传地址:形参的变化有可能改变实参的变化
(5)变量的作用域问题:取决于变量的定义位置
全局变量——在所有函数之外定义的变量,作用于定义点到程序结束处,但应去除掉同名局部量的作用域部分。
局部自动变量——在某函数内部定义的变量,有三个定义位置:形式参数表、函数体刚开始的位置、复合语句刚开始的位置。其作用域只在本函数内的当前语句块(即包含其定义的最近的一对大括号),生命期为:所在函数被调用时(或语句块开始时)分配空间,该函数当次结束时(或退出语句块时)空间不复存在。下次再调用则重新分配空间。在不同函数中定义的局部量可以同名,因此形式参数可以与实在参数同名,因为处于不同的函数。
静态局部变量——在某函数内部定义的变量,有static关键字,其作用域只在本函数内,生命期为整个程序运行期间。 本次函数调用结束后仍然存在,其值保留,但是在函数之外的其余部分均不可见,等该函数再次被调用时,该变量就在原来值的基础上继续变化。静态局部变量只初始化一次,如果不给定初始化的值,将自动初始化为0。
(6)变量的生命期:取决于变量的存储类别:extern,auto(缺省),static,register(一般很少使用了)。形参都只能是auto存储类别的局部变量
extern通常用于多文件结构中,声明来自于另一个文件的全局变量或函数。
- 递归函数。
掌握书中例题
练习:多做书后及配套的补充习题,帮助题解
1、若用一维数组名作为函数调用时的实在参数,则实际上传递给形参的是______。答案:数组首地址
2、若在主函数中定义了数组变量:int arr[20],并调用了函数f(arr),则下列关于函数f的原型声明,哪几个是完全等效的______。答案:ABCE
A、 void f(int x[20]);
B、 void f(int x[ ]);
C、 void f(int x[10 ]);
D、 void f(int x);
E、 void f(int *x);
3、静态局部变量的读程题要刷题练习
4、有函数原型为void f(int,int *);主函数中有变量定义:int a, *p=&a; 则下列几种调用正确的是____?答案:ABEF
A、 f(a,p); B、f(*p,&a); C、f(a,*p); D、f(*p,a);
E、f(a,&a); F、f(*p,p); G、f(a,a); H、f(p,p);
重点函数:判断一个整数是否质数、一维数组中的生成所有元素、输出所有元素、求和求平均、最值求解、排序、查找等功能用函数实现
模块四:数据类型
C语言源程序中所处理的任何数据(常量或变量)都是属于某一种数据类型的,数据类型将决定该类型的值在内存中占用空间的大小、数据值的表现形式及数据范围、可进行的运算及操作、变量值的组成情况等。
C中数据类型分为:基本类型、构造类型、指针类型、空类型四大类,其中基本类型包括整型、实型、字符型;构造类型有:枚举型、数组类型、结构体类型、共同体类型,指针类型的特殊性在于其变量中存放的是内存地址信息而不是内存中的值。
这一模块的重点是数组、指针类型、结构体、字符串(用数组和指针实现)。
(1)数组 [阮20] :
基本知识:
一维及二维数组的定义,注意定义数组时必须在方括号是给整型常量或常量表达式,例:int a[4];(注意:此时元素的有效下标范围为0~3,特别注意:起始下标为0,不可以下标越界访问);定义数组时元素个数不能用一个已有值的变量,例如:int n=10; int a[n];就是错误的定义。
一维及二维数组的初始化 [阮21] ,当初始化时可以缺省元素个数,对于二维数组只能缺省行数。初始化遵循的规则是从左至右依次,初值个数不能超过数组容量;当初值少于元素个数时其余元素的默认初值为0;如何正确访问元素(元素的表示有两种形式,结合指针的知识,有a[i]和*(a+i)两种等效表示 [阮22] ;元素的地址也有两种等效形式:a+i或&a[i])
一维字符数组操作字符串、会正确输入输出数组的元素、正确输入输出字符串(例如:char str[20]=”ABCDE”,则printf(“%s\n”,str+2);的输出结果是CDE,与%s对应的是表示字符串的首地址,输出遇到\0结尾标志就结束)。
掌握string.h函数中的一些常用函数的使用,如:
strlen求长度(有效字符个数,不包括结尾标记),注意转义字符以及八进制和十六进制形式的字符表示,注意sizeof和strlen的区别
strcmp进行两个字符串的比较(例如:判断串s1是否大于s2,不能用s1>s2,而应该用strcmp(s1,s2)>0)、
strcpy实现字符串的赋值、
strcat实现字符串的连接,
会编程实现字符串的逆置、字符串中空格的删除、字符串中指定形式字符的抽取(例如只保留数字字符)、回文的判断等算法。
必须熟练掌握在一维数组中的以下算法:
教材中基本都有对应的函数定义及调用方法:输入数组所有元素、输出数组所有元素、寻找最大数、最小数、求元素的和或平均值、查找某一个值是否是数组中的元素、冒泡和简单选择排序算法、删除元素(方法1:覆盖移动法;方法2:把不需要删除的内容复制下来 [阮23] )、数组元素的逆置(从两边往中间,元素两两交换)等。
二维数组掌握一些最基本的方法 ,例如:按矩阵形式输出、转置、求主对角线或次对角线元素之和
//求二维数组a[][3]={1,2,3,4,5,6,7,8,9}主对角线数字的和
int main()
{
int a[][3] = {1,2,3,4,5,6,7,8,9};
int i, j,sum=0;//定义行i列j,和sum
for (i = 0; i <= 2; i++)
{
for (j = 0; j <= 2; j++)
{
if(i==j)
sum+= a[i][j];
}
}
printf("%d",sum);
return 0;
}
算法:首先要理解一维数组名作实参和形式参数传地址的实质;理解void f(int a[])中的形式参数a实质上是一个指针形式参数,也就是该函数首部可以写成:void f(int *a)或void f(int a[10] ) 这里的10可以是任意整数都是完全等效的
练习:
1、有数组定义int a[m][n],则在a[i][j]之前的元素的个数为______。答案:i*n+j
- 以下一维数组的定义正确的是___.答案:D
A、 int n=3, a[n]; B、#define N=3 int a[N];
C、 const int n=3; int a[n]; D、#define N 3 int a[N*3];
3、以下一维数组定义及初始化正确的是____答案:B
A、int a[ ]; B、int a[]={1,2,3}
C、int a[3]={1,2,3,4} D、int a[3]={,2,}
4、以下选择中,能正确定义二维数组的是______。答案:B
A、int x[ ][2]; B、int x[ ][2] ={2*2};
C、int x[2][ ]={1,2,3,4 }; D、int a[2][3]={ , {1} };
5、若定义int m[10]={9,1,15,6,2,11,8,7,19,3};则m[m[1]+2*m[4]]的值是______,*m+m[8]的值为______。答案: 11 28
6、下列字符数组定义与初始化不正确的是____答案:D
A、char s[ ]=”abc”; B、char s[]={”abc”};
C、char s[3]={‘a’,’b’,’c’}; D、char s[3]=”abc”;
7、下列关于数组的描述中不正确的是______。答案:B
A、可以对字符型数组进行整体输入、输出
B、可以对整型数组进行整体输入、输出
C、利用字符型数组可以实现字符串操作
D、不能通过赋值运算符“=”对字符型数组进行整体赋值
(2)指针类型 [阮24] :定义形式为:数据类型 *指针变量名,例:int *p;注意p与*p的区别。指针变量若未赋值,不能对*p操作,若对指针赋值为空值,也不能对*p操作。该指针变量可赋值为普通int变量地址、一维数组名、二维数组中的元素地址(列地址)等
*p的意思可以简单理解为取出地址p里面的值,p时地址,而*p时数值
Scanf(“%d”,p);因为p是地址,所以后面直接写p与&x的意义相同
Int a =2,*p=&a;与int a=2,*p; p=&a;是等价的
练习:
1、已知: char *s = "student"; 则printf("%s\n", s+3)输出为____答案:dent
2、若有说明:int a, b=9, *p=&a;,则能完成a=b赋值功能的语句是____
A、a=*p; B、*p=*&b; C、a=&b; D、*p =&*b; 答案:B
三名主义 [阮25]
指针与数组的关系:对指针操作一维数组要熟练掌握,理解数组名是地址常量的概念,指针指向数组的时候执行p++、p--的意义,以及数组元素的下标法访问及指针运算符访问方法:例:int a[4]={1,2,3,4} ,*p=a ; ++p;p[2]或*(p+2)都是表示元素a[3],但是p+1或a+2都是表示地址概念的,都等于&a[2].区别*p++和(*p)++这两个表达式的不同含义
指向同一数组空间中的指针可以做减法,表示二者之间相隔的元素个数
用字符指针操作字符串:比用字符数组操作字符串具有更大的灵活性,注意二者的区别
例:char *s=“ABC”; char *s; s=”ABC”;都是正确的
char p[4]=“ABC”;正确,但是char p[4]; p=”ABC”;却错误
s=p;是正确的赋值,但是s=*p,*s=”AB”都是错误的
(3)结构体类型:
会正确定义结构体类型,以及嵌套的结构体类型
掌握三种结构体变量定义的方式(先定义类型再定义变量;定义类型的同时定义变量,类型名可以缺省;用类型别名定义变量);掌握结构体变量的初始化方式,对其成员依次赋初值。理解结构体变量内存占用的情况(各成分依次存放,故结构体变量所占空间至少为所有成员需要的空间之和),会正确访问结构体变量的成员(点运算符的前面必须是结构体类型的变量;箭头运算符前面必须是结构体类型的地址或指针变量),会正确定义结构体数组,并掌握结构体数组元素各个成员的读入、访问方式;掌握用结构体指针正确访问结构体或结构体数组元素的成员。
会用typedef给出一个类型的别名 [阮26] :typedef 原类型名 新类型名;
(4)结构体的应用(根据问题定义合适的结构类型,并进行简单的应用如输入,输出,排序,查找,统计等。要求会用函数实现)
链表的建立,插入和删除,具体算法必须掌握,参见课本例题。
练习:
1、定义struct Point{ int x,y; }pos[ ]={{1,2},{3,4},{5,6}}, *pt=pos;则表达式(++pt)->y的值为______ , ++(pt->x) 的值为_________, ++pt->x 的值为______、(*pt).y的值为_____。答案:4,2,2,2
2、以下对结构体变量stu中成员的非法引用是______。答案:D
struct Point
{
int x;
int y;
} pos, *p=&pos;
A、pos.x B、(*p).y C、p->x D、Point.y
作为函数参数,通常定义结构体的指针作为形式参数,将实参结构体变量的地址传入,这样省时效率高,而且可在被调函数中修改对应实参结构体成员的值
练习:下列程序的运行结果是?
#include <stdio.h>
typedef struct Student
{ char name[10];
double score;
} STU;
//注意:这里STU是结构体类型名,如果前面去掉typedef则为结构体变量名
void f (STU *s)
{ (*s).score*=20;
printf("%s %.1f\n", s->name, (*s).score);
}
int main()
{ STU stu[]={{"LiuBo", 4.5}, {"JiWei", 4.6}, {"Lijie", 5.0}};
f (stu+1);
printf("%s %.1f\n", stu->name, (*stu).score);
return 0;
}
答案: JiWei 92.0
LiuBo 4.5
模块五:文件 [阮27]
C语言中的文件的组成及分类:
C中的文件由数据流形式组成,可按数据的存放形式分为二进制文件和文本文件
C文件打开的方式:
基本方式: r(只读) w(只写)、a(追加)
后面可以加上+表示可读可写(注意,如果打开一个新文件可读可写,则一定是w+;如果打开一个旧文件可读可写,则可能是r+或a+)、加上b表示打开二进制文件,否则默认是对文本文件操作
注意:
文件的操作步骤:
- 定义FILE*文件指针,一个文件对应一个指针
- 利用文件指针打开文件fp=fopen(文件名,打开方式),注意文件名要用双引号括起,并且路径之间用\\,打开方式也要用双引号括起
- 进行文件读写操作,共四对函数:
单字符读写:fgetc、fputc(重点掌握)
字符串读写 [阮28] :fgets、fputs
格式化读写:fscanf、fprintf
块数据读写:fread、fwrite
一般用c=fgetc(fp)来从已有文件中获取一个字符,用fputc(c,fp)向fp所对应的文件中写入一个字符,该操作往往要用循环条件控制执行。
用fwrite和fread实现数据块的读写,这在内存变量为结构体类型时特别有用,
- 文件操作完成后要用fclose(fp)关闭文件。
- 判断一个旧文件是否结束,常用的有两种方式:
!feof(fp)或((c=fgetc(fp))!=EOF)
模块六:编译预处理与多文件工程程序
宏定义
宏定义:也叫宏替换。只是字符的替换(宏定义将一个标识符定义为一个字符串,在编译预处理是,源程序中的改标识符均以指定的字符串代替),不占用运行时间,标识符无类型,其参数也无类型。
C语言编译系统对宏定义的处理在对c程序语句正式编译之前处理
练习:
1.宏定义#define G 9.8中的宏名G表示 C
A.一个单精度实数 B.一个双精度实数
C.一个字符串 D.不确定类型的数
2.对于以下宏定义
#define M(x) x*x
#define N(x,y) M(x)+M(y)
执行语句z=N(2,2+3);后,z的值是 15 [阮29]
编译预处理
预处理命令行一般位于c源程序的起始位置,但也可以位于程序的中间,如条件编译等,c语言的预处理可以实现宏定义和条件编译功能 [阮30] 。
在c语言中,预处理命令行都是以“#”开头,但并不是每个源程序文件必须包含预处理命令行#include<stdio.h>,函数原型声明可以多次出现,但是函数的定义只能出现一次。用户自定义头文件时,使用条件编译指令可以避免重复包含。
文件包含命令:一个#include命令只能指定一个被包含头文件,头文件的包含是可以嵌套的,但在 #include命令中不可以指定多个被包含头文件。
在#include命令中,文件名可以用双引号或者尖括号括起来①在#include<头文件名>格式中,预编译处理程序首先到系统标准目录中查找头文件②在#include“头文件名”格式中,预编译处理程序首先到当前目录,最后到系统标准目录查找
多文件工程程序
组织原则:①将函数的定义和使用相分离②将函数的声明与实现相分离③将不同的功能和数据结构划分到不同的模块中④多文件工程程序中模块的数量应始终,模块太少不利于开发效率,模块太多也会增加管理模块的开销
。
重点掌握:
教材中的例11.1(fputc),11.2(fgetc),,11.4(fscanf\fprintf)这几个例子。
期末复习要点:掌握教材的例题、书后习题;慕课上的单元测验、编程练习、网上期末测试题要再次看看做做,确保掌握;六次实验的编程题再编写一遍;适当做补充习题或你们自己找来的题目,题目关键是掌握方法,不求量多。
RXY24601: 你的理解是对的,第一个RTT发送一个窗口,第二个RTT发送两个窗口。对于第三个RTT会发送四个窗口。之所以是三个RTT而不是两个RTT,是为了保证足够的数据传输,并且确保接收方处理完所有的数据,目的是为了简化和保守估算。从而全面的反映延迟和网络行为
ConanMarisa: 3个窗口为什么是3个rtt,第一次rtt传递一个窗口,第二次rtt传递2个窗口这样2个rtt不就传递够了吗
RXY24601: gcc的-fdump-class-hierarchy选项,它可以用于输出C++程序的虚表结构(在当前目录下生成一个.class文件)
XOXkx: linux中如何查看呢?
卷: 同样的访问代码,为什么显示拒绝访问