高级语言程序设计 (c 语言)

  • c

posted on 23 Oct 2020 under category note

这篇文章是对计算机应用专业自考教材 (迟成文版) 的知识点的汇总整理. 力求全面, 简明.

文中也加入了一些自己读教材时的一些总结. 最后的 Tricks 部分, 是一些书中的技巧性的代码, 后续还会继续往里添新.

写这篇文章主要是为了自己备考自查, 以及日常的快捷参考用. 它的目的不是作为一篇教程.

对于这本自考教材, 我觉得有几点需要注意:

  • 不要迷信书中某些术语, 比如 “有名常量”… 可能叫法和惯例不同
  • 书中内容及惯例用法可能有些过时, 一定要买其他书参考进阶. 推荐 <K&R> 以及 <C 语言: 现代方法>
  • 教材多为一些知识点的罗列, 这这片文章更是对罗列点的罗列
  • 教材侧重对高级语言程序设计概念的讲解, 绝不是针对 C 语言学习的首选参考物

基础

通常将 K&R 的标准称为旧标准, 本书标准 ANSI C 称为新标准.

C 中允许出现的所有基本字符的组合称为 C 语言的字符集, 包括:

  • A~Z, a~z (52 个)
  • 数字 (10 个)
  • 其他可显示字符 (33 个)
  • 转义字符

其他可显示字符:

~ ` ! @ # $ % ^ & * ( ) _ - + = | \ { } [ ] : ; " ' < > , . ? / 空格

部分转义字符:

\n \t \v \b \r \f \a \" \' \\ \ddd \xhh

有特殊含义的单词称为保留字关键字:

auto break case char continue const default do double else enum
extern float for goto int if long register return short signed
sizeof static struct switch typedef union unsigned void volatile while

用户自定义的字符序列叫做标识符. 它由字母或下划线开头的字母, 数字, 下划线组成的字符序列. 不大于 32 个字符, 通常是前 8 个字符有效. 区分大小写

一个 C 程序包括一个或多个函数, 有且仅有一个主函数 main. 程序执行总是从主函数开始, 在主函数中结束.

注释:

  • /* */
  • //

数据类型, 运算符和表达式

常量 (字面量)

常量也叫常数, 它是程序运行过程中其值不改变的数据. 由于它是以字面格式直接写在程序中, 所以也成字面常量.

常量不需要事先定义, 只要在程序中需要的地方直接写出即可.

常量的数据类型是由书写方法自动默认的.

整形

书写形式:

  • 十进制整数: 0, -111
  • 八进制整数, 以 0 开头: 00, -0111
  • 十六进制整数, 以 0x 开头: 0x0, -0x111

整形常量:

  • 短整型: 一般占 2 字节, 范围 -32768~+32767
  • 长整形: 占 4 字节, 范围 -2147483648~+2147483647, 末尾加 l 或 L, 如 100l. 如果没有加 l 但超出短整型范围, 则自动认为该常量是长整形

实型

书写形式:

  • 一般形式: 12.345, -.234, 47.. 注意小数点前后数字可省略
  • 指数形式: 12.345e-2, -1.2345E3

一般占用 4 字节. 有效数字是 7 位, 如 1.234567891.234567 是相同的, 因为 1.23456789 后两位数字无效.

字符

书写形式:

  • 使用单引号括住的单个字符, 如 'a', '1'
  • 也可以使用转义字符如 '\n', '\101' (8进制), '\x41' (16进制)

每个字符常量占用 1 个字节.

字符常量可以作为整形常量使用, 值即该字符的 ASCII 代码值. 相应的, 值在 0~127 之间的整形常量也可以看成一个字符常量.

字符串

书写形式: 使用双引号括住的一串字符, 如 "abc", 字符个数即字符串的长度 (转义字符算做一个字符).

每个字符串在内存中最后一字节需要存储字符串结束标记“空字符” \0 , 所以内存占用字节数为: 字符串长度+1.

符号常量

符号常量是常量的另一种表示方法, 帮助在程序中明确看出某些常量的含义.

定义格式: #define 符号常量 常量.

  • 符号常量名建议用大写字母
  • 一般放在程序开头, 每个定义独占一行
  • 后面不用跟分号

#define 是一条宏定义命令.

宏定义命令一般格式: #define 宏名 一串符号.

在程序中使用已定义的宏名称为宏调用. C 编译程序将在程序编译前将所有符号常量替换为对应的常量, 称为宏替换. 由于宏替换是在编译前进行的, 所以宏定义命令属于预编译命令.

注意: 在宏替换时, 仅仅把宏名替换成对应的一串符号, 并不考虑含义. 如

#define R1 2+3
2 * R1; // 将被替换为 2 * 2+3, 这可能不是你想要的!

所以最好在定义宏的时候, 加上小括号.

变量

变量指程序运行中其值可以发生变化的量. 使用每个变量都必须明确变量名, 变量值, 变量类型这三个概念.

每个变量都要占用若干连续内存字节, 占用的字节数由其数据类型决定, 占用的第一个字节的地址即为变量的地址.

字符串只能是常量, C 中没有字符串变量.

使用一个数据之前, 必须对数据类型加以定义, 以便为其安排长度合适的内存.

定义格式: 数据类型符 变量名, ...;

  • 变量名命名规则同标识符
  • 定义变量后, 会自动分配连续的内存单元
  • 可用的_基本_数据类型符见后表

定义变量的同时给变量赋予初始值称为变量的初始化.

初始化格式: 数据类型符 变量名=初值, ...;


使用 const 可以定义有名常量. 有名常量定义时必须赋初值, 且之后不能改变.

有名常量定义格式: const 数据类型符 变量名=初值, ...;, 如 const int pi = 3.14


基本数据类型表:

数据类型 数据类型符 占用字节数 数值范围 备注
整形 int 2 (或 4) 同短整型 (或长整形) -
短整型 short 2 -32768~+32767 -
长整形 long 4 -2147483648~2147483647 -
无符号整形 unsinged [int] 2 (或 4) 同无符号短整型 (或长整形) -
无符号短整型 unsigned short 2 0~65535 -
无符号长整形 unsigned long 4 0~4294967295 -
单精度实型 float 4 -10^38~10^38 (保留 7 为有效数字) -
双精度实型 double 8 -10^308~10^308 保留 11 位有效数字
字符型 char 1 -128~127 -

运算符

用来表示各种运算的符号称为运算符. 运算符必须有运算对象, 用运算符把运算对象连接起来组成 的运算式称为表达式. 每个表达式都可以按照运算符的运算规则进行运算, 最终获得一个值, 称为表达式的值.

遇到多个运算符, 优先级高的运算符先运算, 优先级相同的运算符根据结合性从左到右或从右到左运算.

有些运算符有双重含义, 如 + (取正或双目加法), & (逻辑与或取地址) 等. 要注意区分.

运算符汇总表

  • 模运算的对象必须是整型数据, 且结果的符号必须与被除数相同.
  • 可以使用圆括号来表示运算的顺序, 增加程序的可读性.
  • &&|| 会造成短路运算.
  • 逗号运算符 , 是双目运算符, 运算对象是表达式, 运算结果值为 最右边表达式的值
  • sizeof 的运算对象可以是数据类型符 (sizeof int), 也可以是变量 (sizeof a)
  • 移位运算符: 左移时补 0, 右移时, 如果是带符号数则补符号位, 否则补 0. 对某个对象移位后, 原对象不变.
13 % -3; // 1
3 > 2 > 1; // 0 (假). 相当于 (3 > 2) > 1

int a = 10, b = 10, c = 10, d = 10;
a += b -= c *= d /= 2; // -30. 相当于 a += (b -= (c *= (d /= 2)))

a == b ? (c = 1) : (d = 0); // 由于条件运算符优先于赋值运算符, 圆括号不能少

类型转换规则

转换规则:

  • 表达式计算时, 就长不就短: 参与运算的数据都转换成数据长度最长的数据类型, 然后计算. 结果值的类型就是数据长度最长的数据类型.
  • 运算结果存入变量时: 就左不就右: 先将结果自动转换成左边变量的类型, 然后赋予该变量.
  • 强制转换: (数据类型符) (表达式)
    • 把数据较长的结果存入较短的变量时, 将截去超长的部分, 可能造成数据错误
    • 过多的类型转换会降低运行效率

控制结构语句

顺序结构

顺序执行的语句有:

  • 赋值语句: 变量 = 表达式
  • 函数调用语句: 函数名(参数...)
  • 表达式语句: 表达式;

    任何表达式都能构成语句. 在表达式后面跟一个分号 ;, 构成的语句就是 表达式语句. 赋值语句, 函数调用语句都是表达式语句的特例

  • 复合语句: { 语句;... }. 复合语句中若有数据定义语句, 要放在其他语句前面

选择结构

if … else

if (表达式) {
    语句;
} else {
    语句;
}
  • 可以嵌套
  • 表达式可以是任何类型, 常用的是关系表达式或逻辑表达式
  • else 总是和前面最近的 if 配对

switch … case

switch (表达式) {
    case 常量表达式: 语句组;
                    break;
    ...
    default: 语句组;
}
  • 可以嵌套
  • 表达式常用的是字符型或整形表达式
  • 常量表达式是由常量或符号常量组成的表达式

循环结构

  • 语句称为循环体, 可以是任何语句, 通常是复合语句
  • 可以嵌套
  • 表达式可以是任何类型, 常用关系型或逻辑型表达式

while

while (表达式) {
    语句;
}

do … while

do {
    语句;
} while (表达式);

for

for (int i = 0; i < 11; i++) {
    语句;
}

break

  • 强制结束当前循环
  • 只能用在三条循环语句的循环体中或 switch

continue

  • 跳过 continue 之后的语句, 重新判断循环控制条件决定是否循环
  • 只能用在三条循环语句的循环体中

数组

数组中的所有元素的数据类型必须相同. 元素的类型就是数组的类型.

一维数组

定义格式: 数组类型符 数组名[长度],...;

引用格式: 数组名[下标]

  • 下表从 0 开始
  • 数组元素要参与表达式运算, 必须已经被赋值
  • 引用数组元素时, 系统不检查下标是否越界

初始化格式: 数据类型符 数组名[长度]={初值表}, ...;

  • 若所有元素均赋初值, 长度可省略
  • 对于没有给出初值的元素, 数值型数组默认初值为 0, 字符型数组默认初值为 '\0'

二维数组

定义格式: 数据类型 数组名[行长度][列长度], ...;

引用格式: 数组名[行下标][列下标]

初始化格式:

  • 分行赋初值: int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
  • 不分行赋初值: int a[2][3] = {1, 2, 3, 4, 5, 6};
  • 对每行前若干元素赋初值: int a[2][3] = { {1}, {4, 5} };
  • 对若干行前若干元素赋初值: int a[2][3] = { {1, 2} };
  • 给所有元素赋初值, 行长度可省略: float a[][3] = { {1, 2, 3}, {4, 5, 6} };
  • 分行给所有行前若干元素赋初值, 行长度可省略: float a[][3] = { {1}, {2, 3} };

字符数组与字符串

因为字符型数组可以存放若干个字符, 所以也可以用来存放字符串. 如果用来存放字符串, 必须有一个 \0 元素标记字符串结束. 注意: 字符串只到第一个 \0 时结束. 而且不赋初值的值也为 \0.

不论存若干字符还是字符串, 每个字符数组的元素都可以作为一个字符型变量来使用. 但是, 对于存放字符串的字符数组还有一些特殊的处理方法.

定义格式: 同普通数组

初始化格式:

  • 同普通数组方式
  • 直接用字符串常量: 注意数组长度至少要比字符串字符数多 1, 以存放 \0
    • char s[3] = {"12"};
    • char s[] = "string";

输入输出函数

字符

  • 输入: getchar(). 从键盘读取单个字符
  • 输出: putchar(ch). 将 ch 对应的字符输出到显示器上

字符串

  • 输入: gets(字符数组)
  • 输出: puts(字符数组)

格式化

  • 输入: scanf(输入格式字符串, 输入变量地址表)

    输入格式字符表

  • 输出: printf(输出格式字符串, 输出表达式表)

    输出格式字符表

// 对于 printf(), \0 不输出
// 对于 puts(), \0 输出成回车换行符
puts("string");
printf("%s%s", "string", "string"); // 字符串结束标记符不输出
// 输出:
// string
// stringstring

// 对于 scanf(), 回车换行符或空格符看成输入结束
// 对于 gets(), 只有回车换行符看成输入结束, 空格看做字符串一部分
char a[4], b[4];
gets(a);
scanf("%s", b);
// 如果输入:
// 123 xyz
// 345 xyz
// 则 a 为 123 xyz, b 为 345

指针

指针是一种数据类型, 就是存放数据的内存单元地址. 指针变量中存放的数据就是指针类型的数据.

直接存取方式:

  • 对于变量: 通过变量名查取变量地址, 再从变量对应地址的字节中取得值获奖某值存入变量对应地址的字节中
  • 对于数组: 通过查数组与地址对照表获得数组首地址, 通过计算元素地址的方式先获得元素地址, 再取值或存值

间接存取方式: 先把要存取的变量地址存入指针变量, 然后通过指针变量内的地址来存取变量值

定义和初始化格式: 数据类型符 *指针变量名[=初值], ...;

  • 数据类型符指的是要指向的变量或数组的数据类型
  • 初值可以是: &变量名, &数组元素, 一维数组名, 其他指针变量, 0
  • 初值中指定的变量名或数组必须已经定义

赋值格式: 指针变量 = 地址型表达式

  • 变量地址只能利用取地址运算符 & 来获得
  • & 的运算对象是变量或数组元素名, 运算结果是其地址
  • C 语言规定, 一维数组名可以看成是地址常量, 其值是一维数组的首地址. 而不允许把整数看成地址常量, 所以地址型表达式不能是整数

引用所指向的变量: *指针变量名

  • 需要事先将指针变量指向这个变量或数组
  • *指针变量名 则代表它所指向的变量或数组元素. * 称为指针运算符
  • 当指针变量的值为一维数组的首地址时, 它代表数组的第一个元素
float a, *pa = &a;
// *&a 是正确的. 相当于 *(&a), &a 是变量 a 的地址, *(a 的地址) 代表变量 a
// &*a 是错误的. 相当于 &(*a), 因为 a 不是指针变量, 所以 *a 不正确
// *&pa 是正确的. 相当于 *(&pa), &pa 是 pa 的地址, *(pa 的地址) 代表 pa
// &*pa 是正确的. 相当于 &(*pa), *pa 代表变量 a, &(变量 a) 代表 a 的地址

int i, *pi = &i;
// (*pi)++ 小括号必须, 因为 * 和 ++ 由右向左结合
// ++*pi 小括号非必须, 理由同上

指向一维数组的指针变量

将指针变量指向一维数组:

  • 初始化: *指针变量 = 数组名
  • 赋值: 指针变量 = 数组名

将指针变量指向一维数组元素:

  • 初始化: *指针变量 = &数组名[下标]
  • 赋值: 指针变量 = &数组名[下标]

引用数组元素 i:

  • 指针法: *(指针变量+i)*(数组名+i)
  • 下标法: 指针变量[i]数组名[i]

    虽然从书写格式上看, 指向一维数组的指针变量和数组名可以互换, 但它们是完全不同的概念. 指针变量是存放地址型数据的变量, 可以按照变量的处理方式对其进行运算或赋值; 而数组名代表数组首地址, 是一个地址常量, 只能按常量方式使用. 例如 pa = a 是正确的, 因为对变量可以赋值, 而 a = pa 是错误的, 因为不能对常量进行赋值.

指针变量和整数的算术运算:

  • 指针变量 + 整数
  • 指针变量 - 整数
  • ++指针变量
  • --指针变量
  • 指针变量++
  • 指针变量--

    运算结果为: (指针变量中的地址) 和 (整数 * 指针变量类型占用字节数) 运算之后的地址.

    上述规则组成的表达式即地址型表达式指针型表达式

指针变量和指针变量的减法运算: 指针变量 - 指针变量. 结果为它们所指向的数组元素下标相差的整数, 要求参与运算的指针变量必须指向同一个数组.

指针变量的关系运算: 指针变量 关系运算符 指针变量. 按指针变量中的地址值比较大小.

short a[10], *pa = a, *pb = a;
pa = pa + 5; // pa 指向了 a[5]
pa--; // pa 指向了 a[4]
pa - pb; // 4
pa < pb; // 假

指向字符串常量的指针变量

将指针变量指向字符串常量:

  • 初始化: *指针变量 = 字符串常量
  • 赋值: 指针变量 = 字符串常量

    注意: 虽然可以用赋值方式使指针变量指向字符串常量, 但不允许将字符串常量用赋值方式赋予字符型数组

char *p = "abcd";
char *p;
p = "abcd";

char a[10];
a = "abcd";

总结一下数组, 字符串以及指针之间的关系

// 初始化:
// - 使用数组保存字符串
// 1.
char s[10] = {"string"};
// 2.
char s[10] = "string"
// 3.
char s[] = "string";
// - 使用指针指向字符串
char *ps = "string";

// 赋值:
// - 使用数组: 报错!
s = "another";
// - 指针则可以
ps = "another";

// 如果 ps 指向 s
char s[10] = "string", *ps = s;
ps = "another";
// 注意, s 值并没有改变. 因为只是把 ps 从指向 s 变为指向另一个字符串常量 "another"
// 如果想改变 s 值, 可以使用 strcpy()
strcpy(ps, "another");
// 或
strcpy(s, "another");

指针数组

所有元素都是指针变量的数组, 就是指针数组.

定义格式: 数据类型 *指针数组名[长度]

赋值格式: 指针数组名[下标] = 地址表达式

引用所指向的变量或数组元素: *指针数组名[下标]

算术运算:

  • 指针数组名[下标] + 整数
  • 指针数组名[下标] - 整数
  • ++指针数组名[下标]
  • --指针数组名[下标]
  • 指针数组名[下标]++
  • 指针数组名[下标]--

关系运算: 指针数组名[下标] 关系运算符 指针数组名[下标]

函数

函数是一个可以反复使用的程序段. 建立函数称为函数的定义, 使用函数称为函数的调用. 调用其他函数的函数称为主调函数, 被调用的函数称为被调函数. C 中的主函数 main 只能是主调函数, 不能是被调函数.

函数定义

函数定义格式:

存储类型符 数据类型符 函数名(形式参数表) {
    数据定义语句序列;
    执行语句序列;
}

花括号括住的部分叫做函数体, 函数体前面部分称为函数头.

  • 存储类型符可以是 externstatic, 也可以省略, 默认 extern
    • extern: 定义外部函数, 它可以被其他编译单位中的函数调用
    • static: 定义内部函数, 它只能被本编译单位中的函数调用
  • 数据类型符可以是各种基本类型符, 指针型 (函数名前加 * 即可) 或 void (代表无返回值)
  • 形式参数表格式: 数据类型符 形式参数, ...
    • 当形式参数是数组时, 一维数组数组的长度, 二维数组的行长度可以省略

函数调用

每个程序文件称为一个编译单位.

为了帮助编译程序找到被调函数, 需要对被调函数进行声明, 除非以下情况:

  • 书写顺序上, 被调函数在主调函数之前
  • 被调函数在之后, 但它有返回值且返回整形或字符型

声明的位置一般在主调函数的函数体开头的数据定义部分

声明格式: 数据类型符 被调函数名(形式参数表);

调用函数格式: 函数名(实际参数表)


函数调用时的数据传递方式

  • 形参是变量时: 使用值传递方式

    计算实参的值, 并把值赋值给形参, 然后在函数体内参与运算. 形参的值改变不影响实参.

  • 形参是数组时: 使用地址传递方式

    实参是地址型的表达式, 例如数组的首地址, 或已经赋值的指针变量, 指针数组元素等. 将实参的 地址 赋予形参数组作为其首地址. 形参数组不分配内存, 占用和实参一样的内存空间. 对形参数组的改变如同对实参的改变.

  • 形参是指针变量时: 使用地址值的值传递方式

    实参必须是地址表达式. 函数调用时为形参分配内存, 计算实参表达式的地址值赋予形参指针. 在被调函数中, 形参对应的指针变量已经指向主函数的某个变量. 所以使用形参指针可以修改主调函数中的变量或数组元素的值.

变量的存储类型和作用域

变量可以存储于 CPU 寄存器, 也可以存于内存中的数据区和堆栈区, 把变量存放在哪个区域称为变量的存储类型.

存储类型 存储类型符 存储区域 备注
自动型 (默认) auto 内存的堆栈区 函数每次被调用时都要初始化, 调用完后释放. 不初始化则初值不确定
寄存器型 register CPU 通用寄存器 类似自动型
静态型 static 内存的数据区 程序开始运行时就分配了固定内存, 运行结束后释放. 每次进入函数, 其值还是上次离开函数的值. 如果不进行初始化, 系统自动赋 “零值”.
外部参照型 extern   用于编译单元之间传递数据, 不允许初始化

含有存储类型符的变量定义格式: 存储类型符 数据类型符 变量名, ...;

我们把在函数内部定义的变量叫做内部变量, 在函数外定义的变量叫做外部变量. 注意外部变量和外部参照型是两个不同概念.

从为变量分配内存单元到被回收的期间, 称为变量的生存期. 生存期覆盖了定义点到程序结束的变量叫全局变量, 而只覆盖某个函数或复合语句的变量称为局部变量.

在变量的生存期内, 可以使用它的程序区域即变量的作用域. 作用域既取决于它是内部变量还是外部变量, 还取决于它的存储类型.

变量生存周期

  • 外部变量不能重名. 如果全局外部变量和内部变量重名, 内部变量优先.
  • 不推荐使用全局变量, 它破坏了模块的独立性, 程序可读性差

函数 A 调用函数 B 即函数的嵌套调用, 递归调用则指自己调用自己.

多文件程序的处理

文件包含命令 #include 可以用指定的 “文件名” 中的文本内容替代该语句. 有两种格式:

  • #include <文件名> 系统仅按规定的路径搜索文件
  • #include "文件名" 先在本程序所在路径下寻找文件, 找不到再在系统规定的路径搜索文件

常用系统函数

"常用函数" "常用函数" "常用函数"

结构型

数组只可以存放同种类型的元素. 用户也可以自定义一种数据类型结构型用于存放类型不同的数据.

结构型的定义格式:

struct 结构型名 {
    数据类型符 成员名;
    ...
};
  • 结构型定义可以嵌套
  • 成员也成, 结构分量
  • 结构型仅仅是一种数据类型, 系统不会给结构型成员分配内存
// 定义结构型
struct birthday {
    int year;
    int month;
    int day;
};

// 定义结构型, 同时定义结构型变量
// 可以省略结构型名称 person, 变成无名称的结构型. 但这样做的话, 以后将无法使用这个结构型定义其他变量
struct person {
    char name[10];
    char sex;
    struct birthday bir; // 嵌套结构型, 注意前面的 struct
    float wage;
} a = {100001l, "zhao", 'f', {89, 94, 86} }, // 结构型变量
  b[2] = { {100002l, "qian", 'm', {78, 86, 92} }, // 结构型数组
          {100003l, "sun", 'f', {85, 68, 82} } };

// 声明结构型变量
struct person b, c;

// 声明结构型变量, 并赋初值
struct person e = {100001l, "zhao", 'f', {89, 94, 86}},
              f = {100002l, "qian", 'm', {78, 86, 92}};

// 声明结构型数组
struct person p[2] = { {100003l, "sun", 'f', {78, 86, 92} },
                      {100004l, "li", 'm', {85, 69, 92} } };

// 引用结构型变量成员
e.birthday.year;

// 引用成员地址
&e.birthday.year;

// 引用结构型变量地址
&e;

// 引用结构型数组元素成员
p[0].birthday.year;

// 引用结构型数组元素成员地址
&p[0].birthday;

// 引用结构型数组元素地址
&p[0];

// 引用结构型数组首地址
p

定义结构型变量时, 需要给其分配内存. 分配的内存字节数等于该结构型所有成员占用的字节数之和. 而且是按照定义时成员的排列顺序依次分配给每个成员的.

对结构型变量只能使用其中的成员, 一般不能直接使用结构型变量. 也不能直接使用结构型数组元素, 只能使用其成员. 对于嵌套的结构型变量, 外层结构型变量的结构型成员一般是不能单独引用的. 总之, 一般只能引用最叶子部分的成员.

引用结构型变量成员格式: 结构型变量名.成员名

引用结构型变量成员地址格式:

  • 成员是变量或者数组首地址: &结构型变量明.成员名
  • 成员是数组, 引用其元素地址: &结构型变量名.成员数组[下标]

. 称为成员运算符.

指向结构型数据的指针变量

指向结构型变量

定义: 和定义结构型变量的方法基本相同, 唯一区别是在变量名前面加一个 *. 可以将结构型和指针变量分开定义; 也可以同时定义结构型和对应的指针变量; 使用后一种方法还可以省略结构型的名称.

引用:

  • (*指针变量).成员名 必须加括号, 因为 . 优先级比 * 高
  • 指针变量->成员名 -> 称为指向运算符 使用指向结构型变量的指针变量来处理其成员的方法比使用指向成员的指针变量来处理成员的方法要简单.

定义某个结构型时, 其成员的类型也可以是本结构型, 但是这个成员只能是指针变量或指针数组.

struct birthday b[2] = { {89, 94, 96}, {89, 94, 96} };

struct person a, *pa = &a, *pb0 = &b[0];

pa->name;
(*pa).name;

(*pb0).day;
pb0->day;

指向结构型数组元素

引用格式: 均表示 数组名[k].成员名

  • (*指针变量).成员名
  • 指针变量->成员名

指向结构型数组

引用格式: 均代表 数组名[k].成员名. 与指向数组的指针大同小异, 注意括号的限定作用.

  • (*(指针变量+k)).成员名
  • (指针变量+k)->成员名

自定义类型

C 允许用户定义自己习惯的数据类型符, 替代系统提供的基本数据类型符, 数组类型, 指针类型和用户自定义的结构型等.

定义格式: typedef 类型符1 类型符2;

  • 类型符1: 可以是基本数据类型符, 也可使是用户自定义的无名称结构型.
  • 类型符2: 用户自选的一个标识符, 作为用户类型符. 建议用大写字母来组成.
typedef float REAL;
typedef char C_ARR[20];
typedef struct {
    ...
} PERSON;
typedef int *I_POINTER;
typedef char *C_ARR_POINTER[6];

typedef struct node {
    DataType data;
    struct node *next;
} Node, *LinkList;

Tricks

// 设 c 是小写字母, 转换成大写字母
c - 32;
// 设 c 是大写字母, 转换成小写字母
c + 32;

// 交换 a, b 的值
// - 方法一
x = a, a = b, b = x;
// - 方法二
a = a + b, b = a - b, a = a - b;

// 取某个正整数 x 的末尾数字
x % 10;
// 去掉某个正整数 x 的末尾数字
x / 10;

// 两整数相处, 结果会**向下取整**, 为了**向上取整**, 可以加上除数-1再除以除数
960 / 166; // 向下取整: 5
(960 + 165) / 166 // 向上取整