核心语法
常量
程序运行时,值不能发生改变的数据
-
整形常量:正数、负数、0
-
实型常量:带小数点的数字(小数点前后为0可省略,eg:.93=0.93)(1.2*10的4次方不是常量,是一个计算过程)(1.2E7是实型常量)
-
字符常量:单引号引起来的字母、数字、英文符号
-
字符串常量:双引号引起来
格式控制符
| 格式控制符 | 说明 |
|---|---|
%d |
整型数据 |
%f |
浮点数 |
%c |
单个字符 |
%s |
文本字符串 |
%n |
将已打印字符的数量写入到参数(整数指针)中 |
%x |
以十六进制形式输出整数 |
%p |
输出指针地址 |
变量
定义格式: 数据类型 变量名
注意事项:
-
变量名不允许重复定义
-
一条语句可定义多个变量
-
变量在使用前必须赋值
标识符
命名规则:
| 规则类型 | 要求 |
|---|---|
| 组成字符 | 英文字母、数字、下划线 |
| 首字符 | 必须是字母或下划线 |
| 大小写敏感 | var ≠ Var ≠ VAR |
| 禁止关键字 | int, return等34个保留字 |
| 禁止符号 | 空格 @ $ - +等 |
数据类型
| 分类 | 数据类型 | 标准头文件 | 大小(字节) | 取值范围/描述 |
|---|---|---|---|---|
| 字符型 | char |
- | 1 | -128 |
signed char |
- | 1 | -128~127 | |
unsigned char |
- | 1 | 0~255 | |
| 整型 | short [int] |
- | 2 | -32,768~32,767 |
unsigned short [int] |
- | 2 | 0~65,535 | |
int |
- | 4 | -2.14e9~2.14e9 | |
unsigned int |
- | 4 | 0~4.29e9 | |
long [int] |
- | 8 | -9.22e18~9.22e18 | |
unsigned long [int] |
- | 8 | 0~1.84e19 | |
long long [int] |
- | 8 | -9.22e18~9.22e18 | |
unsigned long long [int] |
- | 8 | 0~1.84e19 | |
| 扩展整型 | _Bool |
<stdbool.h> |
1 | 0(false)或1(true) |
[u]int8_t |
<stdint.h> |
1 | 精确8位整型 | |
[u]int16_t |
<stdint.h> |
2 | 精确16位整型 | |
[u]int32_t |
<stdint.h> |
4 | 精确32位整型 | |
[u]int64_t |
<stdint.h> |
8 | 精确64位整型 | |
| 浮点型 | float |
- | 4 | 约±3.4e-38~3.4e38 (6-7位精度) |
double |
- | 8 | 约±1.7e-308~1.7e308 (15-16位精度) | |
long double |
- | 16 | 约±1.1e-4932~1.1e4932 (18-19位精度) | |
| 枚举 | enum |
- | 4 | 用户自定义整型常量集合 |
| 空类型 | void |
- | 0 | 1. 函数无返回值 2. 通用指针 void* |
| 派生类型 | 指针类型 | - | 8 | 存储内存地址 如 int*, char** |
| 数组类型 | - | n×元素大小 | 同类型元素集合 如 int arr[5] |
|
| 结构体 | - | 可变 | 不同类型成员集合 内存连续 |
|
| 联合体 | - | 最大成员 | 共享存储空间的不同类型成员 | |
| 函数类型 | - | 0 | 描述函数签名 如 int(*)(void) |
键盘录入
scanf:获取用户从键盘上输入的数据并赋值给变量
int scanf(const char *format, 地址1, 地址2, ...);
运算符
基本运算符
| 类型 | 运算符 | 名称 | 示例 | 说明 | 结合性 |
|---|---|---|---|---|---|
| 算术运算符 | + |
加法 | a + b |
求和 | 左到右 |
- |
减法 | a - b |
求差 | 左到右 | |
* |
乘法 | a * b |
求积 | 左到右 | |
/ |
除法 | a / b |
求商 (整数除法截断) | 左到右 | |
% |
模 | a % b |
整除余数 (仅整数) | 左到右 | |
++ |
自增 | ++a/a++ |
前/后缀增加1 | - | |
-- |
自减 | --a/a-- |
前/后缀减少1 | - | |
| 关系运算符 | == |
等于 | a == b |
相等判断 | 左到右 |
!= |
不等于 | a != b |
不等判断 | 左到右 | |
< |
小于 | a < b |
小于判断 | 左到右 | |
<= |
小于等于 | a <= b |
小于或等于判断 | 左到右 | |
> |
大于 | a > b |
大于判断 | 左到右 | |
>= |
大于等于 | a >= b |
大于或等于判断 | 左到右 | |
| 逻辑运算符 | && |
逻辑与 | a && b |
同真为真 | 左到右 |
| `\ | ` | 逻辑或 | a `\ | ||
! |
逻辑非 | !a |
取反 | 右到左 |
位操作与赋值运算符
| 类型 | 运算符 | 名称 | 示例 | 说明 | 结合性 |
|---|---|---|---|---|---|
| 位运算符 | & |
位与 | a & b |
按位与 | 左到右 |
| `\ | ` | 位或 | `a \ | b` | |
^ |
位异或 | a ^ b |
按位异或 (相同0, 不同1) | 左到右 | |
~ |
位取反 | ~a |
按位取反 | 右到左 | |
<< |
左移位 | a << n |
左移n位 (相当于乘以2^n) | 左到右 | |
>> |
右移位 | a >> n |
右移n位 (相当于除以2^n) | 左到右 | |
| 赋值运算符 | = |
基本赋值 | a = b |
赋值操作 | 右到左 |
+= |
加赋值 | a += b |
等价于 a = a + b | 右到左 | |
-= |
减赋值 | a -= b |
等价于 a = a - b | 右到左 | |
*= |
乘赋值 | a *= b |
等价于 a = a * b | 右到左 | |
/= |
除赋值 | a /= b |
等价于 a = a / b | 右到左 | |
%= |
模赋值 | a %= b |
等价于 a = a % b | 右到左 | |
<<= |
左移位赋值 | a <<= n |
等价于 a = a « n | 右到左 | |
>>= |
右移位赋值 | a >>= n |
等价于 a = a » n | 右到左 | |
&= |
位与赋值 | a &= b |
等价于 a = a & b | 右到左 | |
| `\ | =` | 位或赋值 | `a \ | = b` | |
^= |
位异或赋值 | a ^= b |
等价于 a = a ^ b | 右到左 |
特殊运算符
| 类型 | 运算符 | 名称 | 示例 | 说明 | 结合性 |
|---|---|---|---|---|---|
| 条件运算符 | ? : |
三元条件 | a ? b : c |
如果a真则返回b,否则返回c | 右到左 |
| 地址运算符 | & |
取地址 | &var |
获取变量内存地址 | 右到左 |
* |
解引用 | *ptr |
通过指针访问内存 | 右到左 | |
| 成员运算符 | . |
直接成员 | obj.member |
访问结构体/联合体成员 | 左到右 |
-> |
间接成员 | ptr->member |
通过指针访问成员 | 左到右 | |
| 大小运算符 | sizeof |
求大小 | sizeof(int) |
获取类型或变量所占字节数 | 右到左 |
| 逗号运算符 | , |
逗号 | a, b |
顺序执行表达式 | 左到右 |
数据转换
-
显式转换:主动
// 典型场景 double pi = 3.14159; int int_pi = (int)pi; // 显式截断小数部分 → 3 -
隐式转换:自动
// 典型场景 int num = 5; double result = num / 2.0; // 隐式转换:int → double → 2.5
字符跟数字进行转换会查询ASCII码表
流程控制
条件控制语句
| 语句类型 | 语法结构 | 说明 |
|---|---|---|
| if | if (条件) {代码块} |
条件为真时执行 |
| if-else | if (条件) {块A} else {块B} |
真执行A假执行B |
| if-else 阶梯 | if (条件1) {块1} else if (条件2) {块2} ... else {默认块} |
多条件分支 |
| switch-case | switch (表达式) {case 值1: 块1; break;...default: 默认块;} |
基于值匹配分支 |
循环控制语句
| 语句类型 | 语法结构 | 特点 |
|---|---|---|
| while | while (条件) {循环体} |
先检查条件后执行 |
| do-while | do {循环体} while (条件); |
先执行后检查,至少一次 |
| for | for (初始化; 条件; 更新) {循环体} |
紧凑语法,适合固定迭代次数 |
| 无限循环 | while (1) { } 或 for (;;) { } |
需配合break跳出 |
流程跳转语句
| 语句类型 | 语法 | 作用 |
|---|---|---|
| break | break; |
立即退出当前循环/switch |
| continue | continue; |
跳过本轮循环剩余代码,进入下一轮 |
| goto | goto 标签; |
无条件跳转到指定标签位置 |
| return | return [值]; |
结束函数执行并返回值 |
示例代码
// if
int age = 18;
if (age >= 18) {
printf("您已成年,可以进入。\n");
}
// if-else
int num = -5;
if (num > 0) {
printf("正数\n");
} else {
printf("非正数\n"); // 输出:非正数
}
// if-else阶梯
int score = 85;
if (score >= 90) {
printf("优秀\n");
} else if (score >= 80) {
printf("良好\n"); // 输出:良好
} else if (score >= 60) {
printf("及格\n");
} else {
printf("不及格\n");
}
// switch-case
char command = 'D';
switch (command) {
case 'A':
printf("执行添加操作\n");
break;
case 'D':
printf("执行删除操作\n"); // 输出:执行删除操作
break;
case 'U':
printf("执行更新操作\n");
break;
default:
printf("无效指令\n");
}
// while
int count = 5;
while (count > 0) {
printf("%d...", count);
count--;
}
printf("发射!\n");
// 输出:5...4...3...2...1...发射!
// do-while
int choice;
do {
printf("\n1. 开始游戏\n2. 设置\n3. 退出\n请选择: ");
scanf("%d", &choice);
} while (choice < 1 || choice > 3);
printf("您选择了: %d\n", choice);
// for
int n = 5, factorial = 1;
for (int i = 1; i <= n; i++) {
factorial *= i;
}
printf("%d! = %d\n", n, factorial); // 输出:5! = 120
// 嵌套循环
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
printf("%d*%d=%d\t", j, i, i*j);
}
printf("\n");
}
// break
int arr[] = {2, 4, 6, 8, 10};
int target = 6;
for (int i = 0; i < 5; i++) {
if (arr[i] == target) {
printf("找到目标值 %d,位置: %d\n", target, i); // 输出:找到目标值 6,位置: 2
break; // 找到后立即退出循环
}
}
// continue
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
printf("%d ", i); // 输出:1 3 5 7 9
}
// goto
FILE *file = fopen("data.txt", "r");
if (!file) {
goto cleanup; // 文件打开失败直接跳转清理
}
char buffer[256];
if (fgets(buffer, sizeof(buffer), file) == NULL) {
goto cleanup; // 读取失败跳转清理
}
printf("文件内容: %s", buffer);
cleanup:
if (file) fclose(file);
printf("资源已清理\n");
// return
int max(int a, int b) {
if (a > b) {
return a;
}
return b;
}
int main() {
printf("最大值: %d\n", max(10, 20)); // 输出:最大值: 20
return 0;
}
函数
函数的注意事项
▲ 函数名不能重复
- 同一作用域内禁止使用重复的函数名)
▲ 函数与函数之间是平级关系,不能嵌套定义
- 所有函数都是独立的,禁止在函数内部定义另一个函数
▲ 自定义函数写在main函数的下面,需要在上方声明
- 若函数定义在
main()之后,必须在文件顶部添加函数声明
▲ 函数的返回值类型为void,表示没有返回值
return关键字可省略- 若使用
return,后面禁止跟具体数据,仅允许return;
形参与实参
▲ 形参 (Formal Parameter)
- 位置:函数定义时声明的变量(如
int max(int a, int b)中的a, b) - 作用:接收实参传入的值,仅在函数内部有效
- 特点:
▶ 本质是函数的局部变量,函数结束时销毁
▶ 可任意命名(与实参名称无关)
▲ 实参 (Actual Parameter)
- 位置:函数调用时传入的具体值或变量(如
max(x, y)中的x, y) - 作用:将其值复制给形参(单向传递)
- 关键规则:
▶ 实参与形参的数量、类型、顺序必须严格匹配
▶ 实参可以是:变量、常量、表达式(如max(10, x+5))
数组
基本概念
数组是相同类型数据的顺序集合,存储在连续的内存空间中
-
数组名表示数组首元素的地址 -
元素类型决定每个元素所占内存大小 -
数组长度必须是整数常量表达式 -
内存分配:
总字节数 = 元素个数 × sizeof(元素类型)
一维数组
声明语法
类型说明符 数组名[常量表达式]; // 例如:int scores[5];
初始化方式
| 初始化方式 | 示例 | 说明 |
|---|---|---|
| 完全初始化 | int a[3] = {1, 2, 3}; |
所有元素显式赋值 |
| 部分初始化 | int b[5] = {1, 2}; |
前2元素赋值,后3元素为0 |
| 默认初始化 | int c[4] = {0}; |
所有元素初始化为0 |
| 不指定长度初始化 | int d[] = {1, 2, 3, 4}; |
编译器自动计算长度(4) |
| 指定位置初始化(C99) | int e[5] = {[2]=10, [4]=20} |
索引2处=10,索引4处=20 |
多维数组(以二维数组为例)
声明语法
类型说明符 数组名[行数][列数]; // 例如:int matrix[3][4];
内存布局
-
按行存储:内存中连续存放所有元素
-
内存地址计算:
&matrix[i][j] = 首地址 + i * 列数 * sizeof(int) + j * sizeof(int)
初始化
// 完全初始化
int m1[2][3] = {{1,2,3}, {4,5,6}};
// 部分初始化
int m2[3][2] = {{1}, {2,3}}; // 未显式初始化的元素为0
// 线性初始化(按行顺序)
int m3[2][2] = {1,2,3,4}; // 1(0,0), 2(0,1), 3(1,0), 4(1,1)
字符数组与字符串
基本概念
-
字符数组:
char str[10]; -
字符串:以
'\0'(NULL)结尾的字符序列
初始化
char s1[] = {'H','e','l','l','o','\0'}; // 显式添加\0
char s2[] = "World"; // 自动添加\0
char s3[20] = "C Programming"; // 剩余空间填充\0
数组作为函数参数
传递方式
-
本质传递指针:函数接收数组首地址
-
三种等效声明方式:
void func1(int arr[]); // 最常用
void func2(int arr[10]); // 长度仅作提示,不强制检查
void func3(int *arr); // 最本质形式
特性与限制
-
不会拷贝整个数组:传递的只是首元素地址
-
大小信息丢失:函数内无法直接获取数组长度(需要额外传递size参数)
-
可以修改原始数组元素(因为操作的是原始内存)
最佳实践
void processArray(int arr[], int size) {
for(int i = 0; i < size; i++) {
// 安全操作
}
}
int main() {
int data[5] = {1,2,3,4,5};
processArray(data, sizeof(data)/sizeof(data[0]));
}
常见错误与注意事项
-
数组越界访问
int arr[5]; arr[5] = 10; // 危险!非法访问内存 -
用变量定义数组长度(C99前无效)
int size = 10; int arr[size]; // C99前非法,C99支持变长数组(VLA) -
数组赋值错误
int a[3] = {1,2,3}; int b[3]; b = a; // 错误!不能直接复制数组 -
未初始化的数组使用
int data[100]; printf("%d", data[0]); // 值不确定 -
字符串操作越界
char name[5] = "Alice"; // 需要6字节空间(含\0) -
二维数组传递误解
// 错误:void func(int arr[][]) // 正确:void func(int arr[][5])
指针
指针基础概念
-
本质:存储内存地址的特殊变量
-
声明语法:
数据类型 *指针变量名;int *p; // 整型指针 char *c_ptr; // 字符指针 -
取地址运算符(&):获取变量的内存地址
int num = 10; int *p = # // p指向num的地址 -
解引用运算符(*):访问指针指向的内存值
printf("%d", *p); // 输出10 (num的值) *p = 20; // 等价于 num = 20
5.野指针与悬空指针:
-
野指针:指针指向的空间未分配
-
悬空指针:指针指向的空间已分配,但被释放
二、指针类型系统
| 指针类型 | 声明示例 | 用途说明 |
|---|---|---|
| 基本类型指针 | int *p; |
指向基础数据类型 |
| 常量指针 | int *const p; |
指针地址不可变 |
| 指向常量的指针 | const int *p; |
指向的值不可变 |
| 空指针 | int *p = NULL; |
初始化和错误检查 |
| void指针 | void *ptr; |
通用指针类型(需类型转换) |
| 函数指针 | int (*func)(int); |
指向函数的指针 |
| 双指针 | int **pp; |
指向指针的指针 |
三、指针与数组
1. 数组指针等价关系
int arr[5] = {1,2,3,4,5};
int *p = arr; // 等价于 p = &arr[0]
// 下列表达式等价:
arr[i] ≡ *(arr + i) ≡ *(p + i) ≡ p[i]
2. 指针运算
p++; // 指向下一个元素(地址增加sizeof(int))
p += 2; // 前进2个元素位置
p--; // 指向上一个元素
3. 多维数组指针
int matrix[3][4];
int (*ptr)[4] = matrix; // 指向含4个元素的数组指针
// 访问元素:
matrix[i][j] ≡ *(*(matrix + i) + j)
四、指针与函数
1. 指针参数(实现引用传递)
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
2. 数组参数传递
void printArray(int arr[], int size) {
// 等价于 void printArray(int *arr, int size)
for(int i=0; i<size; i++)
printf("%d ", arr[i]);
}
3. 函数指针
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main() {
int (*operation)(int, int); // 声明函数指针
operation = add; // 指向add函数
printf("%d", operation(2,3)); // 输出5
operation = sub; // 指向sub函数
printf("%d", operation(5,2)); // 输出3
return 0;
}
五、指针与字符串
char *str = "Hello"; // 字符串常量(只读)
char arr[] = "World"; // 字符数组(可修改)
// 常用字符串指针操作
while(*str) { // 遍历直到'\0'
putchar(*str); // 输出当前字符
str++; // 移动到下一字符
}
六、指针与结构体
typedef struct {
int x;
char name[20];
} Point;
Point p1 = {5, "A"};
Point *ptr = &p1;
// 访问成员:
(*ptr).x = 10; // 方式1
ptr->x = 10; // 方式2(更常用)
strcpy(ptr->name, "B");
七、指针高级应用
1. 指针数组
char *names[] = {"Alice", "Bob", "Charlie"};
// names[0]: 指向"Alice"的指针
// names[1]: 指向"Bob"的指针
2. 动态二维数组
int **matrix = (int**)malloc(rows * sizeof(int*));
for(int i=0; i<rows; i++)
matrix[i] = (int*)malloc(cols * sizeof(int));
3. 回调机制(函数指针应用)
void process(int *arr, int size, int (*func)(int)) {
for(int i=0; i<size; i++)
arr[i] = func(arr[i]);
}
八、常见错误与预防
-
空指针解引用:
int *p = NULL; *p = 10;(崩溃) -
野指针:使用未初始化指针
int *p; // 未初始化 *p = 5; // 危险! -
内存泄漏:忘记释放分配的内存
-
指针类型不匹配:
double d = 3.14; int *p = (int*)&d; // 类型强制转换,可能丢失精度 -
数组越界:
int arr[5]; for(int i=0; i<=5; i++) // 越界访问arr[5] arr[i] = i;
结构体
一、结构体基础
1. 定义与声明
// 定义结构体类型
struct Student {
char name[20];
int age;
float score;
};
// 声明结构体变量
struct Student s1;
struct Student s2 = {"Alice", 18, 92.5};
2. 类型重命名(typedef)
typedef struct {
char title[30];
float price;
} Book;
Book b1; // 无需再写struct关键字
3.点运算符和箭头运算符
| 特性 | 点运算符(.) |
箭头运算符(->) |
|---|---|---|
| 操作对象 | 结构体/类实例(直接对象) | 指向结构体/类的指针(间接对象) |
| 底层逻辑 | 直接访问成员 | 等价于 (*指针).成员(解引用 + 点运算符) |
| 返回值性质 | 若对象为左值则返回左值;若为右值则返回右值 | 始终返回左值(因 *指针为左值) |
| 优先级 | 高(与 []同级) |
高(与 .同级) |
| 典型场景 | 栈对象、局部变量、返回值 | 指针对象(动态分配、数组遍历、函数参数传递) |
二、结构体操作
1. 成员访问
// 点运算符(直接访问)
strcpy(s1.name, "Bob");
s1.age = 20;
// 箭头运算符(通过指针访问)
struct Student *ptr = &s1;
ptr->age = 21;
2. 赋值与复制
struct Point {
int x;
int y;
};
Point p1 = {10, 20};
Point p2 = p1; // 结构体支持整体赋值(浅拷贝)
三、内存布局与对齐
1. 内存对齐原则(32位系统示例)
| 成员类型 | 大小 | 对齐值 |
|---|---|---|
| char | 1 | 1 |
| short | 2 | 2 |
| int | 4 | 4 |
| double | 8 | 4 |
| 指针 | 4 | 4 |
2. 计算结构体大小
struct Example {
char a; // 偏移0,大小1
int b; // 偏移4(对齐),大小4
short c; // 偏移8,大小2
}; // 总大小12(10+填充2)
// sizeof(struct Example) = 12
内存对齐规则(以32位系统为例)
编译器在分配结构体内存时遵循以下核心规则:
-
首成员对齐:第一个成员始终位于结构体偏移量
0处 -
成员对齐:后续成员需对齐到
min(自身大小, 编译器默认对齐数)的整数倍地址。默认对齐数通常为8(如Visual Studio) -
整体对齐:结构体总大小必须是 所有成员中最大对齐数 的整数倍
🔍 分步分析 struct Example的内存布局
| 成员 | 类型 | 自身大小 | 对齐要求 | 实际偏移量 | 填充字节 | 占用区间 |
|---|---|---|---|---|---|---|
a |
char |
1字节 | 1(首成员) | 0 | 无 | [0] |
b |
int |
4字节 | min(4, 8) = 4 |
4 | 3字节 | [4-7] |
c |
short |
2字节 | min(2, 8) = 2 |
8 | 无 | [8-9] |
| 末尾填充 | - | - | 最大对齐数=4 | - | 2字节 | [10-11] |
四、高级应用
1. 嵌套结构体
struct Date {
int year;
char month;
char day;
};
struct Employee {
char name[30];
struct Date hire_date; // 嵌套结构体
double salary;
};
2. 结构体数组
struct Point points[5] = {
{0,0}, {1,1}, {2,4}, {3,9}, {4,16}
};
// 访问元素
points[2].x = 5;
3. 结构体指针
Employee *e_ptr = malloc(sizeof(Employee));
strcpy(e_ptr->name, "Charlie");
e_ptr->salary = 8500.0;
// 动态数组
Employee *team = malloc(10 * sizeof(Employee));
team[0].hire_date.year = 2023;
五、函数与结构体
1. 作为函数参数
// 传值(拷贝整个结构体)
void printPoint(struct Point p) {
printf("(%d,%d)", p.x, p.y);
}
// 传指针(高效)
void updateSalary(Employee *e, double new_sal) {
e->salary = new_sal;
}
2. 作为返回值
// 返回结构体值(C语言支持)
struct Point createPoint(int x, int y) {
struct Point p = {x, y};
return p;
}
// 返回结构体指针
Employee* createEmployee(const char* name) {
Employee *e = malloc(sizeof(Employee));
strcpy(e->name, name);
return e;
}
共用体
一、共用体基本概念
1. 定义与声明
// 基础定义
union Data {
int i;
float f;
char str[20];
};
// 声明变量
union Data data;
// 定义时声明变量
union {
int count;
char byte[4];
} packet;
// typedef 简化
typedef union {
unsigned int raw;
struct {
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
} channels;
} Color;
2. 内存特性
-
所有成员共享同一块内存空间
-
大小由最大成员决定
union Example { int a; // 4字节 double b; // 8字节 char c[10]; // 10字节 }; // sizeof(union Example) = 10字节
二、核心特性详解
1. 内存共享机制
union NumberConverter {
int intValue;
float floatValue;
};
union NumberConverter converter;
converter.intValue = 0x40490FDB; // 写入整数
printf("Float: %f", converter.floatValue); // 输出3.141593(IEEE 754浮点表示)
2. 一次只能存储一个有效成员
data.i = 10; // 存储整数值
printf("%d", data.i); // 输出10
data.f = 3.14; // 存储浮点值(覆盖整数)
printf("%f", data.f); // 输出3.14
printf("%d", data.i); // 输出垃圾值(原数据被覆盖)
动态内存分配
一、动态内存分配基础
1. 核心概念
-
堆(Heap)内存:程序运行时申请的内存区域
-
静态分配局限:
int static_arr[100]; // 固定大小,无法扩展 -
动态分配优势:
- 运行时确定内存大小
- 灵活管理大型数据结构
- 创建和使用周期更长的对象
2. 内存管理函数族
| 函数 | 头文件 | 功能说明 | 示例 |
|---|---|---|---|
| malloc | stdlib.h | 分配未初始化的内存块 | int *p = (int*)malloc(100*sizeof(int)); |
| calloc | stdlib.h | 分配并初始化为0的内存块 | int *p = (int*)calloc(100, sizeof(int)); |
| realloc | stdlib.h | 调整已分配内存块的大小 | p = (int*)realloc(p, 200*sizeof(int)); |
| free | stdlib.h | 释放先前分配的内存 | free(p); p = NULL; |
| aligned_alloc | stdlib.h | 分配对齐内存块(C11) | void *p = aligned_alloc(64, 1024); |
二、函数详解与最佳实践
1. malloc & calloc
// malloc标准用法
double *arr = (double*)malloc(500 * sizeof(double));
if(!arr) {
perror("内存分配失败");
exit(EXIT_FAILURE);
}
// calloc优于malloc的场景
int *zeros = (int*)calloc(1000, sizeof(int));
// 等效于: malloc + memset(arr, 0, size)
2. realloc的运作机制
// 正确使用realloc
int *arr = malloc(10 * sizeof(int));
int *tmp = realloc(arr, 20 * sizeof(int));
if (tmp) {
arr = tmp; // 使用新指针
} else {
free(arr); // 保留原始内存
/* 错误处理 */
}
// realloc特殊行为
realloc(NULL, size) ≡ malloc(size) // 空指针时新建内存
realloc(ptr, 0) ≡ free(ptr) // 大小为0时释放内存
3. free操作规范
// 安全释放模板
if (ptr != NULL) {
free(ptr);
ptr = NULL; // 关键:避免悬空指针
}
// 错误示范
free(ptr); // 正确释放
free(ptr); // 二次释放 → 程序崩溃
printf("%d", *ptr); // 使用已释放指针 → 未定义行为
文件
一、文件基础概念
1. 文件指针
FILE *file_ptr; // 声明文件指针
-
FILE结构体:包含文件状态和缓冲区信息 -
标准流指针:
指针名 描述 文件描述符 stdin标准输入 0 stdout标准输出 1 stderr标准错误输出 2
2. 文件操作基本流程
- 打开文件 →
fopen() - 文件操作 → 读/写/定位
- 关闭文件 →
fclose()
二、文件打开与关闭
1. 打开文件函数
FILE *fopen(const char *filename, const char *mode);
filename:文件路径(相对/绝对)mode:访问模式
2. 文件打开模式
| 模式 | 描述 | 文件位置 |
|---|---|---|
"r" |
只读(文本文件) | 文件开头 |
"w" |
只写(创建或覆盖) | 文件开头 |
"a" |
追加写入 | 文件末尾 |
"r+" |
读写 | 文件开头 |
"w+" |
读写(创建或覆盖) | 文件开头 |
"a+" |
读写(追加) | 文件末尾 |
"rb" |
二进制只读 | 文件开头 |
"wb" |
二进制只写 | 文件开头 |
3. 关闭文件
int fclose(FILE *stream);
- 成功关闭返回0
- 失败返回EOF(通常为-1)
- 关闭前自动刷新缓冲区
三、文件读写操作
1. 字符输入/输出
int fgetc(FILE *stream); // 读字符
int fputc(int char, FILE *stream); // 写字符
// 示例:复制文件内容
int ch;
while ((ch = fgetc(source)) != EOF) {
fputc(ch, dest);
}
2. 字符串输入/输出
char *fgets(char *str, int n, FILE *stream);
int fputs(const char *str, FILE *stream);
// 示例:逐行读取
char buffer[256];
while (fgets(buffer, sizeof(buffer), file)) {
// 处理每行内容
}
3. 格式化输入/输出
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
// 示例:写入结构化数据
struct Student s = {101, "Alice", 92.5};
fprintf(file, "%d,%s,%.1f\n", s.id, s.name, s.grade);
// 示例:读取结构化数据
struct Student s;
fscanf(file, "%d,%[^,],%f\n", &s.id, s.name, &s.grade);