c review

c review

KDAIer Lv1

1. 格式字符串

格式字符串由普通字符(除了 % )和转换指示构成,前者被复制到输出而无更改。每个转换指示拥有下列格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*format testing*/
#include <stdio.h>
int main()
{
double radius = 5;
printf("%lf\n", radius);
/*default 8.6lf*/
printf("%10lf\n", radius);
printf("%10.2lf\n", radius);
printf("%-10.2lf\n", radius);
printf("%+10.2lf\n", radius);
printf("% 10.2lf\n", radius);
printf("%010.2lf\n", radius);
return 0;
}

运行结果:

1
2
3
4
5
6
7
5.000000
5.000000
5.00
5.00
+5.00
5.00
0000005.00

2. printf 与 scanf 调用格式

  • printf 调用格式:
    printf("<格式化字符串>", <参量表>);

  • scanf 调用格式:
    scanf("<格式化字符串>", <地址表>);

格式符 说明
%c 读单字符
%d 读十进制整数
%l 读十进制 Long 型整数
%ll 读十进制 Long Long 型整数
%i 读十进制、八进制、十六进制整数
%e 读浮点数
%E 读浮点数
%f 读浮点数
%g 读浮点数
%G 读浮点数
%o 读八进制数
%s 读字符串
%x 读十六进制数(其中 abcdef 小写,大写时会被忽略)
%X 读十六进制数(其中 ABCDEF 大写,小写时会被忽略)
%p 读指针值

3. 常量

3.1 整数常量

用整数前缀表示非十进制。例如:

  • 二进制:前缀 0b
  • 八进制:前缀 0
  • 十六进制:前缀 0x

用整数后缀表示无符号整数(u/U)以及长整数(l/L)。

1
2
3
4
5
212 /*合法的*/
215u /*合法的*/
0xFeeL /*合法的*/
078 /*非法的:8不是八进制的数字*/
032UU /*非法的:U只能出现一次,不能出现重复后缀*/

3.2 浮点常量

浮点常量由整数部分、小数点、小数和指数部分组成,可以使用小数形式或者指数形式来表示。

  • 小数形式:必须包含整数部分、小数部分,或同时包含两者。
  • 指数形式:e 前面为尾数部分(必须有数),e 后为指数部分(必须为整数)。
1
2
3
4
5
3.14159 /*合法的*/
3.14159E-5L /*合法的*/
510E /*非法的:指数部分没有数字*/
210f /*非法的:没有小数或指数*/
.e55 /*非法的:缺少整数部分或分数*/

3.3 字符常量

英文字母用 0-127 的整数表示,类型为 char
常见的表示方法有两种:

  • 直接表示:空格或大部分可见的图形字符
  • 转义符表示:\字符、八进制、十六进制数
转义字符 含义
\ \ 字符
‘ 字符
“ 字符
? ? 字符
\a 警报铃声
\b 退格键
\f 换页符
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
\ooo 一到三位的八进制数
\xhh… 一个或多个数字的十六进制数

4. 存储类

存储类定义了 C 程序中变量/函数的范围(可见性)和生命周期,这些说明符放置在它们所修饰的类型之前。C 中可用的存储类包括:autoregisterstaticextern

  • extern 存储类
    用于提供一个全局变量的引用,全局变量对所有程序文件均可见。当有多个文件并定义了一个可在其他文件中使用的全局变量或函数时,可以使用 extern 来引用已定义的变量或函数。如果将其他文件的变量定义加上 static,则会编译错误,因为 static 修饰后对其他文件不可见。

5. 变量基本类型

类型 类型标识符 字节 数值范围(十进制) 数值范围(指数形式)
整型 [signed] int 4 -2147483648 ~ +2147483647 -2³¹ ~ +2³¹-1
无符号整型 unsigned [int] 4 0 ~ 4294967295 0 ~ +2³²-1
短整型 short [int](int可省略) 2 -32768 ~ +32767 -2¹⁵ ~ +2¹⁵-1
无符号短整型 unsigned short [int] 2 0 ~ 65535 0 ~ +2¹⁶-1
长整型 long [int] 4 -2147483648 ~ +2147483647 -2³¹ ~ +2³¹-1
无符号长整型 unsigned long [int] 4 0 ~ 4294967295 0 ~ +2³²-1
长长整型 long long [int] 8 -9223372036854775808 ~ 9223372036854775807 -2⁶³ ~ +2⁶³-1
无符号长长整型 unsigned long long [int] 8 0 ~ 18446744073709551615 0 ~ +2⁶⁴-1
字符型 [signed] char 1 -128 ~ +127 -2⁷ ~ +2⁷-1
无符号字符型 unsigned char 1 0 ~ +255 0 ~ +2⁸-1
单精度型 float 4 -3.4×10³⁸ ~ 3.4×10³⁸ -3.4×10³⁸ ~ 3.4×10³⁸
双精度型 double 8 -1.7×10³⁰⁸ ~ 1.7×10³⁰⁸ -1.7×10³⁰⁸ ~ 1.7×10³⁰⁸
长双精度型 long double 8 -1.7×10³⁰⁸ ~ 1.7×10³⁰⁸ -1.7×10³⁰⁸ ~ 1.7×10³⁰⁸

[ ] 表示可省略

  • 声明:
    告诉编译器变量的名称和类型,但不一定会分配存储空间或初始化。只是告诉编译器有这个变量,可能没有分配内存。
    注意: 在 C/C++ 中,带 extern 关键字的变量声明不会分配存储空间,而是引用已在其他地方定义的变量;某些语言(如 Java)会默认初始化变量,而 C 语言不会。

  • 定义:
    除了指定变量类型外,还会分配存储空间,并可能进行初始化。变量可以有初始值,也可以没有。在 C/C++ 中,不加 extern 的变量声明即为定义。

1
extern int a;  // 只声明,不分配存储空间
1
int a = 10;  // 定义并初始化,分配存储空间

示例

file1.cpp

1
int globalVar = 100;  // 定义全局变量

file2.cpp

1
extern int globalVar;  // 声明全局变量,不分配存储
  • 局部变量:
    ① 自动变量(动态局部变量,离开函数值消失)
    ② 静态局部变量(离开函数值保留)
    ③ 寄存器变量(离开函数值消失)
    ④ 形式参数(可以定义为自动变量或寄存器变量)

  • 全局变量:
    ① 静态外部变量(仅限本程序文件使用)
    ② 外部变量(允许其它程序文件引用)

注:静态变量也可归于局部变量。

内存区域 说明 典型分配方式
代码区 (Code Area) 存储程序的机器指令,通常是只读的 编译时分配
静态区 (Static Area) 存放全局变量和静态变量,在程序执行前分配 编译时分配
堆 (Heap) 用于动态分配的内存块 new/deletemalloc/free
自由内存 (Free Memory) 堆和栈之间的未分配内存空间,可供使用 N/A
栈 (Stack) 存储函数调用的局部变量、返回地址等 运行时自动分配

6. 运算符

  • 位运算优先级:
    由高到低依次为:~<<>>&|^
    位运算对象只能是整型(int)或字符型(char)数据,运算是对每个二进制位分别进行。(下表优先级从高到低)
类别 运算符 结合性
后缀 () [] -> . ++ -- 从左到右
一元 + - ! ~ ++ -- (type) * & sizeof 从右到左
乘除 * / % 从左到右
加减 + - 从左到右
移位 << >> 从左到右
关系 < <= > >= 从左到右
相等 == != 从左到右
位与 AND & 从左到右
位异或 XOR ^ 从左到右
位或 OR | 从左到右
逻辑与 AND && 从左到右
逻辑或 OR || 从左到右
条件 ?: 从右到左
赋值 = += -= *= /= %= >>= <<= &= ^= |= 从右到左
逗号 , 从左到右

6.1 三目运算符

  1. 条件运算符优先级低于关系运算符和算术运算符,但高于赋值符。
  2. 结合方向是自右至左。
1
2
3
a>b ? a : c>d ? c : d;

a>b ? a : (c>d ? c : d);

6.2 按位与的实际用途

  • 清零:
    将某个数的某个比特清零。
    例如:有数 00101011,若想将其右数第 4 位清零,可构造一个数 11110111(对应位为 0,其余为 1),然后进行按位与运算。
1
2
3
4
  00101011  
& 11110111
------------
00100011
  • 取特定位:
    例如:取两字节整数 a=(16a1)16 的低字节,构造 b=(00ff)16=(255)10,然后 a & b 即可。
1
2
3
4
  0001 0110 1010 0001   (16A1)₁₆
& 0000 0000 1111 1111 (00FF)₁₆
----------------------
0000 0000 1010 0001 (00A1)₁₆

6.3 异或的实际用途

  • 翻转特定位:
    例如:将 01111010 的低 4 位翻转,与 00001111 异或即可。
1
2
3
4
  01111010  
00001111
------------
01110101
  • 与 0 异或:
    保留原值,如 012 ^ 00 = 012
1
2
3
4
  00001010  
00000000
------------
00001010

6.4 取反的实际用途

若一个16 位整数,想使最低一位为 0,可以将 与二进制数 进行按位与:

1
2
3
4
  0000000000111101  
& 1111111111111110
------------------
0000000000111100

但若将此程序移植到 32 位计算机上,由于整数用 4 个字节表示,想将最后一位变成 0 就不能用&了,应改用&。这个算法的移植性很差,可以改用 &

因为在以 2 个字节存一个整数时, 1 的二进制形式为。以 4 个字节存储一个整数时,


7. 隐式转换

  • 如果两个操作数类型不一样,则会发生隐式类型转换。
1
complex > imaginary > long double > double > float > 整数
  • 低等级操作数转换到高等级:
1
2
1.f + 2000000L; // long int 转 float;执行 float 加法
1.0 + 1.0f + 1; // 左结合,float 转 double,执行 double 加法;int 转 double,执行 double 加法
  • 两个整数 i, j 需要执行浮点除法时,可写为 1.0 * i / j(double)i / j

  • 整数类型等级:

1
long long > long >= int >= short > char
  • 整数提升:
    若存在高于 int 等级的类型,则低等级操作数转换为高等级;否则都自动转换为 intunsigned int
    提升后运算时,unsigned 优先:
1
2
3
4
a_char + 'a' // 两个操作数均转为 int
2 + 3LLU // int 转为 unsigned long long
2u - 10 // 10 转为 unsigned int,结果为 4294967288(即 UINT_MAX-7)
2u - 10LL // 2 从 unsigned 转为 long long,结果为 -8

8. switch 语句

  • 基本格式:switch结构中的” 表达式” ,其值的类型应为整数类型(包括字符类型char等)。switch下面的花括号是一个语句块,其中包含了多行以关键字case为开头的语句。每行语句case后面跟最一个常量
1
2
3
4
5
6
7
8
9
switch(表达式)
{
case 常量1: 语句1; break;
case 常量2: 语句2;
case 常量3: 语句3; break;
...
case 常量n: 语句n; break;
default: 语句n+1;
}
  • 示例:
1
2
3
4
5
6
7
case 10: printf("..."); break; // 正确
case 8+9: printf("..."); break; // 正确
case 'A': printf("..."); break; // 正确,字符和整数可互转
case 'A'+19: printf("..."); break; // 正确,字符和整数可互转
case 9.5: printf("..."); break; // 错误,不能为小数
case a: printf("..."); break; // 错误,不能包含变量
case a+10: printf("..."); break; // 错误,不能包含变量

9. 实数比较

在计算机中,连续实数由 floatdouble 表示,由于离散表示,存储的计算结果往往只是近似值。因此,两个实数变量不能直接使用 ==!= 进行比较,或判断其是否为零。特别是在循环语句中使用 (x == y) 比较,可能导致不确定的死循环。实际中,实数比较一般仅使用 ><,而判断相等时可使用 fabs(x-y) < epseps 为合适的小数,如 1e-7),判断零时可使用 fabs(x) < eps

  • 计算 PI(精确到小数点后 5 位)的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*pi*/
pi = 0;
int i = 1;
const double eps = 1e-6; // 精度保证5位
while (1.0 / i > eps)
{ // 不合适用 for 语句
pi += 1.0 / i;
i++;
i++;
pi -= 1.0 / i;
i++;
i++;
}
pi *= 4;
printf("%.5f\n", pi);

10. 头文件

  1. 自定义的头文件使用双引号 " " 包围。
  2. 头文件第一行使用 #pragma once 指令确保只展开一次。
  3. 头文件扩展名为 .h
  4. 头文件通常包含共享的函数声明、全局变量声明与初始化。
    注意: 头文件中函数与变量不能用 static 修饰。

11. 最大公约数(辗转相除法)

欧几里得算法(辗转相除法)的递归公式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
int gcd(int a, int b)
{
if (b == 0)
return a;
return gcd(b, a % b);
}
int main(int argc, char *argv[])
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d", gcd(a, b));
return 0;
}

12. 预处理指令

预处理指令控制预处理器行为,每个指令占据一行,必须以 # 开头。支持的指令包括:defineundefincludeififdefifndefelseelifelifdefendiflineerrorpragma

  • 条件编译示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifdef 标识符
// 程序段1
#else
// 程序段2
#endif

#ifndef 标识符
// 程序段1
#else
// 程序段2
#endif

#if 表达式
// 程序段1
#else
// 程序段2
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#define LETTER 1
int main()
{
char str[20] = "C Language", c;
int i;
i = 0;
while ((c = str[i]) != '\0')
{
i++;
#if LETTER
if (c >= 'a' && c <= 'z')
c = c - 32;
#else
if (c >= 'A' && c <= 'Z')
c = c + 32;
#endif
printf("%c", c);
}
}

// 输出:C LANGUAGE
  • 文本宏:
    利用 #define 定义标识符代表字符串;已定义的宏可用 #undef 撤消。
    注意: 定义时不能有分号(#define N 3; //出错! ,不能有分号,宏定义N 代表3; 注意3后面有” ;”,而将“3;”代入表达式中N,当然出错! )。

  • 包含其他文件:
    #include "stdio.h"(先在当前工程目录下查找)或 #include <stdio.h>(按标准方式查找)。

  • 产生错误:
    使用 #error 指令。

  • 防止头文件重复包含:
    如果一个头文件被引用两次,编译器会处理两次头文件的内容,这可能产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:

1
2
3
4
5
6
#ifndef HEADER_FILE
#define HEADER_FILE

// 整个头文件内容

#endif

13. 数组

注意数组定义时不能用变量作为长度(例如 arr[m],其中 m 为变量,必须为常量)。//m为变量,必须为常量才可以 const int m = 5;

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
int arr1[]; // 没有声明元素数量
int m = 5;
int arr2[m]; // C99 可变长度数组/VLA(仅适用于自动变量)
int arr3[N];
arr3 = {1, 2, 3, 4, 5}; // 错误:数组标识符是值(地址),不能作为左值
int arr4[N] = {1, 2, 3, 4, 5, 6}; // 初始化超长度(warning)
int arr5[N] = {1, 2, 3, 4, 5}, a[N];
a = arr5; // 错误:a 不是左值
return 0;
}
  • 获取数组长度:
    #define LEN_ARRAY(a) (sizeof(a) / sizeof(a[0]))
    sizeof(array) 返回数组定义的字节数。

  • 数组名:
    数组名是地址值(右值),同时也是第一个元素的地址,即 a == &(a[0]),因此 *a == *&(a[0]) == a[0] ,为左值。

  • 类型隐式转换:

    int a[] = {1, 2, 3.14, 4, 5}

  • 数组元素赋零值:
    memset(arr, 0, sizeof(arr))

1
2
3
4
5
6
7
8
9
void vadd(int a[const], const int b[], const size_t sz)
{
for (int i = 0; i < sz; i++)
{
a[i] += b[i];
// a++; // increment of read-only parameter 'a'
// b[i] = 1; // assignment of read-only location
}
}
1
2
3
void matrix_add(int a[][3], int b[][3], size_t m){
vadd(a, b, m * LEN_ARR(a[0]));
}
  • 数组形参是一个变量,可保存数组的地址,用不定长数组表示,如 b[]。如果形参定义为 int b[10], 则编译忽略数组长度。
  • const 在函数申明中应用int a[const] 表示形参 a 不可修改;const int b[] 表示b的元素不可修改;const size_t sz 表示sz不可修改
  • 函数中无法获得数组的长度,通常会将数组长度作为参数传给函数,类型是size_t
  • 多维数组形参仅第一维用不定长数组表示,如 b[][3]。
  • 如果形参定义为 int b[10][3], 则编译解释 int b[][3]。
  • 多维数组形参除第一维外,长度必须是常量

14. 字符数组

在 C 语言中,字符串作为字符数组处理。关心的是字符串的有效长度(以 '\0' 为结束标志)。

1
2
3
4
5
6
7
8
#include <stdio.h>
int main()
{
char str[13];
scanf("%s", str);
printf("%s\n", str);
return 0;
}

例如,从键盘输入 “How are you?” 时,由于空格作为分隔符,实际只读入 “How” 到 str 中。

H o w \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
  • 输入输出方式:
    (1) 逐个字符使用 %c
    (2) 整个字符串使用 %s

15. ASCII 码

ASCII 码表

  • 控制符: 共 33 个(前 32 个字符,例如 7 对应 '\t',10 对应 '\n',13 对应 '\r' 等),127 也是控制符。
  • 可打印字符: 共 95 个(例如 32 为空格,48-57 为 ‘0’… ‘9’,65-90 为 ‘A’… ‘Z’,97-122 为 ‘a’… ‘z’)。
  • 空白字符: 包括空格、水平制表、垂直制表、换页、换行。

16. Unicode

Unicode 是一个字符集,是其他字符集的超集,包含来自 ISO/IEC 6937、ISO/IEC 8859 家族、Big5、KS X 1001、JIS X 0213、GB2312、GBK、GB 18030、HKSCS、CNS11643 等字符集的字符。它包含三种编码形式:

  • UTF-8:变长格式

  • UTF-16:16 位宽字符

  • UTF-32:32 位宽字符

  • 单字节字符: 表示英文或西方字符,类型 char(例如 'a''\n''\13'

  • 16 位宽字符: 表示中文、日文等,类型 char16_t(例如 u'字',但非 u'🍌'

  • 32 位宽字符: 表示 emoji 等扩展,类型 char32_t(例如 U'字'U'🍌'

  • 宽字符: 类型 wchar_t,可设定为 16 位或 32 位(例如 L'β'L'字'


17. 字符与字符串

  • 子串: 由某个字符串中连续的一段字符组成
    例如,“abcd”的所有子串为:Ø, “a”, “b”, “c”, “d”, “ab”, “bc”, “cd”, “abc”, “bcd”, “abcd”。
    长度为 n 的字符串共有 n*(n+1)/2 + 1 = O(n²) 个子串。

  • 前缀: 某个字符串开头的一段子串
    “abcd”的前缀为:Ø, “a”, “ab”, “abc”, “abcd”。共 n+1 个前缀。

  • 后缀: 某个字符串末尾的一段子串
    “abcd”的后缀为:Ø, “d”, “cd”, “bcd”, “abcd”。共 n+1 个后缀。

  • 库函数:
    Isalnum(ch) 判断字符是否为数字或字母;
    tolower(ch) 返回字母的小写形式,非字母则返回原字符。

1
2
3
4
5
6
7
8
char msg[4];
msg = "Hi!"; // Error: array type 'char [4]' is not assignable

char msg[4];
msg[0] = 'H';
msg[1] = 'i';
msg[2] = '!';
msg[3] = '\0'; // Null-terminate the string
操作 代码 操作 代码
输入 scanf("%s", str); 字符串的拷贝 strcpy(toStr, fromStr)
输出 printf("%s", str); 字符串的比较 strcmp(str1, str2) / strncmp(str1, str2, n)
整行输入 gets(str); 转换为小写 strlwr(str)
整行输出 puts(str); 转换为大写 strupr(str)
获取/操作第i个字符 str[i] 字符检索 strchr(str, c)
求字符串str长度 strlen(str) 字符串检索 strstr(str1, str2)
字符串的连接 strcat(toStr, fromStr) / strncat(toStr, fromStr, n) 字符串结束标志 '\0'(ASCII码为0的字符)
头文件 分类 常见函数
ctype.h 字符分类 isalnum, isalpha, isdigit, islower, isupper, isspace, isprint ……
字符操作 tolower, toupper
stdlib.h 转换成数值 atof, atoi(注:itoa 是非标函数)
string.h 字符串操作 strcpy, strncpy, strcat, strncat
字符串检验 strlen, strcmp, strchr, strrchr, strstr ……
字符数组操作 memchr, memcmp, memset, memcpy, memmove ……
杂项 strerror

18. 指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*pointer array*/
#include <stdio.h>
int main()
{
int m[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int (*p)[4] = m; // 指向数组类型数据的指针
for (int i = 0; i < 3; i++)
printf("%x, %x\n", p[i], m[i]);
int *(mp[3]) = {m[0], m[1], m[2]}; // (指向数组的)指针的数组
int **pp = mp; // (指向)指针(数组)的指针
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
printf("%-4d, ", pp[i][j]);
printf("\n");
}
return 0;
}

// 输出:
// 61fe00, 61fe00
// 61fe10, 61fe10
// 61fe20, 61fe20
// 1 , 2 , 3 , 4 ,
// 5 , 6 , 7 , 8 ,
// 9 , 10 , 11 , 12 ,

19. const

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*const point*/
#include <stdio.h>
int main()
{
const int n = 1; // const 类型对象
n = 2; // 错误:n 的类型为 const 限定
int x = 2; // 无限定类型对象
const int *p = &x;
*p = 3; // 错误:左值 *p 的类型为 const 限定
int *const q = &x;
*q = 3;
q = NULL; // 错误:指针 q 的类型为 const 限定
int *p1 = 0;
const int *a = p1; // OK, 非const 转 const
p1 = a; // 错误:舍弃限定符(const int 到 int)
p1 = (int *)a; // 必须显式转型
return 0;
}
  • 说明:
    1. const 限定的左值表达式只能初始化,不能赋值;
    2. const intint 是不同类型;
    3. 非 const 可隐式转换为 const 版本;
    4. const 转为非 const 必须显式转换;
    5. 在函数声明中,double x[const] 等价于 double * const x,而 const double x[] 等价于 const double *x

20. 字符串字面量 —— 常指针

字符串字面量在编译阶段存放在独立资源文件中,与代码一起加载到只读代码段。

  • 注意:
    • char *s 指向只读字面量;
    • char t[N] 保存字面量的副本;
    • 不能修改字符串字面量;
    • 相同字面量只有一份(例如 (char*){"STUDENT"} == s)。
1
2
3
4
5
6
7
8
9
10
11
12
/*const string*/
char *s = "STUDENT";
// 这里等价于 (char*){"STUDENT"}
// "STUDENT" 加载到代码段,只读
char t[] = "STUDENT";
// 等价于 (char[]){"STUDENT"}
// 先开辟 t[8],然后初始化,即
// memcpy(t, (char*){"STUDENT"}, 8);
*t = 'T';
*s = 'T'; // 错误:段错误
printf("%s, %s, %d\n", s, t,
(char*){"STUDENT"} == s);

字符串字面量数组空间不连续,只能使用指针数组或指针的指针。 每个字面量独立存放,与代码一起加载到只读代码段。

  • 注意:
    • char *s[]char t[][N] 的初值有差异:前者指向只读字面量;后者为连续数组。 ss[] 指向只读字面量; t[M][N] 是连续的数组。
    • 不能修改字符串字面量,应使用 const char *ss[]
    • 相同字面量只有一份,因此 pp[0] == pp[1]
1
2
3
4
5
6
7
8
9
/*const string*/
char *(ss[]) = {"hello", "hello", "!"};
char **pp = ss;
pp = (char *[]){"hello", "hello", "!"};
pp[0][0] = 'H'; // 错误:段错误
char sa[][10] = {"hello", "hello", "!"};
sa[0][0] = 'H';
printf("%s, %s, %d\n", *pp, *sa,
pp[0] == pp[1]);

综上:在 C 语言中,字符串字面量(如 “STUDENT”)在编译阶段就会存储在代码段(.rodata 只读数据段),并且在运行时加载到程序的只读内存区域。因此:

  • 字符串字面量存储在只读内存区,不能修改(修改会导致段错误 Segmentation Fault)。
  • 相同的字符串字面量在内存中只有一份,编译器会进行优化。
声明方式 存储位置 是否可修改 共享同一份内容
char *s = "STUDENT"; 只读代码段 ❌ 不能修改 ✅ 共享
char t[] = "STUDENT"; 栈(或静态存储区) ✅ 可以修改 ❌ 不共享
char *s[] = {"hello", "hello", "!"}; 只读代码段 ❌ 不能修改 ✅ 共享
char sa[][10] = {"hello", "hello", "!"}; 栈(或静态存储区) ✅ 可以修改 ❌ 不共享

核心原则:

  • 字符串字面量存储在只读内存区,不能修改。
  • 数组存储字符串副本,可以修改,但不共享同一份数据。
  • 指针数组存储字符串字面量地址,多个指针可能指向同一块内存。

21. void* 指针

void * 类型的指针变量仅保存指针值(地址),忽略指向数据的类型,因此编译器不允许使用 * 解引用或下标运算。
它可以隐式转换为任意类型指针。

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main() {
void* void_p1; // 无类型指针
int* int_p; // 整型指针
void_p1 = int_p; // 任何指针都可以赋值给void指针
// 只获得变量/对象地址而不获得大小
void* void_p2;
char* char_p = (char*)void_p2; // 任何指针都可以赋值给void指针
// 但是void指针不能直接赋值给其他指针(需要转换)
}
  • MemRepeat: 用同类型的变量值,填充 dest数组,例如:int a[10], d=7; MemRepeat(a,&d,sizeof(d),10);
  • Printx: 打印 p 指向对象的二进制值,例如double f=3; Printx(&f,sizeof(f))。

<stdlib.h> 中声明了两个函数:

  • void* malloc(size_t size); 用于分配未初始化内存
  • void free(void* ptr); 用于回收内存

注意:

  • mallocfree 必须成对使用;
  • 多次 free 同一指针或未释放分配的指针都会导致错误或内存泄漏。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*void-malloc-free*/
#include <stdio.h>
#include <stdlib.h>
int main()
{
int m = 3, n = 4;
int *q = malloc(m * n * sizeof(int));
void *t = q;
int (*p)[n] = t;
for (int i = 0; i < m * n; i++)
q[i] = i + 1;
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
printf("%4d", p[i][j]);
printf("\n");
}
free(q);
}

22. 函数指针

程序中定义了一个函数,在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。函数名表示的就是这个地址。函数指针变量,简称函数指针,可用于存放函数参数类型、数量,及返回值类型与定义一致的函数地址。函数指针定义:函数返回值类型 (* 指针变量名) (函数参数类型列表): 𝑖𝑛𝑡(∗ 𝑝)(𝑖𝑛𝑡, 𝑖𝑛𝑡); 注意: 函数指针变量没有算术运算,下标运算

定义示例:

1
2
3
4
5
6
// 声明一个函数
int Func(int x);
// 定义一个函数指针
int (*p)(int x);
// 将函数 Func 的地址赋给指针变量 p
p = Func;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int Max(int a, int b) {
return a > b ? a : b;
}


int main() {
int a = 5, b = 6;
int (*p)(int, int);
p = Max;
int c = (*p)(a, b);
printf("%d is bigger.", c);
return 0;
}

回调函数

1
2
3
void qsort(void *base, int nelem, int width,
int (*fcmp)(const void *, const void *));
int fcmp(const void *, const void *);

调用方式有两种:

  1. 直接调用:在函数 A 内直接使用函数 B 的名称调用;在函数A的函数体里通过书写函数B的函数名来调用之,使内存中对应函数B的代码得以执行。
  2. 间接调用:使用函数指针调用。在函数A的函数体里并不出现函数B的函数名,而是使用指向函数B的函数指针来使内存中属于函数B的代码片断得以执行。

回调函数,函数指针作为某个函数的参数,由函数外部逻辑决定函数行为。例如,在排序时,将数组元素的比较逻辑交给外部传入的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*selected sort callback version*/
void SelectedSortInt(int *nums, int n, int (*fcmp)(int, int))
{
for (int i = 0; i <= n - 2; i++)
{
// 找到最小值下标
int idx = i;
for (int j = i + 1; j <= n - 1; j++)
if ((*fcmp)(nums[j], nums[idx]) < 0)
idx = j;
// 交换
if (i != idx)
{
int temp = nums[i];
nums[i] = nums[idx];
nums[idx] = temp;
}
}
}
定义 含义
int i; 定义整型变量i
int *p p为指向整型数据的指针变量
int a[n]; 定义整型数组a,它有n个元素
int *p[n]; 定义指针数组p,它由n个指向整型数据的指针元素组成
int (*p)[n] p为指向含n个元素的一维数组的指针变量
int f( ); f为带回整型函数值的函数
int *p( ); p为带回一个指针的函数,该指针指向整型数据
int (*p)( ); p为指向函数的指针,该函数返回一个整型值
int **p; p是一个指针变量,它指向一个指向整型数据的指针变量

23. 结构体

结构体(struct)是由一系列数据成员组成的用户自定义类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct STUDENT {
char id[6]; // 字符数组成员
char name[10];
int age;
struct ADDRESS *home; // 指向另一个结构体的指针(需前置声明)
}john;

struct STUDENT anna; //声明变量

struct ADDRESS {
char area_id[6];
// ...其他成员
};
  • 使用关键字 struct 完成类型定义等操作定义结构体类型与变量。”struct1.identifier{ 成员变量表}” 完成结构体定义。然后直接定义该类型的实例变量,注意”;”结束。
  • 仅定义结构体类型变量。注意:c语言中,结构体类型是”struct STUDENT”不是”STUDENT”与C++不同
  • 仅定义结构体类型。以后再申明它的实例变量。注意:定义格式“structidentifierf members list l vars list ;” ,”}”后分号不能省。
  • 习惯上 struct 标识符 用大写风格

23.1 结构体嵌套

注意:嵌套结构体中不能出现自身,否则编译会报错。如果 A 结构体中包含 B 结构体(非指针)成员,则 B 必须在 A 之前定义。

1
2
3
4
5
6
7
8
9
10
11
struct STUDENT
{
char id[6];
char name[10];
int age;
struct ADDRESS
{
char area_id[6];
struct STUDENT receiver; // 多层嵌套,形成循环嵌套,编译错误
} home;
} john;

23.2 typedef 关键字

typedef 可为类型起新的别名,示例如下:

1
2
3
4
typedef oldName newTypeName;

typedef _Bool bool;
typedef unsigned int size_tt;

23.3 给匿名结构体起别名

简化结构体名称,示例如下:

1
2
3
4
5
6
7
typedef struct // 匿名结构体
{
char id[6];
char name[10];
int age;
} Student; // 类型别名 Student,不是变量
Student anna, john, *person; // 建议这样声明变量

23.4 数组类型与函数指针类型别名

1
2
3
4
5
6
typedef int MATRIX_3[3][3]; // 申明数组类型别名,习惯大写风格
MATRIX_3 a = {{1}, {4, 5}, {7, 8, 9}};
typedef int (*CMP_F)(int, int); // 申明函数指针类型别名
typedef int (*PTR_3)[3]; // 申明指针类型别名
void mysort(int *base, int n, CMP_F f);
PTR_3 p1, p2, p3, *p;

23.5 结构体实例变量与成员的操作

结构体的实例变量,成员变量都可以使用 取地址、解引用和赋值运算。其中实例变量赋值一次可复制所有成员的值,包括其中的数组类型成员 :(结构体实例是变量,而数组是地址。变量可以取地址。结构体第一个成员的地址值,一定等于该成员所属实例变量的地址值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct
{
int year, month, day;
} Date;
typedef struct
{
char id[6];
char name[10];
Date birthday;
int age;
} Student;
Student anna = {"0101", "anna", {1995, 10, 1}};

Student anna, john, *person; // 定义结构体实例变量和指针
person = &anna; // 取 anna 地址,赋值给指针
john = *person; // 解引用得到 anna 的副本,赋值给 john

23.6 成员运算符

  • 使用点运算符 . 访问实例变量成员
  • 使用箭头运算符 -> 访问指针变量成员
1
2
3
4
5
strcpy(anna.id, "0101");      // 使用点运算符
strcpy(person->id, "0101"); // 使用箭头运算符
anna.birthday.year = 1995;
person->birthday.year = 1995; // 嵌套结构体可连续使用成员运算
(*person).birthday.year = 1995; // 注意:点运算符优先级高于解引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void readStudents(CLASS_PTR p)
{
printf("id name <enter> q exit input\n");
for (int i = 0; i < p->members; i++)
{
// 通过指针操作结构体成员
STUDENT_PTR st_ptr = &(p->students[i]);
scanf("%s", st_ptr->id);
if (tolower(*(st_ptr->id)) == 'q')
{
*(st_ptr->id) = 0;
return;
}
scanf("%s", st_ptr->name);
}
}
  • 注意:
    指针作为函数的返回值,必须保证所指对象没有被释放,因此不能返回指向函数中自动变量的指针。具体的,以下返回的指针都是安全的:
    • 返回指向函数中静态变量的指针;
    • 返回指向函数中定义的字符串字面量的指针;
    • 返回指向全局变量或函数的指针;
    • 返回函数参数引用的对象及其关联对象的指针,如指向传入数组的元素,或指向传入结构体指针的成员;
    • 返回动态申请内存的指针。

23.7 动态内存与结构体

使用 malloc() 申请内存保存结构体对象/变量,使用 free() 释放内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CLASS_PTR createClass(char *name, int cnt)
{
assert(name && cnt);
CLASS_PTR cls = malloc(sizeof(C_Class)); // 先分配结构体,释放时最后
strcpy(cls->id, name);
cls->members = cnt;
cls->students = malloc(cnt * sizeof(Student)); // 分配成员空间;释放时先释放成员
memset(cls->students, 0, cnt * sizeof(Student));
return cls;
}
void freeClass(CLASS_PTR p)
{
assert(p);
free(p->students); // 先释放成员
free(p); // 后释放结构体
}

23.8 自引用结构体

结构体不能直接嵌套自身,但可以通过成员变量引用自身类型。例如: struct NODE 中有个 next 指针引用 struct NODE

1
2
3
4
5
6
7
typedef struct NODE Node; // 预声明结构体别名
typedef Node *NODE_PTR; // 声明结构体指针类型
struct NODE
{
int customerData;
NODE_PTR next; // 自引用成员
};

考虑链表的示例:

1
2
3
4
5
6
7
8
struct ListNode
{
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};

24. 共用体(Union)

共用体与结构体类似,但其成员共享同一块存储空间。与结构体一样,共用体(union)也是一种派生数据类型。共用体的成员共享同一个存储空间。共用体的成员可以是任意数据类型。 可对共用体执行的操作有三种:实例变量的复制; &运算符取共用体实例变量地址, 注意:实例变量地址值和所有成员变量地址值都相等;用原点成员运算符或指针运算符访问共用体的成员。不能用运算符==或!=来比较两个共用体。

  • 内存分配:
    • 共用体 (union):所有成员共享同一块内存,大小由最大的成员决定任意时刻只能存储一个成员,存储新的成员会覆盖掉之前的成员。
    • 结构体 (struct):每个成员都有自己的存储空间,成员之间不会相互覆盖。结构体的总大小等于所有成员大小之和(考虑字节对齐)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
union U
{
int a;
double b;
};
struct S
{
int a;
double b;
};
int main()
{
printf("Union size: %lu\n", sizeof(union U)); // 8(double 最大)
printf("Struct size: %lu\n", sizeof(struct S)); // 16(4 + 8 + 4 字节填充)
return 0;
}
  • 数据存储示例:
    • 共用体:由于所有成员共享内存,只能存储一个成员的值,读写不同成员可能导致数据损坏。
    • 结构体:各个成员独立存储,可以同时存储多个值,互不干扰。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

union Data
{
int i;
float f;
};

int main()
{
union Data d;
d.i = 10;
printf("d.i = %d\n", d.i);

d.f = 3.14f; // 赋值后覆盖 i
printf("d.i = %d, d.f = %f\n", d.i, d.f);

return 0;
}

// 输出:
// d.i = 10
// d.i = 1078523331, d.f = 3.140000

注:赋值 d.f 后会覆盖 d.i 的值。


25. 枚举类型

枚举类型通过关键字 enum 定义,用标识符表示的整型枚举常量集合。
默认枚举值从 0 开始递增,也可显式赋值。

1
2
enum month {january, february, march, april, may, june, july, august, september, october, november, december}; // 0-11
enum month {january=1, february, march, april, may, june, july, august, september, october, november, december}; // 1-12

在一个枚举类型中,标识符必须唯一。多个成员可以拥有相同的常量值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

enum Color {
RED = 1,
GREEN = 2,
BLUE = 3,
CYAN = 2 // 允许不同名称拥有相同值
};

enum Color {
RED,
GREEN,
BLUE,
GREEN // ❌ 错误:标识符 GREEN 重复定义
};

int main() {
printf("RED = %d\n", RED);
printf("GREEN = %d\n", GREEN);
printf("BLUE = %d\n", BLUE);
printf("CYAN = %d\n", CYAN); // CYAN 和 GREEN 具有相同的值 2
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>

enum months
{
january = 1,
february,
march,
april,
may,
june,
july,
august,
september,
october,
november,
december
};

// 自定义 ++ 操作符
months operator++(months &m)
{
return m = static_cast<months>(m + 1);
}

int main()
{
months m;
for (m = january; m <= december; m++) // 注意:此操作需适用于用户定义的运算符
{
std::cout << m << std::endl;
}
return 0;
}

26. 结构体对齐

在 32 位计算机中,CPU 加法一次处理 32 位数(4 个字节)。大于字宽的数据通常按 4 字节对齐。32位计算机指 CPU 加法指令一次完成 32 位数加法。字宽指 CPU 一次从访问内存读或写的位数, 32位字宽表示一次读或写4个字节。 对齐(alignment)。例如大于字宽的基本类型数据都按字宽边界对齐,即使浪费一些内存也值得。因此,程序中整数变量地址一般是 4 的倍数。

  • 预编译指令:
    #pragma pack(N) 控制后续定义的类或联合体的最大对齐值。字宽 32 默认 𝑁 = 4。 𝑁 ∈ {1, 2, 4, 8, 16, 32}

  • 基本数据类型:
    数据类型所占字节数 ≥ N,则地址为 N 的倍数;< N,则对齐为数据类型本身的大小。例如 char 类型对齐值是1,开始地址是1的倍数 ;short 开始地址是2的倍数; int 开始地址是4的倍数。

  • 结构体:
    对齐值为成员中最大对齐值与 N 的最小值,且 size 也必须是该对齐值的倍数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*struct layout*/
#pragma pack(1)
typedef struct
{
char a[3];
int b;
short c;
} packed_struct;
#pragma pack(4)
typedef struct
{
char a[3];
int b;
short c;
} unpacked_struct;

// sizeof(packed_struct) = 9
// sizeof(unpacked_struct) = 12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*union layout*/
#pragma pack(1)
typedef union
{
char a[3];
int b;
short c;
} packed_union;
#pragma pack(4)
typedef union
{
char a[3];
int b;
short c;
} unpacked_union;

// sizeof(packed_union) = 1
// sizeof(unpacked_union) = 4

27. 位域

位域可以使得结构体支持方便的按位访问 。用于精确控制变量的二进制位数,从而节省存储空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct 结构体名 {
数据类型 变量名 : 位宽;
};

// 例如:
#include <stdio.h>

struct BitField {
unsigned int a : 3; // 占 3 位,最大值 2^3 - 1 = 7
unsigned int b : 5; // 占 5 位,最大值 2^5 - 1 = 31
unsigned int c : 2; // 占 2 位,最大值 2^2 - 1 = 3
};

int main() {
struct BitField bf;

bf.a = 5; // 5 的二进制:101(3 位)
bf.b = 27; // 27 的二进制:11011(5 位)
bf.c = 2; // 2 的二进制:10(2 位)

printf("a = %d\n", bf.a);
printf("b = %d\n", bf.b);
printf("c = %d\n", bf.c);

return 0;
}
// 这样 3 个变量仅占 3+5+2 = 10 位,而不是 3 个完整的 int(96 位)!

27.1 位域对齐

若多个位域连续定义,则会被打包在一起。
注意: 第一个位域必须按整数对齐,一个包最多为一个整数。

1
2
3
4
5
6
7
8
struct Example {
unsigned int x : 4;
unsigned int y : 4;
unsigned int z : 8;
};

printf("Size of struct: %lu\n", sizeof(struct Example));
// 输出:4

27.2 匿名位域

匿名位域用于跳过若干位;0 宽度位域表示当前整数包已满,下一个位域分配在新整数单元。
位域不能取地址,建议与 union 配合使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

struct Example
{
unsigned int a : 3; // 3 位
unsigned int : 5; // 5 位匿名位域(跳过,无法访问)
unsigned int b : 4; // 4 位
unsigned int : 0; // 强制对齐到下一个 unsigned int 边界
unsigned int C : 4; // 4 位
};

int main()
{
struct Example ex = {5, 10};

printf("a = %d\n", ex.a);
printf("b = %d\n", ex.b);
printf("C = %d\n", ex.C);

printf("Size of struct: %lu\n", sizeof(struct Example)); // 可能为 4 字节
return 0;
}

// 输出示例:
// a = 5
// b = 10
// C = 0
// Size of struct: 8

内存分配说明:

变量 位域大小 存储范围 说明
a 3 位 第 0-2 位 位域拼合
匿名位域 :5 5 位 第 3-7 位 跳过,不能访问
b 4 位 第 8-11 位 位域拼合
:0 对齐 新 unsigned int 强制对齐到新整数
C 4 位 新 int 的第 0-3 位 单独占据一个 int
1
sizeof(struct Example) == 8

(两个 unsigned int,每个 4 字节,总计 8 字节)

27.3 类型总结

数据类型 分类 说明 例子
基本类型 整型 存储整数 int (-32768~32767)
字符型 存储单个字符(ASCII 编码) char
实型(浮点型) 存储小数(单精度和双精度) float, double
构造类型 数组 一组相同类型的数据 int arr[10]
结构体 组合多个变量形成新类型 struct
联合体 共享内存的变量结构 union
枚举 一组命名的整数值 enum
指针类型 指针 存储地址的变量 int *p
空类型 void 无返回值或泛型指针类型 void
说明 字符型 短整型 整型 长整型 单精度浮点型 双精度浮点型
数据类型 char short int long float double
长度 1 2 4 4 4 8

28. 文件定义

从数据视角看,文件分为两大类:

  • 文本文件 (text):
    由 ASCII 码及其扩展字符构成的文字序列。可用记事本、Sublime、VS Code 等文本编辑软件打开。
    具有编码属性(如 ASCII、ANSI、GBK、BIG5、UTF-8 等)。
    若包含 EOT(4)、STX(3)、-1 等控制符,可能导致传输中止。

  • 二进制文件:
    以字符数组形式存放数据,每类应用有特定数据格式(如 .zip.obj 等)。
    通常直接将内存数据原样写入文件(例如整数以 4 字节补码形式存储),保存 struct 时需考虑 packed 或 unpack。

从应用角度,文件可分为:

  • 输入流 (stream): 数据通过管道流入应用;
  • 输出流: 数据从应用流出。
  • 输入输出设备: 如磁盘既能产生输入流又接受输出。

从操作角度,文件操作由操作系统提供:

  1. 打开文件: 通过文件名建立流机制。
  2. 操作文件: 读写数据。
  3. 关闭文件: 释放文件占用资源。

29. 文件操作

  • 头文件 <stdio.h>
    包含与操作系统交互的函数和数据结构。

    • 结构体 FILE: 描述 IO 流信息,通常使用 FILE * 指针操作。
    • 标准流:
      • stdin:标准输入(键盘)
      • stdout:标准输出(显示设备)
      • stderr:标准错误输出
  • 常用函数:

    • 文件访问:fopenfclosefreopenfflush
    • 直接 I/O:freadfwrite
    • 无格式 I/O:fgetsfputsfgetc/getcfputc/putcungetc
    • 标准 I/O:getcharputchargetsputsscanfprintf
    • 格式化 I/O:fscanffprintf
    • 字符串 I/O:sscanfsprintf
    • 文件位置:fseekgtellfgetposfsetposrewind
    • 错误处理:clearerrfeofferrorperror
    • 文件操作:removerenametmpfiletmpnam
    • 宏常量:EOF
  • 示例: 格式化文本通常描述一个数据条目(如学生信息),数据域之间用空白字符分隔;无格式文本先读入内存再处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*file copy*/
int FileCopy(char *dest, char *scr)
{
char buf[1024 * 4]; // 任何磁盘文件可看作字符数组
FILE *d = fopen(dest, "wb"); // 以写方式建立 dest 流
if (!d) // 错误时返回 NULL
return -1;
FILE *s = fopen(scr, "rb"); // 以读方式建立 scr 流
if (!s)
return -1;
while (!feof(s)) // 文件未结束时
{
size_t cnt = fread(buf, 1, sizeof(buf), s); // 读入最多 4K 字节
fwrite(buf, 1, cnt, d); // 写入 cnt 字节
}
fclose(s);
fclose(d);
return 0;
}
文件访问模式字符串 含义 解释 若文件已存在的动作 若文件不存在的动作
“r” 打开文件以读取 从头读 打开失败
“w” 创建文件以写入 销毁内容 创建新文件
“a” 后附 后附到文件 写到结尾 创建新文件
“r+” 读扩展 打开文件以读/写 从头读 错误
“w+” 写扩展 创建文件以读/写 销毁内容 创建新文件
“a+” 后附扩展 打开文件以读/写 写到结尾 创建新文件
  • 二进制模式:在文件访问模式字符串后附加 "b"(如 "rb""wb+")可指定以二进制模式打开文件(仅在 Windows 系统生效)。
  • 附加模式特性:在附加模式("a"/"a+")下,数据始终写入文件尾部,与文件位置指示器的当前位置无关。
  • 更新模式:当模式包含 "+" 时(如 "r+""w+"),允许同时对文件进行读写操作。

30. size_t

size_t无符号整数类型,用于表示对象的大小或数组的索引,其具体大小取决于系统架构。

1
2
typedef unsigned int size_t;          // 32 位系统上
typedef unsigned long long size_t; // 64 位系统上
特点 size_t int
是否有符号 无符号(不能存负值) 有符号
是否平台相关 ✅ 取决于 32/64 位系统 ❌ 通常 4 字节(32 位)
主要用途 存储大小、数组索引 一般整数运算
是否用于 sizeof() ✅ 推荐 ❌ 不推荐
1
2
3
4
5
6
7
#include <stdio.h>

int main() {
size_t size = sizeof(int); // 使用 size_t 存储 sizeof 返回值
printf("Size of int: %zu bytes\n", size);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main() {
FILE *file = fopen("example.txt", "r");
if (file) {
char buffer[100];
size_t bytesRead = fread(buffer, 1, sizeof(buffer), file); // fread 使用 size_t
printf("Bytes read: %zu\n", bytesRead);
fclose(file);
}
return 0;
}

逆序中文/英文字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

std::vector<std::string> rev2utf8(std::string &str)
{
std::vector<std::string> res;
for (size_t i = 0; i < str.size();)
{
unsigned char c = str[i];
size_t charLen = 1;
// 根据高 4 位判断字符的字节数
switch (c >> 4)
{
// 0xxxxxxx:单字节字符
case 0x0:
case 0x1:
case 0x2:
case 0x3:
case 0x4:
case 0x5:
case 0x6:
case 0x7:
charLen = 1;
break;
// 110xxxxx:两字节字符
case 0xC:
case 0xD:
charLen = 2;
break;
// 1110xxxx:三字节字符
case 0xE:
charLen = 3;
break;
// 11110xxx:四字节字符
case 0xF:
charLen = 4;
break;
default:
charLen = 1;
break;
}
res.push_back(str.substr(i, charLen));
i += charLen;
}
// 逆序排列存储字符的 vector
std::reverse(res.begin(), res.end());
return res;
}

int main()
{
std::string input;
std::getline(std::cin, input);

auto reversed_chars = rev2utf8(input);

// 拼接逆序后的字符为最终字符串
std::string output;
for (const auto &ch : reversed_chars)
{
output += ch;
}

std::cout << output << std::endl;
return 0;
}

// std::string::size() 返回的正是 size_t 类型,因此用 size_t 声明循环变量 i 可避免类型不匹配警告。
  • Title: c review
  • Author: KDAIer
  • Created at : 2025-03-17 19:07:51
  • Updated at : 2025-03-18 14:29:52
  • Link: https://kdaier.github.io/2025/03/17/c-review/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments