CC 语言程序设计语言程序设计Copyer:VigikingCopyer:Vigiking
第一章 计算机语言与 C 语言概述
1.1 计算机语言概念 ■ 计算机语言定义 计算机能够识别和接受的语言。 要使计算机按自己的意图工作,必须使用计算机所能接受、理解和执行的指令指挥计算机工作。
■计算机语言的种类 机器语言 低级语言 汇编语言 (面向计算机) BASIC 入门语言 F77 科学计算
高级语言 Foxpro 数据库管理 (面向问题 ) C 多用途
计算机语
言
…………
•机器语言 最早问世,用二进制代码构成指令。 如: 100000 ( + ) 110000 ( -)
用机器语言编程的缺点: ─繁琐、不直观、不易调试。 如计算 y=2x2+3x-1 需要七八条指令。 ─移植性差。 依赖于计算机。
•汇编语言 用符号构成指令,如: MOV 、 ADD 用汇编语言编程: 相对直观,但仍繁琐,仍是面向计算机的语言。
汇编语言是计算机间接接受的语言
•高级语言与低级语言相比,有根本性的区别: 是面向问题的语言。高级语言的一条指令(语句): y=2*x*x+3*x-1; 对应于: y=2x2+3x-1 y=sin(x); 对应于: y=sinx用高级语言编程:直观、易懂、移植性好(不依赖于具体计算机)
上机运行高级语言程序需经过 编译:
编译 执行
要上机完成一个计算问题,主要的任务就是用高级语言编写出相应的 源程序。即至少要学会一种计算机语言。
高级语言源程序 机器指令目的程序
结果
编译程序
1.2 C 语言的特点 集高级语言和低级语言的优点于一身 :
●能实现低级语言的大部分功能(如直接访问内存物理地址、进行位操作等)。
●图形功能强。 ●运算符和数据结构丰富。 ●语法限制不太严格,程序设计自由度大。 ●生成目标代码质量高,程序执行效率高。
1.3 简单的 C程序介绍
例 1: main() 主函数说明
{ 程序框架
printf(“abcdef”); 函数体
} 语句
程序的功能是输出字符串: abcdef
例 2:求两数之和。main()
{ int a,b,c; a
a=100; b=50; b
c=a+b; c
printf(“\n c=%d”,c);
}程序运行结果: c=150
例 3:求两数中的最大值。 函数类型 函数名 形参
main() int max (int x,int y)
{ int a,b,c; { int z;
scanf(“%d,%d”,&a,&b); if(x>y) z=x;
c=max(a,b); else z=y;
printf(“\nmax is:%d”,c); return(z);
} }
a x
b y
c z (两个函数组成)
3
5
3
5
55
总结上例可知:( 1) C 程序由函数构成。( 2) 函数由两部分组成: 函数说明部分:函数名、函数类型、形参名、形参类型。
函数体:实现函数的具体操作;由语句构成。
( 3) 程序总是从 main 函数开始执行。( 4) 书写格式自由。( 5)语句必须有分号。 :
第二章 算法
2.1 算法的概念
要利用计算机处理问题,光学习语言的语法规则还不够,最重要的是要学会针对各类型的问题,拟定出有效的解题方法和步骤。解题方法和步骤就是算法。
算法:
为了解决一个问题而采取的有限步骤。
计算机算法:
如何使计算机一步一步地工作的具体过程。
利用计算机处理问题的步骤: 1 )设计好算法—— 算法设计 ; 2 )用计算机语言实现算法—— 程序设计。 算法必须是“有效”的。 算法设计还要充分考虑算法的好坏。 衡量算法好坏的主要标准: ① 程序简练。 ② 执行速度快。 ③ 占空间少。
例:考虑 的算法。
算法 ①:直接表达。直接用语句 s=1+2+3+4+5+6+7+8+9+10
当项数较多时该算法不适用
s=∑i1
10
算法 ②:迭代法(累加求和法) s=1+2+3+4+5+6+7+8+9+10 算法步骤: s i
① 使 s=0 +② 使 i=1 累加器 记数器
③ s+i→s④ i+1→i⑤ 若 i≤10 转③,否则转⑥⑥ 输出 s
0 11 23 36 410 5
该算法通用,是好算法
2.2 算法的表示 算法需要有统一的表示方法 常用的表示方法有: 自然语言 流程图 结构化流程图 N-S 流程图 .
1 、自然语言 对于计算 s=1+2+3+4+5+6+7+8+9+10 用自然语言表示为: ① 使 s=0 ( s为累加器) ② 使 i=1 ( i为计数器) ③ s+i→s (累加求和公式) ④ i+1→i (计数器加 1) ⑤ 若 i≤10 转③ ,否则转⑥ ⑥ 输出 s的值 特点:通俗易懂、文字冗长、含义不大严格。
2、流程图 用流程图符号表示算法。 常用的流程图符号
起止框 输入输出框
处理框 流程线 判断框
对于计算 s=1+2+3+4+5+6+7+8+9+10 用流程图表示为:
s+i → s
i+1 → i S+i → s S+i → s
i≤10
输出 s
0 → s
1 → i
直观形象,易于理解,次序清
楚
Y
N
3 、结构化流程图
传统的流程图有一个弊端:对流程线没有严格的限制,对于较复杂的算法 可能会变成乱麻一般( BS 型算法)。 为克服这一弊端,提出了由三个基本结构组成算法流程图的 思想:
结构化流程图
三个基本结构:① 顺序结构 按固定顺序(从上到下或从左到右)执行的结构。
A
B
a
b
② 选择结构 根据条件 P选择执行哪一个分支。 成立 不成立
p
A B
a
b
成立 不成立
例:计算 y= 1/x 当 x≠0 时 y= 10000 当 x=0 时 的算法流程图图:
选择结构
输入 x
X=0?
10000→y 1/x→y
输出 y
Y N
③ 循环结构 重复执行某些操作的结构。 分为两种: 当型循环和直到型循环。
当型循环 直到型循环
P1
A A
P2
a a
b b
Y
Y
N
N
可以看出,每个基本结构都只有一个入口和一个出口,因此,用三个基本结构构成的流程图不会象乱麻一般,用三个基本结构构成的流程图就成为结构化流程图,用结构化流程图描述的算法 称为结构化算法 ,相应的程序设计就 称为结构化程序设计。
观察前例:
0 → s
1 → i
S+i → s
i+1 → i
i≤10
输出 s
顺序结
构 循环结
构
y
n
4 N-S流程图
N-S流程图的三个基本结构:
A
B
P成立 不成立
A B
A
B当 P1
直到 P2
顺序结构
选择结构
循环结构
例:计算 y= 1/x 当 x≠0 时 y= 10000 当 x=0 时 的 N-S流程图:
输入 x
X=0?是 否10000→y 1/x→y
输出 y
例: 计算 s=1+2+3+4+5+6+7+8+9+10 的 N-S流程图:
i≤10
直到 i>10
直到型 当型
0 → s
1 → i
输出 s
1 → i
0 → s
s+i → s
i+1 → i s+i → s
i+1 → i
输出 s
第三章 数据类型、运算符
与 表达式 3.1 C 的数据类型
●基本类型 (整型、实型、字符型、枚举型)
●构造类型 (略)
●指针类型 (略) ●空类型 各类型包含常量与变量
3.2 常量与变量 ■常量与符号常量 常量 在程序运行过程中其值保持不变的量。 符号常量 用来代表一个常量的标识符。 #define PI 3.1415926
例: #define PI 3.1415926 main() { float r=2,c; c=2*PI*r; printf(“%f”,c); }
■变量 其值可以改变的量。 变量的三要素 : ① 变量名每个变量有一个名字,作为识别该变量的标识符。 ② 变量的值 每个变量有一个值,变量的值是随时可以改变的。
③ 变量的存储单元 每个变量占据一个内存单元,用于存放变量的值。
变量名 a
变量值
存储单元
3
变量的命名规则: 由字母、数字和下划线组成 以字母或下划线开头
a 、 x1 、 y_2 、 _b1 、 _1c 合法 1x 、 a+2 、Ф、 Ω 不合法
变量必须先定义后使用。 程序中何时使用常量?何时使用变量?
3.3 整型数据 ■整型常量 即整常数, c的整常数有三种形式: ①十进制整数 与数学中的整数一致,如: 100 , 123 , 15等。 ②八进制整数 以 0开头的整数,如: 010,07,020等。 ③十六进制整数 以 0x 开头的整数,如: 0x10,0xff,0x2a等。
■整型变量 用于存放整数的变量。 分 4种类型: ①基本型: int a 16 位,可表示的数值范围: -32768—32767 ②短整型: short int b 16 位,可表示的数值范围: -32768—32767 ③长整型: long int c 32位,数值范围: -2147483648—2147483647
④无符号型:加上 unsigned 只存放正数。 如: unsigned int x 变量 x 为无符号整数, 16 位全表示数码, 数值范围: 0—65535 在程序设计中,如果要使用整型变量,必须首先选择以上类型符来定义变量,然后才能使用;
例: main() 定义 { int a,b,c; a=100; b=50; 使用 c=a+b; printf(“%d”,c); }
一般根据什么原则选择变量的类型?
3.4 实型数据
■实型常量
可使用两种形式表示:
①小数形式:如 1.23, 3.1415926 15.48
②指数形式:如 1e-20 1.23e5
■实型变量 用于存放实数的变量 分单精度和双精度两种: float a,b 定义 a 和 b为单精度型变量 32位, 7 位有效数字, 10-38—1038 double x,y 定义 x 和 y为双精度型变量 64位, 15位有效数字, 10-308—10308
例: main() { float r,c; double r,c; r=5; c=2*3.1415926*r; printf(“%f”,c); }
3.5 字符型数据
■字符常量 用单引号括起来的一个字符。 ’a’,’x’,’*’,’1’等
除此外,以’ \’ 开头的字符如’ \n’,’\t’等 称为转义字符,祥见书表 3.3
■字符型变量 用于存放字符的变量。
char c1,c2 定义 c1 和 c2 为字符型变量 c1=’a’; c2=’b’; 字符赋值
字符型变量存放一个字符,占据一个字节
■字符型数据的存储形式 存放 ASCII码 不是 而是 如字符’ a’ 在内存中存放 97 ,’ b’ 存放 98。 与整数的存储形式一致,它们之间可以通用 一个字符数据既可以作字符用,也可以作整数用(取其 ASCII代码)。 如: 32+’a’相当于 32+97 若 int x; char c;则 x=’a’; c=97; x=97; c=’a’; 都允
许
a 97
■字符串常量 用双引号括起来的字符序列。
如:” abcde”,”china” ”a” 也属字符串。 注意” a” 与’a’的区别。 对于: char c; c=”a”; 用法错误
字符串中每个字符各占一个字节,并且在字符串结尾加上一个结束标记’ \0’
如:” china” 在内存中占 6个字节。 c h i n a \0
C 语言中专门的字符串变量,可用字符数组存放(以后介绍)。
3.6 变量赋初值 在定义变量的同时给相应的变量赋初值。 如: int a=3; a float b=5.2; char c=’a’; int x=y=z=6; 效果: 在给变量分配内存单元的同时在相应的单 元中存放初值。
3
3.7 各数值型数据间的混合运算
整型、实型、字符型数据间可以进行混合运算,如: 10-‘a’*1.5 运算时,参加运算的两个数据如果类型不同,则首先将其类型转换成一致再运算,转换规则是: 将优先级低的类型转换到优先级高的类型
数据类型的优先级: 高 double float
long
unsignde 低 int char
3.8 算术运算符与算术表达式 ■基本的算术运算符 + - * / % ■算术表达式 用算术运算符将运算对象连接起来的式子 用于表达数学公式的式子 如: 2*x+y-1/a 表达式经过运算最终得到一个值: 算术表达式的值
■运算符的优先级与结合性
优先级: 在对表达式求值时,如果存在多个运算符,则运算的先后次序按运算符的优先级别从高到底进行。 运算符的优先级关系为: 高: * / % 低: + - 如: a-2*x 先算 * 2*(a+2) 有括号的情况?
结合性: 如果在一个运算对象两边的运算符的优先级相同,则按规定的“结合方向”处理。 如: a-b+c b 与 -结合是从左到右,称“左结合性”。 b 与 +结合是从右到左,称“右结合性”。
每个运算符都有相应的优先级和结合性。 基本算术运算符都是左结合性。
计算表达式例: 2+’A’-1/2.0 1+3/2-1
构造表达式例 2x2+3x-1
a+b a-b
a+b a-b x+y x-y
a+b/a-b ?(a+b)/(a-b)
2*x*x+3*x-1 (* 不能省)
(a+b)/(a-b)/(x+y)/(x-y)(a+b)/(a-b)/((x+y)/(x-y))
(a+b)/(a-b)/(x+y)*(x-y)
■强制类型转换
可以用强制类型转换运算符将一个表达式的值转换成所需类型:如: (int)(x+y) (float)(7%3)
应用举例:
int a=200,b=300,c; c=a*b/100; ? 可知,有自动转换和强制转换, 当自动转换达不到目的时,可用强制转换。
c=(long)a*b/100; c=(long)(a*b)/100;
■自增、自减运算符 自增运算符: ++ 使变量值加 1 自减运算符: -- 使变量值减 1 两种用法: ++i, --i 先加(减)后用 i++, i-- 先用后加(减) 两种用法对 i 效果一样,但表达式的值不同。例:假设 i 的原值为 5: j=++i; j=? j=i++; j=?
注意:●++和 --只能用于变量。 如: 3++ 和( a+1 ) ++ 不合法●++和 -- 为右结合性。 (-i)++ -i++ -(i++) 若 i 的原值是 5,则该表达式的值是多少?
例:分析执行下列语句后的结果:
a=5; a b c d b=a++; c=--a-b++; d=(a++)-(++b)+c--;
5 565 067 -26 -1
两种特殊情况: ① k=(i++)+(i++)+(i++) ② i+++j
是 i+(++j) 还是 (i++)+j ? i++虽然与 i=i+1等效,但使用自增自减运算符的代码优化程度好,因而经常使用;但用时需特别小心。
3.9 赋值运算符和赋值表达式 ■赋值运算符 “=”称赋值运算符,其作用是将一个数据赋给一个变量。 如: a=5 不要理解为“等号”。 执行赋值运算的结果,是将右边的数据存入左边变量所对应的内存单元中。
■赋值规则 如果赋值运算符两侧的类型不一致,则在赋值时要进行类型转换,转换规则为: ●实型→整变量 舍去小数部分。 int a=5.5; a 中为 5。 ●整型→实变量 数值不变,以浮点形式存储。 ●字符型→整变量 放在整形变量低 8 位。保持原值不变原则。 int a=‘A’;
■复合赋值运算符 在赋值运算符前加上其它运算符,可以构成复合赋值运算符。 a+=3 —— a=a+3 b-=x+5 —— b=b-(x+5) x*=c-6 —— x=x*(c-6) y/=a*4 —— y=y/(a*4) k%=b-2 —— k=k%(b-2) 属于高效率运算符。
■赋值表达式 主要实现赋值运算的表达式。 一般形式: <变量 >=<表达式 > 如: a=5 y=2*x+3 a=a+1 不是衡等
作用:将右边表达式的值赋给左边的变量。 赋值表达式的值取左边变量的值。
赋值表达式右边的 <表达式 >可以是任何表达式,如: a=(b=5) 赋值表达式中包含赋值表达式
赋值运算符的优先级低于所有算术运算符,且是右结合性。 a=(b=5) 与 a=b=5 等效。
例:计算以下表达式的值:
a=b=c=5 a=5+(c=6) a=(b=4)+(c=6) a=(b=4.5)+(c=6.5) ( a 、 b 、 c 为整型变量) a+=a-=a*a (设 a 的原值为 3)
赋值表达式是 C 语言中的一个重要成分,在赋值表达式后加一分号就成为常用的赋值语句。如 y=2*x+1; 赋值表达式作为表达式的一种,可以出现在任何表达式中,如: x+2-(b/3-(a=k-5)+’b’
3.10 逗号表达式 逗号也是一种运算符,用它对两个表达式实现连接运算。 3+5,6+8 称逗号表达式。 逗号表达式的一般形式: 表达式 1,表达式 2 取表达式 2 的值作为整个逗号表达式的值。 如: a=3*5,a*4 逗号表达式的值为: 60
一个逗号表达式又可以与另一个表达式组成一个新的逗号表达式,如: (a=3*5,a*4),a+5 因此,逗号表达式的一般形式可以扩展为: 表达式 1 ,表达式 2 ,表达式 3 ,…… , 表达式 n 取表达式 n的值作为整个逗号表达式的值。
逗号运算符的优先级最低 ,且是左结合性。
逗号运算符只起到连接作用,没有实际操作。
第四章 最简单的 C 程序设计
C 程序最基本的成分是语句 目前我们已掌握的语句: 变量说明语句 : int a,b,c; 表达式语句: x+y; 特别地: a=5; 赋值语句
可以编写简单程序如: main() { int x,y; x=5; y=2*x*x+3*x-1; }
该程序语法上完整,但还缺少输出。
■ 数据的输出 用输出函数实现 ,其中的两种输出函数: 1.putchar 函数 (字符输出函数) 用于输出一个字符。 如 : putchar (‘a’); putchar (100); char c=’b’; putchar (c);
例:输出单词 Boy 的完整程序: #include “stdio.h” 注意该语句的作用 main() { char a, b, c; a=’B’; b=’o’; c=’y’; putchar (a); putchar (b); putchar (c); }
2. printf 函数(格式输出函数) 任意类型、任意格式、任意个数。 例如: int a=100,b=56; printf(“a=%d,b=%d”,a,b); 普通字符 格式说明 格式控制 输出表列
输出结果: a=100,b=56
“%” 后的字符称格式字符,不同格式字符对应不同的数据类型。
d 格式符:按整数格式输出
几种用法:
%d 不指定宽度,按实际宽度输出
%md 按指定宽度输出, m为宽度
%ld 用于输出长整型数
例: int a=125,b=453; long c=65535; printf(“a=%d,b=%5d,c=%ld”,a,b,c);
输出结果: a=125,b= 453,c=65535
%ld也可以按指定宽度输出: printf(“c=%8ld”,c); 输出结果: c = 65535
注意:格式字符的类型要与对应的输出对象的类型一致。
c 格式符:用于输出字符
char c=’A’; printf(“c=%c,%c”,c,’B’);
输出结果: c=A,B
输出对象既可以是字符变量、字符常量,还可以是整型表达式。
如:
int a=100; char b=’A’; printf(“\n%d,%c”,a,a); printf(“\n%c,%d”,b,b); 输出结果: 100,d A,65
s 格式符:用于输出字符串
%s 不指定宽度 %-ms 指定宽度,左靠齐 %ms 指定宽度,右靠齐 %m.ns 指定宽度 m ,只取左端 n 个字符, 右靠齐 %-m.ns 指定宽度 m ,只取左端 n 个字符, 左靠齐
例: printf(“1:%s”,”abcd”); printf(“2:%8s”,”abcd”); printf(“3:%-8s”,”abcd”); printf(“4:%8.3s”,”abcd”); printf(“5:%-8.3s”,”abcd”);
1:abcd2: abcd3:abcd 4: abc5:abc
f 格式符:按小数形式输出实数
%f 由系统指定宽度( 6 位小数) %m.nf 指定宽度 m ,小数位数 n,右靠齐 %-m.nf 指定宽度 m ,小数位数 n,左靠齐
注意:宽度包括符号和小数点。
例: float a=3.141592654,b=14.326795, c=-125.2468; printf(“\na=%f,b=%8.3f,c=%-10.2f”,a,b,c);
输出结果:
a=3.141592,b= 14.326,c=-125.24
完整前面的程序:
main()
{ int x,y;
x=5;
y=2*x*x+3*x-1;
printf(“\n y=%d”,y);
}
程序设计例: 编写程序计算如图中的电流 I.
假设 U=220,R1=30,R2=60,R3=45
U
I
R1 R2 R3
算法设计: I=U/R1+U/R2+U/R3程序设计: main() {
}
I=U/R1+U/R2+U/R3;
int U=220,R1=30,R2=60,R3=45;float I;
printf(“\n I=%f”,I);
正确的程序: main() { int U=220,R1=30,R2=60,R3=45; float I;
I=(float)U/R1+(float)U/R2+(float)U/R3; printf(“\n I=%f”,I); }
考虑通用: main() { int U,R1,R2,R3; float I; 输入 U,R1,R2,R3 I=(float)U/R1+(float)U/R2+(float)U/R3; printf(“\n I=%f”,I); }
■ 数据的输入 getchar 函数(字符输入) #include “stdio.h” main() { char c; c=getchar(); 等待键盘输入 putchar(c); }
scanf 函数(格式输入) 与 printf 函数相反。 用于输入若干任意类型的数据。
scanf(“%d%d%d”,&a,&b,&c);
格式控制 地址列表
scanf(“%d%d%d”,&a,&b,&c); 执行此函数时,等待从键盘输入三个整数给 a,b,c 若从键盘输入 3 5 8 则系统即从键盘缓冲区取出这三个数分别赋给 a,b,c 注意与 printf 的区别,注意格式的匹配
如: scanf(“%3d%2d%4d”,&a,&b,&c); 若从键盘输入 123456789 a=123,b=45,c=6789 若想使 a=12,b=5,c=100 则键盘输入应为: 12 5 100 方便的输入格式一般不 指定宽度, 如: scanf(“%d%d%d”,&a,&b,&c);
在键盘输入时,用分隔符把每个数据隔开,标准的分隔符是空格。 如: 123 150 23 若想用逗号作分隔符,则: scanf(“%d , %d , %d”,&a,&b,&c); 不要随便使用普通字符,如使用: scanf(“a=%d , b=%d c=%d”,&a,&b,&c) 对应数据输入: a=123,b=150,c=23
前面的欧姆定律 : main() { int U,R1,R2,R3; float I; scanf(“%d%d%d%d”,&U,&R1,&R2,&R3); I=(float)U/R1+(float)U/R2+(float)U/R3; printf(“\n I=%f”,I); }
求三角形面积#include “math.h”
main()
{ float a,b,c,area,s;
scanf ( “%f,%f,%f”, &a,&b,&c);
s=1.0/2*(a+b+c);
area=sqrt (s*(s-a)*(s-b)*(s-c));
printf(“\n area=%f”,area);
}
使用数学函数
使用三角函数
#include “math.h”
main()
{ float x, y;
scanf ( “%f”, &x);
y=sin(x*3.1415926/180); 以弧度为单位 printf(“\n y=%f”,y);
}
第五章 选择结构程序设计 对于如下的函数计算,算法上属于一个选择结构。
y=
用于实现选择结构的主要是 if语句。
1/x 当 x≠0时
10000 当 x=0时
if 语句的最常见形式为:
if(关系表达式 )语句 1; else 语句 2;
如: if(x!=0) y=1/x; else y=10000;
其中 x!=0 就是一个关系表达式 != 就是一个关系运算符
5. 1 关系运算符和关系表达式1、关系运算符用于进行比较运算的运算符。共有六种: < <= > >= = = !=■优先级与结合性:① 前 4 种大于后两种。② 低于算术运算符而高于赋值运算符。③ 左结合性。
2 、关系表达式
一般形式:〈表达式〉〈关系运算符〉〈表达式〉
如: a>b a+b>b+c
经过关系运算后最终有一个值 --关系表达式的值。
关系表达式的值只有 0(假)或 1 (真)
例:设 a=2, b=4, c=1 计算以下关系表达式的值:
a>b a+b>b+c‘a’>’b’ 可以是字符表达
式( x=2 ) >(y=5) 可以是赋值表达式(a<b)>(b<c) 甚至可以是关系表达式a<b= =b<c
例:假设 x=3, y=5, z=1, 计算以下关系表达式的值:
x+z>yx<y==y<zy>z==x>zy>x>z (x==y-2)<z+1==x+ya=x+y==x+z<y+x!=z+1>x+1
5. 2 逻辑运算符和逻辑表达式有时,只用一个简单的关系表达式无法完整地
表达一个条件,如:
y=
其中的条件需要用逻辑表达式来表达: x!=0 &&a!=0 &&就是一种逻辑运算符。
1/x+1/a 当 x≠0,a≠0时
10000 其它
1 、逻辑运算符
&& 逻辑与 两个操作数都为真时 &&运算结果为真。|| 逻辑或 两个操作数之一为真时即为真。! 逻辑非 (单目运算) 操作数为真(假)时为假(真)。 如:若 a=2, b=3, c=0 则: a<b&&b<c 0 a<b||b<c 1 !(a<b) 0
■优先级(由高到低):! 逻辑非算术运算符关系运算符&& 逻辑与|| 逻辑或赋值运算符
■结合性:左结合性
2 、逻辑表达式 实际上,前面所举例子即为逻辑表达式: a<b&&b<c a<b||b<c !(a<b) 逻辑表达式的值同样只有 1 和 0 ,但参加逻辑 运算的操作数可以是任意类型的数据,可以是 任意大小。例: a+b&&b+c 是合法的逻辑表达式。此时以 0代表假,非 0 代表真。
例:设 a=2, b=3, c=0 , 计算以下表达式的值:
a&&b b&&c a||c !a+c&&b+c !c+a==b||b<a a+c||a+b>c+10
对于逻辑表达式的两种基本技能:① 逻辑表达式的计算。② 逻辑表达式的构造。
逻辑表达式的构造举例: a≥b≥c
a 和 b之一为 0,但不同时为 0
a>=b>=c 5>=4>=3 a>=b&&b>=c
a==0&&b!=0 || a!=0&&b==0
a*b==0
a*b==0&&a+b!=0
对于 a==0 && b!=0 || a!=0 && b==0 a==0 可以用 !a 代替 a!=0 可以直接用 a
!a && b || a && !b
但必须是运算结果作为逻辑量的情况下。
y=(a!=0) 与 y=a 不等效
5. 3 if 语句
1、 if 语句的三种形式
① if (表达式)语句; 有一分支为空。
scanf(“%d”,&score); if(score>=60) printf(“pass”);
② if (表达式)语句 1; else 语句 2;
if(x!=0) y=1/x; else y=10000;
③ if (表达式 1)语句 1; else if( 表达式 2)语句 2; else if( 表达式 3)语句 3; ┇ else 语句 n; if(score==100) printf(“A”); else if(score>=90) printf(“B”); else if(score>=80) printf(“C”); else if(score>=70) printf(“D”); else if(score>=60) printf(“E”); else printf(“F”);
对于:
1/x 当 x≠0时
10000 当 x=0时
一般用:if(x!=0) y=1/x;else y=10000;
也可用:y=10000;if(x!=0) y=1/x;
y=
?y=1/x;if(x==0)y=10000
例:(习题 5.5 ) :x (x<1)
2x-1 (1≤x<10)
3x-11 (x≥10)
y=
main()
{
float x,y;
scanf(“%f”,&x);
if(x<1) y=x;
else if(x<10) y=2*x-1;
else y=3*x-11;
printf(“\n y=%f”,y);
}
说明:① 语句中的表达式可以是任意表达式: if(x) y=1/x; else y=10000;② 一个 if 结构不可分割: if(x) y=1/x; z=10; else y=10000;③ 一个分支中包含多个语句时,要用 { } : if(a<0) { x=1; y=2;} else {x=10; y=20;}
分支程序设计举例(基本技巧和算法)
例:从键盘输入三个整数到变量 a,b,c, 输出其中最大的数。两种典型算法:①枚举法(将各种可能性枚举出来)。②选择法(先假设后判断更新)。
选择法main(){ int a,b,c,max; scanf(“%d,%d,%d”,&a,&b,&c); max=a; if(b>max) max=b; if(c>max) max=c; printf(“\n max=%d”,max);}
}
例:从键盘输入三个整数到变量 a,b,c, 要求按从大到小的顺序输出。 两种典型算法: ① 枚举法(将各种可能的排列枚举出来)。 ②换位法(将 a,b,c 中的数据换位)。
换位法main(){ int a,b,c,t; scanf(“%d,%d,%d”,&a,&b,&c); if(a<b) {t=a; a=b; b=t;} if(a<c) {t=a; a=c; c=t;} if(b<c) {t=b; b=c; c=t; } printf(“\n %d,%d,%d”,a,b,c);}
}
3 5 85 38 55 3
a b c
2 、 if 语句的嵌套在 if语句中,又包含一个或多个 if语句:if(score>=80) if(score>=90) printf(“A”); else printf(“B”);else if(score>=60) printf(“C”); else printf(“D”);
注意 else 与 if 的匹配
3 、条件运算符如果两个分支的内容都是给同一个变量赋值,
则可用简单的条件运算符处理: if(a>b) max=a; else max=b;可用: max=a>b ? a : b;赋值运算符右边为一条件表达式。条件表达式的一般形式: 表达式 1 ? 表达式 2 : 表达式 3
条件表达式的执行过程: a>b ? a : b
优先级:低于关系运算符,高于赋值运算符。结合性:右结合性。
表达式 1
条件表达式取表达式 3的值
条件表达式 取表达式 2的值
非 0 0
例:求 a,b,c 中的最大值:
max= a>b ? (a>c?a:c) : (b>c?b:c) ;
5. 4 switch语句(多分支)
适用于根据一个表达式的值就可确定走哪个分支的情况。 switch(表达式) { 常量表达式 1: 语句 1 常量表达式 2: 语句 2 ┋ 常量表达式 n: 语句 n default : 语句 n+1 }
例:成绩分档:switch(score/10){ case 10: printf(“A”); case 9: printf(“B”); case 8: printf(“C”); case 7: printf(“E”); case 6: printf(“F”); default : printf(“G”);} 注:应使用 break.
switch(score/10){ case 10: printf(“A”); break; case 9: printf(“B”); break; case 8: printf(“C”); break; case 7: printf(“E”); break; case 6: printf(“F”); break; default : printf(“G”);}
5. 5 程序举例 ( 习题 5.10) 有 4个圆塔,圆心分别为:( 2, 2),( -2 , 2 ),( -2 , -2 ),( 2 , -2 ),圆半径为 1。这 4 个塔的高度为 10m,塔以外无建筑物。今输入任一点的坐标,求该点的建筑高度(塔外的高度为 0 )。
算法设计:
条件“在某一圆内” : “ 在圆 1内或在圆 2内或在圆 3内或在圆 4内” 若设变量 c1 、 c2 、 c3 、 c4 分别代表是否
在相应的圆内,则以上条件为: c1||c2||c3||c4
10 在某一圆内
0 在圆外
( x,y )
h=
c1=(x-2)2+(y-2)2≤1
c2=(x+2)2+(y-2)2≤1
c3=(x+2)2+(y+2)2≤1
c4=(x-2)2+(y+2)2≤1
main(){ int h,c1,c2,c3,c4; float x,y; scanf(“%f%f”,&x,&y); c1=(x-2)*(x-2)+(y-2)*(y-2)<=1; c2=(x+2)*(x+2)+(y-2)*(y-2)<=1; c3=(x+2)*(x+2)+(y+2)*(y+2)<=1; c4=(x-2)*(x-2)+(y+2)*(y+2)<=1; if(c1||c2||c3||c4) h=10; else h=0; printf(“\n h=%d”,h);}
第六章 循环控制 6 . 1 概述 所谓循环控制,就是如何实现循环结构的控制问题。有 4种方法: ① 用 goto语句和 if构成循环。 ② 用 while 语句。 ③ 用 do-while 语句。 ④ 用 for 语句。
6 . 2 用 goto语句和 if语句构成循环例 : 对于计算 s=1+2+3+4+5+6+7+8+9+10
0 s
1 i
s+i → s
i+1 → i
i≤10
输出 s
y
n
s=0;
i=1;
lable:s+=i;
i++;
if(i<=10) goto lable;
printf(“%d”,s) ;
语句标号 无条件转向语句
goto 语句可以构造循环,但不主张用,因为它容易破坏结构化程序设计。
goto 语句 可以构造循环,但不主张用,因为它容易破坏结构化程序设计。
6 . 3 while 语句 while 语句是专门用于实现循环控制的语 句之一。 其一般形式为: while ( 表达式 ) 语句
含义:当表达式的值为非 0时,执行循环体,否则执行后续语句。
语句关键 表达循环条件的表达式
循环体 语句 关键字
执行过程 :
while ( 表达式 ) 语句
表 达式
循环体
0
非 0
i≤10?
s=0 i=1
s=s+ii=i+1
例:用 while 语句实现前面算法:main(){ int i=1,s=0; while(i<=10) { s=s+i; i++; } printf(“\n %d”,s); } 注意与 if语句的区别。循环体中要有使循环条件趋于成立的条件
y
n
while(i<=10) { s=s+i; i++; } 可简写为: while(i<=10) s+=i++;
非 0 (真)
0 (假)
表达式
循环体
6 . 4 do-while 语句 do-while 语句主要用于实现直到型循环。
其一般形式为: do 循环体 while( 表达式 );
执行过程:
真
s=0i=1
i≤10?
s=s+ii=i+1
假
例:用 do-while 语句实现前面算法: main() { int i=1,s=0; do s+=i++; while(i<=10); printf(“\n%d”,s); }
注意与 while 语句的区别。
假表达式2
求解表达 式 1
循环体
求解表达 式 3
真
6 . 5 for 语句 for 语句是一种使用最为灵活,并且是用得最多的循环控制语句,
其一般形式为:
for( 表达式 1; 表达式 2; 表达式 3) 循环体
大体含义: 对于()的情况执行循环体内容
。
例:用 for 语句实现前面的算法:
s=0;for(i=1;i<=10;i++) s+=i; 标准形式
表达式 1 表达式 2 表达式 3 循环体
可以理解: 循环变量 i 从初值 1 开始到终值 10 ,步长为 1 ,重复执行循环体。
for 语句的常见变化: s=0; i=1; for(;i<=10;i++)s+=i;
s=0; for(i=1;i<=10;)s+=i++; s=0;i=1; for(;i<=10;)s+=i++;
省略表达式 1
省略表达式 3
省略表达式 1和3
s=0; i=1; for(;;) { s+=i++; if(i>10) break; }
s=10;i=10; for(;--i;) s+=i;
省略表达式2
表达式 2是任意表达式
s=0; for(i=1,j=10;i<j;i++,j--) s+=i+j; 1 2 3 4 5 6 7 8 9 10
在程序设计中不要过分追求它的多变性
i j
用逗号表达式
例:求 n! n!=1*2*3…(n-1)*n 参照累加求和main(){ int i,n=5 , s=1; for(i=1;i<=n;i++)s*=i; printf(“\n s=%d”,s);}注意 s 的初值。注意当 n较大时的情况。求和与连乘都是最常用的算法,要熟练掌握。
例:求自然数 1-100 中能被 3整除的数之和。 main() { int i,s=0; printf(“\n %d”,s); }
求能被 3整除但不被 7 整除的数之和?
for(i=1;i<=100;i++) s+=i;for(i=1;i<=100;i++) if(i%3==0)s+=i;for(i=3;i<=100;i+=3) s+=i; for(i=3;i<=100;i+=3) if (i%7)s+=i;
例:求任意 100 个数中的最大值。 main() { int i,a,max; max=? for(i=1;i<=100;i++) { scanf(“%d”,&a); if(a>max) max=a; } printf(“\n max=%d”,max); } 循环体中没有引用循环变量。 i的作用? 求任意个数中的最大值?
for(i=1; ;i++)
if(a==-9999)break;
max=-32768;
外重循环
内重循环
执行 200 次
要掌握多重循环执行的全过程
6 . 6 循环的嵌套 循环体内又包含另一个完整的循环结构 ( 多
重循环 ) 。 for(i=1;i<=10;i++) { for(j=1;j<=20;j++) { s+=i+j; } }
以上多重循环结构可以简写为: for(i=1;i<=10;i++) for(j=1;j<=20;j++) s+=i+j;
例:找出行号乘以列号等于 100 的座位。 main() { int i,j; for(i=1;i<=30;i++) for(j=1;j<=20;j++) if(i*j==100) printf(“\n%d,%d”,i,j); } 注意循环的关系
例:百钱买百鸡问题。 给定 100块钱,要求正好买 100只鸡,已知公鸡 5元 /只,母鸡 3元 /只,小鸡 1元 /3只,问公鸡、母鸡和小鸡应各买多少只?若考虑用方程组:
x+y+z=100
5x+3y+z/3=100
是一多解问题。
用测试法求解的程序: main() { int x,y,z; for(x=1;x<=100;x++) for(y=1;y<=100;y++) for(z=1;z<=100;z++) if(x+y+z==100&&5*x+3*y+z/3.0==100) printf(“\n%d,%d,%d”,x,y,z); }
程序可进一步简化为: main() { int x,y,z; for(x=1;x<=20;x++) for(y=1;y<=33;y++) { z=100-x-y; if(5*x+3*y+z/3.0==100) printf(“\n%d,%d,%d”,x,y,z); } } 用测试法求解问题的典型例子
测试法求解的程序设计有两个要点: ⑴通过循环列出所有可能的解。
⑵对所有列出的可能的解进行条件测试。
例:判断一个数 m是否为素数。main(){ int i,m; scanf(“%d”,&m); for(i=2;i<m;i++) if(m%i==0)break; if(i==m) printf(“\n %d is a prime”,m); else printf(“\n %d is not a prime”,m); }
用测试法求解
例:(习题 6.6) 打印出所有的“水仙花数”,所谓“水仙花数”是指一个三位数,其各位数字的立方和等于该数本身。如: 153 是一水仙花数,因为 153=13+53+33 。
main(){ int i,j,k,n; for(n=100;n<=999;n++) { i=? j=? k=? if(i*i*i+j*j*j+k*k*k==n) printf(“\n%d”,n); } } 通过循环列出 n的所有可能的范围
i=n/100;
k=n%10;j=n/10%10;
main(){ int i,j,k,n; for(i=1;i<=9;i++) for(j=0;j<=9;j++) for(k=0;k<=9;k++) { n=i*100+j*10+k; if(i*i*i+j*j*j+k*k*k==n) printf(“\n%d”,n); }}通过循环列出 i,j,k的所有可能的范围
例:(习题 6.4 )
求 S=∑i! =1 ! +2 ! +3 ! +4 ! +…+19 ! +20 !
20
i=1
main(){ int i,j; float t,s=0; for(i=1;i<=20;i++) { s+=?; } printf(“\n s=%f”,s); }用两重循环实现
s+=t;
t=1;for(j=1;j<=i;j++) t*=j;
用递推法:
递推公式: ti=ti-1.i
求 S=∑i! =1 ! +2 ! +3 ! +4 ! +…+19 ! +20 !
20
i=1
=1+∑(i-1)!.i = 1+∑ti-1.i 20
i=2
20
i=2
main(){ int i,j; float t=1,s=t; for(i=2;i<=20;i++) { t=t*i; s+=t; } printf(“\n s=%f”,s); }
例:
递推公式:
s=∑—i !1
1
20
=— + — + — + — + …+ — 1! 2! 3! 4! 20!1 1 1 1 1
ti= ti-1 / i
main(){ int i,j; float t=1,s=t; for(i=2;i<=20;i++) { t=t*i; s+=t; } printf(“\n s=%f”,s); }
t=t/i;
习题 6.3: s=a+aa+aaa+aaaa+aaaaa 2+22+222+2222+22222
递推公式: ti= ti-1•?ti= ti-1 * 10+a
main(){ int i,j,a=2; float t=a,s=t; for(i=2;i<=5;i++) { t=t*10+a; s+=t; } printf(“\n s=%f”,s); }
例:求方程 2x3+3x2-4x+1=0 的根。 简单迭代法基本思想: 将原方程 f(x) 化为: x2=g(x1) x2=(2x13+3x12+1)/4 迭代公式
迭代过程:
假定一个 x1
x2=g(x1)
不成立 |x2-x1∣<ε
x1=x
2
输出 x2
成立
main(){ float x1,x2; scanf(“%f”,&x1); while(1) { x2=(2*x1*x1*x1+3*x1*x1+1)/4; if(fabs(x2-x1)<1e-6)break; ? } printf(“\n %f”,x2);}
x1=x2;
牛顿迭代法基本思想:
牛顿迭代公式 f′(x1)=f(x1)/(x1-x2) x2=x1-f(x1)/ f′(x1)
x1x2
y
x
f (x1)
f (x2)
main() { float x1,x2,f1,f; scanf(“%f”,&x1); while(1) { f=2*x1*x1*x1+3*x1*x1-4*x1+1; f1=6*x1*x1+6*x1-4; x2=x1-f/f1; if(fabs(x2-x1)<1e-6) break; x1=x2; } printf(“\n %f”,x2); }
例:求定积分: 数值积分
f (x)
a bx
∫—sinxx
dxa
b
等分 n
h=—b-an
第 i 个矩形: x=?x=a+(i-1).h
#include “math.h” main(){ int n,i; float a,b,x,y,h,s=0; scanf(“%f%f%d”,&a,&b,&n); h=(b-a)/n;
for(i=1;i<=n;i++)
{ x=a+(i-1)*h; y=sin(x)/x;
s+=h*y;
}
printf(“\n s=%f”,s);
}
第七章 数 组 7 . 1 数据结构与数组的概念 影响程序设计的因素除算法外还有数据结构。 ■数据结构概念 编写一个程序除了重视算法的设计外,还需重视数据类型的选择,即选择合适的数据类型来存放要处理的数据。在程序设计中,数据类型就称为数据结构,选择合适的数据类型实际上就是进行数据结构的设计。
在程序设计中有格言: 数据结构 +算法 =程序 说明数据结构与算法同等重要,算法依赖于数据结构,对于同一个问题的求解,可以采用不同的数据结构和不同的算法,对不同的数据结构有不同的算法,其复杂程度也会不同,选择合适的数据结构,可以降低算法的复杂程度。因此,在程序设计中应重视数据结构的设计。
例:求任意 100 个数中的最大值。 main() { int i,a,max; max=-32768 for(i=1;i<=100;i++) { scanf(“%d”,&a); if(a>max) max=a; } printf(“\n max=%d”,max); }
用一个简单变量作为数据结构,合理,算法简单
对于三个数的排序:main(){ int a,b,c,t; scanf(“%d,%d,%d”,&a,&b,&c); if(a<b) {t=a; a=b; b=t;} if(a<c) {t=a; a=c; c=t;} if(b<c) {t=b; b=c; c=t; } printf(“\n %d,%d,%d”,a,b,c);}对于很多个数的排序用变量会很复杂而用数组会使算法很简单。
仍可用变量作为数据结构
■数组的概念int a[10]
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
一组具有同样类型的数据的集合 统一用一个名字代表 --- 数组名(代表一组数)
数组元素 下标
数组名
数组中的各成员称数组元素,由数组名加下标唯一地确定。 将一组数用一个名字代表,便于管理。 只有一个下标的数组称为一维数组; 可有二维数组、三维数组、…、七维数组。
7 . 2 一维数组的定义和引用 ■定义 一般形式: 类型符 数组名 [常量表达式 ];
int a[10]; float b[10] ;
类型符 数组名 长度 作用:分配一组连续的内存单元
说明: ●数组必须先定义后使用。 ●数组名的命名规则与变量相同。 ● 常量表达式表示元素的个数(长度),下标 从 0开始。 ● 常量表达式不能包含变量,即不允许作动态定义。
■引用 逐个引用其元素,不能进行整体引用。 引用的一般形式: 数组名 [下标 ] 如: a[0]=50; a[1]=100; a[2]=a[0]+a[1]; 与 a2=a0+a1 有根本性的区别:下标可变。
例:从键盘输入 10个数。用变量: (不方便 )scanf(“%d%d%d%d%d%d%d%d%d%d”, &a0,&a1,&a2,&a3,&a4,&a5,&a6,&a7,&a8,&a9);用数组: (灵活方便 ) for(i=0;i<10;i++) scanf(“%d”,&a[i]); 用循环控制输入个数和下标的变化。 注意下标的变化范围。
■初始化 在定义数组的同时给数组赋初值。
inta[10]={0,1,2,3,4,5,6,7,8,9}; int a[10]={0,1,2,3,4}; int a[]={0,1,2,3,4};
■应用举例( 1 )对 100 个学生的分数统计最高分、最低分和平均分。 两种方法: 用变量作为存放初始数据的数据结构 用数组作为存放初始数据的数据结构
main() { int i,a,max,min; float aver=0; max=0; min=100; for(i=0;i<100;i++) { scanf(“%d”,&a); if(a>max) max=a; if(a<min) min=a; aver+=a; } aver/=100; printf(“\n %d,%d,%f”,max,min,aver); }
用变量
main() { int i,a[100],max,min; float aver=0; for(i=0;i<100;i++) scanf(“%d”,&a[i]); max=a[0]; min=a[0]; for(i=0;i<100;i++) { if(a[i]>max) max=a[i]; if(a[i]<min) min=a[i]; aver+=a[i]; } aver/=100; printf(“\n %d,%d,%f”,max,min,aver) ; }
用数组
找最大最小的位置?
max=0; min=0;
if(a[i]>a[max]) max=i;if(a[i]<a[min]) min=i;
( 2)统计高于平均分的人数。
main(){ int i,a,n; float aver=0; for(i=0;i<100;i++) { scanf(“%d”,&a); aver+=a; } aver/=100;
n=0; for(i=0;i<100;i++) { scanf(“%d”,&a); if(a>aver)n++; } printf(“\n %d”,n);}
用变量
数据结构不合理
main(){ int i,a[100],n; float aver=0; for(i=0;i<100;i++) scanf(“%d”,&a[i]); for(i=0;i<100;i++) aver+=a[i]; aver/=100; n=0; for(i=0;i<100;i++) if(a[i]>aver)n++; printf(“\n %d”,n);}
用数组
1 ( 3 )对 100 个学生的分数统计出 每分一档人数。
0 ? 1 ? 2 ? 3 ? 4 ? ┇ ┇ 99 ? 100 ?
main(){ int i,a; for(i=1;i<=100;i++) { scanf(“%d”,&a);
} 输出 }
int i,a,n[101];for(i=0;i<101;i++) n[i]=0;
n[a]++;
完整程序:main(){ int i,a,n[101]; for(i=0;i<101;i++)n[i]=0; for(i=1;i<=100;i++) { scanf(“%d”,&a); n[a]++; } for(i=100;i>=0;i--) printf(“\n %3d:%3d”,i,n[i]);}
体会数组作为存放结果的数据结构时的优越性。
按 10 分一档统计?main(){ int i,a,n[101]; for(i=0;i<101;i++)n[i]=0; for(i=1;i<=100;i++) { scanf(“%d”,&a); n[a]++; }}
int i,a,n[11];for(i=0;i<11;i++)n[i]=0;
n[a/10]++;
( 4 )对 10 个学生的分数按从小到大的顺序排序后输出。
两种典型的排序算法:选择法和起泡法。选择法基本思想: 首先选择最小的数放在 0位置,再在剩下的数中选择最小的数放在下一位置,┈┈,依次类推,共进行 9 次选择。
5 8 7 4 3 9 0 1 2 6
每次选择都要与其后的所有数进行比较换位。
5 8 7 4 3 9 0 1 2 6
i j
main(){ int a[10],i,j,t; for(i=0;i<10;i++)scanf(“%d”,&a[i]); for(i=0;i<9;i++) for(j=i+1 ;j<10 ;j++) if(a[i]>a[j]) { t=a[i]; a[i]=a[j]; a[j]=t; } for(j=0;j<10;j++) printf(“%3d”,a[j]); } 5 8 7 4 3 9 0 1 2 6
5 8 7 4 3 9 0 1 2 6
i j
先找最小值所在的位置,最后再换位 :main(){ inta[10],i,j,t,k; for(i=0;i<10;i++)scanf(“%d”,&a[i]); for(i=0;i<9;i++) { k=i; for(j=i+1;j<10;j++) if(a[j]<a[k])k=j; t=a[i]; a[i]=a[k]; a[k]=t; } for(j=0;j<10;j++) printf(“%3d”,a[j]); }
起泡法基本思想: 首先将所有数中的最大值“冒泡”到最后位置,再将剩下的数中的最大值“冒泡”到上一位置,┈┈,依次类推,共进行 9 次“冒泡”。 每次“冒泡”都是一种翻滚过程,即相邻两个数进行比较换位。
5 8 7 4 3 9 0 1 2 6
main(){ int a[10],i,j,t; for(i=0;i<10;i++)scanf(“%d”,&a[i]); for(j=1;j<=9;j++) for(i=0;i<10-j; i++) if(a[i]>a[i+1]) {t= a[i]; a[i]= a[i+1]; a[i+1]=t;} for(j=0;j<10;j++) printf(“%3d”,a[j]); } 要特别注意两个循环的范围。
( (5) 循环移位 对一数列中的每个数向后移 3个位置,最后 3个数移到最前面。
5 8 7 4 3 9 0 1 2 6
1 2 6 5 8 7 4 3 9 0
用循环移位实现: 5 8 7 4 3 9 0 1 2 6main(){ int i,j,k,a[10]; for(i=0;i<10;i++)scanf(“%d”,&a[i]);
for(i=0;i<10;i++)printf(“%3d”,a[i]); }
for(i=1;i<10;i++)a[i]=a[i-1];for(i=9;i>0;i--)a[i]=a[i-1];k=a[9];
a[0]=k;
for(j=1;j<=3;j++){ k=a[9];
}
(6)狐狸找兔子问题绕 围绕着山顶有 10 个洞,一只兔子和一只狐狸分别住在洞里,狐狸总想吃掉兔子;一天,兔子对狐狸说:你想吃掉我有一个条件,先把洞顺序编号,你从最后一个洞出发,第一次先到第一个洞找我,第二次隔一个洞找,第三次隔两个洞找,┈,依次类推,寻找次数不限,我躲在一个洞里不动,只要找到我你就可以饱餐一顿。狐狸一想只有 10 个洞,寻找次数又不限,那有找不到的呢?马上答应了条件,结果狐狸跑断了腿也没找到,请问兔子躲在哪个洞里?
12
3
4
67
8
9
10
5
算法思想:
开辟数组,每个元素代表一个洞,并赋初值 0 ,表示各个洞都还未找,然后按规律找,每找一个洞,对应的数组元素就赋值 1 ,表示已找过,┈┈,最后根据数组元素值 1 与 0来识别各洞是否已找过。
main(){ int i, k=10; int a[10]={0,0,0,0,0,0,0,0,0,0}; for(i=1;i<=10000;i++) { k=(k+i)%10; if(k==0) k=10; a[k-1]=1; } for(i=0;i<10;i++) if(a[i]==0) printf(“%3d”,i+1); }
7 . 3 二维数组的定义和引用
■定义 一般形式:类型符 数组名 [ 常量表达式 ] [ 常量表达式 ];
int a[3][4]; float b[5][10]; 行 列
二维数组的逻辑结构就如同一张表格:
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]
存放形式:按行存放。
a[0]
a[1]
a[2]
二维数组可以看作是一个特殊的一维数组,它的元素又是一个一维数组。 C 语言这样的处理方法在很多情况下显得很方便。 与一维数组相比,二维数组的定义多一个长度,其元素多一个下标。 在应用中,如果要处理的数据如同一数列,则可定义一维数组来存放;而如果要处理的数据如同一张表格,则应定义二维数组来存放。
■引用 引用形式:数组名 [下标 ][下标 ] 如: a[0][3]=a[1][2]+a[2][3]; 其元素有两个下标。 例:从键盘输入 12个数到二维数组中。 int a[3][4],i,j; for(i=0;i<3;i++) for(j=0;j<4;j++) scanf(“%d”,&a[i][j]);
需要用两重循环来控制两个下标的变化。
如果键盘输入的数据是: 1 2 3 4 5 6 7 8 9 10 11 12, 则在数组中如何存放?两个循环换位呢?两个下标换位呢?
int a[3][4],i,j; for(i=0;i<3;i++) for(j=0;j<4;j++) scanf(“%d”,&a[i][j]);
for(j=0;j<4;j++) for(i=0;i<3;i++) scanf(“%d”,&a[i][j]);
for(i=0;i<3;i++) for(j=0;j<4;j++) scanf(“%d”,&a[j][i]);
例:输入一个表格的数据到二维数组中,并找最 大值所在的位置main(){ int a[3][4],i,j,i1,j1; for(i=0;i<3;i++) for(j=0;j<4;j++) scanf(“%d”,&a[i][j]); i1=0; j1=0; for(i=0;i<3;i++) for(j=0;j<4;j++) if(a[i][j]>a[i1][j1]){i1=i; j1=j;} printf(“\n %d,%d”,i1,j1); }
■初始化
对二维数组赋初值的几种方法: int a[3][4]= {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; int a[3][4]={{1},{5},{9}}; int a[3][4]={{1},{0,6},{0,0,11}}; int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12}; int a[][4]={{0,0,3},{},{9,10}};
■ 举例( 1)矩阵的基本操作 二维数组的逻辑结构就如同一个矩阵,因此,矩阵操作都可用二维数组实现。
a11 a12 a13 … a1n
a21 a22 a23 … a2n
a31 a32 a33 … a3n
… … … …am1 am2 am3 … amn
A=
假定 M=3 , N=4
求和:main(){ int a[4][4],i,j,s=0; ┈┈┈ for(i=0;i<4;i++) for(j=0;j<4;j++) s+=a[i][j]; ┈┈┈ } 上三角?下三角?主对角线?
for(i=0;i<4;i++) for(j=i;j<4;j++) s+=a[i][j];
for(i=0;i<4;i++) for(j=0;j<=i;j++) s+=a[i][j];
for(i=0;i<4;i++) for(j=i;j<=i;j++) s+=a[i][j];for(i=0;i<4;i++) s+=a[i][i];
1 2 3 45 6 7 89 10 11 1213 14 15 16
非方阵转置: aij→bji
main(){ int a[3][4],b[4][3],i,j; ┈┈┈ for(i=0;i<3;i++) for(j=0;j<4;j++) b[j][i]=a[i][j]; ┈┈┈ }
1 2 3 45 6 7 89 10 11 12
1 5 9 2 6 103 7 114 8 12
方阵转置: aij∽ajimain(){ int a[3][3],i,j,t; ┈┈┈ for(i=0; i<3; i++) for(j=0; j<3; j++) {t=a[i][j]; a[i][j]=a[j][i]; a[j][i]=t;} ┈┈┈ }
1 2 34 5 67 8 9
for(j=i+1; j<3; j++)
将矩阵中和值为最大的那一行元素与首行对换。main(){ int a[3][4],i,j,t,s,smax=-32768,row; ┈┈┈ for(i=0;i<3;i++) { s=0;for(j=0;j<4;j++)s+=a[i][j]; if(s>smax) {smax=s; row=i;} } for(j=0;j<4;j++) {t=a[0][j];a[0][j]=a[row][j]; a[row][j]=t;} ┈┈┈}
1 5 3 84 6 1 79 2 5 6
7 . 4 字符数组 用于存放字符的数组称字符数组。 字符数组的每一个元素存放一个字符。 字符数组的独特之处: ( 1)字符数组可以看作字符串变量。 ( 2)对字符数组可以进行某些整体操作。 ( 3)有专用的字符串处理函数。
1 、将字符数组作为字符串变量 char c[10]; 给 c 分配 10 个字节的内存单元。 把 c看作数组时,按数组元素的形式访问: c[0]=’a’; c[1]=’b’; c[2]=’c’; c[3]=’d’;
a b c d
char c[10]={‘a’,’b’,’c’,’d’} ; 也属于字符赋初值的形式。
如果把字符序列看作一个整体(字符串),则 c 就可看作是存放这个字符串的串变量;但必须在字符序列后加上“字符串结束标志”后,才能成为完整的字符串。 如: c[4]=’\0’; 或 c[4]=0;
a b c d \0
也可以按字符串形式初始化: char c[10]=”abcd”; char c[]=”abcd”; 分配 5 个字节
2 、对字符数组的整体操作 对字符数组的有些操作可以整体进行,如输入输出。 for(i=0;i<10;i++) printf(“%c”,c[i]); 对数组元素操作 printf(“%s”,c); 整体操作 注意以上两种操作有区别。 可将前者改为: for(i=0;c[i]!=’\0’;i++)printf(“%c”,c[i]);
对于输入: for(i=0;i<10;i++) scanf(“%c”,&c[i]); 对数组元素操作 scanf(“%s”,c); 整体操作 对字符数组输入输出可以整体进行,但不允许整体赋值: char c[10]=”abcd”,x[10]; x=c; 不允许
对于二维字符数组,可以看作是一维的字符串数组。 例:从键盘输入 10个人的名字到计算机:
main() { int i; char name[10][20]; 10 个元素的一维字符串数组
for(i=0;i<10;i++) scanf(“%s”,name[i]); … } 只给一个下标
3 、字符串处理函数 c 语言的函数库中提供了一系列专用于字符串处理的函数,需要时可直接调用。 ( 1) puts( 字符串 ) 用于输出字符串。 其中字符串可以是字符串常量,也可以是字符数组。 例: char str[]=”China”; puts(str); puts(”China”); 两个输出等效
( 2) gets( 字符数组 ) 用于从键盘输入一个字符串到字符数组中。 函数返回字符数组的起始地址。 例: char str[10]; gets(str); 执行该函数调用时,计算机等待输入字符串
( 3) strcat( 字符数组 ,字符串 ) 用于将字符串连接到字符数组的后面。 其中字符串可以是字符串常量,也可以是字符数组。 例: char a[10]=”abcd”, b[10]=”xyz”; strcat(a,b); 与 strcat(a,”xyz”)等效 puts(a);
输出结果是: abcdxyz
( 4) strcpy( 字符数组 ,字符串) 用于将字符串拷贝到字符数组中。 其中字符串可以是字符串常量,也可以是字符数组。 例: char a[10], b[10]=”abcdef”; strcpy(a,b); 与 strcpy(a,”abcdef”)等效 不能用 a=b 赋值 puts(a); 输出结果是: abcdef
( 5) strcmp( 字符串 1 ,字符串 2) 用于比较两个字符串的大小。 比较结果通过函数的返回值体现: 字符串 1= 字符串 2时:返回 0 。 字符串 1>字符串 2时:返回一正整数。 字符串 1<字符串 2时:返回一负整数。
两个字符串之间谁大谁小取决于最先有差异的两个字符的 ASCII代码的大小。 如: strcmp(“abcde”,”abcde”); 返回 0 strcmp(“abcdefgh”,”abcxyz”) ;返回负整数 strcmp(“a”,”ABCD”) ; 返回正整数
例:从键盘输入两个字符串,输出其中大的一个。#include “string.h” main(){ char a[10],b[10]; gets(a); gets(b); if(strcmp(a,b)>0) puts(a); 不能用 a>b else puts(b); } 使用字符串处理函数需要包含头文件 string.h
( 6) strlen( 字符数组 ) 测试字符串的实际长度(从返回值得到)。 ( 7) strlwr( 字符串 ) 将字符串中的大写字母全改为小写字母。 ( 8) strupr( 字符串 ) 将字符串中的小写字母全改为大写字母。 注意:在使用字符串处理函数时,别忘了将头文件 string.h 包含进去。
4 、字符串操作举例( 1 )从键盘输入一字符串到数组 a 中,再拷贝到数组 b中(不用库函数)。 main() { char a[50],b[50]; int i; scanf(“%s”,a); for(i=0; a[i]; i++) b[i]=a[i]; b[i]=0; printf(“%s”,b); }
( 2)从键盘输入两个字符串到数组 a和 b中,在将 b 中的内容连接到 a 中(不用库函数)。main(){ char a[50],b[50]; int i,j; scanf(“%s%s”,a,b); for(i=0; a[i]; i++); for(j=0; b[j]; j++) a[i++]=b[j]; a[i]=0; printf(“%s”,a);}
( 3)从键盘输入一字符串,并将其中的大写字母改成小写字母后输出(不用库函数)。main(){ char a[50]; int i; scanf(“%s”,a); for(i=0; a[i]; i++) if(a[i]>=’A’&&a[i]<=’Z’) a[i]+=32; printf(“%s”,a); }
第八章 函数 8 . 1 概述 C 语言的程序除主函数外,还可以有若干个其他函数—块状结构。
一般把其中相对独立的算法和功能定义成一个独立的函数,以供需要的地方调用。
优点: ( 1) 减少代码的重复现象。 ( 2) 便于分工合作。 ( 3) 便于阅读。 ( 4) 便于独立算法的代码移植。
8 . 2 函数的定义和调用 举例说明: 对于求两个数中的最大值,有三个步骤: ( 1)从键盘输入两个数给 a 和 b。 ( 2)求 a和 b中的最大值。 ( 3)输出结果。 把求最大值的算法部分定义成一个独立的函数:
函数类型 函数名 函数参数(形参)
int max(int x,int y) main() { int z; { int a,b,c; if(x>y)z=x; scanf(“%d%d”,&a,&b); else z=y; c=max(a,b); return(z); printf(“%d”,c); } }
x y z a b c3 5
说明:( 1 )程序由两个函数组成,它们逻辑上相互独立(功能、变量)。
( 2 )程序的执行总是从主函数开始,主函数总是被执行一次,其他函数只有在被调用时才获得控制。
( 3 )函数调用有两个作用:转移控制权和传递参数。
( 4 ) return 的作用也有两个:交回控制权和返回结果。
( 5 )实参可以是常量、变量或表达式,但类型要一致。
定义一个函数除考虑算法外就是 : 如何设计函数的参数,通过何种途径交回结果。
例:求自然数 1—100 中的素数之和。
? prime( ? ) main()
{ int i; { int i,s=0;
for(i=2;i<m;i++) for(i=1;i<=100;i++)
if(m%i==0) ? if(prime(i) ) s+=i;
? printf(“\n %d”,s);
} }
int m)int
return 0;
else return 1;return 1;
例:求 5! +7! +4!的值。 ? fac( ? ){ int i,s=1; for(i=1;i<=n;i++) s*=i;
return(s); }main(){ printf(“\n%d”,fac(5)+fac(7)+fac(4)); }
int nint
main(){ float s,fac(); 对被调函数声明 s=fac(5)+fac(7)+fac(4); printf(“\n%f”,s);}float fac(int n){ int i; float s=1; for(i=1;i<=n;i++) s*=i; return(s);} 不需声明的情况: int char 主调函数在后
若被调用的函数是库函数,则应用 #include命令将所调用函数的有关信息包含进来,如:
例: #include “math.h” main() { float x,y; scanf(%f”,&x); y=sin(x); printf(“%f”,y); }
8 . 3 函数的嵌套调用 C 语言的函数定义虽然相互平行、相互独立的,但可以嵌套调用,形如:
主函数 函数 A 函数 B
例:求多项式 S= ∑i!+ ∑i! + ∑i!的值。 float fac(int n) {…}
? sum( ? ) { int i; float s=0; for( i=? ) s+=fac(i); return(s); } main(){ printf(“%f”,sum(1,5)+sum(7,11)+sum(15,20); }
1
5
7
11
15
20
int a, int bfloat
i=a;i<=b;i++
8 . 4 函数的递归调用 在函数调用的过程中,出现直接或间接地调用该函数本身。如:
f1() f2() f3() { { { f1(); f3(); f2(); } } }
直接 间接 递归调用 递归调用
在实际应用中,有些问题既可用递归实现,也可不用递归(如求 n!);
也有些问题非有递归不可(如汉诺塔问题);不少问题使用递归显得很方便。
用递归方法求 n! : 递推公式: 1 当 n=0或 n=1时 n!= n(n-1)! 当 n>1时
float fac(int n) main() { float f; { float f; if(n==0||n==1) f=1; f=fac(4); else f= ? printf(“\n%f”,f);
return(f); } }
递归调用的执行过程:
n*fac(n-1);
主函数 函数 fac 函数 fac
函数 fac 函数 fac
问题: 4 个 return 的执行顺序?后进先出!
f=fac(4);
n=4if(n==0||n==1)f=1;else f=n*fac(n-1);
return(f)
n=3if(n==0||n==1)f=1;else f=n*fac(n-1);
return(f )
n=2if(n==0||n==1)f=1;else f=n*fac(n-1);
return(f)
n=1if(n==0||n==1)f=1;else f=n*fac(n-1);
return(f)
8 . 5 数组作为函数的参数 当要传递的参数较少时,用简单变量作为函数的参数是方便的 ,但当要传递的参数是批量时,需要用数组作为函数的参数。
例:编写函数,求 100 个数的平均数。float aver(int a[100]){ int i; float s=0; for(i=0;i<100;i++) s+=a[i] ; return(s/100); } main(){ int x[100]; float av; 输入 x; av=aver(x); }
对应的实参也应为数组型参数组的长度可省略
a[]
通用函数考虑: float aver(int a[],int n) main()
{ int i; float s=0; { int x[100],n=100;
for(i=0;i<n;i++) float av;
s+=a[i]; 输入 x
return(s/n); av=aver(x,n);
} }xa
100
n
n
值传递地址传递
100
n=5;
5
a[0]=3 ;
3
例:阅读程序:main() swap(int a, int b)
{ int a=3,b=5; { int t;
swap(a,b); t=a; a=b; b=t;
printf(“\n%d,%d”,a,b); printf(“\n%d,%d”,a,b);
} }
a b a b
3 5
以下程序的运行结果是 ? main() f ( int b[], int x)
{ int a[2]={2,4}, x=5; { x++;
f (a,x); b[0]+=2;
printf(“%d,%d,%d”, b[1]+=3;
x,a[0],a[1]);
} }
A) 5,2,4 B) 6,4,7 C) 6,2,4 D) 5,4,7
例:排序。 void sort(int a[], int n)
{ int i,j,t;
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if(a[i]>a[j]){t=a[i];a[i]=a[j]; a[j]=t;}
}
main()
{ int x[5],i;
for(i=0;i<5;i++) scanf(“%d”,&x[i]);
sort(x,5);
for(i=0;i<5;i++) printf(“%4d”,x[i]);
}
x a
例:方阵转置(二维数组情况)。at(int a[3][3]) x a
{ int i,j,t;
for(i=0;i<3;i++)
for(j=i+1;i<3;i++)
{ t=a[i][j]; a[i][j]=a[j][i];
a[j][i]=t;}
}
int x[3][3];
at(x); 行长不等情况?通用函数的处理?
通用函数,按一维数组处理:at(int a[],int n)
{ int i,j,t;
for(i=0;i<n;i++)
for(j=i+1;i<n;i++)
{ t=a[i*n+j]; a[i*n+j]=a[j*n+i]; a[j*n+i]=t;}
}
int x[3][3];
at(x,3);
8 . 6 局部变量和全局变量 1 、局部变量 在函数内定义的变量称局部变量。 局部变量只在该函数内使用。 float f1(int x) int f2() main()
{ int i,j; { int a,b,c; { int m,n;
┊ ┊ { int i,j; ┊ ┊ ┊ } } } }
说明:( 1 )任何函数(包括主函数)内定义的变量都是局部变量。
( 2)不同函数内定义的变量即使同名也互不干扰。
( 3)复合语句中定义的变量只在该复合语句中有效。
2 、 全局变量 在函数以外定义的变量 ,也称外部变量。 全局变量可以为为本文件中其它函数所共用。它的作用范围是从定义变量的位置开始到本源文件结束。
int p=1,q=5;float f1(int a){ int b,c; ┊}char c1,c2;char f2(int x,int y){ int i,j; ┊ }main(){ int m,n; ┊ }
全局变量 c1,c2的作用范围
全局变量p,q 的作用范围
全局变量主要用于作为不同函数间数据传递的桥梁。
例:编写一个函数,求 n 个数中的最大值、最小值和平均值。并编写主函数完成:输入 100 个数,调用该函数进行统计,输出结果。
int max,min;float aver(int a[],int n){ int i; float s=0; for(i=0;i<n;i++) { if(a[i]>max)max=a[i]; if(a[i]<min)min=a[i]; s+=a[i]; } return (s/n);
}
main()
{ int x[100],i; float av;
for(i=0;i<100;i++) scanf(“%d”,&x[i]);
av=aver(x,100);
printf(“\n %d,%d,%f”,max,min,av);
}
用全局变量传递结果
分析不用全局变量的情况:float aver(int a[],int n){ int i; float s=0; int max,min; for(i=0;i<n;i++) { if(a[i]>max)max=a[i]; if(a[i]<min)min=a[i]; s+=a[i]; } return (s/n);
}
main()
{ int x[100],i; float av; int max,min; for(i=0;i<100;i++) scanf(“%d”,&x[i]);
av=aver(x,100);
printf(“\n %d,%d,%f”,max,min,av);
}
int max,min; float aver(int a[],int n){ int i; float s=0; for(i=0;i<n;i++) { if(a[i]>max)max=a[i]; if(a[i]<min)min=a[i]; s+=a[i]; } return (s/n);
}
main()
{ int x[100],i; float av; int max,min; for(i=0;i<100;i++) scanf(“%d”,&x[i]);
av=aver(x,100);
printf(“\n %d,%d,%f”,max,min,av);
}
分析全局变量与局部变量同名的情况
max
min
max
min
阅读程序,给出运行结果:int a=4,b=5,c=6;
int f(int a,int b)
{ a/=2; c+=b-a;
return(a+b+c);
}
main()
{ int a=2,d;
d=f(a+2,a+b);
printf(“\n %d,%d,%d,%d”, a,b,c,d);
}
4 5
2
6
a b c
a b
a d
4 72
11
20
8 . 7 变量存储类别 1 、动态存储方式和静态存储方式
静态存储方式:在程序运行期间分配固定存储单元的方式。动态存储方式:在程序运行期间根据需要动态分配存储单元的方式。
变量
变量
局部变量
动态存储方式变量全局变量
静态存储方式变量
空间角度
生存期角度
存放在静态存储区的变量:静态存储方式。 存放在动态存储区的变量:动态存储方式。
程序区
静态存储区
动态存储区
程序开始运行时分配空间,运行结束时释放主要存放全局变量和静态局部变量
根据需要动态分配,动态释放的区域主要存放动态局部变量和现场保护等
用户区
2 、局部变量的存储方式 每个局部变量在定义时可以指定其存储方式,即对每个局部变量的定义除定义其数据类型外,还应定义其存储方式。定义存储方式用 auto(自动的) static (静态的)。如:
int f1() int f2() { auto int a=1; {static int b=1; ┊ ┊ } } 缺省时为 auto
动态局部变量在函数调用时分配、赋初值,调用结束时释放。
静态局部变量在第一次调用时分配、赋初值,调用结束时不释放,其单元及其值仍保留,下次调用时不重新分配,不重新赋初值。
在实际应用中,如果希望在函数调用结束后仍保留某个局部变量的值给下次调用时使用,则可定义该变量为静态的。
例:编写一个函数计算:
y=
2x-1 第一次计算
3x+1 第二次计算
4x+2 其他
float f ( float x) main()
{ float y; {
static int n=1; printf(“%f”,f(2.0));
if(n==1)y=2*x-1; printf(“%f”,f(1.0));
else if(n==2)y=3*x+1; printf(“%f”,f(3.0));
else y=4*x+2; }
n++;
return(y);
}
3 、全局变量的存储方式全局变量都是静态存储方式,不允许用 auto来定义全局变量。可以用 static来定义全局变量。如:static int x; 静态全局变量(内部的)int y; 非静态全局变量(外部的)
对于非静态全局变量,其它文件中的函数只要用 extern 加以外部说明,就可以访问。 而对于静态全局变量,只局限在本文件中的所有函数访问,其它文件中的函数即使用 extern 加以外部说明,也不能访问。
int y; main() { y=0; }
int f1() { y=2; }
extern int y; int f3() { y=10; }
int f4() { y=100; }
static int y;
8 . 8 内部函数和外部函数 用 extern 加以定义的函数称外部函数 。 用 extern 加以定义的函数称外部函数 。
extern int f1() static int f2()
{ {
┊ ┊ } }
缺省为外部函数。 外部函数可以为其他文件中的函数所调用。 内部函数只为本文件中的函数所调用(保护)。
main(){ f3(); 允许 }
int f1(){ f4(); 不允许}
extern int f3(){ f4(); 允许 }
static int f4(){ }
第九章 编译预处理C 语言提供了一些以 #开头的指令,如: #define #include 等。这些指令是在编译以前就事先进行处理的,因而称为“编译预处理”指令。可以用编译预处理指令实现以下三种功能: 1 ) 宏定义 2 ) 文件包含 3 ) 条件编译
9 . 1 宏定义 1 、不带参的宏定义用一个指定的标识符(宏名)代表一个字符串。一般形式:#define 标识符 字符串如: #define PI 3.1415926有了这一宏定义后,程序中凡是用到 3.1415926的地方都可以以宏名 PI出现。
例:定义一个宏名来代表一个参数。#define PI 3.1415926
main()
{ float r=2,c,s; c=2*PI*r; s=PI*r*r; }┈
系统在对程序进行编译以前,首先将所有的编译预处理指令进行预处理,对本例来说,就是将程序中所有的宏名 PI还原成 3.1415926,——宏展开。
例:定义一个宏名来代表一个数据个数。#define N 100
main()
{ int a[N],i; float s=0;
for(i=0;i<N;i++)scanf(“%d”,&a[i]);
for(i=0;i<N;i++)s+=a[i];
s/=N;
printf(“\n %f”,s);
}
宏名 N--- 符号常量,可以作为数组说明的长度。方便修改参数。
例:定义一个宏名来代表一个计算公式。#define PI 3.1415926
#define AREA PI*r*r
main()
{ r=3,s;
s=AREA;
printf(“\n %f”,s);
}
注意:系统对宏定义的预处理是一个字符串的还原过程,不要把宏名看成一个整体。
注意以下程序的运行结果:#define F x+ymain(){ int x=3,y=5,z; z=2*F; printf(“\n %d”,z);}
2 、带参的宏定义宏定义也可以带参数,其一般形式为:#define 宏名(参数表) 字符串如: #define S(a,b) a*b area=S(3,2);定义宏名 S 代表矩形面积,参数 a,b 为边长。宏展开过程:
9 . 2 文件包含处理 在一个源文件中将另一个源文件的内容包含进来。文件包含指令的一般形式: #include “文件名”或 #include <文件名 >
#include “file2.c”
A
file1.c
A
B B
file1.cfile2.c
例:#include “math.h”
main()
{ float a,b,c,s,area;
scanf(“%f%f%f”,&a,&,&c);
s=0.5*(a+b+c);
area=sqrt(s*(s-a)*(s-b)*(s-c));
printf(“\n %f”,area);
}
#include “f.h”main(){printf(“\n%f”,f1(3.0));printf(“\n %f”,fac(10));}float f1(float r){return(PI*r*r);}float fac(int n){ int i; float s=1;for(i=1;i<=n;i++) s*=i;return(s);}
#define PI 3.1415926float f1(float r);float fac(int n);
f.cf.h
第十章 指针 指针 :C的一个重要概念、重要特色。它使C 具备了强大的功能,使 C 成为程序设计语言之首。正确而灵活地运用它,就可以方便地处理很多其它高级语言所不能处理的问题。
不掌握指针等于没有掌握 C 语言的精华。
10. 1 指针的概念
简单地说,指针就是地址。
要掌握指针的概念就必须弄清:
■内存地址概念 ?
■变量与地址的关系 ?
■如何通过地址进行变量的存取 ?
说明例:
内存用户数据
1000 3 i
1002 6 j
1004 9 k
对变量值的存取总是按地址进行的 ---- 直接访问。
int i,j,k;i=3; j=6;k=i+j;
程序经编译后,变量名就不复存在,以地址对应。
也可以采用“间接访问”方式: 先将变量 i 的地址存放到另一变量 p1中,要访问 i时,先取出 p1 的内容(变量 i的地址),再去访问该地址所对应的内存单元中的内容(变量 i的值)。
内存用户数据
1000 3 i
1002 6 j
1004 9 k
2000 1000 p1
2004 1002 p2
int i,j,k;i=3; j=6;k=i+j;int *p1, *p2;p1=&i;p2=&j;
在以上概念的基础上对指针下定义:
变量的地址就是该变量的指针。存放地址的变量称指针变量。p1是指向变量 i的指针变量。
1000
1002
1004
1000
1002
i
j
k
p1p2
10. 2 变量的指针 变量的指针 指针变量 指向变量的指针变量 用“ *”代表“指向” 如 *p1 代表它所指向的变量 i ,同一内存单元。 以下两个语句等价: i=3; 直接访问 *p1=3; 间接访问
内存用户数据
1000 3 i
1002 6 j
1004 9 k
2002 1000 p1
2004 1002 p2
int i,j,k;i=3; j=6;k=i+j;int *p1, *p2;p1=&i;p2=&j;*p1=3;
■指针变量的定义 指针变量也必须先定义后使用。 int *p1;注意: ① *表示该变量为指针变量,但变量名是 p1。 ② 一个指针变量只能指向同一类型的变量。 int i,*p1; float a; p1=&i; 合法 p1=&a; 不合法
■指针变量的引用 两种用法: ①用地址运算符 & p1=&i ; ②用指针运算符 * (实行间接访问) *p1=100; k=*p1;
注意:指针变量只能放地址(指针)。 p1=100; 不允许
例:main()
{ int a=100,b=10;
int *p1,*p2; 定义指针变量,尚无具体指向 p1=&a; p1 指向 a p2=&b; p2 指向 b printf(“\n %d,%d”,a,b);
printf(“\n %d,%d”, *p1,*p2);
}
注意:要区别定义和引用中的“ *”
要特别注意以下用法的后果:
int *p1;
*p1=100;
例:输入 a 和 b 两个整数,按先大后小的顺序输出 main()
{ int a,b,*p1, *p2, *p;
scanf(“%d,%d”,&a,&b); 1000 5 a
p1=&a; p2=&b; 1002 9 b
if(a<b)
{ p=p1; p1=p2; p2=p;}
printf(“\n %d,%d”,a,b); 2000 p1
printf(“\n%d,%d”,*p1,*p2); 2004 p2
} 2006 p
改变 p1 和 p2 的指向
1000
1002
1002
1000
重要概念:
只要得到某变量的地址(指针),就可通过指针而不需通过逻辑名来访问该变量。
手段:用指针变量保存变量的地址。
■指针变量作为函数的参数 可将指针变量作函数的参数,接受实参地址,获得具体指向,进而通过指针变量间接访问主调函数的变量。
swap(int *p1, int *p2)
{ int t;
t=*p1;
*p1=*p2;
*p2=t; 1000 5 a
} 1002 9 b
main()
{ int a,b;
scanf(“%d,%d”,&a,&b); 2000 p1
if(a<b)swap(&a,&b); 2004 p2
printf(“\n %d,%d”,a,b); 2006 t
}
1000
1002
5
9
跨越逻辑上的限制
swap(int p1, int p2) 不用指针变量情况 { int t;
t=p1;
p1=p2;
p2=t; 1000 5 a
} 1002 9 b
main()
{ int a,b;
scanf(“%d,%d”,&a,&b); 2000 p1
if(a<b)swap(a, b); 2004 p2
printf(“\n %d,%d”,a,b); 2006 t
}
5
95
9
重要概念:使用指针变量作函数参数,被调函数可以通过主调函数给定的地址去操作主调函数中的局部变量。
(可用于传递多个结果)
例:编写函数,求一元二次方程的两个实根。#include “math.h”
? root(float a,float b,float c, )
{ float d; d=b*b-4*a*c;
if(d<0||a==0)return(0);
? =(-b+sqrt(d))/2/a;
? =(-b-sqrt(d))/2/a;
return(1);
}
float *x1, float *x2
*x1 *x2
int
main()
{ int k; float a,b,c,xa,xb;
scanf(“%f,%f,%f”,&a,&b,&c);
k=root(a,b,c,&xa,&xb);
if(k) printf(“\n %f,%f”,xa,xb);
}
例:求 n个数的最大值、最小值和平均值。float aver(int a[], int n, int *max, int *min)
{ int i; float s=0;
*max=a[0]; *min=a[0];
for(i=0;i<n;i++)
{ s+=a[i];
if(a[i]>*max) *max=a[i];
if(a[i]<*min) *min=a[i];
}
return(s/100);
}
main()
{ int a[100],i,max,min;
float av;
for(i=0;i<100;i++)
scanf(“%d”,&a[i]);
av=aver(a,100,&max,&min);
printf(“ %d,%d,%f”,max,min,av);
}
10. 3 数组的指针数组有一个首地址 :
数组的指针。每个数组元素也都有地址 :
数组元素的指针。
5
3
2
1
6
8
7
4
2000
2002
2004
2006
■ 指向数组元素的指针变量 int a[10],*p;
p=a;
指向数组 p=&a[0];
指向数组元素
5
1
2
4
7
6
8
0
3
9
2000
2002
2004
2006
p
■通过指针引用数组元素 p=&a[0]; p 指向 a[0] *p=1; 等效于 a[0]=1; 即可通过 p来访问 a[0] 也可以通过 p来访问其它元素: *(p+1)=3; 等效于 a[1]=3; 其中 p+1 指向 a[1]
注意: p+1 不是地址加 1,而是加一个数据类型单 位。
一般地 ,当 p 指向 a[0]时:p+i ∽ a+i ∽ &a[i]*(p+i) ∽ *(a+i) ∽ a[i] ∽ p[i]
即以下几个语句等效:a[i]=10; *(p+i)=10; *(a+i)=10; p[i]=10;
例:从键盘输入 10个数到数组 a:
int a[10],i,*p=a,s=0
for(i=0;i<10;i++) scanf(“%d”,&a[i]);
for(i=0;i<10;i++) scanf(“%d”,a+i);
for(i=0;i<10;i++) scanf(“%d”,p+i);
累加求和的各种用法:for(i=0;i<10;i++)s+=a[i];
for(i=0;i<10;i++)s+=*(a+i);
for(i=0;i<10;i++)s+=*(p+i);
for(i=0;i<10;i++)s+=p[i];
for(i=0;i<10;i++)s+=*p++; 等效于 *(p++)
for(p=a;p<a+10;p++)s+=*p; 注意不能使用 a++
后两种用法效率高。
使用指针变量访问数组时,要特别注意指针变量的当前值。 注意下例: main()
{
int a[10],*p=a,i;
for(p=a;p<a+10;p++) scanf(“%d”,p);
for(i=0;i<10;i++)printf(“%d”,*p++);
}
p
p=a;
■ 数组名作为函数参数 有了指针概念的基础上,重新回顾数组名作为函数参数时,数据的传递情况:例:将数组 a中的 n个数按相反顺序存放。
int inv(int x[], int n)
{ i,j,m,t; main()
m=(n-1)/2; {
for(i=0;i<=m;i++) int a[10],i;
{ j=n-1-i; 输入 a
t=x[i]; inv(a,10);
x[i]=x[j]; 输出 a
x[j]=t; }
}
}
a 与 x共用同一片内存单元
ax
int inv(int *x, int n) 指针变量作函数参数时的传递情况
{ i,j,m,t; main()
m=(n-1)/2; {
for(i=0;i<=m;i++) int a[10],i;
{ j=n-1-i; 输入 a
t=x[i]; inv(a,10);
x[i]=x[j]; 输出 a
x[j]=t; }
} 下标法
}
a1000
1000x
{ j=n-1-i ; t=*(x+i); *(x+i)= *(x+j); *(x+j)=t; } 指针法
进一步优化:int inv(int *x, int n)
{ main()
int *i=x,*j=x+n-1,t; {
for(;i<j;i++,j--) int a[10],i;
{ 输入 a
t=*i; inv(a,10);
*i=*j; 输出 a
*j=t; }
}
} 优点:简练 效率高
i
j
例:选择法排序函数
void sort(int *a, int n)
{ int i,j,t;
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if(a[i]>a[j]){t=a[i]; a[i]=a[j]; a[j]=t;}
} 只将形参改为指针变量,仍按下标法使用
void sort(int *a, int n)
{ int i,j,t;
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if(*(a+i)>*(a+j))
{t=*(a+i); *(a+i)= *(a+j); *(a+j)=t;}
} 按指针法使用
进一步优化:void sort(int *a, int n)
{ int *i, *j,t;
for(i=a;i<a+n-1;i++)
for(j=i+1;j<a+n;j++)
if(*i>*j) {t=*i; *i=*j; *j=t;}
}
main()
{ int a[10],j;
for(j=0;j<10;j++)scanf(“%d”,a+j);
sort(a,10);
for(j=0;j<10;j++)printf(“%5d”,a[j]);
}
分段排序?main()
{ int a[10],j;
for(j=0;j<10;j++)scanf(“%d”,a+j);
for(j=0;j<10;j++)printf(“%5d”,a[j]);
}
sort(a,5);
sort(a+5,5);
■ 指向多维数组的指针和指针变量 从本质上说,多维数组的指针与一维数组的指针相同,但在概念上和使用上,多维数组的指针要复杂些。 以二维数组的指针为例:
●二维数组的地址 :一维: a , &a[i], a+i 二维:
a , &a[i][j], a+i ( 行指针 ) , a[i]( 特殊的一维数组元素,列指针 ) , a[i]+j
3 5 8 7
2 4 6 9
1 6 0 4
int a[3][4]
1000
1008
1016
a+0
a+1
a+2
a[0]
a[1]
a[2]
讨论以下用法的效果:for(i=0;i<3;i++) scanf(“%d”,a+i);输入数据: 1 2 3
讨论以下用法的效果:for(i=0;i<3;i++) scanf(“%d”,a[1]+i);输入数据: 1 2 3
讨论以下用法的效果:for(i=0;i<3;i++) scanf(“%d”,a[1]+i+2);输入数据: 1 2 3
讨论以下用法的效果:for(i=0;i<3;i++) scanf(“%d”,a+i+1);输入数据: 1 2 3
注意:指针运算符 * 作用在行指针上的结果仍是指针 ----列指针 ; * 作用在列指针上的结果 --- 具体元素。
*(a+0) , *(a+1) , *(a+2) —— 仍是地址。 *(a+i)
*(a[0]) , *(a[1]) , *(a[1]) —— 具体元素值。 *(a[i])
*(a+i)+j 也是地址,但要区别:(a+i)+j—— 行指针 (a+1)+1 ?
*(a+i)+j——列指针 *(a+1)+1 ?
1000
1008
1016
a+0
a+1
a+2
a[0]
a[1]
a[2]
*(a+1)
如果要通过 a+i 形式的地址访问数组元素的具体内容,则:*(*(a+i)) 或 *(*(a+i)+j)
如: *(*(a+1)) —— a[1][0]
*(*(a+1)+2) —— a[1][2]
讨论:*(a+2)
*(*(a+1)+3)
*(a[1]+1)
*(*(a+1)+5)
例:求数组 a的所有元素之和。 可有多种用法:
for(i=0;i<3;i++) for(i=0;i<3;i++)for(j=0;j<4;j++) for(j=0;j<4;j++)s+=a[i][j]; s+=*(a[i]+j);
for(i=0;i<3;i++)for(j=0;j<4;j++) s+=*(*(a+i)+j);
( ●指向二维数组的指针变量 同样可使一个指针变量 p指向二维数组 a,再通过 p访问数组元素。main(){ int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; int i,k,*p; p=a; k=*p;? k=*(p+2);? for(p=a;p<a+2;p++)printf(“%3d”,*p); ?}
1 2 3 45 6 7 89 10 11 12k=p[1][2]; 不合法
k=*(*(p+1)+2);k=p[1*4+2]; 合法k=*(p+1*4+2);
例:求矩阵的上三角元素之和。main()
{ int a[3][4],*p,i,j,s=0;
输入 a
p=a;
for(i=0;i<3;i++)
for(j=i;j<4;j++)
s+=p[i*4+j]; 或 s+=*(p+4*i+j)
printf(“\n %d”,s);
}
●指向由 m个元素组成的一维数组的指针变量 可以这样定义一个指针变量: int (*p)[4] 表示 p 为指向由 4 个元素组成的行指针变量。 当 p=a时,可通过 p引用 a[i][j]: p[i][j] 或 *(*(p+i)+j)
例:求矩阵的上三角元素之和。 main(){ int a[3][4],(*p)[4],i,j,s=0; 输入 a p=a; for(i=0;i<3;i++) for(j=i;j<4;j++) s+=p[i][j]; 或 s+=*(*(p+i)+j) printf(“\n %d”,s);}
●多维数组的指针作函数参数 用于接受实参数组地址的形参可用两种:行指针和列指针。 以方阵转置为例: void at(int (*a)[3]) 用行指针 { int i,j,t; 缺点:不通用 for(i=0;i<3;i++) for(j=i+1;j<3;j++) { t=a[i][j]; a[i][j]=a[j][i]; a[j][i]=t; } }
用列指针: void at(int *a,int n) { int i,j,t; for(i=0;i<n;i++) for(j=i+1;j<n;j++) { t=a[i*n+j]; a[i*n+j]=a[j*n+i]; a[j*n+i]=t; } } 优点:通用 在编通用函数时,一般使用列指针。
10. 4 字符串的指针和指向字符串的指针变量
■ 字符串的表示形式 可用两种方法访问字符串: ①用字符数组存放字符串 ②用字符指针指向一个字符串
main(){ char c[5]=”abc”; 定义字符数组,并将字符串存入 char *p1=c,*p=”abc”; 定义指针变量,指向字符串 printf(“%s”,c); 通过数组名访问字符串
printf(“%s”,p); 通过指针变量访问字符串 printf(“%-c”,*(p+3)); 通过指针变量访问字符
} c
a b c \0 a b c \0
1000
1000p
与其它一维数组的指针相比,字符串的指针有其独特之处: ① 可以通过指针对字符串进行整体访问。 ②对字符串的操作依赖于结束符。 ③可以整体赋初值。 ④有各种字符串处理函数。
本节重点掌握: ①通过数组和通过指针操作字符串的基本方法。 ②常用的字符串处理方法。
例:字符串拷贝操作。main()
{ char a[]=”abcdef”,b[20]; int i;
for(i=0; *(a+i)!=’\0’; i++) *(b+i)=*(a+i);
*(b+i)=’\0’; printf(“%s”,b);
}
main()
{ char a[]=”abcdef”,b[20],*p1, *p2;
p1=a; p2=b;
for( ; *p1!=’\0’;p1++,p2++) *p2=*p1;
*p2=’\0’;
printf(“%s”,b);
}
用指针变量处理
将拷贝操作编成一函数:void copy_string(char *from,char *to)
{
for(; *from; from++,to++) *to=*from;
*to=0;
}
还可以改成:void copy_string(char *from,char *to)
{
for(; *from;) *to++=*from++;
*to=0;
}
字符串合并函数:void append_string(char *from,char *to){ for(;*to; to++); for(; *from;) *to++=*from++; *to=0; }
阅读程序:void f (char *c) main()
{ {
c+=2; char c[20]=”abcdef”;
(*c)++; f(c+1);
c++; *c=0; printf(“%s”,c);
} }
■内存空间的动态分配 在程序设计中,对于要处理的批量数据,我们往往是选用数组作为存放这些数据的数据结构,然而,数组有一个明显的缺点,就是在定义数组时,其长度必须是常值,无法根据需要动态地定义。这样,在很多情况下,不是定义的数组长度不够,就是定义太长以至于浪费。 采用动态分配可以克服这一缺点,并且可以随时释放。
动态分配内存空间步骤: ①定义一指针变量。 ②申请一片内存空间,并将其首地址赋给指针变量。此时便可通过指针变量访问这片内存。 ③用完后释放这片内存空间。 int *p; p=malloc(byte); …… free(p);
以上函数的原形在 stdio.h中。
p
例:对 n个学生的分数排序后输出。 #include “stdio.h”void sort(int *a, int n) { ┈ }main(){ int *a,j,n; scanf(“%d”,&n); a=malloc(n*sizeof(int)); if(!a) exit(0); for(j=0;j<n;j++) scanf(“%d”,a+j); sort(a,n); for(j=0;j<n;j++) printf(“%5d”,a[j]); free(a);}
10. 5 函数的指针和指向函数的指针变量■用函数指针变量调用函数可以用指针变量指向一个函数,一个函数在编译时被分配给一个入口地址,这个入口地址就称为函数的指针。可以用指针变量指向函数,然后通过该指针变量调用此函数。
int max(int x, int y)
{ int z; if(x>y) z=x; else z=y; return(z);}
main()
{ int (*p)(); 定义指向函数的指针变量 p int a,b,c;
p=max; 将 p 指向函数 max scanf(“%d%d”,&a,&b);
c=(*p)(a,b); 通过 p调用函数 max 等效于 c=max(a,b); printf(“\n %d”,c);
}
■把指向函数的指针变量作为函数参数 指向函数的指针变量最常见的用途是把它作为函数的参数,用于接受主调函数传来的某一函数的入口地址,从而在被调函数中可以通过该指针变量调用它所指向的函数,这样,被调函数中就可实现非固定函数的调用,以达到编写通用函数的目的。 例:用矩形法编写一个通用的求定积分的函数。关键问题:如何处理被积函数是未知的。
double intgral (double a,double b,int n, double (*f)( ) ){ int i; double h,x,y,s=0; h=(b-a)/n; for(i=1;i<=n;i++) { x=a+(i-1)*h; y=(*f)(x); s+=h*y; } return(s);}
double f1(double x){ return(3*x*x+2*x-1);}main(){ double s; s=integral(1.0,2.0,100,f1);}
第十一章 结构体 11.1 概述 在实际应用中,有不少应用问题如果只采用已学的变量和数组作为数据结构显得很不方便。 例:输入 100 个学生的学号、姓名和考试成绩,编写程序找出高分者和低分者。 用变量和数组作数据结构可编写程序如下:
main()
{ int i, num, maxnum, minnum;
char name[20], maxname[20], minname[20];
int score, maxscore, minscore;
maxscore=0; minscore=100;
for(i=1; i<=100; i++)
{ scanf(%d%s%d”,&num,name,&score);
if(score>maxscore)
{ maxscore=score; maxnum=num; strcpy(maxname,name); }
if(score<minscore)
{minscore=score; minnum=num; strcpy(minname,name);}
}
输出}
明显缺点: ① 变量过多,同一学生的各个数据无联系,没有整体概念,不便管理。 ②操作不便(如更新过程)。 显然,选用一种能把一个学生的数据构造成一个整体的构造型数据结构更合适,但不能是数组。 对于这种情况,可以将一个学生的数据定义为一个结构体类型:
struct student 类型名{
int num; 成员表 char name[20];
int score;
};
定义了一个结构体类型,它包含三个成员。
11.2 定义结构体类型变量的方法 前面定义的结构体类型只是一种“模型”,还必须定义结构体变量后才能存放数据。 定义结构体变量有三种方法:
1 、先定义结构体类型再定义结构体变量 定义了结构体类型后: struct student st, stmax, stmin;
类型符 变量名 定义了三个结构体变量,每个变量包含三个成员,每个变量可存放一个学生的数据。
2 、在定义结构体类型的同时定义结构体变量 struct student
{
int num;
char name[20];
int score;
}st, stmax, stmin;
3 、直接定义结构体类型变量 struct 不出现类型名 { int num; char name[20]; int score; }st, stmax, stmin;
常用第一种方法
说明: ①类型与变量不同,只对变量分配空间与操作。 ②对成员可以单独使用,相当于普通变量。 ③成员也可以是一个结构体变量。 struct date struct student
{ int month; { int num;
int day; char name[20];
int year; struct date birthday;
}; }st1, st2;
④成员名可以与程序中的变量名相同,两者代表不同的对象。
11.3 结构体变量的引用 ■成员引用 可以对成员单独引用,形式为: 结构体变量名 . 成员名 成员运算符
st.num=1001;st.score=90;strcpy(st.name,”Li”);
printf(“%d%s%d”,st.num,st.name,st.score);
scanf(“%d%s%d”,&st.num,st.name,&st.score) ; 可以引用成员的地址
如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低一级的成员,只能对最低级的成员进行存取与运算。
st1.birthday.year=1960;
st1.birthday.month=5;
st1.birthday.day=15;
■整体引用 可以对结构体变量进行整体赋值: stmax=st; 将 st 中的所有内容赋值给 stmax 。
对结构体变量的整体操作只限于赋值操作和参数传递,而且要求类型一致。不能对结构体变量进行整体输入输出。
结构体应用举例: 编写程序输入 100 个学生的学号、姓名和考试成绩,找出高分者和低分者。
struct student
{ int num;
char name[20];
int score;
};
main()
{ int i; struct student st,stmax,stmin;
stmax.score=0; stmin.score=100;
for(i=1;i<=100;i++)
{ scanf(“%d%s%d”,&st.num,st.name,&st.score);
if(st.score>stmax.score) stmax=st;
if(st.score<stmin.score) stmin=st;
}
printf(“\n%5d%15s%5d”,stmax.num,stmax.name,
stmax.score);
printf(“\n%5d%15s%5d”,stmin.num,stmin.name,
stmin.score);
}
11.4 结构体变量的初始化 对结构体变量可以在定义时指定初始值 struct student
{
int num;
char name[20];
int score;
}st={1001,”wang”,95};
11.5 结构体数组 可以定义结构体数组来存放批量数据。 ■结构体数组的定义 struct student
{
int num;
char name[20];
int score;
} ; struct student a[100];
定义 a 数组,可以存放 100 个学生的数据。 a 数组的每个元素又是一个结构体变量。
■结构体数组的初始化 在定义结构体数组的同时指定初值。 struct student { int num; char name[20]; int score; } ; struct student a[2]=
{{1001,”LiLi”,85},{1002,”wang”,90}};
或: struct student
{
int num;
char name[20];
int score;
} a[2]=
{{1001,”LiLi”,85},{1002,”wang”,90}};
■结构体数组元素的引用 成员引用: a[0].num=1001; strcpy(a[0].name,”wang”); a[0].score=85; 整体引用: a[1]=a[0]; 与普通数组元素的引用相同
■结构体数组的应用 输入 100 个学生的学号、姓名和考试成绩,然后按从高分到低分的顺序排列后输出。
struct student
{ int num;
char name[20];
int score;
};
main()
{ int i, j;
struct student a[100], t;
for(i=0;i<100;i++)
scanf(“%d%s%d”,&a[i].num,a[i].name,
&a[i].score);
for(i=0;i<99;i++)
for(j=i+1; j<100; j++)
if(a[i].score<a[j].score)
{t=a[i]; a[i]=a[j]; a[j]=t;} 整体引用 for(i=0;i<100;i++)
printf((“\n%5d%15s%5d”,a[i].num,
a[i].name,a[i].score);
}
例:( p266例 11.2 ) 对候选人得票的统计程序。设有三个候选人,每次输入一个得票候选人的名字,要求最后输出各候选人的得票结果。#include “string.h”struct person{ char name[20]; int count; }leader[3]={“Li”,0,”zhang”,0,”wang”,0};
main()
{ int i, j ;
char leader_name[20];
for(i=1;i<=100;i++)
{ scanf(“%s”,leader_name);
for(j=0;j<3;j++)
if(strcmp(leader_name,leader[j].name)==0)
leader[j].count++;
}
printf(“\n”);
for(i=0;i<3;i++)
printf(“\n%15s%5d”,
leader[i].name,leader[i].count);
}
11.6 指向结构体类型数据的指针 ■指向结构体类型变量的指针 struct student st, st1 ; struct student *p; p=&st; 定义指向结构
体类型数据的指针变量 p
通过指针变量引用结构体变量:
①成员引用(*p).num=1001; 或 p->num=1001;
(*p).score=85; 或 p->score=85;
strcpy((*p).name,”wang”);
或 strcpy
(p->name,”wang”);
②整体引用 st1=*p; 等效于 st1=st;
■指向结构体数组的指针 struct student a[100] ; struct student *p;
p=a;
通过指针变量引用结构体数组元素: ①成员引用 (*p).num=1001; 或 p->num=1001;
(*p).score=85; 或 p->score=85;
strcpy((*p).name,”wang”);
或 strcpy(p->name,”wang”);
一般地:(*(p+i)).num=1001; 或 (p+i)->num=1001;
(*(p+i)).score=85; 或 (p+i)->score=85;
strcpy((*(p+i)).name,”wang”);
或 strcpy((p+i)->name,”wang”);
也可以用下标法: p[i].num=1001;
②整体引用*(p+1)=*(p+0); 或 p[1]=p[0];
■用结构体变量和指向结构体的指针作函数参数 用结构体变量作函数参数时,对应的实参应该是同类型的结构体变量(或数组元素),参数传递是“值传递”。 用指向结构体的指针作函数参数时,对应的实参应该是同类型的结构体变量的地址(或数组的地址),参数传递是“地址传递”。
main()
{ struct student st={1001,”LiLi”,70};
f(st);
printf(“\n %5d%10s%5d”,
st.num,st.name,st.score);
}
f(struct student a)
{ a.score=90;
printf(“\n %5d%10s%5d”,
a.num,a.name,a.score);
}
1001LiLi70
st
1001LiLi70
a
90
main()
{ struct student st={1001,”LiLi”,70};
f(&st);
printf(“\n %5d%10s%5d”,st.num,st.name,st.score);
}
f(struct student *a)
{ a->score=90;
printf(“\n%5d%10s%5d”,a->num,a->name,a>score);
}
通过指针变量 a可以访问它所指向的结构体。
1001LiLi70
st
2000
2000
a
90
11.7 用指针处理链表 ■链表概述 链表是一种重要的数据结构─动态数据结构。 以具体例子来说明链表的概念及其应用:例:选择合适的数据结构来存放一批学生的学号及考试成绩,以便进一步处理。 由于学生人数未知,用静态数据结构不合适。用链表处理较恰当。
用链表处理该问题的基本思路: 将各学生的数据进行离散存放,来一个学生就分配一小块内存(结点)。并将各结点用指针依次连接起来─链表。
每结点应包含下一结点的开始地址。 最后一个结点中的指针为空。 链头指针指向第一个结点 ,是访问链表的重要依据。 这样的链表称单向链表。
head 学号成绩指针
学号成绩指针
学号成绩指针
学号成绩指针
学号成绩NULL
一个结点可用如下结构体描述:typedef struct student
{
int num; 学号 int score; 成绩 struct student *next; 下一结点的首地址} STU;
typedef : 自定义类型符(见 11.10 )
■单向链表的建立 ①输入一个学生的数据。 ②分配结点空间,数据存入。 ③将该结点的首地址赋给上一结点的 next ,若该结点是第一个结点,则赋给头指针。 ④将该结点的 next置为空,表示该结点为当前的最后结点。
head 学号成绩next
学号成绩next
学号成绩next
学号成绩next
学号成绩NULL
STU *creat()
{ STU st,*p0=NULL,*p,*head=NULL;
while(1)
{ scanf("%d%d",&st.num,&st.score);
if(st.num<0) break;
p=malloc(sizeof(STU)); *p=st;
(*p).next=NULL;
if(p0==NULL) head=p; p0 为前一结点的指针 else (*p0).next=p;
p0=p;
}
return head;
}
head 学号成绩next
学号成绩next
学号成绩NULL
■单向链表的访问 以输出为例 ①通过头指针找到第一个结点 . ② 输出当前结点的内容,并通过 next找到后继结点,┄┄,直到 next 为空 .
void output(STU *head)
{
STU *p=head;
while(p)
{
printf("\n %d %d",(*p).num,(*p).score);
p=(*p).next;
}
}
head 学号成绩next
学号成绩next
学号成绩NULL
学号成绩next
■删除结点操作 ①按链表的访问方法找到相应结点。 ② 若该结点是第一个结点,则将后继结点指针赋给头指针。 若该结点是最后一个结点,则将前缀结点的 next置为空。 若该结点是中间结点,则将后继结点指针赋给前缀结点的 next 。 ③释放该结点所占的内存单元。
head 学号成绩next
学号成绩next
学号成绩NULL
STU *delete(STU *head,int number)
{ STU *p =head,*p0=NULL;
while(p)
{
if((*p).num==number)
{
if(p==head) head=(*p).next;
else if((*p).next==NULL) (*p0).next=NULL;
else (*p0).next=(*p).next;
free(p); break;
}else {p0=p; p=(*p).next;}
} return head;
}
假定要删除某一指定学号的结点
■插入操作
假定将结点 p 插入到结点 p0 的后面 , 则插入操作的关键为: p->next=p0->next; p0->next=p;
head 学号成绩next
学号成绩next
学号成绩next
学号成绩NULL
第十三章 文件3.1 文件概述
■文件概念 所谓文件就是:存储在外部介质上 的信息集合。 根据存储的介质不同可分为: 磁盘文件、磁带文件等。 根据内容的不同可分为: 程序文件、数据文件等。
■使用文件输入输出的必要性
这里主要讨论数据文件的输入输出,即如何将文件中的数据“输入”到程序的数据结构中,如何将程序的数据结构中的数据“输出”到文件中。
以往的输入输出方法: 键盘输入 ,屏幕输出。
这种方法不适用于数据量大的情况。
键盘输入和屏幕输出例:main()
{ int i, a[1000];
for(i=0;i<1000;i++)
scanf(“%d”,a+i);
┊ for(i=0;i<1000;i++)
printf(“%5d”,a[i]);
}
缺 点:
① 可能出现重复输入。② 输出的数据不能保存,不便于进一步使 用。
采用文件输入输出可以克服这些缺点。
main(){ int i, a[1000]; for(i=0;i<1000;i++) scanf(“%d”,a+i); ┊ for(i=0;i<1000;i++) printf(“%5d”,a[i]);}
}
文件
文件
■ C文件分类 按在磁盘上存储的形式不同,可分为: 文本文件: 以 ASCII字符存放—可见、可编辑、占空间大。 二进制文件:以二进制形式存放—不可见、不可编辑、占空间小。 使用时可根据需要选择。
13.2 文件类型指针 每个被使用的文件都在内存中开辟一个区,用来存放文件的有关信息。这些信息保存在一个 FILE类型的结构体变量中。
若 FILE *fp; 则 fp 就称为指向文件类型的指针变量。访问文件通过文件指针进行。
FILE结构体类型是由系统定义的。具体定义如下: typedef struct { short level; 缓冲区“满”或“空”
的程度 unsigned flags; 文件状态标志 char fd; 文件描述符 unsigned char hold; 如无缓冲区不读取字符 short bsize; 缓冲区的大小 unsigned char *buffer; 缓冲区的位置 unsigned char *curp; 当前读写指针 unsigned istemp; 临时文件,指示器 short token; 用于有效性检验 }FILE;
13.3 文件的打开与关闭 对文件的读写之前应“打开”该文件。 使用结束后“关闭”此文件。 ■文件的打开( fopen 函数) 用 fopen 函数实现对文件的打开。 fopen 函数调用的一般形式: FILE *fp; fp=fopen(文件名,读写方式 );
例如: fp=fopen(“a1.txt”, ”r”); 以只读方式打开文件 a1.txt 。
fopen 函数返回指向 a1.txt 文件的指针,即 fp 是指向 a1.txt 文件的指针变量,往后就可以通过 fp访问 a1.txt文件。
文件读写方式: “r” 按只读方式打开一个文本文件
“w” 按只写方式打开一个文本文件
“a” 按追加方式打开一个文本文件
“rb” 按只读方式打开一个二进制文件
“wb” 按只写方式打开一个二进制文件
“ab” 按追加方式打开一个二进制文件
“r+” 按读写方式打开一个文本文件
“w+” 按读写方式建立一个新的文本文件
“a+” 按读写方式打开一个文本文件
“rb+” 按读写方式打开一个二进制文件
“wb+” 按读写方式建立一个新的二进制文
“ab+” 按读写方式打开一个二进制文件
说明: ( 1 )不能用” r” 方式打开一个不存在的文 件,” r”方式只读不能写。 ( 2 )“ w” 方式只写不能读,具有建立和 覆盖功能。 ( 3 )调用 fopen 函数时,如果返回 NULL则 表示打开不成功。
■文件的关闭( fclose 函数)
在使用完一个文件后应用 fclose 函数关 闭文件,形式为:
fclose (文件指针);
如: fclose(fp); 关闭后 fp 不再指向该文件。
13.4 文件的读写 文件打开后,就可以对它进行读写了。
■文本文件的读写 即如何将以文本方式存放的文件输入到程序的数据结构中。如何将程序的数据结构中的数据以文本方式输出到文件中。
用于对文本文件读写的函数有: fscanf fprintf
fgetc, getc fputc, putc
fgets fputs
以例子说明 fscanf 和 fprintf 的使用。
例:已知文本文件 f1.txt 中存放有 100 个学生的分数,要求读入这些数据,并按从高到低的顺序排序后输出到另一文件中。
#include “stdio.h”void sort(int *a,int n){ ……}main(){ int i,a[100];FILE *fp; fp=fopen(“f1.txt”, “r”); if(fp==NULL) exit(0);
定义一个指向文件的指
针变量
打开文件,使 fp指向文件 f1.txt
for(i=0;i<100;i++)
fscanf(fp,”%d”,a+i);
fclose(fp);
sort(a,100);
fp=fopen(“f2.txt”, “w”);
for(i=0;i<100;i++) fprintf(fp,”%4d”,a[i]);
fclose(fp);
}
从 fp所指的文件中读数据
关闭 fp所指的文件
注意:文本文件的输入格式要与 文件中的数据格式匹配。
■二进制文件的读写 即如何将以二进制方式存放的文件输入到程序的数据结构中。 如何将数据结构中的数据以二进制方式输出到文件中。
读写函数: fread fwrite getw putw
例:将前例中的排序结果改用二进制方式输出到文件 f3.dat 中。
#include “stdio.h”void sort (int *a,int n){ ……}main(){ int i,a[100]; FILE *fp; fp= f open(“f1.txt”, “r”); if(fp==NULL) exit(0);
for(i=0;i<100;i++) fscanf(fp,”%d”,a+i);
fclose1(fp);
sort(a,100);
fp=fopen(“f3.dat”, “wb”);
fwrite(a, sizeof(int), 100, fp );
fclose(fp);
}数据的开始地址
数据的每一项的长度
数 据 的项数
文件的指针
如果要将二进制文件 f3.dat读到数组中,则有: #include “stdio.h”main(){ int a[100];FILE *fp;fp=fopen(“f3.dat”,“rb”);
if(fp==NULL) exit(0);fread(a,sizeof(int),100,fp);fclose(fp); ┊}
13.5 文件的定位 文件中有一个位置指针,指向当前读写位置。如果顺序读写一个文件,每次读写完一个字符后,该位置指针自动指向下一个字符位置。如果想改变这样的规律,强制使位置指针指向指定位置,可以用有关函数。
■rewind 函数 rewind 函数的作用是使位置指针重返回文件的开头。 例:对文本文件 f1.txt 中的 100 个分数求超过平均分的人数。
#include “stdio.h”main(){ int i,a,n=0; float aver=0;FILE *fp;fp=fopen(“f1.txt”,“r”);for(i=0;i<100;i++){ fscanf(fp,”%d”,&a); aver+=a;}
aver/=100;rewind(fp);for(i=0;i<100;i++){ fscanf(fp,”%d”,&score);if(score>aver) n++;} fclose(fp); printf(“\n %d”,n);}
■fseek函数和随机读写 使用 fseek函数可以将位置指针指向所需的位置。 fseek函数调用的一般形式:
fseek(文件指针,位移量,参考点) ;
以起始点为基准,向前移动的字节数
0 或 SEEK_SET 文件开始1 或 SEEK_CUR 当前位置2 或 SEEK_END 文件末尾
例:如果 fp 是指向一个存放 100 个整数的二进制文件,要读取第 50 个数到变量 n时:
fseek(fp,sizeof(int)*(50-1),SEEK_SET);
fread(&n, sizeof(int), 1,fp);
例:如果 fp 是指向一个存放 100 个整数的文本文件,并已知每个数按 3位数字的定长格式存放,要读取第 50 个数到变量 n时:
fseek ( fp, 3*(50-1), SEEK_SE );
fscanf ( fp, ”%3d”, &n);
若要从当前位置跳过 10个数后读取一个数:
fseek ( fp, 3*10, SEEK_CUR );
fscanf ( fp, ”%3d”, &n);
例:已知文本文件 f5.txt 中存放有 100 个学生的学号、姓名和考试成绩;要求从键盘输入任一学号,检索出相应学生的数据。说明:( 1 )文件 f5.txt 中每行为一个学生的数据,按
定长格式存放,依次为:学号(整数,占 5 格)、姓名(占 10 格)、成绩(整数,占 4 格)。
( 2)按学号从小到大的顺序连号存放,起始学号为 1001 。
#include “stdio.h”typedef struct{ int num; char name[20]; int score; }STU;main(){ int no; STU st; FILE *fp;
fp=fopen ( “f5.txt” , ”r” );
scanf ( “%d” , &no );
fseek ( fp, (no-1001)*19, 0 );
fscanf ( fp, ”%5d%10s%4d” ,&st.num
,st.name, &st.score );
printf (“\n%5d%10s%4d” , st.num ,
st.name, st.score ) ;
fclose ( fp ) ;
}
1001 LiLi 901002 WangPing 1001003 HuHeng 75 ┊
如果是二进制文 件呢?
上例的检索方法称为“定位检索”。如果是非定长格式,则需要用“遍历检索”
。 while ( !feof ( fp ) ) { fscanf ( fp, ”%d%s%d”, &st.num, st.name, &st.score ); if ( st.num==no ) { printf ( “\n%5d%10s%4d”, st.num, st.name, st.score ); break; } } 速度慢,但不受限制
综合例: 已知文本文件 f1.txt 中存放有武汉市所有公民的有关性别和年龄的数据,请编写程序分别找出其中 10 名男寿星和 10 名女寿星,并将 20 名寿星的数据以文本文件的方式存入到文件 f2.txt 中(先男后女)。
说明:
① 文件 f1.txt 中每行为一个公民的数据,共有 3项,依次为:姓名(不超过 10个字符)、性别( 0表示男, 1表示女)和年龄(整数),项间以空格分隔。
② 未给出公民个数,将文件中的数据读完为止。
算法思想:
开辟一个存放 20 名寿星数据的结果表 a (结构体数组),然后逐个读取公民数据,每读取一个就向 a 中“判断插入”一个,男性公民往前段插,女性公民往后段插。
读一个公民的数据到 p
读 完 否?
N
Y
Wanghao 0 100Liming 0 98┊wudan 1 99xiaofang 1 95┊
男性插入
女性插入
寿星表 a
#include <stdio.h>
typedef struct
{ char name[10];
int sex;
int age;
} PEP;
插入函数 ,将一个公民的数据插入到寿星表
void insert (PEP *a, int n, PEP t )
{ int i,j;
if ( t.age < a[n-1].age ) return ;
for ( i =0; i<n; i++) if (t.age>a[i].age) break;
for ( j=n-1; j>i; j--) a[j]=a[j-1]; 移位 a[i]=t; 插入}
main(){ int j; PEP p,a[20]; FILE *fp; fp=fopen (“f1.txt”,”r”); if(!fp) exit(0); for (j=0;j<20;j++) a[j].age=0; while( !feof ( fp ) ) { fscanf ( fp, ”%s%d%d”, p.name, &p.sex, &p.age ); insert (a+10*p.sex,10,p); } fclose(fp);
fp=fopen(“f2.txt”,”w”);
for(j=0;j<20;j++)
fprintf(fp,”\n%15s%2d%5d”,
a[j].name, a[j].sex,a[j].age );
fclose ( fp );
}