从零开始的C++学习生活 18:C语言复习课(期末速通)

个人主页:Yupureki-CSDN博客

C++专栏:C++_Yupureki的博客-CSDN博客

目录

前言

1.C语言基础

[1.1 数据类型](#1.1 数据类型)

[1.1.1 整型家族](#1.1.1 整型家族)

[1.1.2 浮点型](#1.1.2 浮点型)

[1.1.3 查看类型范围](#1.1.3 查看类型范围)

[1.1.4 自定义类型](#1.1.4 自定义类型)

[1.2 变量和常量](#1.2 变量和常量)

[1.3 运算符](#1.3 运算符)

关键概念与注意事项

[2. 控制结构](#2. 控制结构)

[2.1 条件语句](#2.1 条件语句)

[2.2 循环语句](#2.2 循环语句)

[3. 函数](#3. 函数)

[3.1 函数定义和调用](#3.1 函数定义和调用)

函数声明(原型)

函数定义

函数参数传递:值传递

函数调用

[4. 数组和字符串(重要)](#4. 数组和字符串(重要))

[4.1 数组基础](#4.1 数组基础)

[4.2 二维数组](#4.2 二维数组)

[4.3 字符串](#4.3 字符串)

[5. 指针(重要)](#5. 指针(重要))

[5.1 指针基础](#5.1 指针基础)

[5.2 指针高级应用](#5.2 指针高级应用)

[5.2.1 指针作为函数参数](#5.2.1 指针作为函数参数)

[5.2.2 指针的指针(二级指针)](#5.2.2 指针的指针(二级指针))

[5.2.3 指针数组](#5.2.3 指针数组)

[5.2.4 数组指针](#5.2.4 数组指针)

[5.2.5 函数指针](#5.2.5 函数指针)

[5.3 数组与指针(重要)](#5.3 数组与指针(重要))

[5.3.1 数组名](#5.3.1 数组名)

[5.3.2 数组和指针笔试题解析](#5.3.2 数组和指针笔试题解析)

一维数组

二维数组

[6. 字符函数和字符串函数(重要)](#6. 字符函数和字符串函数(重要))

[6.1 strlen - 字符串长度计算](#6.1 strlen - 字符串长度计算)

示例

模拟实现

[6.2 strcpy - 字符串复制](#6.2 strcpy - 字符串复制)

示例

模拟实现

[6.3 strcmp - 字符串比较](#6.3 strcmp - 字符串比较)

示例

模拟实现

[6.4 strstr - 字符串查找](#6.4 strstr - 字符串查找)

示例

模拟实现

[7. 结构体和联合体](#7. 结构体和联合体)

[7.1 结构体](#7.1 结构体)

[7.1.1 结构的声明](#7.1.1 结构的声明)

[7.1.2 结构体函数](#7.1.2 结构体函数)

[7.1.3 结构体指针](#7.1.3 结构体指针)

[7.1.4 结构体数组](#7.1.4 结构体数组)

[7.2 结构体内存对齐(重要)](#7.2 结构体内存对齐(重要))

[7.2.1 为什么需要内存对齐?](#7.2.1 为什么需要内存对齐?)

[7.2.2 结构体内存对齐规则](#7.2.2 结构体内存对齐规则)

基本规则

示例分析

示例1:基础结构体

内存布局分析:

示例2:调整成员顺序优化

优化后的内存布局:

复杂结构体示例

示例4:嵌套结构体

内存分析:

[7.3 联合体](#7.3 联合体)

[7.3.1 判断大小端](#7.3.1 判断大小端)

[7.4 枚举](#7.4 枚举)

[8. 内存函数(重要)](#8. 内存函数(重要))

[8.1 内存分配函数](#8.1 内存分配函数)

[8.1.1 malloc - 分配未初始化的内存](#8.1.1 malloc - 分配未初始化的内存)

[8.1.2 calloc - 分配并初始化为0的内存](#8.1.2 calloc - 分配并初始化为0的内存)

[8.1.3 realloc - 重新分配内存](#8.1.3 realloc - 重新分配内存)

[8.1.4 释放内存](#8.1.4 释放内存)

[8.2 memcpy和memmove](#8.2 memcpy和memmove)

[8.2.1 函数原型和基本概念](#8.2.1 函数原型和基本概念)

函数原型

基本功能

[8.2.2 关键区别:内存重叠处理](#8.2.2 关键区别:内存重叠处理)

[8.2.3 内存重叠示例](#8.2.3 内存重叠示例)

[8.2.4 模拟实现](#8.2.4 模拟实现)

memcpy的模拟实现

memmove的模拟实现

[9. 文件操作](#9. 文件操作)

[9.1 文件的打开和关闭](#9.1 文件的打开和关闭)

9.1.1流和标准流

9.1.2标准流

[9.1.3 文件指针](#9.1.3 文件指针)

写入文件

读取文件

关闭文件

[9.2 二进制文件操作](#9.2 二进制文件操作)

10.编译与链接

10.1编译过程的四个主要阶段

[10.2 预处理阶段 (Preprocessing)](#10.2 预处理阶段 (Preprocessing))

[10.2.1 预处理的主要工作](#10.2.1 预处理的主要工作)

头文件包含 (#include))

宏定义和展开 (#define))

[条件编译 (#if, #ifdef, #ifndef)](#if, #ifdef, #ifndef))

其他预处理指令

[10.3 编译阶段 (Compilation)](#10.3 编译阶段 (Compilation))

[10.3.1 编译过程详解](#10.3.1 编译过程详解)

[10.4 汇编阶段 (Assembly)](#10.4 汇编阶段 (Assembly))

[10.4.1 目标文件的结构](#10.4.1 目标文件的结构)

[10.5 链接阶段 (Linking)](#10.5 链接阶段 (Linking))

链接过程详解

[10.5.1 创建多个源文件示例](#10.5.1 创建多个源文件示例)

[10.5.2 分步编译链接过程](#10.5.2 分步编译链接过程)

[10.5.3 符号解析和重定位](#10.5.3 符号解析和重定位)

[10.6. 预处理中的重要细节](#10.6. 预处理中的重要细节)

10.6.1防止头文件重复包含

[10.6.2 宏使用的注意事项](#10.6.2 宏使用的注意事项)

[10.6.3 预定义宏](#10.6.3 预定义宏)


上一篇:从零开始的C++学习生活 17:异常和智能指针-CSDN博客

前言

C++的知识学习告一段落,但是复习是不可或缺的。其中C语言更是基础中的基础,无论是准备面试、考试,还是希望夯实编程基础,系统性地复习C语言都是非常有价值的。

我将按照C语言的知识体系,从基础语法到高级特性,全面梳理核心知识点,帮助你构建完整的C语言知识框架。每个部分都配有实用的代码示例和注意事项,让复习更加高效。

1.C语言基础

1.1 数据类型

C语言提供了丰富的数据类型来描述生活中的各种数据。 使用整型类型来描述整数,使用字符类型来描述字符,使用浮点型类型来描述小数。 所谓"类型",就是相似的数据所拥有的共同特征,编译器只有知道了数据的类型,才知道怎么操作数据。

1.1.1 整型家族

cpp 复制代码
char c = 'A';                    // 1字节,-128到127
unsigned char uc = 255;          // 1字节,0到255
short s = 32767;                 // 2字节,-32768到32767  
int i = 2147483647;              // 4字节,-2147483648到2147483647
long l = 2147483647;             // 4或8字节
long long ll = 9223372036854775807; // 8字节

1.1.2 浮点型

cpp 复制代码
float f = 3.14159f;              // 4字节,约6-7位有效数字
double d = 3.141592653589793;    // 8字节,约15-16位有效数字
long double ld = 3.14159265358979323846L; // 10或16字节

1.1.3 查看类型范围

cpp 复制代码
printf("int范围: %d 到 %d\n", INT_MIN, INT_MAX);
printf("float精度: %d 位\n", FLT_DIG);

1.1.4 自定义类型

cpp 复制代码
struct Date{
    int _year;
    int _month;
    int _day;
}

重要提示

  • 注意有符号和无符号类型的区别。有符号包括我们常见的正负数,而无符号只有正数
  • 浮点数比较时避免直接使用==,因为往往计算的精度不同
  • 了解不同平台的类型大小差异

1.2 变量和常量

变量由数据类型和变量名组成

其中常量指具有常属性的变量,这些变量无法被更改。用define定义或者const修饰的变量为常变量

cpp 复制代码
#include <stdio.h>

// 宏定义常量
#define PI 3.14159
#define MAX_SIZE 100

// const常量
const int MIN_SIZE = 10;

int main() {
    // 变量声明和初始化
    int count = 0;
    float price = 99.99;
    char grade = 'A';
    
    // 不同类型常量
    const int days_in_week = 7;
    enum boolean { FALSE, TRUE };
    enum boolean status = TRUE;
    
    // 变量作用域演示
    {
        int local_var = 42;  // 块作用域
        printf("局部变量: %d\n", local_var);
    }
    // printf("%d\n", local_var);  // 错误:local_var不可见
    
    return 0;
}

1.3 运算符

优先级 类别 运算符 名称 / 含义 结合性 用法示例
1 括号、结构体 () [] . -> 函数调用 数组下标 结构体成员访问 结构体指针成员访问 从左到右 func() arr[5] obj.member ptr->member
2 单目运算符 ! ~ ++ -- + - * & (type) sizeof 逻辑非 按位取反 自增 自减 正号 负号 解引用 取地址 强制类型转换 求类型大小 从右到左 !flag ~a ++i / i++ --i / i-- +5 -10 *ptr &var (float)i sizeof(int)
3 算术运算符 * / % 乘法 除法 取模(求余) 从左到右 a * b a / b a % b
4 算术运算符 + - 加法 减法 从左到右 a + b a - b
5 位运算符 << >> 左移 右移 从左到右 a << 2 a >> 1
6 关系运算符 < <= > >= 小于 小于等于 大于 大于等于 从左到右 a < b a <= b a > b a >= b
7 关系运算符 == != 等于 不等于 从左到右 a == b a != b
8 位运算符 & 按位与 从左到右 a & b
9 位运算符 ^ 按位异或 从左到右 a ^ b
10 位运算符 ` ` 按位或 从左到右
11 逻辑运算符 && 逻辑与 从左到右 a && b
12 逻辑运算符 ` ` 逻辑或
13 条件运算符 ? : 三目条件运算符 从右到左 a > b ? a : b
14 赋值运算符 = += -= *= /= %= &= ^= ` = <<= >>=` 赋值 复合赋值 从右到左
15 逗号运算符 , 逗号(顺序求值) 从左到右 a=1, b=2

关键概念与注意事项

  1. 优先级 (Precedence)

    • 当表达式中出现多个运算符时,优先级决定了谁先计算。例如,乘除 (*, /) 的优先级高于加减 (+, -),所以 a + b * c 等价于 a + (b * c)
  2. 结合性 (Associativity)

    • 当相邻运算符的优先级相同时,结合性决定了计算顺序。

    • 从左到右 :大部分运算符如此,如 a + b + c 等价于 (a + b) + c

    • 从右到左 :单目、赋值和三目运算符如此。如 a = b = c 等价于 a = (b = c)

  3. 单目运算符的"前缀"与"后缀"

    • ++-- 作为前缀和后缀时,优先级不同

    • 后缀 (i++, i--):优先级为 1,与函数调用同级。

    • 前缀 (++i, --i):优先级为 2,与其他单目运算符同级。

    • 示例*ptr++ 等价于 *(ptr++),因为后缀++优先级高于解引用*。而 *++ptr 等价于 *(++ptr)

  4. "短路求值 (Short-Circuit Evaluation)"

    • 逻辑与 (&&) 和逻辑或 (||) 具有此特性。

    • 对于 a && b,如果 a 为假,则整个表达式结果已确定为假,不再计算 b

    • 对于 a || b,如果 a 为真,则整个表达式结果已确定为真,不再计算 b

  5. 运算符的"重载"

    • 某些符号根据上下文有不同的含义。

    • *:乘法 (a * b) 或 解引用 (*ptr)。

    • &:按位与 (a & b) 或 取地址 (&var)。

    • -:减法 (a - b) 或 负号 (-5)。

2. 控制结构

2.1 条件语句

C语言提供if-else语句和switch语句来控制代码的执行

cpp 复制代码
#include <stdio.h>

int main() {
    int score = 85;
    
    // if-else语句
    if (score >= 90) {
        printf("优秀\n");
    } else if (score >= 80) {
        printf("良好\n");
    } else if (score >= 60) {
        printf("及格\n");
    } else {
        printf("不及格\n");
    }
    
    // switch语句
    char grade = 'B';
    switch (grade) {
        case 'A':
            printf("优秀\n");
            break;
        case 'B':
            printf("良好\n");
            break;
        case 'C':
            printf("及格\n");
            break;
        default:
            printf("不及格\n");
    }
    
    // 条件运算符(三元运算符)
    int max = (a > b) ? a : b;
    printf("较大值: %d\n", max);
    
    return 0;
}

2.2 循环语句

C语言提供while,do-while和for语句来使一段代码循环执行

cpp 复制代码
#include <stdio.h>

int main() {
    int i;
    
    // for循环
    printf("for循环: ");
    for (i = 0; i < 5; i++) {
        printf("%d ", i);
    }
    printf("\n");
    
    // while循环
    printf("while循环: ");
    i = 0;
    while (i < 5) {
        printf("%d ", i);
        i++;
    }
    printf("\n");
    
    // do-while循环
    printf("do-while循环: ");
    i = 0;
    do {
        printf("%d ", i);
        i++;
    } while (i < 5);
    printf("\n");
    
    // 循环控制:break和continue
    printf("break示例: ");
    for (i = 0; i < 10; i++) {
        if (i == 5) break;  // 跳出循环
        printf("%d ", i);
    }
    printf("\n");
    
    printf("continue示例: ");
    for (i = 0; i < 5; i++) {
        if (i == 2) continue;  // 跳过本次循环
        printf("%d ", i);
    }
    printf("\n");
    
    return 0;
}

3. 函数

3.1 函数定义和调用

我们日常所执行的程序都在函数内执行。同时函数之间也能相互调用

函数包括返回值,函数名,参数列表和函数体

函数声明(原型)

cpp 复制代码
int add(int a, int b);
void print_hello();
int factorial(int n);

函数定义

cpp 复制代码
int add(int a, int b) {
    return a + b;
}

函数参数传递:值传递

cpp 复制代码
void swap_wrong(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    printf("函数内: a=%d, b=%d\n", a, b);
}

函数调用

cpp 复制代码
int result = add(5, 3);
printf("5 + 3 = %d\n", result);

4. 数组和字符串(重要)

4.1 数组基础

数组是⼀组相同类型元素的集合
在内存中,数组是连续开辟的一段空间,因此数组中的每个元素都是紧挨着的

cpp 复制代码
int numbers[] = {5, 2, 8, 1, 9};

在数组中,下标并不等同于我们所认定的序号,第一个元素下标为0,以此开始

4.2 二维数组

其实二维数组访问也是使用下标的形式的,二维数组是有行和列的,只要锁定了行和列就能唯一锁定数组中的一个元素。

cpp 复制代码
int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};

4.3 字符串

其实可以把字符串看作是一种数组,但是对于对于字符串的访问可以直接通过数组首地址来访问,直到遇到尾部的\0停止,因此\0必须存在于字符串中

对于字符数组,末尾添加\0也是必要的

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    // 字符串初始化
    char str1[] = "Hello";           // 自动包含'\0'
    char str2[10] = "World";
    char str3[] = {'H', 'i', '\0'};  // 手动添加'\0'
    
    // 字符串操作
    printf("str1: %s\n", str1);
    printf("str1长度: %lu\n", strlen(str1));
    
    // 字符串复制
    char copy[10];
    strcpy(copy, str1);
    printf("复制后: %s\n", copy);
    
    // 字符串连接
    strcat(copy, " ");
    strcat(copy, str2);
    printf("连接后: %s\n", copy);
    
    // 字符串比较
    printf("比较str1和str2: %d\n", strcmp(str1, str2));
    
    // 字符串查找
    char *pos = strchr(str1, 'l');
    if (pos) {
        printf("找到字符'l'在位置: %ld\n", pos - str1);
    }
    
    // 安全的字符串函数(C11)
    char buffer[10];
    strncpy(buffer, "Hello World", sizeof(buffer) - 1);
    buffer[sizeof(buffer) - 1] = '\0';  // 确保以'\0'结尾
    printf("安全复制: %s\n", buffer);
    
    return 0;
}

5. 指针(重要)

5.1 指针基础

指针严格来说是指针变量,有自己的类型和值,在内存中开辟了空间

而指针变量的值就是变量的地址,即内存编号

cpp 复制代码
#include <stdio.h>

int main() {
    int num = 42;
    int *ptr = &num;  // ptr指向num的地址
    
    printf("变量值: %d\n", num);
    printf("变量地址: %p\n", &num);
    printf("指针值: %p\n", ptr);
    printf("指针指向的值: %d\n", *ptr);  // 解引用
    
    // 指针运算
    int arr[] = {10, 20, 30, 40, 50};
    int *arr_ptr = arr;
    
    printf("\n数组元素:\n");
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d, *(arr_ptr + %d) = %d\n", 
               i, arr[i], i, *(arr_ptr + i));
    }
    
    // 指针和数组的关系
    printf("\n指针和数组:\n");
    printf("arr = %p\n", arr);
    printf("&arr[0] = %p\n", &arr[0]);
    printf("arr_ptr = %p\n", arr_ptr);
    
    return 0;
}

5.2 指针高级应用

5.2.1 指针作为函数参数

函数参数传递的是形参,在函数体内对形参的改变不会影响外部的实参。因此如果要改变实参,就得传递指针

cpp 复制代码
void modify_value(int *ptr) {
    *ptr = 100;  // 修改指针指向的值
}

5.2.2 指针的指针(二级指针)

我们说过,指针也是变量,因此也可以创建指向指针的指针,俗称二级指针

二级指针可以改变一级指针指向哪个元素

cpp 复制代码
void pointer_to_pointer_demo() {
    int value = 42;
    int *ptr = &value;
    int **pptr = &ptr;
    
    printf("\n指向指针的指针:\n");
    printf("value = %d\n", value);
    printf("*ptr = %d\n", *ptr);
    printf("**pptr = %d\n", **pptr);
}

5.2.3 指针数组

指针数组指的是一个数组中存的的元素每个都是指针

由于[]的优先级比*高,因此ptr_arr被优先为数组

cpp 复制代码
void pointer_array_demo() {
    int a = 1, b = 2, c = 3;
    int *ptr_arr[] = {&a, &b, &c};
    
    printf("指针数组:\n");
    for (int i = 0; i < 3; i++) {
        printf("ptr_arr[%d] = %p, *ptr_arr[%d] = %d\n", 
               i, ptr_arr[i], i, *ptr_arr[i]);
    }
}

5.2.4 数组指针

数组指针是一个指针,指向的是整个数组

cpp 复制代码
int arr[10] = {0}
int (*p)[10] = &arr;

由于p指向的是整个数组,因此p解引用不是arr的首元素,而是整个数组,加1和减1也是跳过一整个数组

5.2.5 函数指针

函数指针指向函数,可以调用函数

cpp 复制代码
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
void function_pointer_demo() {
    // 函数指针声明
    int (*operation)(int, int);
    
    operation = add;
    printf("加法: %d\n", operation(5, 3));
    
    operation = subtract;
    printf("减法: %d\n", operation(5, 3));
    
    operation = multiply;
    printf("乘法: %d\n", operation(5, 3));
}

5.3 数组与指针(重要)

5.3.1 数组名

数组名是数组首元素的地址

只有两种情况下数组名不是数组首元素的地址

  1. sizeof(数组名)中数组名表示的是整个数组,计算的是整个数组的大小

2.&数组名取出的是整个数组,也就是数组指针

5.3.2 数组和指针笔试题解析

一维数组
cpp 复制代码
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//16
printf("%d\n",sizeof(a+0));//4
printf("%d\n",sizeof(*a));//4
printf("%d\n",sizeof(a+1));//4
printf("%d\n",sizeof(a[1]));//4
printf("%d\n",sizeof(&a));//4
printf("%d\n",sizeof(*&a));//16
printf("%d\n",sizeof(&a+1));//4
printf("%d\n",sizeof(&a[0]));//4
printf("%d\n",sizeof(&a[0]+1));//4

逐行分析

  1. printf("%d\n",sizeof(a));
  • a 作为数组名,单独出现在 sizeof 中,表示整个数组

  • 结果16(4个int × 4字节)

  1. printf("%d\n",sizeof(a+0));
  • a+0 中,数组名 a 退化为指向首元素的指针(int*

  • a+0 仍然是 int* 类型

  • 结果4(32位系统下指针的大小)

  1. printf("%d\n",sizeof(*a));
  • *a 等价于 a[0],即数组的第一个元素

  • 类型是 int

  • 结果4(int类型的大小)

  1. printf("%d\n",sizeof(a+1));
  • a+1 中,a 退化为指针,指向第二个元素

  • 类型是 int*

  • 结果4(指针大小)

  1. printf("%d\n",sizeof(a[1]));
  • a[1] 是数组的第二个元素

  • 类型是 int

  • 结果4

  1. printf("%d\n",sizeof(&a));
  • &a 是取整个数组的地址

  • 类型是 int(*)[4](指向包含4个int的数组的指针)

  • 但仍然是指针,所有数据指针在32位系统都是4字节

  • 结果4

  1. printf("%d\n",sizeof(*&a));
  • *&a 先取数组地址再解引用,等价于 a

  • 所以就是整个数组

  • 结果16

  1. printf("%d\n",sizeof(&a+1));
  • &a+1 指向数组末尾的下一个位置

  • 类型仍然是 int(*)[4]

  • 结果4(指针大小)

  1. printf("%d\n",sizeof(&a[0]));
  • &a[0] 取第一个元素的地址

  • 类型是 int*

  • 结果4

  1. printf("%d\n",sizeof(&a[0]+1));
  • &a[0]+1 指向第二个元素

  • 类型是 int*

  • 结果4

二维数组
cpp 复制代码
int a[3][4] = {0};
printf("%d\n",sizeof(a));//48
printf("%d\n",sizeof(a[0][0]));//4
printf("%d\n",sizeof(a[0]));//16
printf("%d\n",sizeof(a[0]+1));//4
printf("%d\n",sizeof(*(a[0]+1)));//4
printf("%d\n",sizeof(a+1));//4
printf("%d\n",sizeof(*(a+1)));//4
printf("%d\n",sizeof(&a[0]+1));//4
printf("%d\n",sizeof(*(&a[0]+1)));//16
printf("%d\n",sizeof(*a));//16
printf("%d\n",sizeof(a[3]));//16

逐行分析

  1. printf("%d\n",sizeof(a));
  • a 作为二维数组名,单独出现在 sizeof 中,表示整个二维数组

  • 结果48(3×4=12个int × 4字节)

  1. printf("%d\n",sizeof(a[0][0]));
  • a[0][0] 是第一行第一列的元素

  • 类型是 int

  • 结果4(int类型的大小)

  1. printf("%d\n",sizeof(a[0]));
  • a[0] 是第一行的数组名,单独出现在 sizeof

  • 类型是 int[4](包含4个int的一维数组)

  • 结果16(4个int × 4字节)

  1. printf("%d\n",sizeof(a[0]+1));
  • a[0]+1 中,a[0] 退化为指向第一行第一个元素的指针(int*

  • a[0]+1 指向第一行的第二个元素

  • 类型是 int*

  • 结果4(指针大小)

  1. printf("%d\n",sizeof(*(a[0]+1)));
  • *(a[0]+1) 解引用得到第一行第二个元素的值

  • 类型是 int

  • 结果4

  1. printf("%d\n",sizeof(a+1));
  • a+1 中,二维数组名 a 退化为指向第一行的指针(int(*)[4]

  • a+1 指向第二行

  • 类型是 int(*)[4]

  • 结果4(指针大小)

  1. printf("%d\n",sizeof(*(a+1)));
  • *(a+1) 解引用得到第二行

  • 类型是 int[4](包含4个int的一维数组)

  • 结果16(4个int × 4字节)

  1. printf("%d\n",sizeof(&a[0]+1));
  • &a[0] 取第一行的地址,类型是 int(*)[4]

  • &a[0]+1 指向第二行

  • 类型是 int(*)[4]

  • 结果4(指针大小)

  1. printf("%d\n",sizeof(*(&a[0]+1)));
  • *(&a[0]+1) 解引用得到第二行

  • 类型是 int[4]

  • 结果16

  1. printf("%d\n",sizeof(*a));
  • *a 中,a 退化为指向第一行的指针,解引用得到第一行

  • 类型是 int[4]

  • 结果16

  1. printf("%d\n",sizeof(a[3]));
  • a[3] 是访问第四行(数组越界),但 sizeof 在编译时确定类型

  • a[3] 的类型是 int[4]

  • 结果16(编译时根据类型确定,不实际访问内存)

6. 字符函数和字符串函数(重要)

6.1 strlen - 字符串长度计算

cpp 复制代码
#include <string.h>
size_t strlen(const char *str);

strlen用来计算一个字符串的长度,即从头开始直到遇到\0,计算这之间的长度,但是不计入\0的长度

示例

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello, World!";
    printf("字符串长度: %zu\n", strlen(str)); // 输出: 13
    return 0;
}

模拟实现

cpp 复制代码
// 方法1: 计数器方式
size_t my_strlen1(const char *str) {
    size_t count = 0;
    while (*str != '\0') {
        count++;
        str++;
    }
    return count;
}

// 方法2: 指针相减方式
size_t my_strlen2(const char *str) {
    const char *start = str;
    while (*str != '\0') {
        str++;
    }
    return str - start;
}

// 方法3: 递归方式
size_t my_strlen3(const char *str) {
    if (*str == '\0') {
        return 0;
    }
    return 1 + my_strlen3(str + 1);
}

6.2 strcpy - 字符串复制

cpp 复制代码
#include <string.h>
char *strcpy(char *dest, const char *src);

示例

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Hello, World!";
    char dest[20];
    
    strcpy(dest, src);
    printf("复制后的字符串: %s\n", dest); // 输出: Hello, World!
    
    return 0;
}

模拟实现

cpp 复制代码
// 基础版本
char *my_strcpy1(char *dest, const char *src) {
    char *ret = dest;
    while (*src != '\0') {
        *dest = *src;
        dest++;
        src++;
    }
    *dest = '\0'; // 添加字符串结束符
    return ret;
}

// 简化版本
char *my_strcpy2(char *dest, const char *src) {
    char *ret = dest;
    while ((*dest++ = *src++) != '\0') {
        // 空循环体
    }
    return ret;
}

// 更简洁版本
char *my_strcpy3(char *dest, const char *src) {
    char *ret = dest;
    while (*dest++ = *src++) {
        // 空循环体
    }
    return ret;
}

6.3 strcmp - 字符串比较

cpp 复制代码
#include <string.h>
int strcmp(const char *str1, const char *str2);

返回值:

  • < 0:str1 < str2

  • = 0:str1 = str2

  • > 0:str1 > str2

示例

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "apple";
    char str2[] = "banana";
    char str3[] = "apple";
    
    printf("str1 vs str2: %d\n", strcmp(str1, str2)); // 负数
    printf("str1 vs str3: %d\n", strcmp(str1, str3)); // 0
    printf("str2 vs str1: %d\n", strcmp(str2, str1)); // 正数
    
    return 0;
}

模拟实现

cpp 复制代码
// 标准实现
int my_strcmp1(const char *str1, const char *str2) {
    while (*str1 && *str2 && *str1 == *str2) {
        str1++;
        str2++;
    }
    return *(unsigned char*)str1 - *(unsigned char*)str2;
}

// 更易理解的版本
int my_strcmp2(const char *str1, const char *str2) {
    while (*str1 != '\0' && *str2 != '\0') {
        if (*str1 != *str2) {
            return *str1 - *str2;
        }
        str1++;
        str2++;
    }
    
    // 处理一个字符串已结束的情况
    if (*str1 == '\0' && *str2 == '\0') {
        return 0;
    } else if (*str1 == '\0') {
        return -1;
    } else {
        return 1;
    }
}

6.4 strstr - 字符串查找

cpp 复制代码
#include <string.h>
char *strstr(const char *haystack, const char *needle);

示例

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello, World! Welcome to C programming.";
    char substr[] = "World";
    
    char *result = strstr(str, substr);
    if (result != NULL) {
        printf("找到子串: %s\n", result); // 输出: World! Welcome to C programming.
        printf("位置: %ld\n", result - str); // 输出: 7
    } else {
        printf("未找到子串\n");
    }
    
    return 0;
}

模拟实现

cpp 复制代码
// 暴力匹配算法
char *my_strstr1(const char *haystack, const char *needle) {
    if (*needle == '\0') {
        return (char*)haystack;
    }
    
    const char *h = haystack;
    const char *n = needle;
    
    while (*h != '\0') {
        const char *h_pos = h;
        n = needle;
        
        // 尝试匹配
        while (*h_pos != '\0' && *n != '\0' && *h_pos == *n) {
            h_pos++;
            n++;
        }
        
        // 如果needle完全匹配
        if (*n == '\0') {
            return (char*)h;
        }
        
        // 如果haystack已到结尾
        if (*h_pos == '\0') {
            return NULL;
        }
        
        h++;
    }
    
    return NULL;
}

// 更简洁的版本
char *my_strstr2(const char *haystack, const char *needle) {
    if (*needle == '\0') return (char*)haystack;
    
    for (int i = 0; haystack[i] != '\0'; i++) {
        int j = 0;
        while (needle[j] != '\0' && haystack[i + j] == needle[j]) {
            j++;
        }
        if (needle[j] == '\0') {
            return (char*)(haystack + i);
        }
    }
    return NULL;
}

7. 结构体和联合体

7.1 结构体

7.1.1 结构的声明

cpp 复制代码
struct tag
{
 member-list;
}variable-list;

例如描述⼀个学生:

cpp 复制代码
struct Student {
    char name[50];
    int age;
    float score;
};

7.1.2 结构体函数

结构体对象使用 **.**来访问成员变量

cpp 复制代码
void print_student(struct Student s) {
    printf("姓名: %s, 年龄: %d, 分数: %.2f\n", 
           s.name, s.age, s.score);
}

7.1.3 结构体指针

结构体的指针用**->**来访问成员变量

cpp 复制代码
void modify_student(struct Student *s) {
    strcpy(s->name, "修改后的名字");
    s->age = 25;
    s->score = 95.5;
}

7.1.4 结构体数组

cpp 复制代码
void struct_array_demo() {
    struct Student class[3] = {
        {"张三", 20, 88.5},
        {"李四", 21, 92.0},
        {"王五", 19, 76.5}
    };
    
    printf("\n学生信息:\n");
    for (int i = 0; i < 3; i++) {
        print_student(class[i]);
    }
}

7.2 结构体内存对齐(重要)

7.2.1 为什么需要内存对齐?

内存对齐是为了提高CPU访问内存的效率。大多数CPU访问对齐的数据(地址是数据大小的整数倍)比访问非对齐的数据要快得多。

7.2.2 结构体内存对齐规则

基本规则
  1. 第一个成员:从结构体起始地址的0偏移处开始

  2. 其他成员 :对齐到 min(#pragma pack指定的数值, 这个成员自身长度) 的整数倍地址

  3. 结构体总大小 :必须是 min(#pragma pack指定的数值, 最大成员长度) 的整数倍

  4. 默认对齐值:visual studio中默认为8

示例分析
示例1:基础结构体
cpp 复制代码
#include <stdio.h>

struct Example1 {
    char a;     // 1字节
    int b;      // 4字节  
    short c;    // 2字节
};

int main() {
    printf("sizeof(struct Example1) = %zu\n", sizeof(struct Example1));
    return 0;
}
内存布局分析
复制代码
偏移地址:  0   1   2   3   4   5   6   7   8   9   10  11
成员:     [a] 填充 填充 填充 [b] [b] [b] [b] [c] [c] 填充 填充
大小:     1字节 + 3字节填充 + 4字节 + 2字节 + 2字节填充 = 12字节
示例2:调整成员顺序优化
cpp 复制代码
struct Example2 {
    char a;     // 1字节
    short c;    // 2字节
    int b;      // 4字节
};

int main() {
    printf("sizeof(struct Example2) = %zu\n", sizeof(struct Example2));
    return 0;
}
优化后的内存布局
cpp 复制代码
偏移地址:  0   1   2   3   4   5   6   7
成员:     [a] 填充 [c] [c] [b] [b] [b] [b]
大小:     1字节 + 1字节填充 + 2字节 + 4字节 = 8字节
复杂结构体示例
示例4:嵌套结构体
cpp 复制代码
struct Inner {
    char x;     // 1字节
    double y;   // 8字节
};              // 大小: 16字节

struct Outer {
    char a;         // 1字节
    struct Inner b; // 16字节
    int c;          // 4字节
};

int main() {
    printf("sizeof(Inner) = %zu\n", sizeof(struct Inner));
    printf("sizeof(Outer) = %zu\n", sizeof(struct Outer));
    return 0;
}
内存分析
复制代码
Outer布局:
[char a] + 7字节填充 + [Inner结构体(16字节)] + [int c] + 4字节填充 = 32字节

7.3 联合体

像结构体一样,联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型。
但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同⼀块内存空间所
以联合体也叫:共用体。 给联合体其中⼀个成员赋值,其他成员的值也跟着变化。

cpp 复制代码
union Data {
    int i;
    float f;
    char str[20];
};

7.3.1 判断大小端

cpp 复制代码
int check_sys()
{
     union
     {
         int i;
         char c;
     }un;
     un.i = 1;
     return un.c;//返回1是⼩端,返回0是⼤端
}

7.4 枚举

枚举顾名思义就是一一列举。

cpp 复制代码
enum Color { RED, GREEN, BLUE };
enum Week { MON = 1, TUE, WED, THU, FRI, SAT, SUN };
cpp 复制代码
// 枚举使用
    enum Color c = RED;
    enum Week today = WED;
    
    printf("颜色: %d\n", c);
    printf("今天是星期: %d\n", today);

8. 内存函数(重要)

8.1 内存分配函数

8.1.1 malloc - 分配未初始化的内存

cpp 复制代码
int *arr1 = (int*)malloc(5 * sizeof(int));
if (arr1 == NULL) {
printf("内存分配失败\n");
return 1;
}
// 使用分配的内存
for (int i = 0; i < 5; i++) {
    arr1[i] = i * 10;
}
    
printf("malloc分配数组: ");
for (int i = 0; i < 5; i++) {
    printf("%d ", arr1[i]);
}
printf("\n");

8.1.2 calloc - 分配并初始化为0的内存

cpp 复制代码
int *arr2 = (int*)calloc(5, sizeof(int));
printf("calloc分配数组(初始为0): ");
for (int i = 0; i < 5; i++) {
    printf("%d ", arr2[i]);
}
printf("\n");

8.1.3 realloc - 重新分配内存

cpp 复制代码
arr1 = (int*)realloc(arr1, 10 * sizeof(int));
for (int i = 5; i < 10; i++) {
    arr1[i] = i * 10;
}
    
printf("realloc扩展后数组: ");
for (int i = 0; i < 10; i++) {
     printf("%d ", arr1[i]);
}
printf("\n");

8.1.4 释放内存

cpp 复制代码
free(arr1);
free(arr2);

8.2 memcpy和memmove

8.2.1 函数原型和基本概念

函数原型
cpp 复制代码
#include <string.h>

void *memcpy(void *dest, const void *src, size_t n);
void *memmove(void *dest, const void *src, size_t n);
基本功能

两个函数都用于从源内存区域复制n个字节到目标内存区域。

8.2.2 关键区别:内存重叠处理

特性 memcpy memmove
内存重叠处理 不处理,行为未定义 安全处理,保证正确复制
性能 通常更快 稍慢(需要额外检查)
使用场景 确定内存不重叠时 不确定内存是否重叠时

memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。

这个函数在遇到 '\0' 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的,因此需要memmove来处理。

8.2.3 内存重叠示例

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "Hello, World!";
    char str2[] = "Hello, World!";
    
    // 内存重叠情况:目标在源之后
    printf("原始字符串: %s\n", str1);
    
    // 使用memcpy(可能有问题)
    memcpy(str1 + 7, str1, 6);
    printf("memcpy结果: %s\n", str1);
    
    // 使用memmove(安全)
    memmove(str2 + 7, str2, 6);
    printf("memmove结果: %s\n", str2);
    
    return 0;
}

输出结果可能不同,因为memcpy在重叠时的行为是未定义的。

8.2.4 模拟实现

memcpy的模拟实现
cpp 复制代码
// 基础版本 - 逐字节复制
void *my_memcpy(void *dest, const void *src, size_t n) {
    if (dest == NULL || src == NULL) {
        return NULL;
    }
    
    char *d = (char *)dest;
    const char *s = (const char *)src;
    
    for (size_t i = 0; i < n; i++) {
        d[i] = s[i];
    }
    
    return dest;
}

// 优化版本 - 使用字长复制(假设系统为32位)
void *my_memcpy_optimized(void *dest, const void *src, size_t n) {
    if (dest == NULL || src == NULL || n == 0) {
        return dest;
    }
    
    char *d = (char *)dest;
    const char *s = (const char *)src;
    
    // 按字节复制直到对齐边界
    while (((uintptr_t)d % sizeof(int)) != 0 && n > 0) {
        *d++ = *s++;
        n--;
    }
    
    // 按字长(4字节)复制
    int *d_int = (int *)d;
    const int *s_int = (const int *)s;
    while (n >= sizeof(int)) {
        *d_int++ = *s_int++;
        n -= sizeof(int);
    }
    
    // 复制剩余的字节
    d = (char *)d_int;
    s = (const char *)s_int;
    while (n > 0) {
        *d++ = *s++;
        n--;
    }
    
    return dest;
}
memmove的模拟实现
cpp 复制代码
// memmove的模拟实现 - 处理内存重叠
void *my_memmove(void *dest, const void *src, size_t n) {
    if (dest == NULL || src == NULL || n == 0) {
        return dest;
    }
    
    char *d = (char *)dest;
    const char *s = (const char *)src;
    
    // 判断内存是否重叠以及重叠的方式
    if (d < s) {
        // 目标在源之前,从前往后复制(不会覆盖未复制数据)
        for (size_t i = 0; i < n; i++) {
            d[i] = s[i];
        }
    } else if (d > s) {
        // 目标在源之后,从后往前复制(避免覆盖未复制数据)
        for (size_t i = n; i > 0; i--) {
            d[i - 1] = s[i - 1];
        }
    }
    // 如果地址相等,不需要复制
    
    return dest;
}

// 更简洁的memmove实现
void *my_memmove_simple(void *dest, const void *src, size_t n) {
    if (dest == NULL || src == NULL || n == 0) {
        return dest;
    }
    
    char *d = (char *)dest;
    const char *s = (const char *)src;
    
    // 创建临时缓冲区避免重叠问题
    char *temp = (char *)malloc(n);
    if (temp == NULL) {
        return NULL; // 内存分配失败
    }
    
    // 先复制到临时缓冲区
    for (size_t i = 0; i < n; i++) {
        temp[i] = s[i];
    }
    
    // 再从临时缓冲区复制到目标
    for (size_t i = 0; i < n; i++) {
        d[i] = temp[i];
    }
    
    free(temp);
    return dest;
}

9. 文件操作

9.1 文件的打开和关闭

9.1.1流和标准流

C语言中,所有的I/O操作都是通过"流"来进行的。流是数据在输入输出设备之间流动的抽象概念。

一般情况下,我们要向流里写入数据或读取数据,都需要打开流

9.1.2标准流

stdin:标准输入流(键盘)

stdout:标准输出流(屏幕)

stderr:标准错误流(屏幕)

我们使用scanf利用键盘读取数据,所利用的就是stdin标准输入流

我们使用printf向屏幕输出数据,所利用的就是stdout标准输出流

而C语言默认打开了这三个流,因此我们无需说明打开什么流,系统会默认为我们操作

9.1.3 文件指针

我们通过FILE*类型的指针来操作文件。

写入文件
cpp 复制代码
FILE *file;
char buffer[100];
file = fopen("example.txt", "w");
if (file == NULL) {
    printf("无法打开文件\n");
    return 1;
 }
读取文件
cpp 复制代码
file = fopen("example.txt", "r");
if (file == NULL) {
    printf("无法打开文件\n");
    return 1;
}
关闭文件
cpp 复制代码
fclose(file);

9.2 二进制文件操作

cpp 复制代码
struct Student {
     char name[50];
     int age;
     float score;
};
    
struct Student students[3] = {
     {"张三", 20, 88.5},
     {"李四", 21, 92.0},
     {"王五", 19, 76.5}
};
    
    // 写入二进制文件
    file = fopen("students.dat", "wb");
    if (file) {
        fwrite(students, sizeof(struct Student), 3, file);
        fclose(file);
    }
    
    // 读取二进制文件
    struct Student read_students[3];
    file = fopen("students.dat", "rb");
    if (file) {
        fread(read_students, sizeof(struct Student), 3, file);
        fclose(file);
        
        printf("\n从二进制文件读取的学生信息:\n");
        for (int i = 0; i < 3; i++) {
            printf("姓名: %s, 年龄: %d, 分数: %.2f\n",
                   read_students[i].name, 
                   read_students[i].age, 
                   read_students[i].score);
        }

10.编译与链接

10.1编译过程的四个主要阶段

复制代码
源代码(.c) → 预处理(.i) → 编译(.s) → 汇编(.o) → 链接(可执行文件)

10.2 预处理阶段 (Preprocessing)

预处理是编译过程的第一步,由预处理器完成。

10.2.1 预处理的主要工作

头文件包含 (#include)

预处理阶段编译器会将包含的头文件展开到.c文件中,可以理解为复制粘贴

cpp 复制代码
// 示例:math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

#define PI 3.14159

double circle_area(double radius);
double circle_circumference(double radius);

#endif
cpp 复制代码
// main.c
#include <stdio.h>      // 系统头文件
#include "math_utils.h" // 用户头文件

int main() {
    double r = 5.0;
    printf("面积: %.2f\n", circle_area(r));
    return 0;
}

预处理后

cpp 复制代码
// 预处理后的main.i文件(简化版)
// stdio.h的数千行代码被插入到这里
// math_utils.h的内容被插入到这里

double circle_area(double radius);
double circle_circumference(double radius);

int main() {
    double r = 5.0;
    printf("面积: %.2f\n", circle_area(r));
    return 0;
}
宏定义和展开 (#define)

编译器会将宏的内容直接覆盖到代码中

cpp 复制代码
#include <stdio.h>

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
#define DEBUG 1

int main() {
    int x = 5, y = 10;
    
    #if DEBUG
    printf("调试信息: x=%d, y=%d\n", x, y);
    #endif
    
    printf("较大值: %d\n", MAX(x, y));
    printf("平方: %d\n", SQUARE(x + 1)); // 展开为 ((x + 1) * (x + 1))
    
    return 0;
}

预处理后

cpp 复制代码
// stdio.h内容...
int main() {
    int x = 5, y = 10;
    
    printf("调试信息: x=%d, y=%d\n", x, y);
    
    printf("较大值: %d\n", ((x) > (y) ? (x) : (y)));
    printf("平方: %d\n", ((x + 1) * (x + 1)));
    
    return 0;
}
条件编译 (#if, #ifdef, #ifndef)
cpp 复制代码
#include <stdio.h>

#define VERSION 2
#define DEBUG

int main() {
    #if VERSION == 1
    printf("版本1\n");
    #elif VERSION == 2
    printf("版本2\n");
    #else
    printf("未知版本\n");
    #endif
    
    #ifdef DEBUG
    printf("调试模式开启\n");
    #endif
    
    #ifndef RELEASE
    printf("不是发布版本\n");
    #endif
    
    return 0;
}
其他预处理指令
cpp 复制代码
#include <stdio.h>

// #error 在条件不满足时报错
#ifndef __STDC__
#error "需要ANSI C编译器"
#endif

// #pragma 编译器特定指令
#pragma pack(1) // 设置结构体对齐方式

// #line 改变行号信息
#line 100 "myfile.c"

int main() {
    printf("当前行号: %d\n", __LINE__);
    printf("文件名: %s\n", __FILE__);
    return 0;
}

10.3 编译阶段 (Compilation)

编译阶段将预处理后的C代码转换为汇编代码。

10.3.1 编译过程详解

cpp 复制代码
// 示例代码:compute.c
int add(int a, int b) {
    return a + b;
}

double multiply(double x, double y) {
    return x * y;
}

int main() {
    int result = add(5, 3);
    double product = multiply(2.5, 4.0);
    return 0;
}

编译为汇编代码

复制代码
gcc -S compute.c -o compute.s

生成的汇编代码示例(x86架构):

cpp 复制代码
	.file	"compute.c"
	.text
	.globl	add
	.type	add, @function
add:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	ret
	.size	add, .-add
	.globl	multiply
	.type	multiply, @function
multiply:
	// ... 类似代码
	.size	multiply, .-multiply
	.globl	main
	.type	main, @function
main:
	// ... 主函数代码
	.size	main, .-main

10.4 汇编阶段 (Assembly)

汇编阶段将汇编代码转换为机器代码(目标文件)。

10.4.1 目标文件的结构

cpp 复制代码
# 生成目标文件
gcc -c compute.s -o compute.o

# 查看目标文件信息
objdump -t compute.o    # 符号表
nm compute.o           # 符号表(另一种格式)
readelf -h compute.o   # ELF头信息

目标文件包含

  • 代码段(.text):编译后的机器指令

  • 数据段(.data):已初始化的全局变量

  • BSS段(.bss):未初始化的全局变量

  • 符号表:函数和变量名及其地址信息

  • 重定位表:需要链接时解析的符号引用

10.5 链接阶段 (Linking)

链接阶段将一个或多个目标文件与库文件合并,生成可执行文件。

链接过程详解

10.5.1 创建多个源文件示例

math_ops.c

cpp 复制代码
#include "math_ops.h"

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

math_ops.h

cpp 复制代码
#ifndef MATH_OPS_H
#define MATH_OPS_H

int add(int a, int b);
int subtract(int a, int b);

#endif

main.c

cpp 复制代码
#include <stdio.h>
#include "math_ops.h"

int main() {
    int x = 10, y = 5;
    
    printf("%d + %d = %d\n", x, y, add(x, y));
    printf("%d - %d = %d\n", x, y, subtract(x, y));
    
    return 0;
}
10.5.2 分步编译链接过程
cpp 复制代码
# 1. 预处理
gcc -E main.c -o main.i
gcc -E math_ops.c -o math_ops.i

# 2. 编译为汇编代码
gcc -S main.i -o main.s
gcc -S math_ops.i -o math_ops.s

# 3. 汇编为目标文件
gcc -c main.s -o main.o
gcc -c math_ops.s -o math_ops.o

# 4. 链接为可执行文件
gcc main.o math_ops.o -o program

# 或者一步完成
gcc main.c math_ops.c -o program
10.5.3 符号解析和重定位

查看符号表

cpp 复制代码
# 查看目标文件中的符号
nm main.o
nm math_ops.o

# 查看可执行文件中的符号
nm program

输出示例

cpp 复制代码
main.o:
                 U add           # 未定义符号,需要链接时解析
                 U printf        # 未定义符号,来自C标准库
0000000000000000 T main          # 已定义符号

math_ops.o:
0000000000000000 T add           # 已定义符号
0000000000000014 T subtract      # 已定义符号

program:
0000000000001139 T add           # 链接后已解析
000000000000114d T main
0000000000001159 T subtract
                 U printf@@GLIBC_2.2.5  # 动态链接符号

10.6. 预处理中的重要细节

10.6.1防止头文件重复包含

cpp 复制代码
// math.h
#ifndef MATH_H
#define MATH_H

// 头文件内容...

#endif

10.6.2 宏使用的注意事项

cpp 复制代码
// 错误的宏定义
#define SQUARE(x) x * x
// 问题:SQUARE(1+2) 展开为 1+2*1+2 = 5,而不是9

// 正确的宏定义
#define SQUARE(x) ((x) * (x))

// 多行宏定义
#define SWAP(a, b) do { \
    typeof(a) temp = a; \
    a = b; \
    b = temp; \
} while(0)

10.6.3 预定义宏

cpp 复制代码
#include <stdio.h>

int main() {
    printf("文件名: %s\n", __FILE__);
    printf("行号: %d\n", __LINE__);
    printf("编译日期: %s\n", __DATE__);
    printf("编译时间: %s\n", __TIME__);
    printf("函数名: %s\n", __func__);
    
    #ifdef __STDC__
    printf("ANSI C兼容: %d\n", __STDC__);
    #endif
    
    return 0;
}
相关推荐
做科研的周师兄6 小时前
【机器学习入门】9.2:感知机 Python 实践代码模板(苹果香蕉分类任务适配)
人工智能·python·学习·机器学习·分类·数据挖掘·numpy
小兔崽子去哪了7 小时前
数据结构和算法(Python)
数据结构·python
永远有缘7 小时前
四种编程语言常用函数对比表
java·开发语言·c++·python
C++_girl7 小时前
c++、java/python语言有什么区别?为什么c++更快?
java·开发语言·c++
艾莉丝努力练剑7 小时前
【Linux权限 (二)】Linux权限机制深度解析:umask如何决定默认权限与粘滞位的妙用
大数据·linux·服务器·c++·ubuntu·centos·1024程序员节
彩妙不是菜喵7 小时前
基于C语言上,面向对象语言:C++基础(学完C语言后再看)
c语言·开发语言·c++
夜晚中的人海8 小时前
【C++】位运算算法习题
开发语言·c++·算法
椰羊sqrt8 小时前
MetaTwo靶机实战:SQL注入到权限提升全解析
python·学习·网络安全