记录 C 语言填坑过程
结构体
- 在定义一个结构体变量之时,其已经被分配了应有的空间。(实际上,C 语言任何变量在定义时都会自动分配空间)
- 结构体在定义时可以进行初始化,使用{},但除了在初始化时可以使用{}赋值外,其余任何时候都不用{}赋值。(实际上,数组也是这样)
- 结构体数组同样,当进行初始化时,会自动分配整个数组的空间,也可以通过{}嵌套赋值。(也就是说,C 语言数组定义时要么要通过数字显式给出数组长度,要么通过{}进行初始化隐式给出长度,否则会语法错误)
- 结构体当中如果有数组,那么在定义结构体类型时,是否需要给出数组成员的长度?需要
- 结构体能否直接通过 A = B 来将B结构体的值赋给A?可以
文件
- 文件定义方式 FILE * fp; fopen(path, mode)/fclose(FILE *) mode 包含 r/w/a/b
- 文件读取方法
- 按字符 fgetc(fp) 返回字符,结尾 EOF
- 按行 fgets(buffer, buffer_l, fp),读入 buffer,通过
feof(fp)来检测文件尾,每行 buffer 从头写。fgets 当文件尾和出错时都会返回
NULL。固定写法:
while(!feof(fp)){if(fgets(…) != NULL){}} - 按格式 fscanf(fp, “format”, aim)返回读取项数
- 文件写入方法
- 按字符 fputc(char, fp)
- 按行 fputs(string, fp)
- 按格式 fprintf(fp, format, src)
数组
- 任何类型的数组,只有在定义时才能通过{}进行赋值(初始化)。
- 任何类型的数组,在定义时必须显式或隐式的给出数组长度,不可以不给出。
- 任何类型的数组在定义时便会分配好空间,后续无需再次分配。
- 数组名,本质上是一种指针常量,其不是普通的指针,不可以被赋值。切记不要把值赋给一个数组名,尤其是字符数组构成的字符串特别容易写错。
- 对于数组名 A,A[i]是值,A 是地址,可以有 A+1 来指向下一个值,但 A 本身的值无法更改,不可以 A++。
字符串
- C 语言中的字符串本质上就是一个字符数组。
- 字符串的输入输出
- 虽然本质上是一个数组,但是可以通过数组名与 printf 和 scanf
直接进行输入输出
- 无论在 printf 还是 scanf 其中 %s 接收的类型都是 char *,在 printf 中无需取值。
- 此外,fgets 也可进行输入,不过输入流是 stdin
- 虽然本质上是一个数组,但是可以通过数组名与 printf 和 scanf
直接进行输入输出
- 赋值方式:
- 定义时(初始化时),可以直接赋值。char A[] = "This is a String"; char B[] = {'H', 'I', '\0'};
- 除了定义时,其余任何时候,都不能通过数组名来直接赋值。
- 那么如何赋值?通常使用 strcpy(dest, str);
- C 语言字符串的最后必须包含一个隐藏的 '\0' 作为字符串的末尾标识。在通过{}定义字符数组时如果没加,则在使用字符串相关函数时会可能溢出数组。
- 为什么字符串本质上作为一种字符数组可以在printf中直接通过传入数组名来输出呢?其他类型数组不行。因为 %s 要接收的类型就是 char *,而字符数组名在传入函数时会退化为指针,因此可以传入。
- 千万别忘 C 语言任何字符串的最后都有一个
'\0',在定义字符数组长度时千万要注意!例如名字最多有 4
个字,但是字符数组必须长度为 5,例如学号固定长度 10
位,字符数组必须长度为 11。strlen()函数不会算入 '\0'。
1
char s[3] = "abc" // 该语句无法执行。直接通过字符串来初始化时,会自动在其末尾添加 '\0'
- 注意,C 语言中,char 类型只占 1 字节,这根本不够存储中文字符,在 UTF-8 下每个字符长达 3 字节。因此,在 C 语言中,中文字符不能用单个 char 来存储。同理,在字符数组构成的字符串中,如果该字符串要存储中文,切记不要把长度设定为要存储的中文长度,否则会超界。应该设为长度*3+1。
- 使用字符串相关函数如:strlen/strcat/strcpy/strcmp 时,记得加头文件 <string.h>
指针(极其易错 😭)
- 虽然数组名是一种特殊的指针常量,其包含一些数组信息,可以通过 sizeof(A)来获得整个数组的空间大小。但是,如果数组名传入函数当中,其会退化为普通指针,其不再包含数组信息,sizeof 只会获得指针本身的空间大小。(sizeof函数不会触发退化)
- 在 C
语言 中,普通变量(如
int a;)的内存地址是固定的,一旦分配,无法更改它的地址指向。- 你可以改变 指针 的值,让它指向不同的变量,但你 不能改变变量本身的地址 。
swap(&a, &b)交换的是a和b存储的值,但a和b本身在内存中的位置不会改变。要想实现传地址到函数,来修改普通变量的值,必须通过指针指向来修改值,不能改变指针本身,如果改变指针本身,修改的也只是传入的局部的指针(地址)变量,不会影响原有位置的值。- 不能通过取地址符号 & 获取普通变量地址之后修改普通变量的地址。普通变量的地址永远不变。&a = &b 是非法的。&a 是不可变的。而 *a 是可变的。
- 注意对指针进行运算时,一定要先保证已经分配好指针指向的空间。int * a
只会分配指针的空间,不会分配指针指向的值的空间(int)。当通过 * a
来赋值给 a 指向的空间时,如果该空间没有被分配,则会报错。但是可以给 a
赋值一个已经分配好空间的 int 变量地址,如
int i; int * a; a = &i;但不可以* a = i;int a 会分配 a 的空间,int * a 可不会分配,只会分配地址占用的空间。 - 再次强调:修改地址,是不会修改地址原先指向的值的,只会修改地址本身的指向,而普通变量的地址指向是没办法修改的,因此不可以通过修改普通变量的地址来实现修改普通变量的值。
运算
- int/int会截断小数部分。
- 如果想得到精确运算,将其中一个操作数进行类型转换。(float)int.
- 浮点数进行比较时,不可直接用 == 运算符,应该用 a - b < 1e-6 进行。
- scanf()读取整行会中断在空格处,而 fgets 可以读取整行,但 fgets 会读取末尾的换行符。gets 也可以读取整行,且不会读取换行符,但是过时了。
- ASCII 编码中,a-z、A-Z、0-9 都是按顺序编码的。因此单个字符可以通过比较来判断大小写和数字。将数字字符转为真数字,则可以通过 将字符减去 48 来实现。
- 在 printf 输出时,若要四舍五入输出浮点数保留到二位小数点,则 %.2f 会自动的四舍五入。
scanf处理空白字符(空格、回车、Tab)时会自动跳过,不影响读取下一个数字。因此连续读取多个值可以通过 %d %d %d 来实现。- switch 的写法
1
2
3
4
5
6
7
8
9
10
11
12switch(c){ // Switch 括号内可以写表达式,但 switch 只能处理整形结果(包括 char)
case 'a':
// 可正常写多个语句和语句块
break; // 勿忘!
case 'b':
break;
case 'c':
case 'd':
case 'e': // 多个 case 相同语句时的写法!!别写到一行里。
default:
...
} // switch 可以不包含 default,但尽量加上。 - C语言支持 -a 得到相反数。
- 在 OJ 评测中,C 语言若提前计算完毕要中途退出,最好用 return 0; 而不要用 exit()。
- 二维数组 A[i][j]中,A[0] 代表的是二维数组中第一个数组的==地址==。
- C 语言中的常量(如 1、2、3、'a'、"abc")是存储在只读区域,对其进行修改会导致错误。