程序设计的基本概念#
算法的特性#
- 有穷性:算法必须在执行有限的步骤后终止,不会无限循环或进入死循环
- 确定性:算法的每个步骤必须明确定义,没有歧义。相同输入应产生相同的输出
- 可执行性:算法中的每个步骤都必须能够被执行,不会包含无法实现的操作
- 有零个或多个输入:算法可以接受零个或多个输入参数,这些参数是问题的输入数据
- 有一个或多个输出:算法产生一个或多个输出结果,这是问题的解决方案
三种基本结构#
三种基本结构组成的算法可以解决任何复杂的问题
- 顺序结构:程序中的语句按顺序执行,从上到下,每个语句都执行一次
- 选择结构:选择结构允许根据条件的真假来执行不同的代码块。这包括
if
语句和switch
语句等。例如,使用if
语句可以根据条件执行不同的代码块,从而实现分支逻辑if (condition) { // 代码块1 } else { // 代码块2 }
- 循环结构:循环结构允许多次执行相同的代码块,直到满足特定条件为止。这可以通过
for
、while
和do-while
等循环语句来实现while (condition) { // 循环执行的代码 } for (int i = 0; i < n; i++) { // 循环执行的代码 }
第二章 C 程序设计的初步认识#
2.1 简单 C 语言程序的构成和格式#
/*求矩形面积*/
#include "stdio.h"
int main() {
double a, b, area;
a = 1.2; /*将矩形的两条边长分别赋给a和b*/
b = 3.6;
area = a * b; /*计算矩形的面积并储存到变量area中*/
printf("a=%f,b=%f,area=%f\n", a, b, area);
}
- C 语言规定必须用 main 作为主函数名,每一个可执行的 C 程序都必须有且只有一个主函数
- C 程序中的每一条执行语句都必须用分号 “;” 结束
- 注释内容必须放在符号 “/*” 和 “*/” 之间,在注释之间不可以再嵌套 “/*” 和 “*/”
2.2 标识符、常量和变量#
2.2.1 标识符#
- 由字母、数字和下划线组成,并且第一个字符必须为字母或下划线
- 类别
- 关键字
- 预定义标识符
- 用户标识符
2.2.2 常量#
- 程序运行中,其值不能被改变的量
- 分类
- 整型常量:只用数字表示,不带小数点
- 短整型常量
- 长整型常量
- 实型常量:必须带小数点的数
- 字符常量
- 字符串常量
- 整型常量:只用数字表示,不带小数点
2.2.3 符号常量#
/*计算圆面积*/
#include "stdio.h"
#define PI 3.14159 /*定义符号名PI为3.14159*/
int main() {
double r, s;
r = 5.0;
s = PI * r * r;
printf("s=%f\n", s);
}
2.2.4 变量#
- 变量指程序运行过程中其值可以改变的量
2.3 整型数据#
2.3.1 整型常量#
- 整型常量可以用十进制、八进制和十六进制等形式表示
- 八进制数:开头必须是数字 “0”
- 十六进制数:使用 “0x” 或 “0X” 开头;十六进制数中的字母 a、b、c、d、e、f 既可以小写也可以大写
- 在 C 程序中,只有十进制数可以是负数,而八进制和十六进制数只能是整数
2.3.2 整型变量#
- 基本型的整型变量用类型名关键字 int 进行定义
int k;
int i, j, k;
int i = 1, j = 0, k = 2;
2.3.3 整型数据的分类#
- 短整型(short int)
- 基本整型(int)
- 长整型(long int)
- 无符号型(unsigned):无符号整数在数的末尾加上字母后缀 u 或 U;若是长整型无符号整型常量,加上后缀 lu 或 LU
若不指定变量为无符号型,则变量隐含为有符号型(signed)
2.3.4 整数在内存中的储存形式#
- 通常把一个字节的最右边一位称为最低位,最左边一位称为最高位。对于一个有符号整数,其中最高位用来存放整数的符号,称为符号位。若是正整数,最高位放置 0;若是负整数,最高位放置 1
- 负整数在内存中以 “补码” 形式存放
取某个二进制数的补码,例如 10000101(十进制数 - 5)的补码,步骤如下:
- 求原码的反码。把原码除符号位之外的二进制码按位取反,得 11111010
- 把所得的反码加 1,得到原码的补码。得 11111011
把内存中以补码形式存放的二进制码转化成十进制的负整数
- 先对除符号位之外的各位取反
- 将所得二进制数转换成十进制数
- 对所求得的数再减 1
2.4 实型数据#
2.4.1 实型常量#
- 小数形式:必须要有小数点
2.4.2 实型变量#
- 单精度型(float)
- 定义:
float a,b,c;
- 占 4 字节的存储单元
- 定义:
- 双精度型(double)
- 定义:
double x,y,z;
- 占 8 字节的存储单元
- 定义:
2.5 算数表达式#
2.5.1 基本的算数运算符#
- 加(+)
- 减(-)
- 乘(*)
- 除(/)
- 求余(%):运算对象只能是整型
- 这些运算符需要两个运算对象,称为双目运算符;
- “+” 和 “-” 也可用作单目运算符,运算符必须出现在运算数的左边
- 如果双面运算符两边运算数的类型一致,则所得结果的类型与运算数的类型一致
- 如果双目运算符两边运算数的类型不一致,系统将自动进行类型转换,使运算符两边的类型达到一致后再进行运算
- 在 C 语言中,所有实型数的运算均以双精度方式进行
2.5.2 运算符的优先级、结合性和算数表达式#
-
算数运算符的优先级
-
算数运算符和圆括号的结合性
- 以上运算符中,只有单目运算符 “+” 和 “-” 的结合性是从右到左的,其余运算符的结合性都是从左到右
-
算数表达式
- 定义:用算术运算符和一对圆括号将运算符(或称操作数)连接起来的、符合 C 语言语法的表达式
- 运算对象可以是常量、变量和函数等
2.5.3 强制类型转换表达式#
- 格式:
(类型名)(表达式)
(类型名)
称为强制类型转换运算符
2.6 赋值表达式#
2.6.1 赋值运算符和赋值表达式#
- 格式:
变量名=表达式
- 赋值运算符的左侧只能是变量,不能是常量或表达式
- C 语言规定最左边变量中所得到的新值就是赋值表达式的值
2.6.2 复合赋值表达式#
- 定义:在赋值运算符之前加上其他运算符
2.6.3 赋值运算中的类型转换#
- 如果赋值运算符两侧的数据类型不一致,在赋值前,系统将自动先把右侧表达式求得的数值按赋值号左边变量的类型进行转换
- 在 C 语言的表达式(不包括赋值表达式)中的转换规则
- 一个短整型,一个长整型 $ 短整型 \to 长整型 $
- 一个是有符号整型,一个是无符号整型 $ 有符号整型 \to 无符号整型 $
2.7 自加、自减运算符和逗号#
2.7.1 自加运算符 “++” 和自减运算符 “--”#
- “++” 和 “--” 都是单目运算符,不能给常量或表达式赋值
- 既可以前缀形式出现,也可以后缀形式出现
- 对于变量来说自增或自减 1;对于表达式来说,前置先自增后使用变量值,后置先使用变量值再自增
- 结合方向:从右至左
2.7.2 逗号运算符和逗号表达式#
- 逗号运算符结合方向:从左至右
- 逗号运算符优先级最低
第三章 顺序结构#
3.1 赋值语句#
- 赋值语句(表达式语句):在赋值表达式的尾部加上 “;”
3.2 数据输出#
3.2.1 printf 函数的一般调用形式#
- 调用形式:
printf(格式控制,输出项1,输出项2,···)
printf("a=%d,b=%d", a, b);
3.2.2 printf 函数中常用的格式说明#
每个格式说明都必须用 “%” 开头,以一个格式字符作为结束,在此之间可以根据需要插入 “宽度说明”、左对齐符号 “-”、前导零符号 “0”
- 格式字符
格式字符 | 说明 |
---|---|
c | 输出一个字符 |
d 或 i | 输出带符号的十进制整型数。% ld 为长整型,% hd 为短整型,% I64d 为 64 位长整数 |
o | 以八进制格式输出整型数。%#o 加先导 0 |
x 或 X | 以十六进制格式输出整型数。%#x 或 %#X 输出带先导 0x 或 0X 的十六进制数 |
u | 以无符号十进制形式输出整型数 |
f | 以带小数点的数学形式输出浮点数(单精度和双精度数) |
e 或 E | 以指数形式输出浮点是(单精度和双精度数) |
g 或 G | 有系统决定采用 % f 还是采用 % e(或 % E)格式输出,以使输出宽度最小 |
s | 输出一个字符串,直到遇到 “\0” |
p | 输出变量的内存地址 |
% | 输出一个 % |
- 输出数据所占宽度说明
- 在 % 和格式字符之间插入一个整数常量来指定输出的宽度。如果指定的宽度超过输出数据的实际宽度,输出时将会右对齐,左边补上空格
- 对于 float 和 double 类型的实数,可以用 “n1.n2” 的形式来指定输出宽度(n1 和 n2 分别代表一个整常数),其中 n1 指定输出数据的宽度(包括小数点),n2 指定小数点后小数位的位数,n2 也称精度
- 对于 f、e 或 E,当输出数据的小数位数多于 n2 位时,截去右边多余小数,并对截去部分的第一位小数做四舍五入处理;当输出数据的小数位少于 n2 时,在小数最右边补 0
- 也可用 “.n2” 格式,不指定总宽度;如果指定 “n1.0” 或 “.0” 格式,则不输出小数点和小数部分
- 对于 g 或 G,宽度用来指定输出的有效数字位数。默认 6 位有效数字
- 输出数据左对齐:在 “%” 和宽度之间加 “-”
- 使输出数据总带 +/-:在 “%” 和格式字符间加 “+”
3.2.3 使用 printf 函数时的注意事项#
- 调用函数 printf 时,其参数是从右至左进行处理的
- 输出数据的域宽可以改变
printf("%*d",m,i); /*将按照m指定的域宽输出i值,并不输出m的值*/
3.3 数据输入#
3.3.1 scanf 函数的一般调用形式#
- 调用形式:
scanf(格式控制,输入项1,输入项二,···)
scanf("%d%f%lf",&k,&a,&y);
3.3.2 scanf 函数中常用的格式说明#
格式字符 | 说明 |
---|---|
c | 输入一个字符 |
d | 输入带符号的十进制整型数 |
i | 输入整型数,整型数可以是带先导 0 的八进制数,也可以是带先导 0x(或 0X)的十六进制数 |
o | 以八进制格式输入整型数,可以带先导 0,也可以不带 |
x | 以十六进制格式输入整型数,可以带先导 0x 或 0X,也可以不带 |
u | 以无符号十进制形式输入整型数 |
f(lf) | 以带小数点的数学形式或指数形式输入浮点数(单精度数用 f,双精度数用 lf) |
e(le) | 同上 |
s | 输入一个字符串,知道遇到 “\0” |
- scanf 函数右返回值,其值就是本次 scanf 调用正确输入的数据项个数
3.3.3 通过 scanf 函数从键盘输入数据#
- 输入数值数据:在输入整数或实数这类数值型数据后,输入的数据之间必须用空格、回车符、制表符等间隔符隔开
- 跳过某个输入数据:在 % 和字符之间加入 “*”
3.4 复合语句和空语句#
3.4.1 复合语句#
- 语句形式:
{语句1 语句2 语句3 ··· 语句n}
3.4.2 空语句#
int main(){
; /*空语句*/
}
3.5 程序举例#
/*
* Created by binxin on 2023/8/6.
*
* 由终端输入两个整数给变量x和y;然后输出x和y;在交换x和y中的值后,再输出x和y
*/
#include "stdio.h"
int main() {
int x, y, t;
printf("输入整数x和y的值:");
scanf("%d%d", &x, &y);
printf("x=%d,y=%d\n", x, y);
t = x;
x = y;
y = t;
printf("x=%d,y=%d", x, y);
}
/*
* Created by binxin on 2023/8/6.
*
* 输入一个double类型的数,使该数保留小数点后两位,对第三位小数进行四舍五入处理,然后输出此数
*/
#include "stdio.h"
int main() {
double x;
printf("输入数据:");
scanf("%lf", &x);
printf("x=%f\n", x);
x = x * 100;
x = x + 0.5;
x = (int) x;
x = x / 100;
printf("x=%f", x);
}
第四章 选择结构#
4.1 关系运算和逻辑运算#
4.1.1 C 语言的逻辑值#
- 再 C 语言中,用非 0 表示 “真”,用 0 表示 “假”
4.1.2 关系运算符和关系表达式#
- 6 种关系运算符
- 小于 <
- 大于 >
- 等于 ==
- 小于或等于 <=
- 大于或等于 >=
- 不等于!=
- 由两个字符组成的运算符之间不允许由空格
- 关系运算符使双目运算符,具有自左至右的结合性
- 关系运算的值为 “逻辑值”,只有可能是整数 0 或 1
4.1.3 逻辑运算符和逻辑表达式#
4.1.3.1 C 语言的逻辑运算符#
- 三种逻辑运算符
- 逻辑与 &&
- 逻辑或 ||
- 逻辑非!
- && 和 || 为双目运算符,! 为单目运算符,出现在运算对象的左边。结合方向自左至右
4.1.3.2 逻辑表达式和逻辑表达式的值#
- 逻辑表达式的运算结果为 1 或 0
- 由 && 或 || 构成的逻辑表达式,在特定情况下会产生 “短路” 现象
4.2 if 语句和用 if 语句构成的选择结构#
4.2.1 if 语句#
- 基本形式
if(表达式) 语句 /\* 不含else子句的if语句 \*/
if(表达式) 语句1 /\* 含else子句的if语句 \*/
else 语句2
4.2.2 嵌套的 if 语句#
- 语句形式
if(表达式1){
if(表达式2) 语句1
} else {
语句2
}
- else 子句总是与前面最近的不带 else 的 if 相结合
4.3 条件表达式构成的选择结构#
- 条件运算符:
? :
,C 语言提供的唯一的三目运算符 - 右条件运算符构成的条件表达式:
表达式1 ? 表达式2 : 表达式3
- 运算功能:当表达式 1 的值为非零时,求出表达式 2 的值为整个表达式的值;当表达式 1 的值为零时,求出表达式 3 的值为整个表达式的值
4.4 switch 语句以及用 switch 语句和 break 语句构成的选择结构#
switch(表达式){
case 常量表达式1:语句1
case 常量表达式2:语句2
·
·
·
case 常量表达式n:语句n
default :语句n+1
}
- 关键字 case 和常量表达式之间一定要有空格
- 通常在 case 之后的语句最后加上 break 语句,每当执行到 break 语句时,立即跳出 switch 语句体
第五章 循环结构#
5.1 while 语句和用 while 语句构成的循环结构#
- 一般形式:
while(表达式) 循环体
- 表达式不能为空
5.2 do-while 语句和用 do-while 语句构成的循环结构#
- 一般形式
do
循环体
while(表达式);
- do 必须和 while 联合使用
5.3 for 语句和用 for 语句构成的循环结构#
- 一般形式:
for(表达式1;表达式2;表达式3) 循环体
- 执行过程
- 计算表达式 1
- 计算表达式 2. 若其值为非 0,转步骤 3;若其值为 0,转步骤 5
- 执行一次 for 循环体
- 计算表达式 3,转向步骤 2
- 结束循环
- for 语句中的表达式可以部分或全部省略,但两个 “;” 不可以省略
5.4 循环结构的嵌套#
- 嵌套循环:在一个循环体内又完整地包含了另一个循环
5.5 break 和 continue 语句在循环体中的作用#
- break 语句只能在循环体内和 switch 语句体内使用
- continue 语句的作用是跳过本次循环体中余下尚未执行的语句,立刻进行下一次的循环条件判定
5.6 程序举例#
/*
从输入的若干个正整数中选出最大值,用-1结束输入
*/
#include <stdio.h>
int main(){
int max, n;
do{
printf("输入数据,用-1结束输入:");
scanf("%d", &n);
if(max<n){
max = n;
}
} while (n != -1);
printf("max=%d", max);
}
/*
用迭代法求某数a的平方根。已知求平方根的迭代公式为x1=(x0+a/x0)/2
*/
#include <stdio.h>
#include <math.h>
int main(){
double x0, x1, a;
printf("输入数据:");
scanf("%lf", &a);
x0 = a / 2;
x1 = (x0 + a / x0) / 2;
do{
x0 = x1;
x1 = (x0 + a / x0) / 2;
} while (a > 0 && fabs(x1 - x0) > 1e-6);
printf("x=%f", x1);
}
第六章 字符型数据#
6.1 字符型常量#
6.1.1 字符常量#
- 单引号中的大写字母和小写字母代表不同的字符常量
- 单引号中的空格符也是一个字符常量
- 字符常量只能包含一个字符
- 字符常量只能用单引号括起来,不能用双引号括起来
- 字符常量在内存中占一个字节,存放的是字符的 ASCII 代码值
6.1.2 转义字符常量#
字符形式 | 功能 |
---|---|
\n | 回车换行 |
\t | 横向跳若干个(Tab 键) |
\v | 竖向跳格 |
\r | 回车符 |
\f | 换页符 |
\b | 退格符(Backspace 键) |
\ | 反斜杠字符 “\” |
' | 单引号字符 |
" | 双引号字符 |
\ddd | 三位八进制数代表的一个 ASCII 字符 |
\xhh | 二位十六进制数代表的一个 ASCII 字符 |
\0 | 空值,其 ASCII 码值为 0 |
- 转义字符常量只代表一个字符
- 反斜线后的八进制数可以不带 0 开头
- 在一对单引号内,可以用反斜线后跟一个十六进制数来表示一个 ASCII 字符
6.1.3 字符串常量#
- 定义:由双引号括起来的一串字符
- 在 C 语言中,系统在每个字符串的最后自动加入一个字符
'\0'
作为字符串的结束标志
6.1.4 可对字符量进行的运算#
- 字符量可参加任何整数运算
6.2 字符变量#
- C 语言中,字符变量用关键字 char 定义,在定义的同时可以赋初值
- 占一个字节
- 字符变量可以作为整型变量来处理,可以参与对整型变量所允许的任何运算
6.3 字符的输入和输出#
6.3.1 调用 printf 和 scanf 函数输出和输入字符#
- 调用函数进行输入和输出时,必须在程序的开头出现包含头文件 stdio.h 的命令行:
#include <stdio.h>
6.3.2 调用 putchar 和 getchar 函数输出和输入字符#
- putchar 函数用于输出字符,调用形式:
putchar(ch)
- ch 可以是字符常量也可以是字符变量
- getchar 函数用于输入字符,调用形式:
ch=getchar()
6.4 程序举例#
/*
输出26个大写字母和它们的ASCII代码,每行输出两组数据
*/
#include <stdio.h>
int main(){
char c = 'A';
for (int i = 0; i < 26; i=i+2){
printf("%c %d %c %d\n", c + i, c + i, c + i + 1, c + i + 1);
}
}
/*
从终端输入一个字符,当按Enter键时,程序才继续往下进行
*/
#include <stdio.h>
int main(){
printf("输入数据:");
while (getchar() != '\n'){
}
printf("end");
}
/*
把从终端输入的一行字符中所有小写字母转换为大写字母,其他字符不变
*/
#include <stdio.h>
int main(){
char ch;
printf("输入数据:");
while ((ch = getchar()) != '\n'){
if(ch>='a' && ch<='z'){
ch = ch - 'a' + 'A';
}
putchar(ch);
}
}
/*
统计输入的字符中空格符、换行符和横向跳格(制表)符的个数,用!结束输入
*/
#include <stdio.h>
int main(){
char ch;
int sum = 0;
printf("输入数据:");
while ((ch = getchar()) != '!'){
if(ch==' '||ch=='\n'||ch=='\t'){
sum++;
}
}
printf("sum=%d", sum);
}
/*
把一串密文译成明文,密文以字符@表示结束,译码规则如下:
(1)如果是字母,转换成字母序列的下一个字母。如A译成B
(2)如果是字母Z,译成A
(3)无论是大小写字母都译成小写字母
(4)其他字符一律照原样译出
*/
#include <stdio.h>
#include <ctype.h>
int main(){
char ch;
printf("输入数据:");
while ((ch = getchar()) != '@'){
if(isalpha(ch)){ /*isalpha(ch)判断ch中的字符是否为字母,如果是,函数值为1*/
ch = tolower(ch); /*把大写字母转为小写字母*/
ch = (ch - 'a' + 1) % 26 + 'a';
}
putchar(ch);
}
}
第七章 函数#
7.1 库函数#
- 调用 C 语言库函数时要求的 include 命令行
- 标准库函数的调用的一般形式:
函数名(参数表)
7.2 函数的定义和返回值#
7.2.1 函数定义的语法#
- 函数定义的一般形式
函数返回值的类型名 函数名(类型名 形式参数1,类型名 形式参数2,······){
说明部分
语句部分
}
- 函数名和形式参数都是由用户命名的标识符。在同一程序中,函数名必须唯一,形式参数名只要在同一函数中唯一即可
- 不能在函数的内部定义函数
- 默认函数返回值的类型是 int 类型
- 除了返回值类型为 int 类型的函数外,函数必须先定义(或说明)后调用
- 若函数只是用于完成某些操作,没有函数值返回,则必须把函数定义成 void 类型
7.2.2 函数的返回值#
- 函数的值通过 return 语句返回,一般形式:
return 表达式;
或return (表达式)
- 在同一个函数内,return 语句只可能执行一次
7.3 函数的调用#
7.3.1 函数的两种调用方式#
- 函数的一般调用形式:
函数名(实际参数表)
- 两种调用方式
- 当调用的函数用于求出某个值时,函数的调用可作为表达式出现在允许表达式出现的任何地方
- 当函数不需要返回值时,函数的调用可作为一条独立的语句
7.3.2 函数调用时的语法要求#
- 调用函数时,函数名必须与所调用的函数名字完全一致
- 实际参数的个数必须与形式参数的个数一致
- 函数必须先定义,后调用(函数的返回值为 int 或 char 时除外)
- 函数可以直接或间接的自己调用自己,称为递归调用
7.4 函数的说明#
7.4.1 函数说明的形式#
- 除主函数外,对于用户定义的函数遵循 “先定义,后使用” 的规则
- 一般形式
类型名 函数名(参数类型1,参数类型2,······)
类型名 函数名(参数类型1 参数名1,参数类型2 参数名2,······)
7.4.2 函数说明的位置#
- 当在所有函数的外部、被调用之前说明函数时,在对函数进行说明的语句后面所有位置上都可以对该函数进行调用
- 函数说明也可以放在调用函数内的说明部分,如在 main 函数内部进行说明,则只能在 main 函数内部才能识别该函数
7.5 调用函数和被调用函数之间的数据传递#
- 实际参数和形式参数之间的进行数据传递
- 通过 return 语句把函数值返回调用函数
- 通过全局变量(不提倡)
7.6 程序举例#
/*
编写函数isprime(int a),用来判断自变量a是否为素数。若是素数,返回1,否则返回0
*/
#include <stdio.h>
int isprime(int a);
int main(){
int x;
printf("输入数据:");
scanf("%d", &x);
if(isprime(x)){
printf("素数");
} else{
printf("不是素数");
}
}
int isprime(int a){
for (int i = 2; i <= a / 2;i++){
if (a % i == 0){
return 0;
}
}
return 1;
}
第八章 地址和指针#
8.1 指针变量的定义和指针变量的基类型#
- 定义指针变量的一般形式:
类型名 *指针变量名1,*指针变量名2,······
- 指针变量必须区分基类型,基类型不同的指针变量不能混合使用
8.2 给指针变量赋值#
8.2.1 给指针变量赋地址值#
- 通过求地址运算符(&)获得地址值
- 求地址运算符只能应用于变量和数组元素,不可以用于表达式、常量或者被说明为 register 的变量
- & 必须放在运算对象的左边,而且运算对象的类型必须与指针变量的基类型相同
- 通过指针变量获得地址值
- 当进行赋值运算时,赋值号两边指针变量的基类型必须相同
- 通过标准函数获得地址值
- 可以通过调用库函数 malloc 和 calloc 在内存中开辟动态存储单元,并把所开辟的动态存储单元的地址赋值给指针变量
8.2.2 给指针变量赋 “空” 值#
p=NULL
- 指针 p 并不是指向地址为 0 的存储单元,而是具有一个确定的值 ——“空”
8.3 对指针变量的操作#
8.3.1 通过指针来引用一个存储单元#
- 间接访问运算符(间址运算符):*
- 当指针变量中存放了一个确切的地址值时,就可以用 “*” 通过指针来引用该地址的存储单元
- 单目运算符,必须出现在运算对象的左边
8.3.2 移动指针#
- 定义:对指针变量加上或减去一个整数,或通过赋值运算,使指针变量指向相邻的存储单元
- 只有当指针指向一串连续的存储单元时,指针的移动才有意义
8.4 函数之间地址值的传递#
8.4.1 形参为指针变量时实参和形参之间的数据传递#
/*
编写函数myadd(int *a,int *b),函数把指针a和b所指的存储单元中的两个值相加,然后将和值作为函数值返回
*/
#include <stdio.h>
int myadd(int *a, int *b);
int main(){
int a, b,sum;
printf("输入数据:");
scanf("%d%d", &a, &b);
sum = myadd(&a, &b);
printf("sum=%d", sum);
}
int myadd(int *a, int *b){
int sum;
sum = *a + *b;
return sum;
}
8.4.2 通过传送地址在被调用函数中直接改变调用函数中的变量的值#
/*
调用swap函数,交换主函数中变量x和y中的数据
*/
#include <stdio.h>
void swap(int *, int *);
int main(){
int a = 30, b = 20;
printf("a=%d b=%d\n", a, b);
swap(&a, &b);
printf("a=%d b=%d", a, b);
}
void swap(int *a,int *b){
int t;
t = *a;
*a = *b;
*b = t;
}
8.4.3 函数返回地址值#
/*
把主函数中变量i和j中存放较大数的那个地址作为函数值返回
*/
#include <stdio.h>
int *fun(int *, int *);
int main(){
int i, j;
i = 10;
j = 20;
printf("max=%d",*fun(&i, &j));
}
int *fun(int *i, int *j){
if(*i>*j){
return i;
} else {
return j;
}
}
第九章 数组#
9.1 一维数组的定义和一维数组元素的引用#
9.1.1 一维数组的定义#
- 定义:数组中的每个元素只带一个下标
- 一般形式:
类型名 数组名[整型常量表达式],······
- 数组说明符和普通变量名可同时出现在一个类型定义语句中
9.1.2 一维数组的引用#
- 引用形式:
数组名[下标表达式]
9.1.3 一维数组的初始化#
- 当所赋初值少于所定义数组的元素个数时,将自动给后面的元素补以初值 0
- 对于字符型数组同样补以初值 0,即
\0
9.1.4 通过赋初值定义数组大小#
- C 语言规定可以通过赋初值来定义数组的大小,这时数组说明符的一对方括号中可以不指定数组的大小
9.1.5 一维数组的定义和数组元素引用举例#
/*
定义一个含有30个元素的int类型数组,依次给数组元素赋奇数1、3、5、···,然后按每行10个数顺序输出,最后再按每行10个数逆序输出
*/
#include <stdio.h>
int main(){
int a[30];
int i, k = 1;
for (i = 0; i < 30; i ++){
a[i] = k;
k = k + 2;
}
for (i = 0; i < 30; i ++){
printf("%4d", a[i]);
if ((i+1) % 10 == 0){
printf("\n");
}
}
printf("\n");
for (i = 29; i >= 0; i --){
printf("%4d", a[i]);
if (i % 10 == 0){
printf("\n");
}
}
}
9.2 一维数组和指针#
- 在函数体中或在函数外部定义的数组名可以认为是一个存放地址值的指针变量名,其中的地址值是数组第一个元素的地址,也就是数组所占一串连续存储单元的起始地址,定义数组时的类型即是此指针变量的基类型
- 这个指针变量中的地址值不可改变,即不可以给数组名重新赋值
- 可以对数组名加一个整数的办法,来依次表达该数组中不同元素的地址
9.3 函数之间对一维数组和数组元素的引用#
9.3.1 数组元素作实参#
- 每个数组元素实际上代表内存中的一个存储单元,数组元素的值可以传送给该变量,在函数中只能对该变量进行操作,而不能直接引用对应的数组元素,不可能在函数中改变对应数组元素中的值
9.3.2 数组名作实参#
- 数组名本身是一个地址值,对应的形参应当是一个指针变量,此指针变量的基类型必须与数组的类型一致。在函数中,可以通过此指针变量来引用调用函数中的对应的数组元素,从而达到对调用函数中对应的数组元素进行操作而改变其中的值
/*
通过一个函数给主函数中定义的数组输入若干大于或等于0的整数,用负数作为输入结束标志;调用另一个函数输出该数组中的数据
*/
#include <stdio.h>
#define M 100
int input(int *a);
void output(int *a,int n);
int main(){
int a[M],n;
n = input(a);
output(a,n);
}
int input(int *a){
int n,i;
i = 0;
printf("输入数据:");
scanf("%d", &n);
while (n >= 0){
a[i] = n;
i++;
scanf("%d", &n);
}
return i - 1;
}
void output(int *a,int n){
for (int i = 0; i <= n;i++){
printf("%d ", a[i]);
}
}
9.3.3 数组元素地址作为实参#
- 当用数组元素作为实参时,因为是地址值,所以对应的形参也应当是基类型相同的指针变量
/*
对具有10个元素的char类型数组,从下标为4的元素开始,全部设置星号,保持前四个元素不变
*/
#include <stdio.h>
#define M 10
#define B 4
void setstar(char *c);
int main(){
char c[M] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'};
setstar(c);
for (int i = 0; i < M; i++){
printf("%c ", c[i]);
}
}
void setstar(char *c){
for (int i = B; i < M; i++){
c[i] = '*';
}
}
9.4 一维数组应用举例#
/*
定义一个含有15个元素的数组,并编写函数分别完成以下操作:
1. 调用C库函数中的随机函数给所有元素赋以0~49的随机数
2. 输出数组元素中的值
3. 按顺序对每三个数求一个和数,并传回主函数
4. 最后输出所有求出的和值
*/
#include <stdio.h>
#include <stdlib.h>
#define M 15
#define N 3
void getrand(int *a);
void arrout(int *a,int n);
void getsum(int *a, int n,int *sum);
int main(){
int a[M],sum[M/N];
getrand(a);
arrout(a,M);
getsum(a, N,sum);
arrout(sum,M/N);
}
void getrand(int *a){
for (int i = 0; i < M; i++){
a[i] = rand() % 50;
}
}
void arrout(int *a,int n){
for (int i = 0; i < n; i++){
printf("%d ", a[i]);
}
printf("\n");
}
void getsum(int *a, int n,int *sum){
for (int i = 0; i < M/N; i++){
sum[i] = a[i * N] + a[i * N + 1] + a[i * N + 2];
}
}
9.5 二维数组的定义和二维数组元素的引用#
9.5.1 二维数组的定义#
- 定义语句形式:
类型名 数组名[常量表达式1][常量表达式2],······
9.5.2 二维数组的引用#
- 引用形式:
数组名[下标表达式1][下标表达式2]
9.5.3 二维数组的初始化#
- 所赋初值个数与数组元素的个数相同
- 可以在定义二维数组的同时给二维数组的各元素赋初值
int a[4][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
- 可以在定义二维数组的同时给二维数组的各元素赋初值
- 每行所赋初值个数与数组元素的个数不同
- 系统将自动给该行后面元素赋值
- 所赋初值行数少于数组行数
- 系统将自动给后面各行的元素补初值 0
- 赋初值时省略花括号对
int a[4,3]={1,2,4,5};
- 系统将按 a 数组元素在内存中排列的顺序,将花括号内的数据一一对应地赋给各个元素,若数据不足,系统将给后面的元素自动补初值 0
9.5.4 通过赋初值定义二维数组的大小#
- 只可以省略第一个方括号中的常量表达式,而不能省略第二个方括号中的常量表达式
9.5.5 二维数组的定义和数组元素引用举例#
/*
通过键盘给出2*3的二维数组输入数据,第一行赋1、2、3,第二行赋10、20、30,然后按行输出此二维数组
*/
#include <stdio.h>
int main(){
int a[2][3];
printf("输入数据:");
for (int i = 0; i < 2; i++){
for (int j = 0; j < 3; j++){
scanf("%d", &a[i][j]);
}
}
for (int i = 0; i < 2; i++){
for (int j = 0; j < 3; j++){
printf("%4d", a[i][j]);
}
printf("\n");
}
}
9.6 二维数组和指针#
先给出以下定义:
in *p,a[3][4];
- 二维数组 a 由若干个一维数组组成
- 在以上二维数组中,a [0],a [1],a [2] 都是一维数组名,同样也代表一个不可变的地址常量,其值依次为二维数组每行第一个元素的地址,其基类型就是数组元素的类型
- 二维数组名也是一个地址值常量
- 二维数组名同样也是一个存放地址常量的指针,其值为二维数组中第一个元素的地址
- 二维数组名应理解为一个行指针
- 二维数组元素的地址
- 二维数组元素的地址可以由表达式 & a [i][j] 求得,也可以通过每行的首地址来表示
9.7 二维数组名和指针数组作为实参#
9.7.1 二维数组名作为实参时实参和形参之间的数据传递#
- 当二维数组名作为实参时,对应的形参必须是一个行指针变量
9.7.2 指针数组作为实参时实参和形参之间的数据传递#
- 当指针数组作为实参时,对应的形参应当是一个指向指针的指针
9.8 二维数组程序举例#
/*
通过调用随机函数给5*6的二维数组元素赋10~40范围内的整数,求出二维数组每行元素的平均值
*/
#include <stdio.h>
#include <stdlib.h>
void getrand(int a[][6]);
void getave(int a[][6], double *ave);
int main(){
int a[5][6];
double ave[5]={0};
getrand(a);
for (int i = 0; i < 5; i++){
for (int j = 0; j < 6; j++){
printf("%d ", a[i][j]);
}
printf("\n");
}
getave(a, ave);
for (int i = 0; i < 5; i++){
printf("%f ", ave[i]);
}
}
void getrand(int a[][6]){
for (int i = 0; i < 5; i++){
for (int j = 0; j < 6; j++){
a[i][j] = rand() % 31 + 10;
}
}
}
void getave(int a[][6], double *ave){
for (int i = 0; i < 5; i++){
for (int j = 0; j < 6; j++){
ave[i] = ave[i] + a[i][j];
}
ave[i] = ave[i] / 6;
}
}
第十章 字符串#
10.1 用一维字符数组存放字符串#
- C 语言对字符串的约定
- 字符串是借助与字符型一维数组来存放的,并规定以字符
\0
作为 “字符串结束标志” \0
作为标志占用存储空间,但不计入串的实际长度
- 字符串是借助与字符型一维数组来存放的,并规定以字符
- C 语言中表示字符串常量的约定
- 字符串常量是由双引号括起来的一串字符,系统将自动在末尾添加字符
\0
- 字符串常量是由双引号括起来的一串字符,系统将自动在末尾添加字符
- C 语言中字符串常量给出的是地址值
- 字符数组与字符串的区别
- 仅可以在字符数组内存放字符串,不能通过赋值语句将字符串常量或其他字符数组中的字符串直接赋给字符串变量
- 字符串是字符数组的一种具体应用
10.1.1 通过赋初值的方式给一维字符数组赋字符串#
- 用给一般数组赋初值的相同方式给一维字符数组赋初值
- 所赋初值的字符个数少于数组的元素个数时,系统都将自动在其后的元素中加入
\0
- 所赋初值的字符个数少于数组的元素个数时,系统都将自动在其后的元素中加入
- 在赋初值时直接赋字符串常量
10.1.2 在 C 程序执行过程中给一维字符数组赋字符串#
- 不可以用赋值语句给字符数组整体赋一串字符
- 给数组元素逐个赋字符值,最后人为加入串结束标志
10.2 使指针指向一个字符串#
- 通过赋初值的方式使指针指向一个字符串
- 可以在定义字符指针变量的同时,将存放字符串的存储单元起始地址赋给指针变量
- 通过赋值运算使指针指向一个字符串
10.3 字符串的输入和输出#
10.3.1 输入和输出字符串时的必要条件#
- 对字符串进行输出时,输出项既可以是字符串常量或字符数组名,也可以是已指向字符串的字符指针变量
- 对字符串进行输入时,输入项可以是字符数组名,也可以是字符指针变量
10.3.2 用格式说明符 %s
进行整串输入和输出#
- 在 scanf 函数中使用可以实现字符串的整体输入
- 空格和回车符都作为输入数据的分隔符而不能被读入
- 若输入字符串的长度超过字符数组所能容纳的字符个数时,系统不报错。相当于下标越界
- 当输入项是数组元素的地址时,输入的字符将从这一元素开始依次存放在该数组中
- 当输入项为字符指针变量时,该指针变量必须已指向确定的由足够空间的连续存储单元
- 在 printf 函数中使用可以实现字符串的整体输出
- 依次输出存储单元中的字符,直到遇到第一个
\0
为止 \0
是结束标志,不在输出字符之列- 输出结束后不自动换行
- 依次输出存储单元中的字符,直到遇到第一个
10.3.3 调用 gets、puts 函数在终端输入或输出一行字符串#
- gets 函数:
gets(str_adr)
- gets 函数从终端键盘读入字符串(包括空格符),直到读入一个换行符为止
- 换行符读入后,不作为字符串的内容,系统将自动用
\0
代替
- puts 函数:
puts(str_adr)
- 依次输出存储单元中的字符,遇到第一个
\0
即结束输出,并自动输出一个换行符
- 依次输出存储单元中的字符,遇到第一个
10.4 用于字符串处理的函数#
使用以下函数时,需添加头文件
<string.h>
- 字符串赋值(拷贝)函数 strcpy:
strcpy(s1,s2)
- 把 s2 所指字符串(源)的内容复制到 s1 所指存储空间(目的)中,函数返回 s1 的值,即目的字符串的首地址
- s1 必须指向一个足够容纳 s2 串的存储空间
- 字符串连接函数 strcat:
stract(s1,s2)
- 将 s2 所指字符串的内容连接到 s1 所指的字符串后,并自动覆盖 s1 串末尾的
\0
,函数返回 s1 所指的地址值 - s1 所指字符串应由足够的空间容纳两串合并后的内容
- 将 s2 所指字符串的内容连接到 s1 所指的字符串后,并自动覆盖 s1 串末尾的
- 求字符串长度函数 strlen:
strlen(s)
- 计算以 s 为起始地址的字符串长度,并作为函数值返回。不包括串尾的结束标志
\0
- 计算以 s 为起始地址的字符串长度,并作为函数值返回。不包括串尾的结束标志
- 字符串比较函数 strcmp:
strcmp(s1,s2)
- 依次对 s1 和 s2 中对应位置上的字符两两比较,字符大小依据是其 ASCII 码值
10.5 程序举例#
/*
编写函数slenth(char *s),函数返回指针s所指字符串的长度,即相当于库函数strlen的功能
*/
#include <stdio.h>
int slenth(char *s);
int main(){
int length;
char str[] = "ABCDEFG";
length = slenth(str);
printf("%d", length);
}
int slenth(char *s){
int i = 0;
while (*(s + i) != '\0'){
i++;
}
return i;
}
第十一章 对函数的进一步讨论#
11.1 传给 main 函数的参数#
- main 函数通常可用两个参数
- 第一个参数 argc 必须是整型
- 第二个参数 argv 是一个指向字符型的指针数组指针,每个指针都指向一个字符串
main(int argc, char **argv){
···
}
11.2 通过实参向函数传递函数名或指向函数的指针变量#
- 指向函数指针变量的定义:C 语言中函数名代表该函数的入口地址,因此可以定义一种指向函数的指针来存放这种地址
#include <stdio>
double fun(int a, int *p){
···
}
main(){
double (*fp)(int, int *),y;
int n;
fp = fun;
·
·
·
y = (*fp)(56, &n); /* 此处通过指向函数的指针调用fun函数 */
·
·
·
}
- 函数名或指向函数的指针变量作为实参:函数名或指向函数的指针变量可以作为实参传送给函数
11.3 函数的递归调用#
/*
用递归的方法求n!
*/
#include <stdio.h>
int fac(int n);
int main(){
int n;
printf("输入数据:");
scanf("%d", &n);
printf("%d!=%d", n, fac(n));
}
int fac(int n){
int f;
if (n == 1 || n == 0){
return 1;
} else {
f = n * fac(n - 1);
return f;
}
}
第十二章 C 语言中用户标识符的作用域和存储类#
12.1 局部变量#
- 局部变量(内部变量):在函数内部或符合语句内部定义的变量
- 函数的形参也属于局部变量
- 全局变量(外部变量):在函数外部定义的变量
- C 语言中,由两种存储类别:一种是自动类,一种是静态类。局部变量既可以说明成自动类,也可以说明成静态类;局部变量只能是静态类
- 四个与两个存储类别相关的说明符:auto(自动)、register(寄存器)、static(静态)、extern(外部)。它们可以放在类型名的左边,也可以放在类型名的右边
- 动态存储区用来保存函数调用时的返回地址、自动类别的局部变量等。静态存储区用以存放局部变量及静态类别的局部变量
12.2 局部变量及其作用域和生存期#
12.2.1 auto 变量#
- 当在函数内部或复合语句内定义变量时,如果没有指定存储类,或使用了 auto 说明符,系统就认为所定义的变量具有自动类别
- auto 变量的存储单元被分配在内存的动态存储区。每当进入函数体(或复合语句)时,系统自动为 auto 变量分配存储单元;退出时自动释放这些存储单元另作他用
12.2.2 register 变量#
- 寄存器变量也是自动类变量。它与 auto 变量的区别在于用 register 说明的变量建议编译程序将变量的值保留在 CPU 寄存器中,而不是像一般变量一样,占存储单元
- CPU 中寄存器的数目是有限的,因此只能说明少数的寄存器变量
- register 变量没有地址,不能进行求地址运算
- register 变量的说明应尽量靠近其使用的地方,用完之后尽快释放
12.2.3 静态存储类的局部变量#
- 当在函数体(或复合函数)内部使用 static 来说明一个变量时,称该变量为静态局部变量
- 在程序运行期间,静态局部变量在内存的静态存储区中占据着永久性的存储单元
- 静态局部变量的初值是在编译时赋予的,不是在程序执行期间赋予的。对未赋初值的静态局部变量,C 编译程序自动给它赋初值 0
12.3 全局变量及其作用域和生存期#
全局变量只有静态一种类别。对于全局变量可使用 extern 和 static 两种说明符
12.3.1 全局变量的作用域和生存期#
- 全局变量是在函数外部任意位置上定义的变量,它的作用域是从变量定义的位置开始,到整个源文件结束
- 全局变量的生存期是整个程序的运行期间
- 若全局变量和某个函数中的局部变量同名,则在该函数中,此全局变量被屏蔽
12.3.2 在同一编译单位内使用 extern 说明扩展全局变量的作用域#
- 当全局变量定义在后,引用它的函数在前时,应该在引用它的函数中用 extern 对此全局变量进行说明:该变量是一个已在外部定义了的全局变量,已经分配了存储单元,不需再为它另外开辟存储单元。
12.3.3 在不同编译单位内用 extern 说明符扩展全局变量的作用域#
- 在其中一个文件中定义所有全局变量,而在其他用到这些全局变量的文件中用 extern 对这些变量进行说明
12.3.4 静态全局变量#
- 静态全局变量只限于本编译单位使用,不能被其他编译单位所引用
12.4 函数的存储分类#
12.4.1 用 extern 说明函数#
- 若在函数返回值的类型前加上说明符 extern,称此函数为 “外部” 函数
- extern 说明可以省略,一般函数都隐含说明为 extern
- 外部函数的特征为可以被其他编译单位中的函数调用
12.4.2 用 static 说明函数#
- 若在函数返回值的类型前加上说明符 static,称此函数为 “静态” 函数
- 静态函数的特征是只限于本编译单位的其他函数调用它,而不允许其他编译单位中的函数对它进行调用
第十三章 编译预处理和动态存储分配#
13.1 编译预处理#
C 语言中,凡是以 “#” 开头的行,都被称为 “编译预处理” 命令行
13.1.1 宏替换#
- 不带参数的宏定义
- 命令行形式
#define 宏名 替换文本 或 #define 宏名
- 替换文本可以包含已定义过的宏名
- 当宏定义在一行中写不下,只需要在最后一个字符后紧接着加一个反斜线 “\”
- 如果在 “\” 前或在下一行的开头留有许多空格,则在宏替换时也加入这些空格
- 同一个宏名不能重复定义,除非两个宏定义命令行完全一致
- 替换文本不能替换双引号中与宏名相同的字符串
- 替换文本并不替换用户标识符中的成分
- 用作宏名的标识符通常用大写字母表示
- 宏定义的定义位置一般写在程序的开头
- 带参数的宏定义
- 命令行形式:
#define 宏名(形参表) 替换文本
- 同一个宏名不能重复定义,除非两个宏定义命令行完全一致
- 在调用带参数的宏名时,一对圆括号必不可少,圆括号中实参的个数应该与形参个数相同,若有多个参数,它们之间用逗号隔开
- 在 “替换文本” 中的形参和整个表达式应该用括号括起来
- 在宏替换中,对参数没有类型的要求
- 宏替换中,实参不能替换括在双引号中的形参
- 命令行形式:
- 终止宏定义
- 可以用
#undef
提前终止宏定义的作用域
#define PI 3.14 main(){ · · · #undef PI · · · }
- 可以用
13.1.2 文件包含#
- 一般形式:
#include <文件名>
或#include "文件名"
13.2 动态存储分配#
13.2.1 malloc 函数和 free 函数#
malloc 函数#
- malloc 函数返回值类型为 void *,函数的调用形式为
malloc(size)
,要求 size 的类型为 unsigned int - malloc 函数用来分配 size 个字节的存储区,返回一个指向存储区首地址的基类型为 void 的地址。若没有足够的内存单元供分配,函数返回空 (NULL)
- 在动态申请存储空间时,若不能确定数据类型所占字节数,可以使用 sizeof 运算符来求得
pi=(int *)malloc(sizeof(int));
free 函数#
- 函数的调用形式:
free(p);
- 指针变量 p 必须指向有动态分配函数 malloc 或 calloc 分配的地址
- free 函数将指针 p 所指的存储空间释放
- 此函数没有返回值
13.2.2 calloc 函数#
- 函数的返回值类型为 void *
- 调用形式:
calloc(n,size);
,要求 n 和 size 的类型都为 unsigned int - calloc 函数用来给 n 个同一类型的数据项分配连续的存储空间,每个数据项的长度为 size 个字节。若分配成功,函数返回存储空间的首地址;否则返回空
- 通过调用 calloc 函数所分配的存储单元,系统自动置初值 0
第十四章 结构体、共用体和用户定义类型#
14.1 用 typedef 说明一种新类型名#
- 一般形式:
typedef 类型名 标识符;
- “类型名” 必须是在此语句之前已有定义的类型标识符
- “标识符” 是一个用户定义标识符,用作新的类型名
- typedef 语句的作用仅仅是用 “标识符” 来代表已存在的 “类型名”,并未产生新的数据类型,原有类型名依然有效
14.2 结构体类型#
14.2.1 结构体类型的说明#
- 一般形式
struct 结构体标识名{
类型名1 结构成员名表1;
类型名2 结构成员名表2;
·
·
·
类型名n 结构成员名表n;
};
- struct 是关键字,是结构体类型的标志
- “结构体标识名” 和 “结构成员名” 都是用户定义的标识符,其中 “结构体标识符” 是可选项,在说明中可以不出现
- 每个 “结构成员名表” 中都可以含有多个同类型的成员名,它们之间以逗号分隔
- 结构体中的成员名可以和程序中的其他变量同名;不同结构体中的成员也可以同名
- 结构体类型说明中的 “类型名 1”~“类型名 n” 不仅可以是简单数据类型,也可以是构造类型、某种结构体类型
- ANSI C 标准规定结构体至少允许嵌套 15 层,并且允许内嵌结构体成员的名字与外层成员的名字相同
14.2.2 结构体类型的变量、数组和指针变量的定义#
- 紧跟在结构体类型说明之后进行定义
- std 只能存放 1 组数据;pers 可以存放 3 组;pstd 可以指向具有 struct student 类型的存储单元,但目前还没有具体的指向
struct student{
char name[12];
char sex;
struct data birthday;
float sc[4];
} std,pers[3],*pstd;
- 在说明一个无名结构体类型的同时,直接进行定义
struct{
···
} std,pers[3],*pstd;
- 先说明结构体类型,再单独进行变量定义
- 这种定义方法不能只使用 struct 而不写结构体标识符 student
struct student{
···
};
struct student std,pers[3],*pstd;
- 使用 typedef 说明一个结构体类型名,再用新类型名来定义变量
typedef struct{
char name[12];
char sex;
struct data birthday;
float sc[4];
} STREC;
STREC std,pers[4],*pstd;
14.2.3 给结构体变量、数组赋初值#
- 给结构体变量赋初值:所赋初值顺序放在一对花括号中
- 不允许跳过前面的成员给后面的成员赋初值,可以只给前面的若干个成员赋初值,对于后面未赋初值的成员,系统将自动为数值型和字符型数据赋初值零
struct student{
char name[12];
char sex;
struct data birthday;
float sc[4];
} std={"Li Ming",'M',1962,5,10,88,76,86.5,90};
- 给结构体数组赋初值:通常将其成员的值依次放在一对花括号中
struct bookcard{
char num[5];
float money;
} bk[3]={{"No.1",35.5},{"No.2",25.0},{"No.3",66.7}};
14.2.4 引用结构体变量中的数据#
对结构体变量成员的引用#
- 引用形式
- 结构体变量名。成员名
- 指针变量名 -> 成员名
- (* 指针变量名). 成员名
- “.” 称为运算成员符;“->” 称为结构指向运算符,之间没有空格
- 访问结构体变量中哥内嵌结构体成员时,必须逐层使用成员名定位
相同类型结构体变量之间的整体赋值#
- ANSI C 标准允许相同类型的结构体变量之间进行整体赋值
14.2.5 函数之间结构体变量的数据传递#
- 向函数传递结构体变量中单个成员的数据
- 向函数传递整个结构体变量中的数据
- 传递结构体变量的地址
- 向函数传递结构体数组名
- 函数返回值是结构体类型
- 函数返回值可以是指向结构体变量的指针类型
14.2.6 利用结构体变量构成链表#
- 结构体中含有可以指向本结构体的指针成员
/*
一个简单的链表
*/
#include <stdio.h>
struct node{
int data;
struct node *next;
};
typedef struct node NODETYPE;
int main(){
NODETYPE a, b, c, *h, *p;
a.data = 10;
b.data = 20;
c.data = 30; /*给变量中的data域赋值*/
h = &a; /*将结点相连*/
a.next = &b;
b.next = &c;
c.next = NULL;
p = h;
while(p){ /*移动p,使之依次指向a、b、c,输出它们data域中的值*/
printf("%d\n", p->data);
p = p->next; /*p顺序后移*/
}
}
- 单向链表
- 每个结点应该由两个成员组成:一个是整型的成员;一个是指向自身结构的指针类型成员
14.3 共用体#
结构体变量中的成员各自占有自己的存储空间,而共用体变量中的所有成员占有同一个存储空间
14.3.1 共用体类型的说明和变量定义#
- 一般形式
union 共用体标识名{
类型名1 共用体成员名1;
类型名2 共用体成员名2;
·
·
·
类型名n 共用体成员名n;
}
- union 是关键字,是共用体类型的标志
- 共用体标识名是可选项,在说明中可以不出现
- 共用体成员可以是简单变量,也可以是数组、指针、结构体和共用体(结构体成员也可以是共用体)
- 共用体变量的定义与结构体变量的定义类似
(1) 共用体变量在定义的同时只能用第一个成员的类型的值进行初始化
(2) 由于共用体变量中的所有成员共享存储空间,因此变量中的所有成员的首地址相同,而且变量的首地址也就是该成员变量的首地址
14.3.2 共用体变量的引用#
- 共用体变量中成员的引用
- 一般形式
共用体变量名.成员名 指针变量名->成员名 (*指针变量名).成员名
- 共用体变量中起作用的是最近一次存入的成员变量的值,原有成员变量的值将被覆盖
- 共用体变量的整体赋值
- ANSI C 标准允许在两个类型相同的共用体变量之间进行赋值操作
- 向函数传递共用体变量的值
- 共用体类型的变量可以作为实参进行传递,也可以传送共用体变量的地址
第十五章 位运算#
15.1 位运算符#
- 只有
~
为单目运算符,其余均为双目运算符
运算符 | 含义 | 优先级 |
---|---|---|
~ | 按位求反 | 1(高) |
<< | 左移 | 2 |
>> | 右移 | 2 |
& | 按位与 | 3 |
^ | 按位异或 | 4 |
| | 按位或 | 5(低) |
15.2 位运算符的运算功能#
“按位取反” 运算#
- 把运算对象的内容按位取反
~0115
将八进制数 115 按位取反
~ 0 1 0 0 1 1 0 1
把运算对象用二进制来表示
————————
1 0 1 1 0 0 1 0
“左移” 运算#
- 运算符左边是移位运算符,右边是整型表达式,代表移位位数
- 左移时,右端(低位)补 0;左端(高位)移出的部分舍弃
char a=6,b;
b=a<<2;
用二进制来表示运算过程如下:
a:0 0 0 0 0 1 1 0
b=a<<2:0 0 0 1 1 0 0 0
“右移” 运算#
- 右移时,右端(低位)移出的二进制数舍弃,左端(高位)移入的二进制数分两种情况:对于无符号整数和正整数,高位补 0;对于负整数,高位补 1
“按位与” 运算#
- 把参与运算的两个运算数按对应的二进制位分别进行 “与” 运算,当两个相应的位数都为 1 时,该位的结果为 1;否则为 0
“按位异或” 运算#
- 参与运算的两个运算数中相对应的二进制位上,若数相同,则该位的结果为 0;若数不同,则该位的结果为 1
“按位或” 运算#
- 参与运算的两个运算数中,只要两个相应的二进制位中有一个为 1,该位的运算结果即为 1;只有当两个相应位的数都为 0 时,该位的运算结果才为 0
位数不同的运算数之间的运算规则#
- 位运算的对象可以是整型(long int 或 int 或 short)和字符型(char)数据
- 当两个运算数类型不同时位数也会不同,系统将进行以下处理
- 先将两个运算数右端对齐
- 再将位数短的一个运算数往高位扩充,即无符号和正整数左侧用 0 补全,负数左侧用 1 补全,然后对位数相等的这两个运算数按位进行位运算
第十六章 文件#
16.1 C 语言文件的概念#
- 顺序存取
- 进行读或写操作时,总是从文件的开头开始,从头到尾顺序地读或写
- 直接存取(随机存取)
- 通过调用 C 语言的函数库去指定开始读或写的字节号,然后直接对此位置上的数据进行读,或把数据写到此位置上
16.2 文件指针#
- 一般形式:
FILE *指针变量名
16.3 打开文件#
- fopen 函数的一般调用形式:
fopen(文件名,文件使用方式);
- 若调用成功,函数返回一个 FILE 类型的指针,赋给文件指针变量 fp,从而把指针 fp 与文件 file_a 联系起来
- 若打开文件失败,返回 NULL
- 文件使用方式
- “r”:为读打开文本文件,对打开文件只能进行 “读” 操作
- “rb”:为读打开二进制文件,其余功能与 “r” 相同
- “w”:为写打开文本文件。如果指定文件不存在,系统将用在 fopen 调用中指定的文件名建立一个新文件;如果指定文件存在,则从文件的起始位置开始写,文件中原有的内容将全部消失
- “wb”:为写打开一个二进制文件。可以在指定文件位置进行写操作,其余功能与 “w” 相似
- “a”:为在文件后面添加数据而打开文本文件。如果指定文件不存在,系统将用在 fopen 调用中指定的文件名建立一个新文件;如果指定文件存在,则文件中原有的内容将保存,新的数据写在原有内容之后
- “ab”:为在文件后面添加数据而打开二进制文件。其余功能与 “a” 相同
- “r+”:为读和写打开文本文件。指定文件应当已经存在,读和写总是从文件的起始位置开始;在写新数据时,只覆盖新数据所占的空间,其后的老数据并不丢失
- “rb+”:为读和写打开二进制文件。功能与 “r+” 相同,只是在读和写时,可以由位置函数设置读和写的起始位置
- “w+”:首先建立一个新文件,进行写操作,随后可以从头开始读。如果指定文件已存在,则原有的内容将全部消失
- “wb+”:功能与 “w+” 相同,只是在随后的读和写时,可以由位置函数设置读和写的起始位置
- “a+”:功能与 “a” 相同,只是在文件尾部添加新的数据后,可以从头开始读
- “ab+”:功能与 “a+” 相同,只是在文件尾部添加新的数据后,可以由位置函数设置开始读的起始位置
16.4 关闭文件#
- fclose 函数的调用形式:
fclose(文件指针);
- 当成功执行了关闭操作后,函数返回 0,否则返回非 0
16.5 调用 getc (fgetc) 和 putc (fputc) 函数进行输入和输出#
一、调用 putc(或 fputc)函数输出一个字符#
- putc 函数调用形式:
putc(ch,fp);
- ch 是待输出的某个字符,它可以是一个字符常量,也允许是一个字符变量
- fp 是文件指针
- 将字符 ch 写到文件指针 fp 文件所指的文件中去
- 如果输出成功,putc 函数返回所输出的字符;如果输出失败,则返回一个 EOF 值。EOF 是在 stdio.h 库函数文件中定义的符号常量,其值等于 - 1
- fputc 函数的调用形式和功能与 putc 函数完全相同
二、调用 getc(或 fgetc)函数输出一个字符#
- getc 函数的调用形式:
ch=getc(pf);
- pf 指文件指针
- 从 pf 指定的文件中读入一个字符,并把它作为函数值返回
- fgetc 函数的调用形式和功能与 getc 函数完全相同
16.6 判断文件结束函数 feof#
- 如果遇到文件结束,函数 feof (fp) 的值为 1,否则为 0
16.7 fscanf 函数和 fprintf 函数#
- fsacnf 函数:
fscanf(文件指针,格式控制字符串,输入项表);
- fscanf 函数只能从文本文件中按格式输入
- fprintf 函数:
fprint(文件指针,格式控制字符串,输入项表);
- fprintf 函数按格式将内存中的数据转换成对应的字符,并以 ASCII 代码形式输出到文本文件中
16.8 fgets 函数和 fputs 函数#
- fgets 函数:
fgets(str,n,fp);
- fp 是文件指针,str 是存放字符串的起始地址,n 是一个 int 型变量
- 从 fp 所指文件中读入 n-1 个字符放入以 str 为起始地址的空间内。如果在未读满 n-1 个字符时,已读到一个换行符或一个 EOF(文件结束标志),则结束本次读操作,读入字符串的最后包含读到的换行符
- 读入结束后,系统将自动在最后加
\0
,并以 str 作为函数值返回
- fputs 函数:
fputs(str,fp);
- fp 是文件指针;str 是待输出的字符串,可以是字符串常量,指向字符串的指针或存放字符串的字符数组名等
- 最后的
\0
并不输出,也不自动加\n
- 输出成功函数值为正整数,否则为 - 1(EOF)
- 调用函数输出字符时,文件中各字符串将首尾相连,它们之间将不存在任何间隔符
16.9 fread 函数和 fwrite 函数#
- fread 函数:
fread(buffer,size,count,fp);
- buffer 是数据块的指针,内存块的首地址,输入的数据存入此内存块中
- fwrite 函数:
fwrite(buffer,size,count,fp);
- buffer 是数据块的指针,准备输出的内存块的起始地址
- size 表示每个数据块的字节数
- count 用来指定每读、写一次,输入或输出数据块的个数(每个数据块具有 size 字节)
16.10 文件定位函数#
16.10.1 fseek 函数#
-
调用形式:
fseek(fp,offset,origin);
- pf 是文件指针
- offset 是以字节为单位的位移量,为长整型
- origin 是起始点,用以指定位移量是以哪个位置为基准,起始点既可以用标识符来表示,也可以用数字来表示
标识符 数字 代表的起始点 SEEK_SET 0 文件开始 SEEK_END 2 文件末尾 SEEK_CUR 1 文件的当前位置 -
对于二进制文件,当位移量为正整数时,表示位置指针从指定的起始点向文件尾部方向移动;当位移量为负整数时,表示位置指针从指定的起始点向文件首部方向移动
-
对于文本文件,位移量必须是 0
16.10.2 ftell 函数#
- ftell 函数用以获得文件当前位置指针的位置,函数给出当前位置指针相对于文件开头的字节数
- 调用形式:
long t = ftell(fp);
- 当函数调用出错时,函数返回 - 1L
16.10.3 rewind 函数#
- 调用形式:
rewind(pf);
- 函数没有返回值,函数的功能是使文件的位置指针回到文件的开头