C 语言学习笔记
本书参考自谭浩强的《C程序设计(第四版)》
C 语言程序的结构特点
- 一个程序由一个或多个源程序文件组成。一个源程序文件中可以包括
预处理指令
、全局声明
、函数定义
三个部分。 函数
是 C 程序的主要组成部分。- 一个函数包括两个部分:
函数首部
和函数体
。 - 程序总是从
main
函数开始执行的。 - 程序中对计算机的操作是由函数中的 C 语句完成的。
- C 语言本身不提供输入输出语句。
- C 语言程序开发步骤:
预处理
->编译
->链接
->运行
,文件生成:源文件
->目标文件
->可执行文件
。 "\"
+ 3位数字如"\101"
表示八进制数 101 的 ASCII 字符,即'A'
;"\x"
+ 2位数字如"\x41"
表示十六进制 41 的 ASCII 字符,即'A'
;"\"
+ 2位数字如"\99"
则直接输出99
。- 在计算机高级语言中,用来对变量、符号常量名、函数、数组、类型等命名的有效字符序列统称为标识符。C 语言规定标识符只能由
字母
、数字
和下划线
3 种字符组成,且第一个字符不能为数字。C 语言大小写敏感
。 - 正数的补码为该数的二进制;负数的补码为该数的绝对值的补码按位取反再加 1。
float
的有效数字为 6 位,double
的有效数字为 15 位,long double
的有效数字为 15 或 19 位(取决于占 8 字节还是 16 字节)。- 两个整数相除的结果为整数,如
5/3
的结果是1
;但是如果除数和被除数中有一个负值,则舍入的方向是不固定的,多数系统采用向零取整
的方法,即-5/3
的值为-1
。 %
运算符要求参加运算的对象为整数
,结果也是整数。a=b=c
的赋值顺序是从右到左
,先把 c 的值赋值给 b,再把 b 的值赋值给 a。- 运算符两侧存在小数时,会将所有的数都转换成
double
型再计算。(然而实际上并不是) - 一个表达式的最后加一个分号就成了一个语句。
选择结构程序设计
- 6 种关系运算符的优先级:
<, <=, >, >=
的优先级高于==, !=
。 - 关系运算符的优先级低于算术运算符:
c > a + b
等效于c > (a + b)
。 - 关系运算符的优先级高于赋值运算符:
a = b > c
等效于a = (b > c)
。 - 逻辑运算符优先级:
!
>&&
>||
。 - 逻辑运算符中的
&&
和||
优先级低于关系运算符:a > b && x > y
。 - 逻辑运算符中的
!
高于算术运算符。 switch
后面括号内的表达式值类型应为整数
类型(包括字符型)。switch
中如果 case 子句中没有 break 语句,将连续输出。
数组
- 可变长数组不可指定为静态存储方式:
static int a[2*n]
不合法。 - 二维数组的初始化方法:
// 分别赋值
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}, {2}}; // 结果是 1 0 0 2 0 0
// 若对全部元素赋初值,则第 1 维的长度可不指定
int a[][2] = {1, 2, 3, 4};
// 若第 1 维长度不指定且不给全部初始值,则只初始化给定数据的行的值:
int a[][2] = {1}; // 值为 1 0
int a[][2] = {1, 2, 3}; // 值为 1 2 3 0 - 二维数组的第一维的值是一个地址。
int a[2][3]; // a[1] 的值为一个地址 (0x00BBFD44)
- 字符数组相关函数(需引入
string.h
头文件):strcat(dest, src); // 将 src 的值拼接在 dest 后面,会自动去掉 dest 的 "\0", 函数返回 dest 的地址。
strcpy(dest, src); // 复制 src 的值到 dest 中,复制的字符数量为 src 的长度,包括 "\0",dest 的剩余字符不会被修改。
strncpy(dest, src, maxlen); // strcpy 的升级版,第三个参数为复制的字符数量,不包括 "\0"。
strcmp(s1 ,s2); // 从左到右依次对比两个字符串的字符,直到出现不同或 "\0" 为止。完全相同返回 0;不相同时,若 s1 的字符 ASCII 码大,返回正数,反之返回负数。
strlen(s); //返回字符串的实际长度,不包括 "\0"。
strlwr(s); //将字符串中的大写字母转换为小写字母。
strupr(s); //将字符串中的小写字母转换为大写字母。
函数 (function)
- 函数在声明的时候可以不写形参名
int test(int);
,也可以不写类型名int test(a);
。 - 函数的声明可以放在函数体内(作用域为当前函数)。
- 在定义和声明变量和函数时可以指定存储类别:
自动的(auto)
、静态的(static)
、寄存器的(register)
、外部的(extern)
。 - 自动变量(auto):函数中的局部变量,如果不专门声明为 static 存储类别,都是动态地分配存储空间的,数据存储在动态存储区中,函数结束时自动释放。
- 静态变量(static):在静态存储区内分配存储单元,在程序整个运行期间都不释放,会自动初始化变量。
- 寄存器变量(register):数据存放在寄存器中,存取速度远高于内存。编译系统会自动将频繁使用的变量放在寄存器中,实际使用的必要性并不大。
- 外部变量(extern):函数外部定义的全局变量,作用域为从变量定义处开始到本程序文件末尾。用 extern 声明变量时类型名可以不写,如
extern A
。提倡将外部变量定义放在引用它的所有函数之前,这样可以避免多加一个 extern 声明。 - 若需将变量作用域扩展到其他文件,可以在其他文件里使用 extern 来扩展其作用域。
- 外部变量放在静态存储区中;若用 static 修饰外部变量,则会限制于本文件访问,其他文件使用 extern 也无法访问。
- 内部函数(static):只能被本文件中的其他函数所调用,也称为静态函数。
- 外部函数(extern):如果在定义函数时不指定类别,默认为外部函数,可供其他文件调用(在文件中用 extern 声明函数)。
指针
- 指针
+1
时,实际增加的地址值取决于指针的类型字节数。 - 在编译时,对数组元素
a[1]
就是按*(a+1)
来处理的。 - 两个指针指向同一个数组时,可以相减,结果为两个指针指向的元素之间的个数。
*
与++
同优先级,结合方向为自右而左
。(*p++
等价于*(p++)
)- 字符指针变量指向的字符串常量中的内容是不可被改变的。(
char* s = "abcde";
) - 函数指针:
int (*p)(int a, int b);
,*p
两侧的括号不能省略。 - 函数指针作参数:
int fun(int (*p)(int a, int b)){}
。 - main 函数的参数可以在操作系统启动程序的时候传递实参:(
a.exe "abc" "def"
)。 - 内存动态分配相关函数(需引入
stdlib.h
头文件):malloc(size); // 分配的内存是连续的空间,失败返回 NULL。
calloc(n, size); // 分配 n 个连续的空间,一般用来给数组分配空间(动态数组),失败返回 NULL。
free(p); // 可以释放指针所指向的动态空间。
realloc(p, size); // 可以重新分配由 malloc 或 calloc 分配的内存大小,失败返回 NULL。
自定义数据类型
- struct 是声明结构体类型时所必须使用的关键字,不能省略。
- 定义结构体变量时也需要加上 struct 关键字:
struct Student stu1, stu2
;。 - 计算机对内存的管理是以字为单位的,如果在一个字中只存放了一个字符,虽然只占一个字节,但该字的其他三个字节不会接着存放下一个数据,而会从下一个字开始存放其他数据,因此在使用 sizeof 运算符测量结构体的长度时得到的往往不是理论值。
- 可以在声明结构体类型同时定义该结构体变量(
struct Student { 成员列表 } 变量名列表;
)。 - 可以不指定结构体类型名而直接定义结构体变量(
struct { 成员列表 } 变量名列表;
)。 - 结构体可以定义在函数体内。
- 可以对结构体内某一成员进行初始化(``),此时其他成员也会被初始化为各类型初始值。
struct Student b = {.age = 18};
- 同类型的结构体变量可以互相赋值(
stu1=stu2;
)。 - 共用体为几个变量共用一个内存区,其所占的内存长度等于最长的成员的长度。
- 对共用体变量进行初始化时,初始化表中只能有一个量。
- 每一个枚举元素都代表一个整数,默认是从 0 开始,也可以在定义的时候手动指定。
- typedef 可以声明一种新的数据类型代替原有的数据类型。
typedef struct { int year; int month; int day } Date;
- typedef 定义数组变量:
typedef int nums[100];
- typedef 定义指针变量:
typedef int* Pnum;
- typedef 定义函数指针变量:
typedef int(*Pointer)(int a, int b);
- typedef 可以用来解决将程序移植在不同操作系统时遇到的字节大小不同或函数名不一致的问题。
文件的输入与输出
- 文件名也叫文件标识,包括文件路径、文件名主干和文件后缀三个部分。
- ANSI C标准采用缓冲文件系统来处理数据文件,系统自动在内存区为程序中每一个正在使用的文件开辟一个缓冲区。从内存向磁盘输出数据时必须先送到缓冲区,
等数据装满缓冲区之后才一起送到磁盘
;从磁盘读入数据到内存时,则一次从磁盘将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个将数据送到程序的数据区(程序变量)。缓冲区的大小由编译系统决定。 - 每一个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的信息,这些信息放在一个结构体变量中(FILE)。
- 打开文件会为文件建立相应的信息区和文件缓冲区;关闭文件会撤销信息区和缓冲区。
- 通常将 fopen 函数的返回值赋给一个文件指针变量(
FILE *fp
)。 - 打开文件的方式:
r
、w
、a
分别为只读、只写、追加一个文本文件。如果需要读写二进制数据,则在其后面加b
。如果打开文件既要读又要写,则在其后面加+
。 - 打开一个不存在的文件时,如果打开方式中有
w
,会新建一个文件,否则会出错返回 NULL。 - 回车符为
\r
,换行符为\n
。读文本文档时,会自动将文件中的回车换行符(\r\n
)转换为一个换行符(\n
);写文本文档时,会自动将换行符转换为回车换行两个字符。读写二进制文件则不会自动转换。 - C语言系统已经把 fputc 和 fgetc 函数宏定义为 putc 和 getc。(然而并没有找到)
- 文件操作相关函数:
fopen(filename, mode); // 打开一个文件。
fclose(fp); // 关闭打开的文件。
fgetc(fp); // 从文件中读取一个字节并将索引后移一位,成功返回读取的字符,失败返回 EOF(-1)。
fputc(ch, fp); // 写入一个字节到文件中并将索引后移一位,成功返回输出的字符,失败返回 EOF(-1)。
feof(fp); // 判断文件指针的索引是否移动到文件末尾,遇到文件结束返回非 0,否则返回 0。
fgets(str, n, fp); // 从 fp 指向的文件读入 n 个字节放入字符数组 str 中,成功返回 str 地址,失败返回 NULL。
fputs(str, fp); // 把 str 所指向的字符串写到文件指针变量 fp 所指向的文件中,成功返回 0,失败返回非 0 值。
fprintf(fp, str, list); // 格式化输出文件,fprintf(fp, "%d", i) 输出的内容就是 i 的值。
fscanf(fp, str, list); // 格式化输入文件,fscanf(fp, "%d", &i) 读取文件中的整数赋值给变量 i。
fread(buffer, size, count, fp); // 用二进制方式读文件。其中 size 是字节数,count 是数据项。比如读取数据给一个整数数组,size 就是 4,count 就是数组长度。
fwrite(buffer, size, count, fp); // 用二进制方式写文件。
rewind(fp); // 将文件内部的指针重新指向一个流的开头。
fseek(fp, offset, fromwhere); // 重定位流(数据流/文件)上的文件内部位置指针。fromwhere 可取值 文件头 SEEK_SET、当前位置 SEEK_CUR、和文件尾 SEEK_END,实际值分别对应 0 1 2。offset 是基于 fromwhere 的偏移量。
ftell(); // 用于得到文件位置指针当前位置相对于文件首的偏移字节数。
ferror(fp); // 判断文件操作是否出错,返回 0 表示未出错,返回非 0 表示出错。每次调用文件输入输出函数都会产生一个新的 ferror 值,因此应当在输入输出后立即检查避免信息丢失。执行 fopen 时会自动置 0。
flearerr(fp); // 将文件错误标志和文件结束标志置为 0。一般用在 ferror 检测到出错之后将标志置 0,以便下一次检测。
预处理指令
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 向日葵!
评论