C/C++核心知识点详解教程

本教程从零开始,系统讲解C/C++语言中的关键概念,适合初学者循序渐进地学习。


目录

  1. 宏定义与预处理
  2. volatile关键字详解
  3. static关键字详解
  4. const关键字详解
  5. 指针与const的组合
  6. new/delete与malloc/free对比
  7. sizeof运算符
  8. 结构体与联合体
  9. 内存布局:栈与堆
  10. extern "C"的作用

1. 宏定义与预处理

1.1 什么是宏定义?

宏定义是C/C++预处理器的功能,在编译前进行文本替换。

基本语法:

#define 宏名 替换文本

1.2 简单宏示例

#define PI 3.14159
#define MAX_SIZE 100

int main() {
    double radius = 5.0;
    double area = PI * radius * radius;  // 编译时替换为 3.14159
    return 0;
}

1.3 带参数的宏

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

int result = SQUARE(5);      // 展开为 ((5) * (5))
int max_val = MAX(10, 20);   // 展开为 ((10) > (20) ? (10) : (20))

注意事项:

  • 参数要加括号,避免运算优先级问题
  • 整个表达式也要加括号

1.4 #和##操作符

# (字符串化操作符)

#define TO_STRING(x) #x

TO_STRING(hello)  // 展开为 "hello"

## (连接操作符)

#define CONCAT(a, b) a##b

int xy = 10;
int value = CONCAT(x, y);  // 展开为 xy,即访问变量xy

1.5 宏的陷阱

#define SQUARE(x) x * x

int a = SQUARE(2 + 3);  // 展开为 2 + 3 * 2 + 3 = 11 (错误!)
// 正确写法: #define SQUARE(x) ((x) * (x))

2. volatile关键字详解

2.1 什么是volatile?

volatile告诉编译器:“这个变量可能会意外改变,不要对它进行优化”。

简单理解: 每次使用这个变量时,都要从内存中重新读取,而不是使用缓存值。

2.2 为什么需要volatile?

编译器为了提高效率,可能会把变量缓存到寄存器中:

int flag = 0;

// 编译器可能优化为无限循环
while (flag == 0) {
    // 编译器认为flag不会改变,只读取一次
}

如果其他线程或硬件改变了flag,主循环可能永远检测不到!

2.3 使用场景

场景1: 硬件寄存器
volatile unsigned int *reg = (unsigned int *)0x40000000;

*reg = 0x01;  // 写入硬件寄存器
*reg = 0x02;  // 再次写入
*reg = 0x04;  
*reg = 0x08;  

// 不加volatile,编译器可能只保留最后一次写入
// 加了volatile,四次写入都会执行
场景2: 中断服务程序
volatile int interrupt_flag = 0;

void interrupt_handler() {
    interrupt_flag = 1;  // 中断中修改
}

int main() {
    while (interrupt_flag == 0) {
        // 等待中断
    }
    // 处理中断后的逻辑
}
场景3: 多线程共享变量
volatile bool is_running = true;

// 线程1
void thread1() {
    while (is_running) {
        // 执行任务
    }
}

// 线程2
void thread2() {
    is_running = false;  // 停止线程1
}

2.4 关键要点

  • volatile 不保证原子性,只保证可见性
  • 多线程编程应配合互斥锁等同步机制
  • volatile主要用于与硬件交互和信号处理

3. static关键字详解

static在不同位置有不同含义,我们逐一讲解。

3.1 函数内的static变量

特点: 只初始化一次,保持值不变

void counter() {
    static int count = 0;  // 只在第一次调用时初始化
    count++;
    printf("调用次数: %d\n", count);
}

int main() {
    counter();  // 输出: 1
    counter();  // 输出: 2
    counter();  // 输出: 3
    return 0;
}

普通变量对比:

void counter_normal() {
    int count = 0;  // 每次调用都重新初始化
    count++;
    printf("调用次数: %d\n", count);  // 始终输出1
}

3.2 文件作用域的static变量

作用: 限制变量只能在当前文件中使用

// file1.c
static int internal_var = 100;  // 只能在file1.c中访问

void func() {
    internal_var++;  // 可以访问
}
// file2.c
extern int internal_var;  // 错误!无法访问file1.c中的static变量

3.3 static函数

作用: 限制函数只能在当前文件中调用

// utils.c
static void helper_function() {
    // 内部辅助函数
}

void public_function() {
    helper_function();  // 可以调用
}
// main.c
extern void helper_function();  // 错误!无法访问static函数

3.4 为什么static变量只初始化一次?

static变量存储在静态存储区(非栈区),程序运行期间一直存在:

变量类型存储位置生命周期
普通局部变量函数调用期间
static局部变量静态区整个程序运行期间
全局变量静态区整个程序运行期间

4. const关键字详解

4.1 基本用法

const表示"只读",修饰的内容不能被修改。

const int MAX = 100;
MAX = 200;  // 错误!const变量不能修改

4.2 const vs #define

特性const#define
类型检查
作用域遵循作用域规则全局替换
调试可调试难以调试
内存分配内存不分配(编译时替换)

示例:

#define PI 3.14
const double PI_CONST = 3.14;

double area1 = PI * 5 * 5;        // 每次使用都替换
double area2 = PI_CONST * 5 * 5;  // 只有一个变量

4.3 const修饰函数参数

防止函数内部意外修改参数:

void print_string(const char *str) {
    // str[0] = 'A';  // 错误!不能修改
    printf("%s\n", str);  // 可以读取
}

4.4 const修饰函数返回值

const char* get_name() {
    return "Alice";
}

int main() {
    const char *name = get_name();
    // name[0] = 'B';  // 错误!返回的字符串不能修改
    return 0;
}

4.5 const数组

const int arr[5] = {1, 2, 3, 4, 5};
// arr[0] = 10;  // 错误!

5. 指针与const的组合

这是最容易混淆的部分,我们用口诀记忆。

5.1 四种组合方式

int value = 10;

// 1. 指向常量的指针 (pointer to const)
const int *p1 = &value;
// *p1 = 20;  // 错误!不能通过p1修改value
p1 = &other;  // 正确!可以改变指针指向

// 2. 常量指针 (const pointer)
int * const p2 = &value;
*p2 = 20;     // 正确!可以修改value
// p2 = &other;  // 错误!不能改变指针指向

// 3. 指向常量的常量指针
const int * const p3 = &value;
// *p3 = 20;     // 错误!不能修改value
// p3 = &other;  // 错误!不能改变指针指向

// 4. 普通指针
int *p4 = &value;
*p4 = 20;     // 正确!
p4 = &other;  // 正确!

5.2 记忆技巧

“const在星号左边,指向的内容不能变;const在星号右边,指针本身不能变”

const int *p;     // const在*左边 → 内容不能变
int * const p;    // const在*右边 → 指针不能变
const int * const p;  // 都不能变

5.3 实际应用场景

// 函数不会修改字符串内容
void print_message(const char *msg) {
    printf("%s\n", msg);
}

// 函数不会改变指针指向(但可能修改内容)
void modify_value(int * const ptr) {
    *ptr = 100;  // 可以修改
}

6. new/delete与malloc/free对比

6.1 基本区别

特性new/deletemalloc/free
语言C++运算符C语言函数
返回类型具体类型指针void*
大小计算自动手动指定
构造/析构自动调用不调用
失败处理抛出异常返回NULL

6.2 使用示例

malloc/free (C语言):

int *p = (int *)malloc(sizeof(int) * 10);  // 分配10个int
if (p == NULL) {
    // 分配失败处理
}
free(p);  // 释放内存

new/delete (C++):

int *p = new int[10];  // 自动计算大小
delete[] p;            // 释放数组

int *single = new int(5);  // 分配单个int并初始化为5
delete single;

6.3 对象的构造和析构

class MyClass {
public:
    MyClass() { cout << "构造函数" << endl; }
    ~MyClass() { cout << "析构函数" << endl; }
};

// 使用new - 会调用构造函数和析构函数
MyClass *obj1 = new MyClass();
delete obj1;

// 使用malloc - 不会调用构造和析构
MyClass *obj2 = (MyClass *)malloc(sizeof(MyClass));
free(obj2);  // 危险!没有调用析构函数

6.4 关键要点

  • C++中优先使用new/delete
  • new配对delete,new[]配对delete[]
  • 不要混用newfree,或mallocdelete

7. sizeof运算符

7.1 sizeof是什么?

sizeof编译时运算符(不是函数!),返回类型或变量的字节大小。

7.2 基本用法

printf("%zu\n", sizeof(int));        // 4 (32/64位系统)
printf("%zu\n", sizeof(char));       // 1
printf("%zu\n", sizeof(double));     // 8

int arr[10];
printf("%zu\n", sizeof(arr));        // 40 (10 * 4)

7.3 sizeof vs strlen

char str[] = "hello";

sizeof(str);   // 6 (包括'\0')
strlen(str);   // 5 (不包括'\0')

关键区别:

特性sizeofstrlen
计算时机编译时运行时
作用对象任何类型字符串
是否包含’\0’

7.4 指针的sizeof

int arr[10];
int *p = arr;

sizeof(arr);  // 40 (数组大小)
sizeof(p);    // 8 (64位系统指针大小) 或 4 (32位)

易错点:

void func(int arr[]) {
    // arr实际是指针!
    int size = sizeof(arr);  // 错误!得到的是指针大小,不是数组大小
}

7.5 结构体的sizeof

struct Example {
    char a;    // 1字节
    int b;     // 4字节
    char c;    // 1字节
};

sizeof(struct Example);  // 可能是12,不是6!

原因:字节对齐,下一节详细讲解。


8. 结构体与联合体

8.1 结构体 (struct)

特点: 所有成员各自占用独立内存

struct Student {
    char name[20];  // 20字节
    int age;        // 4字节
    float score;    // 4字节
};  // 总大小 = 28字节(可能因对齐而变化)

8.2 联合体 (union)

特点: 所有成员共享同一块内存

union Data {
    int i;      // 4字节
    float f;    // 4字节
    char c;     // 1字节
};  // 总大小 = 4字节(最大成员的大小)

示例:

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

d.f = 3.14;
printf("%f\n", d.f);  // 3.14
printf("%d\n", d.i);  // 未定义!被覆盖了

8.3 内存布局对比

struct S {
    int a;
    char b;
    double c;
};

union U {
    int a;
    char b;
    double c;
};

sizeof(struct S);  // 16 (字节对齐)
sizeof(union U);   // 8  (最大成员double的大小)

8.4 字节对齐规则

编译器为了提高访问效率,会对结构体成员进行对齐:

规则:

  1. 每个成员按其类型大小对齐
  2. 结构体总大小是最大成员的整数倍
struct Example1 {
    char a;     // 偏移0, 占1字节
    // 填充3字节
    int b;      // 偏移4, 占4字节
    char c;     // 偏移8, 占1字节
    // 填充3字节
};  // 总大小12字节

struct Example2 {
    char a;     // 偏移0
    char c;     // 偏移1
    // 填充2字节
    int b;      // 偏移4
};  // 总大小8字节 (优化后)

8.5 联合体的应用场景

场景1: 节省内存

struct Packet {
    int type;
    union {
        int int_data;
        float float_data;
        char str_data[20];
    } data;  // 根据type决定使用哪个成员
};

场景2: 类型转换

union FloatBits {
    float f;
    unsigned int bits;
};

union FloatBits fb;
fb.f = 3.14;
printf("0x%X\n", fb.bits);  // 查看浮点数的二进制表示

9. 内存布局:栈与堆

9.1 内存分区

C程序的内存分为几个区域:

高地址
+------------------+
|      栈 (Stack)   |  ← 局部变量、函数参数
+------------------+  ↓ 向下增长
|        ↓         |
|                  |
|        ↑         |
+------------------+  ↑ 向上增长
|      堆 (Heap)    |  ← 动态分配内存
+------------------+
| 全局/静态区       |  ← 全局变量、static变量
+------------------+
|   常量区          |  ← 字符串常量
+------------------+
|   代码区          |  ← 程序代码
+------------------+
低地址

9.2 栈 (Stack)

特点:

  • 自动管理,函数结束自动释放
  • 容量有限(通常几MB)
  • 速度快
  • 存储局部变量
void func() {
    int a = 10;        // 栈上分配
    char str[100];     // 栈上分配
}  // 函数结束,自动释放

9.3 堆 (Heap)

特点:

  • 手动管理(malloc/free 或 new/delete)
  • 容量大(受系统内存限制)
  • 速度较慢
  • 适合大数据或生命周期不确定的数据
void func() {
    int *p = (int *)malloc(1000 * sizeof(int));  // 堆上分配
    // 使用p...
    free(p);  // 必须手动释放!
}

9.4 常见错误

1. 栈溢出

void func() {
    int huge_array[1000000];  // 太大!可能栈溢出
}

2. 内存泄漏

void func() {
    int *p = (int *)malloc(100);
    // 忘记free(p)!  → 内存泄漏
}

3. 悬空指针

int* func() {
    int a = 10;
    return &a;  // 危险!返回栈上变量的地址
}  // a已释放,返回的指针无效

9.5 栈的多线程应用

每个线程都有独立的栈:

// 线程1
void thread1() {
    int local1 = 1;  // 线程1的栈
}

// 线程2
void thread2() {
    int local2 = 2;  // 线程2的栈
}

10. extern "C"的作用

10.1 问题背景

C++支持函数重载,C语言不支持:

// C++可以这样
void print(int x);
void print(double x);
void print(const char *x);

编译器通过名字修饰(Name Mangling)区分:

  • print(int)_Z5printi
  • print(double)_Z5printd

但C语言编译器不做名字修饰:

  • printprint

10.2 extern "C"的作用

告诉C++编译器:“这段代码按C语言方式编译”

// C++文件中
extern "C" {
    void c_function();  // 按C方式编译
}

// 等价于
extern "C" void c_function();

10.3 实际应用场景

场景1: C++调用C库

// math_lib.h (C语言库)
#ifdef __cplusplus
extern "C" {
#endif

void calculate(int a, int b);

#ifdef __cplusplus
}
#endif

场景2: 提供C接口给其他语言

// my_library.cpp
extern "C" {
    int add(int a, int b) {
        return a + b;  // C接口
    }
}

10.4 为什么需要条件编译?

#ifdef __cplusplus
extern "C" {
#endif

// 函数声明

#ifdef __cplusplus
}
#endif
  • C编译器不认识extern "C",需要条件编译
  • __cplusplus是C++编译器预定义的宏

总结与学习路径

关键知识点回顾

  1. 预处理: 宏定义在编译前进行文本替换
  2. volatile: 防止编译器优化,用于硬件和多线程
  3. static: 控制变量生命周期和作用域
  4. const: 定义只读数据,提高代码安全性
  5. 指针+const: 记住口诀"const在星号哪边,哪边就不能变"
  6. 内存管理: new/delete(C++) vs malloc/free©
  7. sizeof: 编译时计算大小
  8. 结构体vs联合体: 独立内存 vs 共享内存
  9. 栈vs堆: 自动管理 vs 手动管理
  10. extern “C”: 实现C/C++互操作

学习建议

  1. 多敲代码: 每个知识点都要亲手实践
  2. 看编译结果: 使用gcc -E查看预处理结果
  3. 调试验证: 用GDB等工具观察内存布局
  4. 循序渐进: 先掌握基础,再学习高级特性
  5. 阅读经典书籍:
    • 《C Primer Plus》
    • 《C++ Primer》
    • 《深入理解计算机系统》

练习建议

// 练习1: 写一个安全的字符串拷贝函数
void safe_strcpy(char *dest, const char *src, size_t size);

// 练习2: 实现一个简单的内存池
typedef struct MemPool {
    char *memory;
    size_t size;
    size_t used;
} MemPool;

// 练习3: 理解字节对齐
struct AlignTest {
    char a;
    int b;
    short c;
};
// 计算sizeof并画出内存布局
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐