笔记 - C
posted on 23 Oct 2018 under category note
这篇文章是对计算机应用专业自考教材 (迟成文版) 的知识点的汇总整理. 力求全面, 简明.
文中也加入了一些自己读教材时的一些总结. 最后的 Tricks 部分, 是一些书中的技巧性的代码, 后续还会继续往里添新.
写这篇文章主要是为了自己备考自查, 以及日常的快捷参考用. 它的目的不是作为一篇教程.
对于这本自考教材, 我觉得有几点需要注意:
通常将 K&R 的标准称为旧标准, 本书标准 ANSI C 称为新标准.
C 中允许出现的所有基本字符的组合称为 C 语言的字符集, 包括:
其他可显示字符:
~ ` ! @ # $ % ^ & * ( ) _ - + = | \ { } [ ] : ; " ' < > , . ? / 空格
部分转义字符:
\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
00
, -0111
0x0
, -0x111
整形常量:
100l
. 如果没有加 l 但超出短整型范围, 则自动认为该常量是长整形书写形式:
12.345
, -.234
, 47.
. 注意小数点前后数字可省略12.345e-2
, -1.2345E3
一般占用 4 字节. 有效数字是 7 位, 如 1.23456789
和 1.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
)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 {
语句;
}
else
总是和前面最近的 if
配对switch (表达式) {
case 常量表达式: 语句组;
break;
...
default: 语句组;
}
while (表达式) {
语句;
}
do {
语句;
} while (表达式);
for (int i = 0; i < 11; i++) {
语句;
}
switch
中continue
之后的语句, 重新判断循环控制条件决定是否循环数组中的所有元素的数据类型必须相同. 元素的类型就是数组的类型.
定义格式: 数组类型符 数组名[长度],...;
引用格式: 数组名[下标]
初始化格式: 数据类型符 数组名[长度]={初值表}, ...;
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
.
不论存若干字符还是字符串, 每个字符数组的元素都可以作为一个字符型变量来使用. 但是, 对于存放字符串的字符数组还有一些特殊的处理方法.
定义格式: 同普通数组
初始化格式:
\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
赋值格式: 指针变量 = 地址型表达式
&
来获得&
的运算对象是变量或数组元素名, 运算结果是其地址引用所指向的变量: *指针变量名
*指针变量名
则代表它所指向的变量或数组元素. *
称为指针运算符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
只能是主调函数, 不能是被调函数.
函数定义格式:
存储类型符 数据类型符 函数名(形式参数表) {
数据定义语句序列;
执行语句序列;
}
花括号括住的部分叫做函数体, 函数体前面部分称为函数头.
extern
或 static
, 也可以省略, 默认 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;
// 设 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 // 向上取整