笔记 | C 语言复习框架 ( 应试篇 )

从标题中得知,本系列文章是围绕 “C语言” 展开学习的笔记总结。且目的很明确,笔记内容偏应试,适用于计算机等级考试、考研专业课 ( C语言 ) 等的复习使用。文章推崇总结性、对比性的学习方法,对于模糊的知识模块需自行查阅参考书目,深化理解以取得理想效果。

针对 C 语言程序,推荐几本辅导复习的书目:

基础篇

  • 《谭浩强: C 语言程序设计》: 必不可少的经典教程,权威性的标准答案源 ( 当然仅限于应试范围 )。例如,因讨论条件而异,如编译系统不同,部分题目的答案就具有了争议性。
  • 《明解 C 语言》: 入门基础教学。值得称赞的是,每个知识模块都附有实例,且实例的源码结构清晰,代码规范及注释到位,适合入门使用。

进阶篇

  • 《征服 C 指针》: C 语言的学习过程中,指针的运用是最大的难关。无论是在实际应用、应试中都是不可忽视的。对于作者前桥和弥,其一针见血的文风,在掌握一定基础之后,是深入了解 C 语言的一位不可多得 “良师益友” ( 书中有不少作者交谈式的独白,别有一番阅读风味 )。

内容总览

壹 程序设计和 C 语言

计算机程序与语言

  • 程序:计算机能识别和执行的指令。
  • 语言:人和计算机交流、人和计算机能识别的语言。
  • 计算机语言发展阶段:
机器语言 符号语言 高级语言 ( 面向过程、面向对象 )
0和1指针 英文、数字表示指令 人类自然语言和数字语言

C语言

  • 特点:
    • 语言简洁、紧凑,使用方便、灵活;
    • 运算符丰富 ( 单目、双目、三目运算符 );
    • 数据类型丰富,其中包括:整型、浮点型、字符型、数组类型、指针类型、结构体类型、共用体类型、枚举型 );
    • 结构体控制语句;
    • 直接访问物理地址 ( 对硬件直接操作 );
    • 可移植性好.
  • 结构:
    • 以程序由一个或着多个 源文件 组成, 源文件中包括:
      • 预处理命令 ( #include、#define、#typedef 等 );
      • 全局声明 ( 全局变量、局部变量 );
      • 函数定义 ( 参考函数原型 ).
    • 函数是 C 程序的主要组成部分。
    • 函数包括 函数首部函数体。 函数体包括 声明部分执行部分
    • 程序总是从 main() 函数开始执行的。且 main() 函数有且仅有一个。
    • C 程序对计算机的操作有 C 语言完成。
    • 数据声明和语句必须有分号 ( 作为结束 )。
    • C 本身不提供输入输出语句。

程序设计的任务

  • 程序设计的任务:
    • 问题分析;
    • 设计算法;
    • 编写程序;
    • 对源文件编辑、编译 ( *.obj ) 和连接 ( *.exe );
    • 运行程序并分析结果;
    • 编写程序文档.
  • 对于编译,预编译和连接的概念及比对:
    • 编译:检索语言错误;把源程序转为二进制形式的目标程序。
    • 预编译:通过预处理得到的信息与程序其他部分一起,组成完整的、可以正式编译的源程序。
    • 连接:与函数库相连接。

贰 程序之魂:算法

引入

  • 对数据的描述:所用数据的类型和数据的 组织形式

    组织形式:数据结构,特定关系的数据元素的集合。

  • 对操作的描述:计算机进行操作的步骤 — 算法

  • 从简理解:数据结构 + 算法 = 程序

算法

  • 概念:对特定问题求解的方法和描述。
  • 特征:
    • 有穷性:有穷时间 执行结束;
    • 确定性:算法 唯一执行路径,既相同输入执行相同路径;
    • 可行性:有限次
    • 零或一个以上的 输入
    • 一个或以上的 输出
  • 要求:
    • 正确性;
    • 可读性;
    • 健壮性;
    • 效率与低存储量需求 ( 时间复杂度和空间复杂度 )
  • 时间复杂度:
    • 时间复杂度:也称渐进时间复杂度,即算法执行时间的增长率和 f(n) 的增长率相同。
    • 渐进时间复杂度:$T(n) = O(f(n))$
    • $f(n)$ 为问题规模 n 的某个函数。
    • 算法中的基本运算 ( 最深层循环内的语句 ) 的频度与 T(n) 同数量级。
  • 空间复杂度:
    • 空间复杂度:算法所需存储空间的量度。
    • 渐进空间复杂度:$S(n) = O(f(n))$
    • 原地工作:额外空间相对输入的数据量来说是常数。

三种基本结构和改进流程图

  • 三种基本结构:
    • 顺序结构;
    • 选择结构;
    • 循环结构:当型循环结构 / 直到型循环结构;
  • 改进的流程图:N-S 流程图

结构化程序设计方法

  • 自顶向下;
  • 逐步细化;
  • 模块化设计:分而治之注意模块独立性
  • 结构化编码;

叁 简单的 C 语言程序设计

数据的表现形式及运算

常量

  • 概念:程序运行期间,其值不能改变。
  • 类型:

    • 整型常量
    • 字符常量 ( 与常变量作比对 [ 注释1 ] )
      • 普通字符
      • 转移字符:\n, \t, \012 ( 8 进制 ), \x41 ( 16 进制 )
      • 符号常量:#define PI 3.14159
    • 实型常量

      • 10 进制小数形式:3.14L
      • 指数形式 ( 科学计数法 ):

        1
        2
        3
        8.7e-25(正确);  
        8.7e-2.5(错误);
        87e+25(正确);

[ 注释1 ] 符号常量与常变量的比较。

符号常量 常变量
不占内存单元,预编译后符号不复存在 占存储单元
不能重新赋值 不能改变其值

变量

先定义,后使用。

  • 概念:程序运行期间,其值可以改变。
  • 包含属性:
    • 数据类型 ( 整型、浮点型、字符型 )
    • 存储类别 ( 自动变量,静态变量 )
  • 类型:

    • 常变量:变量存在期间其值不能改变。 const int a = 10
    • 自动变量与静态变量
    • 全局变量与局部变量

      从存储位置、生存周期、作用区域讨论差异性。[ 注释2 ]

  • 标识符:一个对象的名称。除关键字外,字符、数字和下划线组成。且要求只能是字母或下划线开头。

[ 注释2 ] 局部变量与全局变量,自动变量与静态变量,内部函数与外部函数的比较。

局部变量 全局变量
存放于动态存储区 存放于静态存储区 位置
在定义函数内起作用 自定义位置开始,本文件起作用 作用域
函数调用完释放内存 程序结束时释放内存 生存期
  • 静态的局部变量,存放于静态存储区,程序结束时释放内存。
  • 静态的全局变量,不是因声明 static,而误解全局变量才存放于静态存储区。
  • 局部变量,声明存储类型指变量存储区以及产生的生存期问题。
  • 全局变量,声明存储类型指变量作用域的扩展问题。
自动变量 静态变量
1. 声明该变量的语句块被执行结束释放内存() 1. 程序结束时才释放内存
2. 每次函数调用时赋值 2. 保留上一步的赋值
3. 在编时赋予初值0或’\0’
  • 对比 malloc() 函数分配的内存,需调用 free() 函数释放内存 ( )。
内部函数 外部函数 (default)
本文件内使用(不限位置) 可供其他文件使用(不限位置)
定义:static 函数类型 函数名 定义:(extern) 函数类型 函数名

数据类型

  • 基本类型:
关键字 字节 取值范围
整型 int 2/4 $-2^{15}$ ~ $-2^{15}-1$ / $-2^{31}$ ~ $2^{31}-1$
unsigned int 2/4 0 ~ $-2^{16}-1$ / 0 ~ $-2^{32}-1$
字符型 char 1 $-2^7$ ~ $2^7-1$
unsigned char 1 0 ~ $2^8-1$
单浮点 float (有效小数:6) 4
双浮点 double (有效小数:15) 8
  • 关于基本类型的特别说明:
    • 字符是按其 ASCII 形式存储的。
    • 单浮点定义:float a = 3.14f
    • 双浮点定义:double a = 3.14
    • 长浮点定义:long double a = 3.14L
  • 派生类型:
    • 指针类型:指向函数的指针、多重指针
    • 数组类型:指针数组
  • 构造类型:详情见第玖章:构造类型
    • 结构体类型
    • 共同体类型
    • 枚举类型
  • 类型转换:
    • 低精度向高精度转换;
    • 强制转换括号加类型; int a = (int)3.14
    • 多类型变量混合运算,取最高精度的类型;

肆 选择结构程序设计

关系运算符及其优先次序

  • 各类运算符的优先级:

    • 单目运算符 > 双目运算符 (算术、关系、逻辑) > 三目运算符
    • 优先级由高到低排序:
      • 初等运算符:(),[],->,.
      • 单目运算符:!,++,--,~
      • 算术运算符:*,/,%+,-
      • 关系运算符:>,<,>=,<=!=,==
      • 逻辑运算符:&&,||
      • 条件运算符:a > b : a : b
      • 赋值运算符:a += 1
      • 逗号运算符:(a,b)
    • 结合方式:

      同一级的运算符,由结合方式决定优先级。

      • 自左向右:初等、单目、关系、逻辑、逗号运算符
      • 自右向左:条件、赋值运算符

表达式

  • 算术表达式:先乘除模,后加减,再由“自左向右”原则运算。
  • 混合运算
    • 优先级:遵循各运算符的优先次序。
    • 结合性:算术运算符 (自左向右);赋值运算符 (自右向左)。
    • 不同类型的混合运算:结果的类型为 最高精度 的数据类型。

运算符与表达式

  • 关系运算符和关系表达式 ( a+b>c ) -> True or False?

    0 表示假,!0 表示真。

  • 逻辑运算符和逻辑表达式

    • 逻辑运算:5 && 4 => 1;5 && 0 => 0;
    • 按位逻辑:5 & 4 => 4;

      关于逻辑运算与按位逻辑的比较:
      优先级:按位逻辑运算 > 逻辑运算
      按位逻辑的巧用:取最大 max = a | b;取最小 min = a & b

  • 条件运算符和条件表达式:a > b ? a : b

选择结构的嵌套

  • if 语句只有两个分支可供选择,else 总是与它上面最近的未配对的 if() 配对。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    if(express1){
    if(express2){
    ...
    } else {
    ...
    }
    } else {
    if(express3){
    ...
    } else {
    ...
    }
    }
  • switch 语句实现多分支选择结构。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    switch(express1){ // 整型、字符型
    case 常量/常量表达式:
    语句1;
    break; // break 为拦截作用
    case 常量/常量表达式:
    语句2;
    break;
    default: 语句3;
    }

伍 循环结构程序设计

  • while 语句实现:

    1
    2
    3
    4
    5
    express1;
    while(express2){
    express3;
    ...
    }
  • for 语句实现循环:

    1
    2
    3
    for(express1; express2; express3){
    ...
    }
  • do...while() 语句实现循环:

    1
    2
    3
    4
    5
    express1;

    do{
    express3;
    } while(express2);
  • breakcontinuegoto 语句:

    • break:从循环体内跳出循环体。多层嵌套循环,跳出相邻一层循环。
    • continue:提前结束本次循环。
    • goto:跳出多层循环。

陆 数组

概念

  • 一组有序数据的集合。
  • 数组中每一元素同属一个数据类型。
  • sname[0] <=> *(p+0) <=> 第一个数组元素。

定义

一维数组

  • 定义:

    • 类型符 数组名[常量表达式]:正确的定义方式。
    • 类型符 数组名[变量]: 错误的定义方式,不能为变量。
  • 初始化:

    1
    2
    int array[] = {1, 2, 3, 4, 5};  
    in array[5] = {0}; // 5个元素都为 0。
  • 引用:

    1
    2
    3
    int *p = &array[0];	// 等同于 int *p = array
    p++; // 指针运算
    *(p+i); // 取第i位元素

二维数组

  • 定义:类型符 数组名[常量表达式][常量表达式]

  • 初始化:

    1
    2
    3
    4
    5
    6
    int array[2][2] = { {1, 2}, {3, 4} };
    int array[2][2] = { 1, 2, 3, 4 };
    int array[][2] = { {1, 2}, {3, 4} }; // 既只允许最外层元素个数定义时为空

    int array[][2] = { {0}, {3, 4} }; // 正确
    int array[][2] = { {}, {3, 4} }; // 错误
  • 引用:

    1
    2
    3
    int num = array[1][1];
    int *p = array;
    *(*(p+j)+j); // 等同于array[i][j];

字符数组

  • 定义:char array[10]; <=> int array[10];

    字符型数组是以整型形式存放的 ( ASCII )。

  • 初始化:

    • 字符串常量不可以数组形式取具体位置进行元素修改。
    • ( array == “Hello” ) => True or False ?

      False,array 与字符串常量比较的是内存地址。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      char array[0] = 'A';

      char array[] = {"Hello"};
      // 字符数组的存储情况:| H | e | l | l | o | \0 |
      // sizeof() -- 6
      // strlen() -- 5

      char array[] = {'H', 'e', 'l', 'l', 'o'};
      // sizeof() -- 5
      // strlen() -- 5

      int array[] = {"Hello"};
      // sizeof() -- 4
      // strlen() -- 1

      int array[] = {'H', 'e', 'l', 'l', 'o'};
      // sizeof() -- 20
      // strlen() -- 4

      char *array = "Hello"; // 字符串常量
  • 引用:

    • 若字符数组中,存在 ‘\0’ 两个或以上,系统则以第一次出现的位置提前终止字符输出。
    • stdin 和 gets() 搭配,可获得换行符、空格等字符。 ( 需结束标记符来终止输入 )

      1
      2
      3
      4
      5
      scanf("%c", &array[0]);
      printf("%c", array[0]);

      scanf("%s", array);
      printf("%s", array);
  • 应用:

    • gets(字符数组) — 输入一字符串到字符数组中
    • puts(字符数组) — 输出一字符串到终端
    • strlen(字符数组) — 测一字符串的实际长度
    • strcat(char *src1, const char *src2);

      数组src2后接于src1,src1中的’\0’被覆盖。且数组src1必须足够大,以容纳数组src2。

    • strcpy(char *src1, const char *src2);

      数组src1必须足够大,以容纳数组src2。

    • strcmp(const char *src1, const char *src2);

      实际为ASCII的比较,其返回值为 <0、==0,>0 的情况。

    • strlwr(字符串) — 将字符串中大写字母转为小写字母

    • strupr(字符串) — 将字符串中小写字母转为大写字母
    • atoi(字符串) — 字符串转int型
    • atol(字符串) — 字符串转long型
    • atof(字符串) — 字符串转double型

      有几点需要特别声明:
      1) 字符串处理函数需加入头文件:#include <string.h>.
      2) 需掌握字符串函数自定义方法实现。
      3) 大部份字符串处理函数多数以标记量’\0’为临界点,若字符数组中含两个或或以上,需注意实际的结果。
      4) 引用atoi()、atol()、atof()函数需引用#include <stdlib.h>

柒 函数

为什么要用函数

  • 模块化程序设计:每一函数实现一特定的功能,函数的名称既反映功能。
  • 更好地代码复用:使用库函数;使用自己编写的函数。

    代码复用:减少重复编码程序段的工作量。对于所有完成相同功能的组件,应抽象出一个接口,它们都实现该接口。具体在 Java 中,所有完成相同功能的组件都 实现该接口从该抽象类中的继承

定义函数

建立存储空间的声明。

  • 定义函数:函数返回类型 函数名 函数参数 函数体 (变量定义、声明,执行语句)
    • 函数返回类型:基本数据类型 / void型;
    • 函数名:驼峰式命名法;
    • 函数参数:实参、形参.

函数声明

不需要建立存储空间的声明。

  • 函数原型 (Prototype):函数返回类型、函数名、参数类型、参数个数、参数顺序。
  • 函数声明的方法:
    • 使用函数原型;
    • 同一源文件,在调用该函数的前面定义 (可打包到自定义头文件中)。

函数调用

  • 嵌套调用、递归调用 ( 直接或间接调用该函数本身 )。
  • 实参和形参:
    • 概念:
      • 实参:常量、变量或表达式、函数 (返回值)
      • 形参:函数调用期间临时分配内存,值从实参中获得,调用结束后释放内存空间。
    • 实质:值传递、地址传递

捌 指针

指针是什么

  • 指针变量:保存变量地址的变量。
  • 指针类型:

    • 指针类型的变量:存放地址
    • 指针类型的值:对应内存地址存放的值

      swap(int \*a, int \*b); 的案例中可以形象说明两者的区别。

指针移动 (运算:加、减)

  • 对指针加一、减一运算,即地址会增加或减少一单位长度。
  • 单位长度具体指当前指针所指向数据类型的所占空间大小。

指针类型

空指针

  • 确保没有指向任何一个对象的指针,通常以宏定义 NULL(0) 表示空指针的常量值。
  • 关于 NULL0'\0',大部分情况都为零。特别地:

    1
    2
    int *p = 0;	// 正确,编译器将指针指向内存地址为 0 处。 
    int *p = 3; // 错误,赋值的数据类型不相符。

指针类型的派生

  • 指向函数的指针:

    1
    void (*func(int));
  • 指向数组的指针 ( 多重指针 ):

    1
    int (*p)[5];

数组类型的派生

  • 指针数组:

    1
    int *p[5]; // 存放5个指向int类型的指针。
  • 用英语解读各种各样的C语言声明:

C语言 英语表示 中文表示
int huge; huge is int hoge是int型
int huge[10]; huge is array[10] of int hoge是int型的数组
int huge[2][4]; huge is array[2] of array[4] of int hoge是int型的数组的数组
int *huge[10]; huge is array[10] of point to int hoge是指向int型的指针的数组(存放指针变量)
int (*huge)[10]; hoge is pointer to array[10] of int hoge是指向int型的数组的指针
int func(int a); func is function(int a) returning int func是返回int型的函数
int (*func)(int a); func is pointer to function(int a) returning int func是指向返回int型值的函数的指针

指针的应用

指针与数组

  • 一维:

    1
    2
    3
    p[i]	// 等同于 *(p+i)
    i[p] // 等同于 *(i+p)
    &p[i] // 等同于 (p+i),即第i个元素的地址
  • 二维:

    1
    2
    huge[i]		// 等同于 *(huge+i),即第i行的首地址
    *(huge+i)[j] // 等同于 *(*(p+j)+j),即 huge[i][j]

指针与字符串

字符指针变量
  • 定义:

    1
    2
    3
    4
    5
    6
    7
    8
    char *array = "World";
    array = "hello"; // 改变指向

    char array[] = "Hello";
    array = "World"; // 错误的做法

    char *array = "Hello World";
    array += 6; // 改变指向 (首地址改变)
字符数组
  • 定义: int array[] = "Hello"

  • 使用:printf("%c", array[0])

    字符指针变量的值是不能改变的,即已是字符串常量。

    1
    2
    char *array = "Hello";
    array[0] = 'W';

指针与函数

作为参数

即传递的是指向初始元素的指针。

  • 数组名作函数参数:
    • int func( int array[] )
    • int func( int \*array )
  • 多维数组作函数参数:
    • int func( int (\*huge)[10] )
    • int func( int huge[2][4] )
  • 指向函数的指针作函数参数:int func( int (\*p)(int) )
  • 指针数组作main函数形参:int func( int argc, char \*argv[] )

    argv: 文件名 + 其他参数

  • 字符指针作函数参数。

作为返回值

返回指针值的函数,即返回的是地址。比如,返回的指针指向结构体变量、字符变量等。

玖 构造类型:用户自己建立数据结构

结构体类型

  • 定义:

    1
    2
    3
    4
    struct Name {
    int num;
    char word[59];
    } *p, name[5];
  • 初始化:所有成员一起赋值。

  • 使用:

    1
    2
    3
    4
    5
    6
    7
    name[i].num;
    p->word[i];
    (*p).num;

    struct Name *tmp;
    tmp = name;
    (tmp++)->num; // 先'++'操作,后'->'操作
  • 大小:成员变量所占内存长度总和。

共用体类型

  • 定义:

    1
    2
    3
    4
    5
    union Name {
    int num;
    double digital;
    char word;
    } *p, name[5];
  • 初始化:只允许给一个成员变量赋值。

    1
    2
    3
    4
    union Name tmp = {10};
    union Name tmp = {.word = 'Y'};
    t.digital = 2.0;
    t.word = 'N'; // 最终的赋值
  • 使用:

    1
    2
    3
    name[i].num;
    p->word[i];
    (*p).num;
  • 大小:成员变量所占内存长度最大者。

    关于结构体、共用体类型的内存长度问题,遵循 4 字节倍数的原则进行内存布局对齐。

    1
    2
    sizeof(struct Name) // ==> 64 (63)  
    sizeof(union Name) // ==> 4 (4)

枚举类型

  • 定义:

    1
    2
    3
    enum Week {
    sun, mon, tue, wed, thu, fir, sat // 默认参数从0开始
    } week;
  • 初始化:

    1
    2
    3
    4
    enum Week {
    mon = 1, tue = 2, wed = 3,
    thu = 4, fir = 5, sat = 6,sun = 7 // 默认参数从 0 开始
    } week;
  • 使用:week.mon;

Typedef 声明新类型名

  • 含义:引入变量别名,而不是另外地给变量分配空间。
  • 使用:

    1
    2
    3
    4
    typedef int Integer;
    // 若编译器中,int 为 2 字节,满足移值需求可以 long 型替换。
    typedef long Integer;
    Interger num = 1;
  • #define 宏定义的区别:

    • #typedef:编译阶段处理。
    • #define:预编译阶段处理,实质是字符串替换。

拾 文件处理

文件与流

  • stdin:标准输入流,用于读取普通输入的流,在大多数环境中为键盘输入。scanf() 与 getchar() 等函数会从这个流中读取字符。
  • stdout:标准输入流,用于写入普通输入的流,在大多数环境中为输出至显示器界面。printf()、puts() 与 putchar() 等函数会向这个流写入字符。
  • stderr:标准错误流,用于写出错误的流,在大多数环境中为输出至显示器界面。

文件分类

  • ASCII 文件 ( 文本文件 ):每一字节存放一字符的 ASCII 代码。
  • 二进制文件:

    • 优:节约存储空间
    • 劣:精度有限

      例如:整数 10000,ASCII 形式存储空间为 5 字节,二进制形式存储空间为 4 字节。

文件类型指针:FILE 型

  • 需引用 #include <stdio.h>

    指向文件的指针变量并不是指向外部介质上的数据文件开头,而是指向内存中的文件信息区的开头。

打开文件

  • 原型:FILE *fopen(const char *filename, const char *mode);
  • 定义:FILE *fp = fopen("example.txt", "r");
文件类型 文本文件 二进制文件
模式 r w a rb wb ab
只读;只写(文件存在,则长度清零);追加 只读;只写(文件存在,则长度清零);追加
r+ w+ a+ rb+ wb+ ab+
读和写(打开文件);读和写(建立文件;文件存在,则长度清零);读和写(打开文件) 读和写(打开文件;文件存在,则长度清零);读和写(建立文件);读和写(打开文件)

关闭文件

  • 原型:int fclose(FILE *stream);
    • 返回值 ( True:0;False:EOF(-1) )
    • 数据存储的过程:数据 —> 缓存区 (充满) —> 文件
    • 若不关闭文件,将会造成数据丢失。
    • 若突然关闭文件,缓存区传输到文件的过程给中断,造成数据丢失。

顺序读写数据文件

  • 格式化读取文件:

    1
    2
    3
    4
    5
    6
    7
    int fscanf(FILE *stream, const char *format, ...);
    // 返回值:
    // Ture - 返回成功赋值的输入项数
    // False - 返回文件结束标记EOF(-1)

    // 使用实例
    fscanf(fp, "%s%lf%lf", name, &height, &weight);
  • 格式化写入文件:

    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
    int fprintf(FILE *stream, const char *format, ...);
    // 返回值:
    // Ture - 返回发送的字符数
    // False - 返回文件结束标记EOF(-1)

    // 使用实例:获得当前运行时间,并存入文本中
    time_t current = time(NULL);
    struct tm *timer = Localtime(&current);

    // 将日历时间time_t型的值转换为分解时间tm结构体类型的值
    // 其中,tm结构体为:
    struct tm {
    int tm_sec; // 秒(0 - 61)
    int tm_min; // 分 (0 - 59)
    int tm_hour; // 时 (0 - 24)
    int tm_mday; // 日 (1 - 31)
    int tm_mon; // 月 (0 - 11)
    int tm_year; // 从1900至今,经历了多少年
    int tm_wday; // 星期 (0 - 6)
    int tm_yday; // 经历天数 (从1月1日计起)
    int tm_tm_isdst; // 夏时令 (夏季时间将提前1小时)
    };

    fprintf(fp, "%d %d %d %d %d",
    timer->tm_year + 1900, timer->tm_mon + 1,
    timer->tm_day, timer->tm_hour,
    timer->tm_min, timer->tm_sec);
    fclose(fp);
  • 读入/写入一个字符:

    1
    2
    3
    4
    5
    6
    int fgetc(FILE *stream);	// 读入一个字符

    int fputc(FILE *stream); // 写入一个字符
    // 返回值:
    // Ture - 返回所读的字符数
    // False - 返回文件结束标记EOF(-1)
  • 用二进制方式向文件读写一组数据:

    1
    2
    3
    4
    5
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    // 从ptr指向的数组中将最多nmemb个长度为size的元素写入stream指向的流中。

    size_t fread(const void *ptr, size_t size, szie_t nmemb, FILE *stream);
    // 从stream流中读取nmemb个长度为size的元素写入到ptr数组。

随机读写数据文件

文件位置标记及其定位

  • 文件位置标记:文件头、读写当前位置、文件尾。
  • 文件位置标记的定位:fseek(文件类型指针, 位移量, 起始点);
    • 文件开始位置 -> SEEK_SET -> 0
    • 文件当前位置 -> SEEK_CUR -> 1
    • 文件末尾位置 -> SEEK_END -> 2

随机读写

  • 结合 fseek()fread() 函数实现。例如,读取第 1,3,5,7,9 个学生数据并输出。

    1
    2
    3
    4
    for(i = 0; i<10; i+=2){
    fseek(fp, i*sizeof(struct Student), 0);
    fread(&student[i], sizeof(struct Student), 1, fp);
    }