【C】总结复习

249 阅读25分钟

仅供个人学习使用。

第1章 初识C语言

  • 源程序是人们作为字符序列创建出来的,不能直接执行,需要进行编译(翻译),将其变为可执行程序。

  • 源程序中 /**/ 之间的部分是注释。注释可以有多行。在创建程序时,应用简洁的语言在注释中记录下恰当的内容,以供读程序的人参考,包括自己。

  • 下方程序是固定代码。

    #include <stdio.h>
    
    int main(void)
    {
        printf("%d", 15 + 37);
        return 0;
    }
    
  • 请注意不要把 stdio.hstudio.h 混淆。

  • 语句的末尾原则上需要加上分号。

  • 执行程序时,{} 之间的语句会被按顺序执行。

  • 表示换行符的转义字符是 \n,表示响铃(通常发出蜂鸣音)的转义字符是 \a。一般使用反斜线 \ 代替货币符号

  • 像“ABC”、“您好!”这样用双引号括起来的一连串连续排列的文字,称为字符串常量,用来表示字符序列。

  • 能够自由地读取和写入数值等数据的变量,是根据“类型”生成的实体。要使用变量,需要声明变量的类型和名称。int 型表示整数。

  • 变量在生成的时候会被放入不确定的值。因此在声明变量时,除了有特别要求之外,一定要为其赋初始值,进行初始化。

  • 同是在变量中放入数值,“初始化”和“赋值”的区别如下所示。

    • 初始化:在生成变量的时候放入数值。

    • 赋值:在已生成的变量中放入数值。

  • 一次声明多个变量时,用逗号分隔变量名,比如 int a,b;

  • 函数调用就是发出进行某种处理的请求。此时括号中的实参起到了“辅助性的指示”作用。当实参有多个时,需要用逗号隔开。

  • 用于显示的函数有 printf 函数和 puts 函数。

  • printf 函数的第一个实参是格式化字符串。格式化字符串中可以包含用来指定实参的格式的转换说明。格式化字符串中转化说明以外的字符,基本上都会原样输出。

    转换说明 %d 指定了实参要以十进制数的形式显示。

  • puts 函数在输出字符串后,还会输出换行符。

  • scanf 函数是读取通过键盘输入的数值并将其存储在变量中的函数。使用 scanf 函数时,变量名前需要加上 &

    转换说明 %d 指定了读取十进制数。

  • 进行加法运算的符号是 +,进行减法运算的符号是 -,进行乘法运算的符号是 *

/* 求长方形的面积 */
#include <stdio.h>

int main(void)
{
    int width;
    int height;
    
    puts("求长方形的面积。");
    
    printf("长:");
    scanf("%d", &width);
    
    printf("宽:");
    scanf("%d", &height);
    
    printf("面积是 %d。\a\n", width * height);
    
    return 0;
}

第2章 运算和数据类型

  • +* 等可以进行运算的符号称为运算符。根据运算对象——操作数的个数,运算符大致可分为单目运算符、双目运算符、三目运算符三类。

  • 各运算符的优先级有所不同。例如,乘除运算要先于加减运算进行。如果要优先执行某个特定的运算,可以用 () 将该运算括起来。

  • 乘除运算符有双目 * 运算符、/ 运算符、% 运算符三个。双目 * 运算符求两个操作数的积。求商的运算符 / 和求余数的运算符 %,只要操作数中有一个是负数,运算结果就要取决于编译器。另外,% 运算符的操作数的类型必须是整数。

  • 加减运算符有进行加法运算的双目 + 运算符和进行减法运算的双目 - 运算符两个。

  • 单目 + 运算符的功能就是输出操作数本身的值;单目- 运算符的功能就是对运算符进行符号取反操作。

  • 将右操作数的值赋给左操作数的 =,称为(基本)赋值运算符。

  • 由变量和常量,以及连接它们的运算符所构成的是表达式。

  • 在表达式的后面加上分号(;),就形成了表达式语句。

  • 使用“OO运算符”的表达式,其名称就是“OO表达式”。例如,使用赋值运算符 = 的表达式 a=b,就称为赋值表达式。

  • 数据类型实际上相当于一个隐藏着各种属性的设计蓝图(可以想象成做章鱼小丸子用的模具),包含某个类型的对象(变量),就是根据这个设计蓝图创建出来的实体(相当于用模具做出来的章鱼小丸子)。

  • 整数型的 int 类型,只能表示整数。即使被赋给含有小数的值,小数部分也会被舍去。像5和37这样的常量,称为整型常量。

  • 浮点型的 double 类型,只能表示浮点数(带有小数部分的实数值)。像3.14这样的包含小数的常量,称为浮点型常量。

  • 整数之间的运算结果是整数;浮点数之间的运算结果是浮点数。

  • 如果一个运算中有不同类型的操作数,就会进行“隐式类型转换”。运算对象——操作数的类型不同时,较小的数据类型的操作数会转换为较大的数据类型,然后再进行运算。因此,当一个运算中既有 int 类型又有 double 类型时,各操作数都会被转换为 double 类型。

  • 若要将某个表达式的值转换为别的数据类型所对应的值,需要使用类型转换运算符 () 进行类型转换。例如,(double)5 就表示将 int 类型的整型常量5转换为 double 类型的5.0。

  • 当用 printf 函数来显示 double 类型的值时,转换说明是 %f;当用 scanf 函数来读取时,转换说明是 %1f

  • 传递给 printf 函数和 scanf 函数的格式化字符串中可以包含多个转换说明。各转换说明从头开始依次对应第2个实参、第3个实参······

    • printf("a和b的和是%d,积是%d。\n", a + b, a * b);

    • scanf("%d%d", &a, &b);

  • 转换说明由0标志、最小字段宽度、精度、转换说明符等构成。

  • 若要在 printf 函数中显示 % 字符,需要在格式化字符串中写入 %%

/* 第2章总结 */
#include <stdio.h>

int main(void)
{
    int a;
    int b;
    double r;
    
    printf("整数 a 和 b 的值");
    scanf("%d%d", &a, &b);
    
    printf("a + = %d\n", a + b);
    printf("a - = %d\n", a - b);
    printf("a * = %d\n", a * b);
    printf("a / = %d\n", a / b);
    printf("a %% = %d\n", a % b);
    
    printf("(a + b)/2 = %d\n", (a + b)/2);
    
    printf("平均值 = %f\n\n", (double)(a + b)/2);
    
    printf("半径:");
    scanf("%1f", &r);
    
    printf("半径为 %.3f 的圆的面积是 %.3f。\n", r, 3.14 * r * r);
    
    return 0;
}

第3章 分支结构程序

  • 程序执行时会对表达式进行判断。对表达式进行判断后会得到表达式的类型和值。

  • 对左右操作数的相等性进行判断的是相等运算符 ==!=。前者判断二者是否相等,后者判断二者是否不相等。无论是哪一个,当判断成立时结果都为 int 类型的1,否则就为0。

  • 对左右操作数的大小关系进行判断的是关系运算符 <><=>=

  • 无论是哪一个,当判断成立时结果都为 int 类型的1,否则就为0。

  • 对左右操作数进行逻辑与(若二者都为真则结果为真)和逻辑或(只要一方为真则结果就为真)的逻辑运算的是逻辑与运算符 && 和逻辑或运算符 ||

    无论是哪一个,当判断成立时结果都为 int 类型的1,否则就为0。

  • 逻辑或运算符会进行短路求值,所以如果左操作数的判断结果为1,则不对右操作数进行判断(这是因为即使不判断,表达式的判断结果也显然为真)。

  • 如果仅在某条件成立时(控制表达式的判断结果不为0时)进行处理的话,则使用不带 elseif 语句;而如果是根据某条件成立与否进行不同的处理的话,则使用带 elseif 语句。

  • 在需要单一语句的位置,如果一定要使用多个语句,可以把它们组合成复合语句(程序块)来实现。

  • 使用条件运算符 ?: 就可以将 if 语句的功能凝缩在一个单一语句中。根据第1个操作数的判断结果,只对第2、第3个操作数中的一个进行判断。

    /*将a和b中较小的一个赋值给min*/
    min=a < b ? a : b;
    
  • 根据某一整数类型的单一表达式的值,需要将程序分为多个分支的时候,可以使用 switch 语句。根据判断结果,程序会跳转到(整数类型的常量指定的)相应的标签处。

    如果没有相应的标签,程序则会跳转到 default 处。

  • switch 语句中,break 语句执行后,switch 语句便执行结束。如果没有 break 语句,程序将落到下一条语句上。

  • if 语句和 switch 语句统称为选择语句。

/* 第3章总结1 */
if (month < 1 || month > 12)
    printf("%d 月不存在!!\a\n", month);
else if (month <= 2 || month == 12)
    printf("%d 月是冬季。\n", month);
else if (month >= 9)
    printf("%d 月是秋季。\n", month);
else if (month >= 6)
    printf("%d 月是夏季。\n", month);
else
    printf("%d 月是春季。\n", month);
/* 第3章总结2 */
switch (sw) {
    case 1 : printf("红色"); break;
    case 2 : printf("蓝色"); break;
    case 3 : printf("白色"); break;
}
/* 第3章总结3 */
if (n1 > n2) {
    printf("较大的数是 n1。\n");
    printf("它们的差是 %d。\n", n1 - n2);
} else {
    printf("较大的数是 n2。\n");
    printf("它们的差是 %d。\n", n2 - n1);
}

第4章 程序的循环控制

  • do 语句、while 语句和 for 语句统称为循环语句。无论哪种循环语句,只要控制表达式的判断结果不为0,都将执行循环体。另外,循环语句的循环体也可以是循环语句。这种结构的循环语句是多重循环。

  • 先循环后判断可以通过 do 语句来实现。循环体至少执行一次。即使是单一语句,也可以使之成为程序块,这样程序会更易读。

  • 先判断后循环可以通过 while 语句和 for 语句来实现。循环体有可能一次也不执行。使用单一变量控制的固定类型的循环,可以通过 for 语句简单地实现。

  • 循环语句中的 break 语句会中断该循环语句的执行。循环语句中 continue 语句,会跳过循环体剩余部分的执行。

  • 递增运算符 ++ 和递减运算符 -- 是使操作数的值递增(加一)/递减(减一)的运算符

    对使用后置(前置)递增运算符/递减运算符的表达式进行判断,结果会得到递增/送成前(后)的值。

  • 表达式后带有分号的语句称为表达式语句。省略表达式,只有分号的表达式语句,称为空语句。

  • 复合语句中特有的变量,在该复合语句中声明并使用。

  • 对两个条件分别取非,然后将逻辑与变为逻辑或、逻辑或变为逻辑与,然后再取其否定结果和原条件一样。这称为德摩根定律。

  • 使用单引号 ' 将字符括起来,形成 '*' 形式。单一字符就可以通过这种形式的字符常量来表示。通过使用 putchar 函数,可以显示单一字符。

  • 复合赋值运算符是既进行运算又进行赋值的运算符。与用两个运算符分别进行运算和赋值相比,使用复合赋值运算符可以使程序更简洁,而且对左操作数的判断仅需进行一次即可。

  • ifelse 这样被赋予特殊意义的标识符称为关键字。标识符是赋予变量和函数等的名称。

  • 分隔符是用来分割关键字和标识符等单位的符号。

  • 我们可以把被空格类(空格、Tab、换行等)字符和注释分割开的相邻的字符串常量作为一个整体来看待。

  • C语言程序的书写格式很自由。通过加入适当的缩进,可以使程序更易读。

/* 第4章总结 */
#include <stdio.h>

int main(void)
{
    int i, j;
    int x, y, z;
    do {
        printf("0~100的整数值:");
        scanf("%d", &x);
    } while (x < 0 || x > 100);
    
    y = x;
    z = x;
    
    while (y >= 0)
        printf("%d %d\n", y--, ++z);
    
    printf("宽和高为整数面积为 %d 的长方形的边长是:\n", x);
    
    for (i = 1; i < x; i++) {
        if (i * i > x) break;
        if (x % i != 0) continue;
        printf("%d × %d\n", i , x / i);
    }
    
    puts("5行7列的星号");
    
    for (i = 1; i <= 5; i++) {
        for (j = 1; j <= 7; j++) {
            putchar('*');
        putchar('\n');
        }
    }
    return 0;
}

第5章 数组

  • 同一类型的对象集中在一起,在内存上排列成一条直线,这就是数组。数组通过指定元素类型、元素个数以及数组名来声明。

  • 元素类型为 Type 的数组,称为Type数组。元素个数为n的Type数组,写作Type[n]

  • 访问数组的各个元素时使用下标运算符 [][] 中的下标是表示该元素在首个元素后的位置的整数值。也就是说,假设某数组有几个元素,那么访问该数组各元素的表达式从头开始依次是 a[0], a[1], a[n-1]

  • 对象式宏由 #define 指令进行定义。

    #define a b
    

    表示将该指令之后的a替换为b。

  • 使用对象式宏为常量定义名称,可以消除幻数。

  • 声明数组时,元素个数必须使用常量表达式。若使用对象式宏定义表示元素个数的值则元素个数的变更将变得很容易。

  • 按顺序逐个查看数组的元素,称为遍历。

  • 将各个元素的初始值 X、Y、Z 从头开始按顺序排列,中间用逗号隔开,并用大括号括起来,形成 {X, Y, Z} 这种形式,这就是数组的初始值。最后一个逗号可以省略。

  • 在没有指定元素个数的情况下,由初始值的个数决定元素个数。另外,在指定元素个数的情况下,如果大括号中的初始值不够,则使用0对没有初始值的元素进行初始化。

  • 以数组为元素的数组,称为多维数组。构成数组的最小单位的元素,称为构成元素。各构成元素通过使用了多个下标运算符 [] 的表达式来访问。有几维就使用几个下标运算符。

  • 多维数组的构成元素在排列时,首先从位于末尾的下标开始增加。

  • 赋值表达式的判断结果与赋值后左操作数的类型和值相同。

  • 无法使用赋值运算符 = 复制元素的所有元素。

/* 第5章总结 */
#include <stdio.h>
#define SIZE 5

int main()
{
    int i, j;
    int sum;
    
    int a[SIZE];
    int b[SIZE] = {1, 2, 3};
    
    int c[2][3] = {
        {11, 22, 33},
        {44, 55, 66},
    };
    
    /* 将数组 b 的所有元素全部复制给 a */
    for (i = 0; i < SIZE; i++)
        a[i] = b[i];
        
    /* 显示数组 a 的所有元素 */
    for (i = 0; i < SIZE; i++)
        printf("a[%d] = %d\n", i, a[i]);
    
    /* 显示数组 b 的所有元素 */
    for (i = 0; i < SIZE; i++)
        printf("b[%d] = %d\n", i, b[i]);
        
    /* 将数组 a 的所有元素的和赋给 sum 并显示 */
    sum = 0;
    for (i = 0; i < SIZE; i++)
        sum += a[i];
    printf("数组 a 的所有元素的和 = %d\n", sum);
    
    /* 显示数组 c 的全部构成元素的值 */
    for (i = 0; i < 2; i++) {
        for (j = 0; j < 3; j++) {
            printf("c[%d][%d] = %d\n", i, j, c[i][j]);
        }
    }
    
    return 0;
}

第6章 函数

  • 将多个处理集中到一起进行时,可以使用函数这一程序的零件。返回类型、函数名、形参这三个部分决定了函数的特征。不接收参数的函数,其形参类型为 void

  • 函数体是复合语句(程序块)。如果有仅在函数中使用的变量,原则上应在该函数中声明和使用。

  • 函数调用的形式是在函数名后面加上小括号。这个小括号称为函数调用运算符。如果没有实参,则小括号中为空。有多个实参的情况下,用逗号分隔。

  • 进行函数调用后,程序的流程将一下子跳转到该函数处。

  • 参数的传递是通过值的传递进行的,实参的值会被赋给形参。因此即便修改所接收的形参的值,也不会影响到实参。反之,通过灵活应用值传递的优点,可以让函数更加简洁紧凑。

  • 在函数内执行 return 语句时,或函数体执行结束时,程序流就会返回到原来进行调用的地方。如果函数的返回值类型不是 void,则在返回到原来进行调用的地方时,会返回单一的值。

  • 对函数调用表达式进行判断,会得到该函数返回的值。

  • 创建变量或函数实体的声明称为定义声明,否则为非定义声明。

  • 程序运行的时候,会执行 main 函数的主体部分。main 函数之外的函数不会被率先执行。

  • 将被调用的函数定义在前面,进行调用的函数定义在后面。调用定义在前面的函数时需要进行原型声明,声明函数的返回值类型、形参类型和个数。

  • 函数应该具有高通用性。

  • C语言提供的 printfscanf 等函数,称为库函数。

  • <stdio.h> 等头文件中包含库函数的函数原型声明等。#include 指令行引入头文件的内容。

  • 接收数组的形参的声明为“类型名 参数名 []”,一般使用别的形参来接收元素个数。另外,如果只是引用所接收的数组的元素值,而不改写的话,在声明接收数组的形参时就应该加上 const

  • 从数组的开头出发按顺序搜索,直到找出与目标相同的元素,这一系列操作称为顺序查找。还可以使用哨兵查找法。

  • 在函数外定义的变量,拥有文件作用域;在函数内定义的变量,拥有块作用域。

  • 如果两个同名变量拥有不同的作用域,那么内层的变量是“可见”的,而外层的变量会被“隐藏”起来。

  • 在函数外定义的对象,或者在函数中使用 static 定义出来的对象,其“寿命”是从程序开始执行到程序执行结束,拥有静态存储期。如果不显式地进行初始化,则该对象会被初始化为0。

  • 函数中不使用存储类说明符 static 定义出来的对象,具有自动存储期。如果不显式地进行初始化,则该对象会被初始化为不确定的值。

/* 求两个整数的平均值 */
#include <stdio.h>
double ave2(int a, int b)
{
    return (double)(a + b) / 2;
}

int main(void)
{
    int n1, n2;
    puts("请输入两个整数。");
    printf("整数1:");
    scanf("%d", &n1);
    printf("整数2:");
    scanf("%d", &n2);
    printf("平均值是 %.1f。\n", ave2(n1, n2));
    return 0;
}
/* 记下 no 返回上一次的值 */
int val (int no)
{
    static int v;
    int temp = v;
    v = no;
    return temp;
}
/* 以实数的形式返回数组 a 的所有元素的平均值 */
double ave_ary(const int a[], int n)
{
    int i;
    double sum = 0;
    for (i = 0; i < n; i++)
    sum += a[i];
    return sum / n;
}
/* 输出响铃 */
void put_alert(void)
{
    putchar('\a');
}
/* 将数组 b 开头的 n 个元素复制个数组 a */
void cpy_ary(int a[], const int b[], int n)
{
    int i;
    for (i = 0; i < n; i++)
        a[i] = b[i];
}
/* 返回二维数组 a 的所有构成元素的总和 */
int sum_ary2D(const int a[][3], int n)
{
    int i, j;
    int sum = 0;
    for (i = 0; i < n; i++)
        for (j = 0; j < 3; j++)
            sum += a[i][j];
    return sum;
}

第7章 基本数据类型

  • 算术类型是以下数据类型的总称。

    • 整数类数据类型(字符型/整型/枚举型)

    • 浮点型

    字符型、整型和浮点型,只需要关键字就能够表示其数据类型,因此将它们统称为基本数据类型。

  • 整型和字符型是用来表示一定的数值范围的整数数据类型的。

  • 整型和字符型中存在有符号和无符号的分类。可以使用类型说明符 signedunsigned 来指定其中一种数据类型。

    若不加类型说明符,则可以像下面这样处理。

    • 整型:默认为有符号。

    • 字符型:因编译器而定

  • 整型有 shortintlong 三种。在程序运行环境中,int 型是最易处理、运算速度最快的类型。

  • 各类型能够表示的值的范围因编译器而异。其中最小值和最大值在 <limits.h> 头文件中以对象式宏的形式定义。

  • char 型在内存上占据的位数因编译器而异,因此 char 型是以对象式宏 CHAR_BIT 的形式在 <limits.h> 头文件中定义的。

  • char型的长度定义为1。通过使用 sizeof 运算符,可以判断出所有数据类型的长度。

    sizeof 运算符生成的值的数据类型是以 size_t 型的形式定义的无符号整型。

  • typedef 声明是创建数据类型的同义词的声明。“typedef A B;”表示为已有的数据类型A创建别名B。B将作为类型名来使用。该名称一般称为 typedef 名。

  • 整型的值使用纯二进制计数法表示。

  • 无符号整型的数值在计算机内部是以二进制数来表示的,该二进制数与各二进制位一一对应。

  • 有符号整型数值的内部表示法有补码、反码、符号和绝对值3种。正数的位串和无符号整数一样。

  • 求两个整型操作数的按位逻辑与、逻辑或、逻辑异或的双目运算符分别是 &|^~ 是求整型操作数的反码的单目运算符。

  • <<>> 是将整型操作数的位进行左移或右移的位移运算符。注意不要对负数进行位移,

    这是因为具体会执行逻辑位移还是算术位移,要由编译器而定。

  • 整型常量可以用十进制常量、八进制常量、十六进制常量这3种基数来表示。另外,整型常量的末尾可以加上以下整型后缀。

    • uU ······ 表示该整型常量为无符号类型

    • lL ······ 表示该整型常量为 long 型。

  • 整型常量的数据类型由以下三个因素决定。

    • 该整型常量的值。

    • 该整型常量的后缀。

    • 所在编译器中各数据类型的表示范围。

  • 无符号整型的运算中不会发生数据溢出。当计算结果超出最大值时,结果为“数学计算结果 %(该无符号整数能够表示的最大值+1)”。

  • 浮点型表示带有小数部分的实数,有 floatdoublelong double 三种。

  • 浮点型常量末尾可以加上下列浮点型后缀。

    • fF ······ 表示浮点型常量为 float 型。

    • lL ······ 表示浮点型常量为 long double 型。

    如果没有这些后缀,则默认浮点型常量为 double 型。

  • 循环判断基准所使用的变量最好是整型,而非浮点型,因为这样可以避免误差累积。

  • 各运算符的优先级不同。另外还存在左结合性和右结合性。

  • 许多具有算术类型操作数的双目运算符都会在运算过程中进行“普通算术类型转换”。

/* 第7章总结 */
#include <stdio.h>

int main(void)
{
    int i, no;
    float vlaue;
    float sum = 0.0f;
    
    puts("对浮点数进行多次加法运算。");
    printf("值");
    scanf("%f", &value);
    printf("次数:");
    scanf("%d", &no);
    
    for (i = 0; i < no; i++)
        sum += value;
        
    printf("加法运算的结果是 %f。\n", sum);
    
    return 0;
}

第8章 动手编写各种程序吧

  • 对象式宏进行的代换非常简单,而函数式宏所做的工作则是宏展开,包括参数在内(也可以定义没有参数的函数式宏)。

    define max2(a,b)(((a))(b))?(a):(b))
    
  • 相较于函数要区分使用不同的类型,函数式宏可以用一个定义应对多种类型。另外,因为不需要参数和返回值方面的处理,所以更加灵活有效。

  • 因为展开后的表达式可能会判断两次以上,所以可能会导致预料之外的结果,这是宏的缺点。在生成和使用函数式宏时,要充分考虑到这一点。

  • 使用逗号运算符的表达式“a, b”中,会按顺序判断 a 和 b,所得结果为对右操作数 b 进行判断的结果。

  • 在语法上规定只能放置一个表达式的地方需要放置多个表达式时,可以使用逗号运算符将这些表达式连起来。

  • 排序就是以一定的基准,将数据的集合按升序或降序重新排列。排序的方法有冒泡排序法等。

  • 枚举类型是一定范围的整数值的集合。赋给枚举类型的标识符是枚举名,与各个数值相对的标识符是枚举常量。

  • 枚举名不是类型名,“enum 枚举名”才是类型名。

  • 枚举名和变量名属于不同的命名空间。

  • 递归就是用自己定义自己。

  • 递归函数调用,就是调用和自身相同的函数。

  • getchar 函数是从键盘(标准输入流)中读取单一字符的库函数。

  • 对象式宏 EOF 表示文件结束,在 <stdio.h> 头文件中,EOF 被定义为负值(不一定为-1)。

  • C语言中的“字符”都有与之对应的字符编码,即整数值。

  • 数字字符 '0'、'1'、······、'9' 的值是递增的。因此,数字字符 'n' 减 '0' 后,就会得到整数 n

  • 表示字符 ' 的转义字符是,表示字符 " 的转义字符是 \"

  • 八进制转义字符和十六进制转义字符可以通过字符编码来表示特定的字符。

/* 第8章总结1 */
#include <stdio.h>

enum RGB (Red, Green, Blue);

int main(void)
{
    int color;
    
    printf("0~2的值:");
    scanf("%d", &color);
    
    printf("你选择了");
    switch (color) {
        case 0 : printf("红色。\n");
            break;
        case 1 : printf("绿色。\n");
            break;
        case 2 : printf("蓝色。\n");
            break;
    }
    return 0;
}
/* 第8章总结2 */
#include <stdio.h>

/* 响铃 */
#define alert() (puchar('\a'))

/* 显示字符 c 并换行 */
#define putchar_ln(c) (putchar(c), putchar('\n'))

int main(void)
{
    int ch;
    int sum = 0;
    
    while ((ch = getchar()) != EOF) {
        if (ch >= '0' && ch <= '9')
            sum += ch - '0';
        
        if (ch == '\n') {
            alert();
            putchar('\n'); 
        } else {
            putchar_ln(ch);
        }
    }
    printf("所有数字之和为 %d。\n", sum);
    
    return 0;
}

第9章 字符串的基本知识

  • null 字符是值为0的字符。用八进制转义字符表示就是 \0,用整数常量来表示就是0。

  • 字符串字面量的末尾是 null 字符。因此,字符串字面量"ABC"实际上占用了4个字符的内存空间,一个字符也没有的字符串字面量 "" 占用1个字节。

  • 字符串字面量"..."的长度,和包括末尾的 null 字符在内的字符数一致。该值可以通过 sizeof("...") 求得。

  • 字符串字面量具有静态存储期,因此它“活在”从程序开始到结束的整个生命周期内。

  • 当具有多个拼写相同的字符串字面量时,如果将其作为一个存储,就能减少所需的内存空间;反之也可以分别进行存储。至于采用哪种方式,要由编译器而定。

  • 字符串最适合存储在 char 数组中。字符串的末尾是首次出现的 null 字符。

  • 存储字符串的字符数组的初始化,可以通过以下任意一种方式进行。

    char str[] = ('A', 'B', 'C', '\0');
    char str[]="ABC";
    

    后者的初始值,可以使用 {} 括起来。

  • 一个字符也没有,只有 null 字符的字符串,称为空字符串。

  • 遍历每个字符,直到出现null 字符为止,就可以实现对字符串中所有字符的遍历。

  • 对字符串进行遍历,并计算 null 字符之前的字符个数,就可以求得字符串的长度(不包括 null 字符的字符个数)

  • 为了在画面中显示字符串,需要把 printf 函数的转换说明设为 %s。显示的位数、左对齐或右对齐等,可以通过输出最小宽度和精度来指定。

  • 为了从键盘读取字符串,需要把 scanf 函数的转换说明设为 %s。用来进行存储的实参的数组后不可附带 & 运算符。

  • 函数所接收的字符串,就是调用方赋予的数组本身。因为字符串的末尾有 null 字符,所以无需将元素个数作为别的参数进行传递。

  • 字符串数组可以用数组的数组,即二维数组来表示。例如,5个最多能够存储12个字(包括 null 字符在内)的字符串(即 char[12] 型数组)集中在一起形成的数组,可以像下面这样定义。

    /* 元素类型为char[12],元素个数为5的数组 */
    char ss[5][12]; 
    

    因为ss为二维数组,所以其构成元素可以通过表达式 ss[i][j] 来访问。

  • 将小写英文字符转换为大写的是 toupper 函数,将大写转换为小写的是 tolower 函数。

    二者都是 <ctype.h> 提供的库函数。这些函数不会转换除英文字符以外的字符。

/* 遍历字符串并显示 */
#include <stdio.h>

#define STR_LENGTH 128

void put_string_rep(const char s[])
{
    int i = 0;
    while (s[i])
        putchar(s[i++]);
    printf(" { ");
    i = 0;
    while (s[i]) {
        putchar('"');
        putchar(s[i++]);
        printf("'");
    }
    printf("'\\0' }\n");
}

int main(void)
{
    int i;
    char s[STR_LENGTH];
    char ss[5][STR_LENGTH];
    
    printf("字符串 s:");
    scanf("%s", s);
    
    printf(请输入5个字符串。\n);
    for (i = 0; i < 5; i++) {
        printf("ss[%d]:", i);
        scanf("%s", ss[i]);
    }
    printf("字符串 s:");
    put_string_rep(s);
    
    printf("字符串数组 ss\n");
    for (i = 0; i < 5; i++) {
        printf("ss[%d]:", i);
        put_string_rep(ss[i]);
    }
    return 0;
}

第10章 指针

  • 地址表示对象在内存空间上的位置。

  • Type 型对象 x 前写上取址运算符 & 得到表达式 &x,该表达式会生成指向对象 x 的指针。生成的指针的类型为 Type* 型,值为 x 的地址。

image.png

  • Type* 型指针 p 的值为 Type 型对象 x 的地址时,可以写作“p 指向 x”。

  • 原则上应该避免让 Type* 型指针 p 指向非 Type 型的对象。

  • Type* 型指针 p 前写上指针运算符 * 得到的表达式 *p,表示指针 p 指向的 Type 型 对象本身。也就是说,p 指向 x 时,*p 是 x 的别名。

  • 通过在指针前写上指针运算符 * 来访问该指针指向的对象,称为解引用。

  • 如果被调用的函数的参数是指针类型,那么通过在该指针前写上指针运算符并进行解引用,就可以间接访问调用方的对象。

  • 除一部分特殊情况之外,数组名都会被解释为指向该数组起始元素的指针。也就是说如果 a 是数组,那么数组名 a 就是 &a[0]

  • 对指向数组内元素的指针 p 加上 / 减去整数 i 的表达式 p+ip-i,分别是指向 p 所指元素后 / 前第 i 个元素的指针。

  • 对指向数组内元素的指针 p 加上或减去整数 i 的表达式分别为 p+ip-i,在这两个表达式前写上指针运算符得到的 *(p+i)*(p-i),分别与 p[i]p[-i] 等价。

  • Type* 型的指针 p 指向元素类型为 Type 的数组 a 的起始元素 a[0] 时,p 的行为和数组 a 本身一样。

  • 不可将数组名作为赋值运算符的左操作数。

  • 函数间数组的传递,是以指向第一个元素的指针形式进行的。在被调用的函数中作为指针接收的数组,实际上就是调用方传递的数组。

image.png

  • 不指向任何对象和函数的指针,称为空指针。表示空指针的空指针常量,在 <stddef.h> 头文件中以对象式宏 NULL 的形式定义。

  • 算术类型和指针类型统称为标量型。

#include <stdio.h>

#define NUMBER 5;

/* 交换px和py所指对象的值 */
void swap(int *px, int *py)
{
    int temp = *px;
    *px = *py;
    *Py = temp;
}

/* 冒泡排序法 */
void bsort(int a[], int n)
{
    int i, j;
    
    for (i = 0; i < j; i++)
        for (j = n - 1; j > i; j--)
            if (a[j-1] > a[j])
                swap(&a[j], &a[j-1]);
}

int main(void)
{
    int i;
    int point [NUMBER];
    printf("请输入 %d 人的分数。\n", NUMBER);
    for (i = 0; i < NUMBER; i++) {
        printf("%2d 号:", i + 1);
        scanf("%d", &point[i]);
    }
    
    bsort(point, NUMBER);
    
    puts("按升序排列。");
    for (i = 0; i < NUMBER; i++)
        printf("%2d 号:%d\n", i + 1, point[i]);
        
    return 0;
}

第11章 字符串和指针

  • “用数组实现字符串”是一种表示字符串的方法。

    /* 用数组实现的字符串 */
    char a[] = "CIA"; 
    
  • “用指针实现字符串”也是一种表示字符串的方法。

    /* 用指针实现的字符串 */
    char *p = "FBI";
    

    因为字符串字面量会被解释为指向第一个字符的指针,所以指针 p 会被初始化为指向字符串字面量“FBI”的第一个字符 'F'。字符串字面量以及指向它的指针,二者都占用内存空间。

  • 为指针 p 赋上指向别的字符串字面量(的第一个字符)的指针后,p 就会变为指向后来被赋上的字符串字面量(的第一个字符)。

  • 表示字符串数组的一个方法是使用“用数组实现的字符串”的数组。

    /* 用数组实现的字符串的数组 */
    char a2[][5] = {"LISP", "C", "Ada"};
    

    所有字符(二维数组的构成元素)都被保存在连续的内存空间中。

    数组 a2 所占用的内存空间大小可通过 sizeof(a2) 求得,结果和二维数组的(行数×列数)一致。

  • 表示字符串数组的另一个方法是使用“用指针实现的字符串”的数组。

    /* 用指针实现的字符中的数组 */
    char *p2[] = {"PAUL","X","NAC"};
    

    无法保证各字符串都被保存在连续的内存空间中。

    数组 p2 的大小是 sizeof(p2),即(sizeof(char*) × 元素个数)。除数组本身之外,各字符串字面量也占用内存空间。

  • 指向数组元素的指针递增后将变为指向下一个元素,递减后将变为指向上一个元素。

  • 因为无法保证字符串字面量被保存在能够改写的空间中,所以不要对该空间和其前后的空间进行写入操作。

  • 应该灵活应用返回指向字符串的指针的函数的返回值。

  • 字符串处理所需的库函数,主要由 <string.h> 头文件提供。

  • strLen 函数是求字符串长度的函数,该函数返回不包含 null 字符在内的字符串长度。

  • strcpy 函数是将字符串全部进行复制的函数。strncpy 函数则是在对字符个数加以限制的基础上复制字符串的。

  • strcat 函数是在已有的字符串后连接别的字符串的函数。strncat 函数则是在对要连接的字符串个数加以限制的基础上连接字符串的。

  • strcmp 函数是比较字符串的大小关系的函数。strncmp 函数则是在对字符个数加以限制的基础上比较字符数组的大小的。字符串 / 字符数组的大小关系依赖于字符编码。

  • <stdlib.h> 中提供的 atoiatofatol 函数是转换字符串的函数。

/* 字符串和字符串数组 */
#include <ctype.h>
#include <stdio.h>

#define put_str_ln(s) (put_str(s), putchar('\n'))

void put_str(const char *s)
{
    putchar('\"');
    while (*s)
        putchar(*s++);
    putchar('\"');
}

char *str_cpy_toupper(char *d, const char *s)
{
    char *tmp = d;
    while (*d++ = toupper(*s++))
        ;
    return tmp;
}

int main(void)
{
    int i;
    char s[128], t[128];
    char a[] = "CIA";
    char *p = "FBI";
    char a2[][5] = {"LISP", "C", "Ada"};
    char *p2[] = {"PAUL", "X", "MAC"};
    
    printf("字符串 s = ");
    scanf("%s", s);
    printf("转换为大写并复制到了数组 t。\n");
    printf("字符串 t = %s\n", str_cpy_toupper(t, s));
    
    printf("a = ");
    put_str_ln(a);
    printf("p = ");
    put_str_ln(p);
    
    for (i = 0; i < sizeof(a2) / sizeof(a2[0]); i++) {
        printf("a2[%d] = ", i);
        put_str_ln(a2[i]);
    }
    
    for (i = 0; i < sizeof(p2) / sizeof(p2[0]); i++) {
        printf("p2[%d] = ", i);
        put_str_ln(p2[i]);
    }
}

第12章 结构体

  • 对基本类型加以组合创建的数据类型称为派生类型,派生类型有以下5种。

    • 数组类型 Type a1[n];

    • 结构体类型 struct { Type m1; Type m2; /*···*/ } a2;

    • 共用体类型 union { Type m1; Type m2; /*···*/ } a3;

    • 函数类型 Type a4 ( Type p1, Type p2, /*···*/ ) { /*···*/ }

    • 指针类型 Type *a5;

  • 将多个数据的集合对应到程序中时,最好在将其聚合后再对应。结构体表示任意类型的数据的集合,最适合用来实现这种结构的数据。

  • 构成结构体的元素称为成员。结构体的成员也可以是结构体。而不能继续分解的成员,称为构成成员。

  • 结构体成员在内存空间上的排列顺序和成员声明的顺序一样。

  • 如果给结构体赋结构名,则由两个单词构成的“struct 结构名”为类型名。没有赋结构名的情况下,在结构体声明之外的地方就无法定义该结构体类型的对象。

  • 如果为结构体赋 typedef 名,则 typedef 名就可以作为类型名使用。

  • 结构体对象声明时的初始值的形式是,各成员的初始值依次排列在 {} 中,并用逗号进行分隔。没有初始值的成员会被初始化为0。

  • 访问结构体对象 o 中的成员 m 的表达式是 o.m。访问结构体成员的 . 运算符称为句点运算符。

  • 访问指针 p 所指的结构体成员 m 的表达式是 (*p).mp->m。访问结构体成员的 -> 运算符又称为箭头运算符。

  • 数组和结构体在处理多个对象的集合方面具有诸多相同点,它们统称为聚合类型。

  • 数组即使元素个数相同,也不能进行赋值。与之相对,结构体只要是同一类型,就可以进行赋值。经过结构体对象的赋值,赋值前的对象的所有成员就会被复制到赋值目标对象的所有成员。

  • 函数不能返回数组,但是能返回结构体。

  • 命名空间可分为标签名、小标签名、成员名、一般性标识符4类。

/* 表示日期的结构体和表示人的结构体 */
#include <stdio.h>
#define NAME_LEN 128  /* 姓名的字符数 */

/*===表示日期的结构体===*/
struct Date {
    int y;
    int m;
    int d;
}

/*=== 表示人的结构体 ===*/
typedef struct {
    char name[NAME_LEN];
    struct Date birthday;
} Human;

/*--- 显示指针 h 所指向的人的姓名和生日 ---*/
void print_Human(const Human *h)
{
    printf("%s (%04d 年 %02d 月 %02d 日生)\n",
        h->name, h->birthday.y, h->birthday.m, h-birthday.d);
}

int main(void)
{
    int i;
    struct Date today;  /* 今天的日期 */
    Human member[]= {
        {"古贺政男", {1904,11,18}},
        {"柴田望洋", {1963,11,18}},
        {"冈田准一", {1980,11,18}},
    };

    printf("请输入今天的日期。\n");
    printf("年:");
    scanf("%d", &today.y);
    
    printf("今天是 %d 年 %d 月 %d 日。\n', today.y, today.m, today.d);
    
    printf("---会员一览表---\n");
    for (i = 0; i < sizeof(member) / sizeof(member[0]); 1++)
        print_Human(&member[i]);
    
    return 0;
}

第13章 文件处理

  • 应该将程序运行结束后仍需保存的数值和字符串等数据保存在文件中。

  • 针对文件、键盘、显示器、打印机等的数据读写操作都是通过流进行的。我们可以将流想象成流滴着字符的河。

  • C语言程序在启动时准备好了以下3种类型的标准流。

    • 标准输入流 stdin

    • 标准输出流 stdout

    • 标准错误流 stderr

  • 记录控制流所需要的信息的数据类型是 FILE 型,该数据类型是在 <stdio.h> 头文件中定义的。

  • 打开文件的操作称为打开。函数库中的 fopen 函数用于打开文件。

  • 使用 fopen 函数成功打开文件后,返回指向 FILE 型对象的指针,该对象用于控制与所打开的文件相关联的流;打开操作失败时,返回空指针。

  • 打开文件时可以指定以下四种模式。

    • 只读模式······只从文件输入。

    • 只写模式······只向文件输出。

    • 更新模式······既从文件输入,也向文件输出。

    • 追加模式······从文件末尾处开始向文件输出。

  • 在文件使用结束后,会断开文件与流的关联,将流关闭。这个操作称为关闭。用于关闭文件的函数是 fclose 函数。

  • fscanf函数可以对任意流执行与 scanf 函数相同的输入操作。二者都返回成功读取的项数。

  • fprintf函数可以对任意流执行与 printf 函数相同的输出操作。

  • fgrtc函数是从任意流读取数据的函数。

  • fputc 函数是向任意流写入数据的函数。

  • 文本文件的字符数取决于数值位数。

  • 二进制文件直接对内存空间上的位进行读写操作。Type 型数据的读写通过 sizeof(Type)进行,因此字符数不依赖于数值位数。

  • 二进制文件中可以在不遗失精度的情况下对浮点数进行书写操作。

  • 对二进制文件进行写入使用 fwrite 函数,读取使用 fread 函数

    读取写入
    1个字符c = fgetc(stream)fputc(c, stream)
    整型fscanf(stream, "格式字符串", ···)fprintf(stream, "格式字符串", ···)
    二进制fread(ptr, size, nmemb, stream)fwrite(ptr, size, nmemb, stream)
  • 判断任意字符是否为可打印字符的函数是 isprint 函数,该函数在 <ctype.h> 头文件中定义。

  • <time.h> 头文件中定义了获取日期和时间所需的各种数据类型和函数。

/* 将标准输入的数据写入文件 */
#include <stdio.h>
int main(void)
{
    int ch;
    /* 原文件名 */
    FILE *fp;
    /*目标文件名*/
    char fname[FILENAME_MAX];
    
    printf("目标文件名:");
    scanf("%s\n", fname);
    
    /* 打开目标文件 */
    if((fp = fopen(fname, "w")) == NULL)
        printf("\a无法打开目标文件。\n");
    else {
        while((ch = fgetc(stdin)) != EOF)
            fputc(ch, fp);
        /* 关闭目标文件 */
        fclose(fp);
        
    return 0;
}