Featured image of post C语言笔记

C语言笔记

比较浅陋的c语言笔记,适合有基础者使用,便于快速回忆复习知识点

核心语法

常量

程序运行时,值不能发生改变的数据

  • 整形常量:正数、负数、0

  • 实型常量:带小数点的数字(小数点前后为0可省略,eg:.93=0.93)(1.2*10的4次方不是常量,是一个计算过程)(1.2E7是实型常量)

  • 字符常量:单引号引起来的字母、数字、英文符号

  • 字符串常量:双引号引起来

格式控制符

格式控制符 说明
%d 整型数据
%f 浮点数
%c 单个字符
%s 文本字符串
%n 将已打印字符的数量写入到参数(整数指针)中
%x 以十六进制形式输出整数
%p 输出指针地址

变量

定义格式: 数据类型 变量名

注意事项:

  • 变量名不允许重复定义

  • 一条语句可定义多个变量

  • 变量在使用前必须赋值

标识符

命名规则:

规则类型 要求
组成字符 英文字母、数字、下划线
首字符 必须是字母或下划线
大小写敏感 varVarVAR
禁止关键字 int, return等34个保留字
禁止符号 空格 @ $ - +

数据类型

分类 数据类型 标准头文件 大小(字节) 取值范围/描述
字符型 char - 1 -128127 或 0255 (编译器相关)
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,表示没有返回值

  1. return关键字可省略
  2. 若使用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])

指针

指针基础概念

  1. ​本质​​:存储内存地址的特殊变量

  2. ​声明语法​​:数据类型 *指针变量名;

    int *p;      // 整型指针
    char *c_ptr; // 字符指针
    
  3. ​取地址运算符(&)​​:获取变量的内存地址

    int num = 10;
    int *p = &num; // p指向num的地址
    
  4. ​解引用运算符(*)​​:访问指针指向的内存值

    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]);
}

八、常见错误与预防

  1. ​空指针解引用​​:int *p = NULL; *p = 10;(崩溃)

  2. ​野指针​​:使用未初始化指针

    int *p; // 未初始化
    *p = 5; // 危险!
    
  3. ​内存泄漏​​:忘记释放分配的内存

  4. ​指针类型不匹配​​:

    double d = 3.14;
    int *p = (int*)&d; // 类型强制转换,可能丢失精度
    
  5. ​数组越界​​:

    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. 文件操作基本流程

  1. ​打开文件​​ → fopen()
  2. ​文件操作​​ → 读/写/定位
  3. ​关闭文件​​ → 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);
This blog has been running for
Published 11 posts · Total 48.46k words