C程复习

第一章 什么是C语言

第一章内容更多的是对C语言的基本语法和程序学习相关基础知识的总述,其中会涉及到函数,ADT等内容,这里只是做一个基础认识,后续章节会有更加详细的表述和代码示例。:kiss

C程序是由函数所组成的。

程序和数据一样,共同存放在存储器中。当程序要运行时,当前准备运行的指令从内存被调入CPU中,由CPU处理该条指令。这种将程序和数据共同存储的思想是冯·诺依曼模型的存储程序概念。

指令部分主要涉及汇编,C程要求了解即可:
指令作为最基础的计算机处理单元,一般由多个指令组合实现计算机的复杂功能
一般程序会经过编译器(也就是C程要求的DevC++,vscode等)编译成电脑可执行的二进制文件,其中内容为顺序排列的指令码

程序设计语言

  1. 数据表达
    • 数据类型(data type):对某种具有共同特征的数据集合的总称

      • 代表什么数据

      • 可以对数据进行什么操作(运算)

      • 常见数据类型:

        • 整形:整数变量
        • 实型(浮点型):小数变量

        1 和1.0在计算机语言中一般不同,主要体现在数据类型导致的存储结构不同

        • 字符型:中英文字符内容
      • 形式:

        • 常量:表示一些程序运行过程的不变值
        • 变量:可以对其进行相关的操作,改变它的值

        这之中的区别在实际代码中个人理解主要体现在后期函数间交流和项目跨代码调用中体现

    • 抽象数据类型(abstract data type):
      为了完成程序的相关工作设计的一些复杂的数据类型,如数组,结构,文件,指针等。

  2. 流程控制
    程序的运行通常会要求比较复杂的控制过程,因此有必要对程序工作进行结构化设计。
    • 顺序结构:按照自然顺序执行模块
    • 分支结构:根据设定条件进行判断,然后选择不同的模块进行运行
    • 循环结构:反复执行相同重复的模块的结构

这3中结构是构成一切程序设计语言基本结构

语法

主要作用起到程序规范化,使得编译器可以正确识别程序内容,并编译。可以理解为你写的代码只是一串字符的组合,背后有人写好的更底层的代码对你的字符组合进行阅读理解,从而写出计算机可以理解的二进制文件(计算机的直接处理只能为01)。

标识符

由字母、数字和下划线组成,其中第一个字符必须是字母或下划线。同时字母的大小写是有区别的。

  • 保留字(关键字):C语言规定的、赋予特定含义和有专门用途的标识符,如int,float,if,else等。
  • 用户自定义标识符:变量名、数据类型名(结构体属于一种用户自定义的数据类型)、函数名和符号常量

其他

  1. 常量:即有数据类型的不变量
  2. 运算符:表示对各种数据类型对象运算,详见可查看附表
  3. 分隔符:; ( ) [ ] # 等均为分隔符

语法单位

  1. 表达式:运算符与运算对象(常量、函数、变量等)的有意义组合
  2. 变量定义
1
2
3
4
int i;
float f;
double d;
char c;
  1. 语句:程序最基本的执行单位
    • 表达式语句:表达式加分号(分号在C中表示一个表达式语句的结束)
    • 分支语句
1
2
3
4
5
if(a > b){
x = a;
}else{
x = b;
}
  • 循环语句
1
2
3
4
5
6
sum = 0;
i = 1;
while(i <= 100){
sum = sum + i;
i = i + 1
}
  • 复合语句:用大括号将若干语句顺序组合

在C语言中复合语句的产生主要来自于C语言对于分支语句和循环语句的要求均为仅能包含一个表达式语句,而在大多数情况下这是不够的,因此采用{}包裹的复合语句,在语法上就认定为它是一个表达式语句

  1. 函数定义与调用:函数主要是对一些重复过程进行打包化处理的工作,在多数情况会极大减少代码体量,且利于修改
  2. 输入与输出
  3. 命令:include<>

值得强调的是,因为C语言对语句的分割由;或{}判断,本身对缩进没有任何要求,即使你全写在一行里也是可以运行的,但是为了程序调试和结构观察,强烈建议按照一定的缩进格式进行代码撰写,可参考隔壁python缩进规范

编译

对源程序进行词法分析,语法和语义分析,最后生成可执行文件

  1. 生成一个二进制代码表示的目标程序(.obj),这发生在检查语法正确性之后
  2. 与编译环境提供的库函数进行连接,形成可执行文件(.exe)

C语言特点

  1. 结构化语言
  2. 语句简洁紧凑,使用方便灵活
  3. 易于移植
  4. 强大处理能力
  5. 生成目标代码质量高、运行效率高
  6. 不足
    • 数据类型检查不严格
    • 表达式出现二义性
    • 不能检查数据越界
    • 初学者难掌握运算符优先级和结合性概念
    • ……

第二章 if分段与for循环

这章就会涉及简单的分支和循环结构,以及C语言程序的代码基本架构,但是,这之中还是会涉及以些深层的本质内容,如main函数等,本章学习要求更多就只需要简单记忆和学会使用if和for语句

  1. main函数

    • 任何程序有且仅有一个main函数,程序运行首先从main函数开始
    • 从本质上来讲,main就是一个函数,因此它拥有一个函数要求的所有内容
1
2
3
4
int main(void){                 /*()中的void表示函数无需参数输入,在进阶使用会改变*/
/*You should put your code here*/
return 0; /*main函数根据定义为int 整型,因此需要return一个整数,通常为0*/
}
  1. 算数运算和赋值运算
    • 算数运算:
运算符 + - * / %
名称 (整)除 取模(取余)
优先级
两个除法操作须注意一下:

/(除法运算符)

/ 是除法运算符,用于计算两个数的商。它的行为取决于操作数的类型(整数或浮点数)

数据类型要求

  • 操作数:可以是整数类型(intlongshortchar 等)或浮点数类型(floatdouble)。
  • 结果:结果的类型取决于操作数的类型组合。

不同数据类型处理

  1. 整数除法
    • 如果两个操作数都是整数类型,结果也是整数类型(intlong),并且会进行截断(丢弃小数部分)。
    • 例如:
      1
      2
      3
      int a = 10;
      int b = 3;
      int result = a / b; // result = 3
  2. 浮点数除法
    • 如果至少有一个操作数是浮点数类型(floatdouble),结果会是浮点数类型(floatdouble),并且会保留小数部分。
    • 例如:
      1
      2
      3
      float a = 10.0f;
      int b = 3;
      float result = a / b; // result = 3.333333
  3. 混合类型除法
    • 如果一个操作数是整数类型,另一个是浮点数类型,整数操作数会被隐式转换为浮点数类型,结果也是浮点数类型。
    • 例如:
      1
      2
      3
      int a = 10;
      float b = 3.0f;
      float result = a / b; // result = 3.333333

2. %(取模运算符)

% 是取模运算符,用于计算两个整数相除后的余数。它只能用于整数类型,不能用于浮点数。

数据类型要求

  • 操作数:两个操作数都必须是整数类型(intlongshortchar 等)。
  • 结果:结果的类型与操作数的类型一致,通常是操作数中较大的类型。

不同数据类型处理

  • 如果操作数是 int,结果也是 int
  • 如果操作数是 long,结果是 long
  • 如果操作数是 charshort,它们会被隐式提升为 int,结果也是 int

示例

1
2
3
int a = 10;
int b = 3;
int result = a % b; // result = 1

注意事项

  • 取模运算符 % 的第二个操作数不能为零,否则会导致运行时错误。
  • 对于负数,取模运算的结果取决于编译器的实现。在 C 语言中,a % b 的结果符号与 a 的符号一致。

  1. printf(格式控制字符串,输出参数1,……)

  2. scanf(格式控制字符串,输入参数1,……)

    • 格式控制字符串中尽量不要出现普通字符
      如"%d and %d"输入中须有and存在
  3. 表达式的值

    • 赋值句:返回赋值号左侧变量的最终值(若数组整体赋值返回值为何(不可以这么干))
    • 表达式:返回值为表达式结果
  4. if语句中判断条件若无关系运算符,则其中语句等价于:表达式!=0

1
2
3
4
5
if (表达式){
语句1
}else{
语句2
}
  1. 测试程序数据边界条件单独考虑
  2. math.h函数库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <math.h>

// 1. 绝对值函数
double result1 = fabs(x); // 计算 x 的绝对值

// 2. 平方根函数
double result2 = sqrt(x); // 计算 x 的平方根

// 3. 幂函数
double result3 = pow(base, exponent); // 计算 base 的 exponent 次幂

// 4. 三角函数
double result4 = sin(angle); // 计算 angle(弧度)的正弦值
double result5 = cos(angle); // 计算 angle(弧度)的余弦值
double result6 = tan(angle); // 计算 angle(弧度)的正切值

// 5. 指数函数
double result7 = exp(x); // 计算 e 的 x 次幂

// 6. 自然对数函数
double result8 = log(x); // 计算 x 的自然对数

// 7. 取整函数
double result9 = floor(x); // 计算不大于 x 的最大整数
double result10 = ceil(x); // 计算不小于 x 的最小整数

// 8. 取最大值和最小值
double result11 = fmax(x, y); // 返回 x 和 y 中的较大值
double result12 = fmin(x, y); // 返回 x 和 y 中的较小值

// 9. 四舍五入函数
double result13 = round(x); // 返回 x 的四舍五入值

// 10. 浮点数取模
double result14 = fmod(x, y); // 返回 x 除以 y 的余数
  1. for循环

    • 表达式1:为循环变量赋初值
    • 表达式2:循环条件
    • 表达式3:设置步长
    • 循环体语句:只能是一条语句复合语句在语法上被认为是一条语句
    • for循环用于循环次数已知的循环时
1
2
for(表达式1; 表达式2; 表达式3)
循环体语句;

第三章 分支结构

  1. if else语句:内嵌语句,只允许一条语句

  2. switch语句:

1
2
3
4
5
6
7
8
switch(表达式){
case 常量表达式1:语句段1;break;
case 常量表达式2:语句段2;break;
case 常量表达式3:语句段3;break;
……
case 常量表达式n:语句段n;break;
default : 语句段n+1;break;
}
  • 所有常量表达式的值不能都相等
  • 每个语句段可以包括一条或多条语句,也可以为空语句
  • default可省略(相当于没有else),在C语言中,switch语句是可以没有case部分,只有default部分的。这种情况下,程序就会执行default部分的代码。这种用法通常被称为"空的switch",它可以用来指示某些特定条件下不需要执行任何操作。
  • 在switch中不用break语句,从第一个符合的case进入,执行后面所有语句段
  • 无论default放在哪,所有case不进入都会进入default
  1. if-else的嵌套:else在花括号以外与最近的未匹配的if语句对应

第四章 while循环

  1. while语句
1
2
while(表达式)
循环体语句;
  • 表达式可以是任意合法语句,循环体语句只能是一条语句
  1. do-while语句
1
2
3
do{
循环体语句
}while(表达式);
  • 无论循环条件的值为何,至少会执行一次循环体
  1. break语句&continue语句

  2. 嵌套循环,内外层循环变量不能相同

  3. 贪心算法


第五章 函数

  1. 定义:函数是一个完成特定工作的独立程序模块,包括库函数和自定义函数两种

  2. 构成:

1
2
3
4
函数类型 函数名(函数名)			/*函数首部*/
{
函数实现过程 /*函数体*/
}
  • 函数首部:函数首部后面不能加分号,和函数体一起构成完整的函数定义

    • 函数类型:函数结果返回的类型,若不用返回结果类型为void,默认定义为int
    • 函数名
    • 形参表:各个形参之间用逗号分隔,每个形参前面的类型必须分别写明
  1. 形参vs实参

    1. 实参:可以是常量、变量和表达式
    2. 形参:只能是变量,用于接收实参传递来的值
    3. 形参和实参必须一一对应,两者数量相同,类型尽量一致
  2. 函数的调用:

    1. 若自定义函数在主调函数之前定义,无需声明
    2. 若自定义函数的定义在主调函数之后,须在调用之前加上函数原型声明
      • 函数声明是一条C语句,而函数定义是函数首部不是语句,故不能加分号
      • 若函数声明在其他函数内,仅在声明的函数中可调用
      • 若定义后声明不会影响
  3. 结构化程序设计思想

    1. 自顶向下分析问题的方法:将大问题拆分
    2. 模块化设计:
      1. 一个模块只完成一个特定的功能
      2. 模块之间指通过参数进行调用
      3. 一个模块只有一个入口和出口
      4. 慎用全局变量
    3. 原则:
      1. 对变量、函数、常量等命名时,要见名知义
      2. 添加必要的注释
      3. 良好缩进格式
  4. 局部变量vs全局变量

    1. 局部变量:
      4. 定义在函数内部的变量
      5. 若在复合语句中定义的变量,作用范围仅限于复合语句
      6. 局部变量一般定义在函数或复合语句开始处,标准C规定不能定义在中间位置
    2. 全局变量:
      7. 一般情况下把全局变量定义在程序的最前面
    3. 相较:
      • 二者作用范围不同,故可同名
      • 同名时:在该函数内局部变量起作用复合语句中局部变量起作用
  5. 变量

    1. 自动变量:函数被调用时自动分配存储空间,auto可忽略
    2. 静态变量:生存周期持续到程序结束,static
    3. 自动变量若没赋初值,存储单元中将是随机值,静态变量则为1

第六章 数据类型

  1. 数据存储:

    • 整型数据:
      • 正数:
        • 原码、反码和补码相同:符号位为0,其余为数的二进制表示
      • 负数:
        • 原码:符号位为1,其余为数的绝对值的二进制表示
        • 反码:符号位为1,其余位取反
        • 补码:符号位为1,对反码加1
      • 整数的加法可以用补码加法直接实现
    • 实数存储:
      • 分为符号位、阶码和尾数三部分
    • 字符型数据存储:
      • 存储ASCⅡ码
  2. image-20231116103427030

  3. 整型

    1. 整数的表示:

      • 十进制:首位数字不能是0
      • 八进制:首位数字必须是0
      • 十六进制:必有前缀0x或0X
    2. 整数的类型:

      • 后缀l或L表示long型常量
      • 后缀u或U表示unsigned型常量
      • 如果整数后面没有出现字母,就根据整型常量的值确定它的类型
    3. 格式控制说明

数据类型 十进制 八进制 十六进制
int %d %o %x
long %ld %lo %lx
unsigned %u %o %x
unsigned long %lu %lo %lx
  1. 无论几进制输出后省略前缀

  2. 字符型

    1. 字符型:

      • 在内存中占用一个字节,用于存储ASCⅡ码
      • 因为字符型变量的值可以是字符或整数,整型变量和字符型变量的定义和值可以互换
    2. 字符型常量

      1. ASCⅡ字符集

      2. 转义字符:反斜杠加上一个字符或数字组成

字符 含义
\n 换行
\t 横向跳格
\ 反斜杠
" 双引号
' 单引号
\ddd 1~3位八进制整数所表示的字符
\xhh 1~2位十六进制整数所代表的字符
  1. getchar()函数与putchar()函数
    • 仅能处理单个字符的输入和输出
1
2
3
char ch;
scanf("%3c",&ch); /*取输入框3个字符,但ch只存储 其中第一个字符*/
printf("%c",ch); /*输出第一个字符*/
  1. 实型

  2. 单精度浮点型&双精度浮点型

  3. 实型常量:

    1. 都是双精度浮点型
    2. 浮点表示法:小数
    3. 科学计数法:正负号、数字和e(或E)组成
  4. 格式控制说明:

函数 数据类型 格式 含义
printf float %f 以小数形式输出浮点数(保留6位小数)
double %e 以指数形式输出浮点数(小数点前有且仅有一位非零的数字)
scanf float %f 以小数形式或指数形式输入一个单精度浮点数
%e
double %lf 以小数形式或指数形式输入一个双精度浮点数
%le
  1. 类型转换

  2. 自动类型转换

    1. 非赋值运算:image-20231116211606755
  1. 赋值运算的类型转换
	 - 将赋值号**右侧表达式的类型自动转换成赋值号左侧变量类型**
	 - 若左右类型不同会导致精度的降低
  1. 强制类型转换

    1. 强制类型转换并不改变数据的定义
    2. 强制类型转换是运算符,不是函数
  2. 移位运算

    1. 实现方式:
      1. 循环移位:移入的位等于移出的位
      2. 逻辑移位:移出的位丢失,移入的位取0
      3. 算数移位:移出的位丢失,左移入的位取0,右移入的位取符号位
      4. 操作数的移位不改变原操作数的值

第七章 数组

  1. 数组(数字)

    1. 定义:

      1
      类型名 数组名[数组长度]
      • 数组长度须为常量
      • 数组内每个元素具有相同的数据类型
      • 数组名表示该数组所分配连续内存空间中第一个单元的地址,由于数组空间一经分配后,在运行过程中不会改变,因此数组名是一个地址常量,不允许修改
    2. 初始化:

      • 初值表
        • 对于任意维度数组,初值表若仅包含定义中的部分空间,剩余自动赋0
        • 对于多维度的数组,仅可省略第一个长度
      • 动态数组自动赋随机值,静态数组自动赋0
  2. 字符串

    1. 用双引号括起来的字符序列,以’\0’结束

    2. 数组长度至少是字符串的有效长度+1

    3. 两种输入方式

      1
      2
      3
      4
      5
      6
      /*输入1*/
      while((ch[k]=getchar())!='\n'){ /*逐个加入*/
      k++;
      }
      /*输入2*/
      scanf("%[^\n]s",ch); /*直到换行符结束读入*/

动态地址分配操作

动态地址分配是指程序在运行的过程中,根据需要从操作系统中申请一块内存空间,并在不再需要时归还给操作系统。常见的动态地址分配方式包括 malloccallocreallocfree 等操作。

以下是这些操作的说明:

malloc

malloc 函数用于动态分配一块指定大小的内存空间,并返回指向该内存空间的指针。其函数原型为:

1
void *malloc(size_t size);

其中,size 参数表示需要分配的内存大小(以字节为单位)。如果分配成功,则返回一个指针,指向所分配的内存空间的起始位置;否则,返回空指针 NULL

1
2
3
4
int *p = (int *)malloc(sizeof(int) * n);
if (p == NULL) {
printf("内存分配失败\n");
}

上述代码示例中,使用 malloc 动态分配了 n 个整数大小的内存空间,并将指针类型强制转换为 int* 类型,以便后续使用。

calloc

calloc 函数与 malloc 类似,也用于动态分配内存空间,但它可以在分配内存的同时初始化所有位元为零。其函数原型为:

1
void *calloc(size_t num, size_t size);

其中,num 表示要分配的元素数量,size 表示每个元素的大小。calloc 函数分配的内存块大小为 num * size 字节,并返回指向该内存块起始位置的指针。

1
2
3
4
int *p = (int *)calloc(n, sizeof(int));
if (p == NULL) {
printf("内存分配失败\n");
}

上述代码示例中,使用 calloc 分配了 n 个整数大小的内存空间,并初始化为零。

realloc

realloc 函数用于重新分配之前已经分配的内存空间,可以扩大或缩小内存空间。其函数原型为:

1
void *realloc(void *ptr, size_t size);

其中,ptr 参数是之前分配内存时返回的指针,size 表示需要重新分配的内存大小。如果 ptr 是空指针,则 realloc 的功能与 malloc 相同;如果 size 的值为零,则 realloc 的功能与 free 相同。

1
2
3
4
5
6
7
8
9
int *p = (int *)malloc(sizeof(int) * n);
if (p == NULL) {
printf("内存分配失败\n");
}

p = (int *)realloc(p, sizeof(int) * m);
if (p == NULL) {
printf("内存分配失败\n");
}

上述代码示例中,首先使用 malloc 分配了 n 个整数大小的内存空间,并将指针赋值给变量 p。然后使用 reallocp 所指向的内存空间扩大为 m 个整数大小的内存空间。如果重新分配失败,则返回空指针 NULL

free

free 函数用于释放之前已经分配的内存空间。其函数原型为:

1
void free(void *ptr);

其中,ptr 参数是指向之前分配的内存空间的指针。使用 free 函数释放内存空间后,该指针不再有效,不能再使用它来访问已经释放的内存空间。

1
2
3
4
5
6
7
8
int *p = (int *)malloc(sizeof(int) * n);
if (p == NULL) {
printf("内存分配失败\n");
}

// 使用动态分配的内存空间

free(p);

上述代码示例中,首先使用 malloc 分配了 n 个整数大小的内存空间,并将指针赋值给变量 p。使用完内存空间后,使用 free 函数释放了内存。


第八章 指针

地址:

对内存单元的表示编号

指针变量

  • 定义:

    1
    类型名 *指针变量名;
    • 类型名指定指针变量的类型,指针变量名是指针变量的名称,必须是一个合法的标识符。

    • 指针值可以是特殊地址0,也可以是一个代表机器地址的正整数

  • 不同类型的指针变量所占的空间相同

  • 特殊指针0或者NULL为空指针,不指向任何单元

  • 指针变量定义后,一般只能指向相同类型的变量

  • 基本运算

  • &:

    • 取地址
  • *:

    • 访问指针所指向内容

    • 间接访问
      • 通过指针中间变量进行访问
      • 直接访问则直接通过变量名访问
      • 间接访问动态性更强,更灵活
    • 注意点:

      *优先值低于++,故 *p++等价于 *(p++)

      1
      2
      3
      int a=1,x,*p;
      p=&a;
      x=*p++

      该代码段运行结果为,将a的值赋值给x,p指向a后一个内存单位

  • 初始化

    • 可以用初始化了的指针变量给另一个指针变量作初始化值
    • 不能用数值作为指针变量的初值,但可以将一个指针变量初始化为一个空指针
    • 指针如果没有被赋值,其值不确定,指向不确定单元,不可轻易使用

数组、指针和地址

  • 基地址:内存中存储数组的起始位置

  • 数组名其实为数组的基地址,但与指针不同

    1
    2
    3
    4
    5
    int a[100],*p
    p=a+1;
    p=&a[1];
    /*以上两句等价*/
    /*p=a+1合法,而a=a+1不合法*/

第九章 结构

结构类型是一种允许程序员把一些数据分量聚合成一个整体的数据类型

定义与初始化:

  • 结构类型是由用户根据需要,按照规定的格式自行定义的数据类型

    1
    2
    3
    4
    5
    6
    7
    struct 结构名{					/*struct为定义结构类型的关键字*/
    /*struct与结构名两者合起来共同组成结 构类型名*/
    类型名 结构成员名1; /*结构成员又称结构分量*/
    类型名 结构成员名2;
    ……
    类型名 结构成员名n;
    }; /*定义以分号结束,故视为一条语句*/
  • 嵌套定义:

    • 在定义嵌套的结构类型时,必须先定义成员的结构类型,再定义主结构类型
  • 三种定义方式以及初始化:

    1. 单独定义

      • 先定义一个结构类型,再定义这种结构类型的变量

      • 关键字struct和结构名必须联合使用,因为它们合起来表示一个数据类型名

    2. 混合定义

      • 在定义结构变量时同时定义结构变量

        1
        2
        3
        4
        5
        6
        struct 结构名{					
        类型名 结构成员名1;
        类型名 结构成员名2;
        ……
        类型名 结构成员名n;
        }结构变量名表;
    3. 无类型名定义

      • 在定义时省略结构名

        1
        2
        3
        4
        5
        6
        struct {					
        类型名 结构成员名1;
        类型名 结构成员名2;
        ……
        类型名 结构成员名n;
        }结构变量名表;
      • 由于没有给出结构名,在定义此语句后面无法再定义这个类型的其他结构变量

    4. 初始化:

      • 采用初始化表的方法,大括号内各项数据间用逗号隔开,将大括号内的数据项按顺序对应地赋给结构变量内各个成员,并且要求数据类型一致
  • 通常,一个结构类型变量所占的内存空间是各个成员所占空间之和。可以用**sizeof(struct stu)sizeof(stu)**计算所需存储空间(假定定义了一个stu)

结构变量的使用

  1. 引用:

    1. 结构成员操作符“.”来引用结构成员

      1
      结构变量名.结构成员名
      • 由于结构成员运算符的优先级属最高级别,一般情况下优先执行
  2. 整体赋值:

    • 允许将一个结构变量的值直接赋给另一个结构变量(唯一整体操作方式
    • 只有相同结构类型的变量之间可以直接赋值
  3. 结构变量

    • 可以传递多个数据且参数形式较简单
    • 对于成员较多的大型结构,参数传递时多进行的结构数据复制使得效率降低

结构数组

  • 定义:

    1
    struct 结构变量名 数组变量名[大小]
  • 初始化:

    初始化格式与二维数组初始化类似

  • 引用:

    结构数组名[下标].结构成员名

结构指针

  • 结构类型的数据往往由多个成员组成,结构指针实际上是结构变量的首地址

  • 用指针访问结构成员:

    1. 用*p访问:
    1
    (*p).num=101;
    • 其中(*p)表示的是p指向的结构变量

    • 括号必不可少(“*”的优先级低于“.”)

    • 若无括号,*p.num等价于*(p.num)

    1. 用指向运算符->访问:
1
p->num=101;
  • 两种访问方式结果一致,而在用指针访问时,通常使用指向运算符->

第十章 函数与程序结构

结构化的项目建构思想

  • “自顶向下,逐步求精,函数实现”
    • 先考虑问题的总体步骤,后考虑步骤的细节
    • 将大的步骤拆分为子步骤的序列,逐步明晰实现过程
    • 将最终小目标由函数实现
  • 注意点:
    1. 限制函数长度
    2. 避免函数功能间的重复
    3. 减少全局变量的使用

递归

这里就少些一点了。

  1. 核心:递归式&递归出口
  2. 间接引用自己(like f(x)=不啦不啦g(x),g(x)=不啦不啦f(x),understand?)

C语言的编译预处理

不属于C语言中真正的语句,但增强了C语言的编译功能,改进了C语言程序设计环境,提高编译效率

  1. 文件包含(#include)

    • 把指定的文件模板内容插入到#include所在的位置,编译连接时,系统会将#include指定的文件拼接生成可执行代码

    • 在程序编译时起作用,把指定的文件模板包含进来,当经过链接生成可执行代码后,include便不再存在,所以include不为真正的C语句

    • 文件包含格式

      1
      2
      #include<需包含的文件名>
      #include"需包含的文件名"
    • 若用双引号,则编译程序首先到当前文件工作夹寻找被包含语句,若无,再到系统include文件夹中查找文件,若为自己编写头文件,须用双引号

    • 头文件:

      • .h文件

      • 既有系统头文件,也可以自己编写头文件

      • ANSI中常见头文件

头文件名 作用
ctype.h 字符处理
math.h 与数学处理函数有关的说明与定义
stdio.h 输入输出函数中使用的有关说明与定义
string.h 字符串函数的有关说明和定义
stddef.h 定义某些常用内容
stdlib.h 杂项说明
time.h 支持系统时间函数
  1. 宏定义(#define)

    • 不为真正的C语句

    • 宏名可以按照C语言标识符规定自己定义,常采用大写字母串

    • 宏名与宏定义字符串间用空格分隔

1
#define 宏名 宏定义字符串
  • 用途:

    • 符号常量
    • 简单的函数功能实现
    • 简化多次书写的相同内容
  • 带参数宏定义:

    • 宏替换:不做计算,直接替换
  1. 条件编译

    1
    2
    3
    4
    5
    6
    #define FLAG 1
    #if FLAG /*条件仅能为宏名*/
    程序段1
    #else
    程序段2
    #endif
  2. 更多请见 #指令 部分内容

大程序

  • 一个大程序可以由几个程序文件模块组成

  • 每个程序文件模块又可能包含若干个函数

  • 整个程序只允许有一个main()函数,包含main()函数的模块叫主模块

  • 文件模块间通信:

    1. 外部变量

      • 全局变量只能在某个模块中定义一次,若其他模块使用,则需通过外部变量声明
    1
    extern 类型名 变量名表;
     只起说明作用,不分配存储单元,对应的存储单元在全局变量定义时分配
    
    1. 静态全局变量

      • 若只有一个模块,与一般的全局变量作用完全相同
      • 若存在多个模块,则其作用范围仅限于当前文件模块
    2. 函数与程序模块文件

    1
    extern 函数类型 函数名(参数说明表);
    • 一般情况下,extern可省略,编译程序如果在当前文件模块中找不到函数定义体,自动认为该函数是外部函数

第十一章 指针进阶

指针数组

1
类型名 *数组名[数组长度];				/*类型名指定数组元素所指向的变量类型*/

指向指针的指针

1
类型名 **变量名;
  • 二维数组的指针形式:

    • 数组名a是一个二级指针,a[i]是一个一级指针
    • 需要注意判断等价关系
    • 行地址:二级指针;列地址:一级指针

命令行参数

  • 源程序经编译和连接后会生成可执行程序
1
2
3
4
int main(int argc,char *argv[])
{

}
  • C语言程序中,main函数可以有两个参数,用于接收命令行参数

  • argc接收命令行参数个数(包括命令

  • argv接收以字符串常量形式存放的命令行参数(包括命令本身也作为一个参数

函数指针

1
类型名(*变量名)(参数类型表);
  • 赋值时,该函数必须已定义或声明,且函数返回值的类型和函数指针的类型一致
1
(*函数指针名)(参数表);

链表

  • 这个就不多说了,主要有几点
    1. 每个结点创建的时候要对指针进行动态地址分配
    2. 结点不是连续分布,所以不可用++操作

第十二章 文件

文件的概念

  • 文件系统功能是操作系统重要功能和组成部分
  • 文件,指驻留在外部介质中的一个有序数据集
    • 程序文件:源文件、目标程序文件、可执行程序
    • 数据文件:输入输出文件
  • 数据种类:
    1. 文本文件:
      • 字符ASCⅡ码值进行存储与编码的文件
    2. 二进制文件:
      • 存储二进制数据的文件
  • C语言把文件看作数据流,并将数据按顺序以一维方式组织存储

缓冲文件系统

  1. 缓冲文件系统:
    • 系统在进行文件操作时自动为每一个文件分配一块文件内存缓冲区(内存单元)
  2. 非缓冲文件系统:
    • 文件缓冲区不是由系统自动分配,而需要编程者在程序中用C语句实现分配

自定义类型

1
typeof <已有类型名><新类型名>;
  • typeof为关键字

  • 已有类型包括C语言中规定的类型和一定义过的自定义类型,新类型名可由一个和多个重新定义的类型名组成

  • 一般要求重新定义的类型名用大写

  • 基本使用方式:

    1. 写出原有类型定义变量的语句
    2. 用新类型名替换变量名
    3. 开头加上typeof

文件结构

1
2
3
4
5
6
7
8
9
10
11
typeof struct{
short level; /*缓冲区使用量*/
unsigned flags; /*文件状态标志*/
char fd; /*文件描述符*/
short bsize; /*缓冲区大小*/
unsigned char *buffer; /*文件缓冲区的首地址*/
unsigned char *curp; /*指向文件缓存区的工作指针*/
unsigned char hold; /*其他信息*/
unsigned istemp;
short token;
} FILE;
  • 文件结构在头文件stdio.h中定义

文件类型指针

由于文件缓冲区由系统自动分配,并不像数组那样可以通过数组名加下标进行定义

1
FILE *fp;
  • 文件指针是特殊指针,指向的是文件类型结构,而FILE结构中curp成员指示文件缓冲区中数据存取的位置

  • 文件指针不可进行++或*操作,++操作意味着指向下一个FILE结构

  • 文件操作具有顺序性特点,前一个数据取出后,下次将顺序取后一个数据

文件控制模块&文件处理步骤

  • FCB包括:文件属性、文件名、驱动器号、扩展名、文件长度以及文件记录状态等信息
  • 处理步骤:
    1. 定义文件指针
    2. 打开文件(此处书本说是指向磁盘文件缓存区,应该是指向文件类型结构不会有歧义一点)
    3. 文件处理
    4. 关闭文件

文件操作函数

先粗略的总结一下一些特性的内容以及函数调用的形式:

  1. 打开文件 fopen()
1
fopen("文件名","文件打开方式");
  • 文件名须指出对哪个具体文件进行操作,一般要指定文件的路径,若不写出路径,则默认与应用程序的当前路径相同。

  • 文件名路径中"\“需用”\\“,因为C说认为”\"是转义符

文本文件(二进制文件)
使用方式 含义
“r”(“rb”) 打开文件进行只读
“w”(“wb”) 建立新文件进行只写
“a”(“ab”) 打开文件进行追加(我说append就懂了吧)
“r+”(“rb+”) 打开文件进行读/写
“w+”(“wb+”) 建立新文件进行读/写
“a+”(“ab+”) 打开文件进行读/写/追加
  • 文件打开的实质:

    • 把磁盘文件与文件缓存区对应起来,并返回FILE结构地址
    • exit(0)是系统标准函数,关闭所有打开文件,并终止程序的执行,参数0表示程序正常结束,非0参数为异常
  • C语言允许同时打开多文件,不同文件采用不同文件指针指示,但不允许同一个文件关闭前被再次打开

  1. 关闭文件 fclose(文件指针)

    • 关闭文件,确保数据完整写入文件,同时释放不用的文件缓存区单元
  2. 文件读写


#指令

在 C 语言中,# 指令是预处理器指令的一种形式。预处理器指令是在编译源代码之前被处理的指令。# 指令以 # 开头,通常出现在源代码文件的开头或空行之后。

以下是一些常见的 # 指令:

指令 解释
#include <header_file> 插入指定的头文件到当前文件中
#define macro_name value 定义一个宏
#ifdef macro_name 如果宏已定义,则编译下面的代码
#ifndef macro_name 如果宏未定义,则编译下面的代码
#endif 结束一个 #ifdef 或 #ifndef 块
#if expression 编译下面的代码,如果指定的表达式为真
#else 当 #ifdef 或 #ifndef 中的宏未定义时,编译下面的代码
#elif expression 当 #if 中的表达式为假时,如果指定的表达式为真,编译下面的代码
#undef macro_name 取消已定义的宏

这些指令可以帮助我们在编译期间执行一些特定的操作,例如包含其他文件、定义宏、编写条件编译代码等。


附表:运算符

优先级 运算符 名称 说明
1 () 圆括号 初等运算符
[] 下标
-> 指针引用结构体成员
. 取结构体变量成员
2 逻辑非 单目运算符
~ 按位取反
+ 正好
- 负号
(类型名) 强制类型转换
* 取指针内容
& 取地址
++ 自增
自减
sizeof 长度运算符 返回表达式或数据类型的内存字节数
3 * 相乘 算术运算
/ 相除 两操作数中若存在负数,取整方向不定
% 取模
4 + 相加
- 相减
5 << 左移 移位运算
>> 右移
6 > 大于 关系运算
< 小于
>= 大于等于
<= 小于等于
7 == 等于
!= 不等于
8 & 按位与 位逻辑运算
9 ^ 按位异或
10 | 按位或
11 && 逻辑与 逻辑运算
逻辑的真假判断为是否为0
12 || 逻辑或
13 ?= 条件运算 三目运算
14 =,+=,-=,*=,/=,%= 赋值运算
&=,^=,>>=,<<=
15 , 逗号运算符 整个表达式结果和类型等于最后一个表达式