跳转至

C/C++ 基础

概述

C 语言是系统编程的基石,C++ 在其基础上引入了面向对象、泛型编程和现代内存管理。理解 C/C++ 对于深入理解计算机系统(操作系统、编译器、嵌入式)至关重要。


1. C 语言核心

1.1 指针

指针是存储内存地址的变量,是 C 的核心概念。

int x = 42;
int *p = &x;    // p 存储 x 的地址

printf("%d\n", *p);    // 42(解引用)
printf("%p\n", p);     // 0x7ffd5e8a3c(地址)
printf("%p\n", &x);    // 同上

指针与数组的关系

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;       // 数组名退化为指针

printf("%d\n", *p);       // 10
printf("%d\n", *(p+2));   // 30
printf("%d\n", p[3]);     // 40 (等价于 *(p+3))

多级指针

int x = 10;
int *p = &x;
int **pp = &p;

printf("%d\n", **pp);  // 10

函数指针

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

int (*op)(int, int);  // 函数指针声明
op = add;
printf("%d\n", op(3, 4));  // 7
op = sub;
printf("%d\n", op(3, 4));  // -1

1.2 指针运算

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;

p++;      // 移动 sizeof(int) = 4 字节
// p 现在指向 arr[1]

ptrdiff_t diff = &arr[4] - &arr[0];  // 4(元素个数差)

1.3 动态内存管理:malloc/free

#include <stdlib.h>

// 分配
int *arr = (int *)malloc(100 * sizeof(int));
if (arr == NULL) {
    // 分配失败处理
    return -1;
}

// 使用
for (int i = 0; i < 100; i++) {
    arr[i] = i * i;
}

// 释放
free(arr);
arr = NULL;  // 避免悬垂指针

常见陷阱

问题 原因 后果
内存泄漏 malloc 后未 free 内存持续增长
悬垂指针 free 后继续使用指针 未定义行为
双重释放 同一地址 free 两次 堆损坏/崩溃
缓冲区溢出 越界写入 安全漏洞

1.4 栈 vs 堆

高地址 ┌──────────────────┐
       │     Stack        │ ← 局部变量,自动管理
       │   (向下增长)     │    函数调用栈帧
       ├──────────────────┤
       │        ↓         │
       │    (未使用空间)    │
       │        ↑         │
       ├──────────────────┤
       │      Heap        │ ← malloc/free,手动管理
       │   (向上增长)     │    动态分配
       ├──────────────────┤
       │    BSS段         │ ← 未初始化全局变量
       ├──────────────────┤
       │    Data段        │ ← 已初始化全局变量
       ├──────────────────┤
低地址 │    Text段        │ ← 代码(只读)
       └──────────────────┘
特性 栈 (Stack) 堆 (Heap)
管理方式 自动(编译器) 手动(malloc/free)
分配速度 极快(移动栈指针) 较慢(搜索空闲块)
大小限制 较小(通常 1-8MB) 较大(受限于内存)
生命周期 函数返回即释放 直到显式 free
碎片 可能产生碎片

2. C++ 现代特性

2.1 RAII(Resource Acquisition Is Initialization)

RAII 原则:资源获取即初始化,利用对象生命周期自动管理资源。

class FileHandle {
    FILE* file_;
public:
    FileHandle(const char* path, const char* mode) 
        : file_(fopen(path, mode)) {
        if (!file_) throw std::runtime_error("Cannot open file");
    }

    ~FileHandle() {
        if (file_) fclose(file_);  // 析构时自动关闭
    }

    // 禁止拷贝
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;

    FILE* get() const { return file_; }
};

void process() {
    FileHandle fh("data.txt", "r");
    // 使用 fh.get()
}  // 离开作用域,自动调用析构函数关闭文件

2.2 智能指针

C++11 引入智能指针,自动管理堆内存。

std::unique_ptr:独占所有权

#include <memory>

auto ptr = std::make_unique<int>(42);
// *ptr == 42

// 所有权转移
auto ptr2 = std::move(ptr);
// ptr 现在为 nullptr

// 自定义删除器
auto file_ptr = std::unique_ptr<FILE, decltype(&fclose)>(
    fopen("data.txt", "r"), &fclose
);

std::shared_ptr:共享所有权(引用计数)

auto sp1 = std::make_shared<std::vector<int>>(100);
auto sp2 = sp1;  // 引用计数 = 2

sp1.reset();     // 引用计数 = 1
sp2.reset();     // 引用计数 = 0,对象被销毁

std::weak_ptr:解决循环引用

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 用 weak_ptr 打破循环
};

2.3 模板(Templates)

// 函数模板
template<typename T>
T max_val(T a, T b) {
    return (a > b) ? a : b;
}

// 类模板
template<typename T, size_t N>
class Array {
    T data_[N];
public:
    T& operator[](size_t i) { return data_[i]; }
    constexpr size_t size() const { return N; }
};

// 使用
Array<int, 10> arr;
arr[0] = 42;

模板特化

// 通用版本
template<typename T>
std::string to_string(T val) {
    return std::to_string(val);
}

// bool 的特化
template<>
std::string to_string<bool>(bool val) {
    return val ? "true" : "false";
}

2.4 STL 容器

容器 底层实现 随机访问 插入/删除 查找
vector 动态数组 \(O(1)\) 尾部 \(O(1)\),中间 \(O(n)\) \(O(n)\)
list 双向链表 \(O(n)\) \(O(1)\) \(O(n)\)
deque 分段数组 \(O(1)\) 首尾 \(O(1)\) \(O(n)\)
map 红黑树 - \(O(\log n)\) \(O(\log n)\)
unordered_map 哈希表 - 平均 \(O(1)\) 平均 \(O(1)\)
set 红黑树 - \(O(\log n)\) \(O(\log n)\)
#include <vector>
#include <map>
#include <unordered_map>

// vector
std::vector<int> v = {1, 2, 3};
v.push_back(4);
v.emplace_back(5);  // 原地构造,避免拷贝

// map(有序)
std::map<std::string, int> scores;
scores["Alice"] = 95;
scores["Bob"] = 87;

// unordered_map(哈希,更快)
std::unordered_map<std::string, int> cache;
cache.insert({"key1", 100});
auto it = cache.find("key1");
if (it != cache.end()) {
    std::cout << it->second << std::endl;
}

2.5 移动语义(Move Semantics)

C++11 引入右值引用和移动语义,避免不必要的深拷贝。

class Buffer {
    int* data_;
    size_t size_;
public:
    // 构造函数
    Buffer(size_t n) : data_(new int[n]), size_(n) {}

    // 拷贝构造(深拷贝,开销大)
    Buffer(const Buffer& other) : data_(new int[other.size_]), size_(other.size_) {
        std::copy(other.data_, other.data_ + size_, data_);
    }

    // 移动构造(窃取资源,开销小)
    Buffer(Buffer&& other) noexcept 
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;  // 原对象置空
        other.size_ = 0;
    }

    ~Buffer() { delete[] data_; }
};

Buffer create_buffer() {
    Buffer buf(1000000);
    return buf;  // 触发移动构造(或 NRVO)
}

std::move 的本质:将左值强制转换为右值引用。

std::string s1 = "Hello, World!";
std::string s2 = std::move(s1);  // s1 的内容被"窃取"
// s1 现在为空字符串

2.6 Lambda 表达式

// 基本语法: [捕获列表](参数) -> 返回类型 { 函数体 }
auto add = [](int a, int b) -> int { return a + b; };

// 捕获方式
int x = 10;
auto by_value = [x]() { return x; };        // 值捕获
auto by_ref = [&x]() { x++; return x; };    // 引用捕获
auto all_val = [=]() { return x; };          // 全部值捕获
auto all_ref = [&]() { x++; };              // 全部引用捕获

// 与 STL 算法配合
std::vector<int> v = {5, 3, 1, 4, 2};
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a > b;  // 降序
});

// 泛型 Lambda (C++14)
auto generic_add = [](auto a, auto b) { return a + b; };

3. 内存布局

3.1 C++ 对象内存布局

class Base {
    int x;          // 4 bytes
    virtual void f(); // vtable pointer: 8 bytes
};
// sizeof(Base) = 16 (8 vtable ptr + 4 int + 4 padding)

class Derived : public Base {
    int y;          // 4 bytes
    void f() override;
};
// sizeof(Derived) = 16 (vtable ptr 共享 + 4 + 4)

虚函数表(vtable)机制:

Derived 对象布局:
┌──────────────────┐
│ vptr → vtable    │ 8 bytes(指向虚函数表)
├──────────────────┤
│ Base::x          │ 4 bytes
├──────────────────┤
│ Derived::y       │ 4 bytes
└──────────────────┘

vtable (Derived):
┌──────────────────┐
│ &Derived::f      │  → Derived 覆写的版本
└──────────────────┘

3.2 内存对齐

struct Bad {
    char a;    // 1 byte + 3 padding
    int b;     // 4 bytes
    char c;    // 1 byte + 3 padding
};  // sizeof = 12

struct Good {
    int b;     // 4 bytes
    char a;    // 1 byte
    char c;    // 1 byte + 2 padding
};  // sizeof = 8

4. 编译模型

C/C++ 的编译过程分为四个阶段:

源代码 (.c/.cpp)
    │
    ▼  预处理 (Preprocessing)
展开后的源代码
    │  - #include 文件包含
    │  - #define 宏替换
    │  - #ifdef 条件编译
    ▼  编译 (Compilation)
汇编代码 (.s)
    │  - 语法分析
    │  - 语义分析
    │  - 优化
    ▼  汇编 (Assembly)
目标文件 (.o/.obj)
    │  - 机器码
    │  - 符号表
    ▼  链接 (Linking)
可执行文件 (a.out/.exe)
    │  - 符号解析
    │  - 地址重定位
    │  - 静态链接 vs 动态链接 (.so/.dll)
# GCC 各阶段命令
gcc -E main.c -o main.i   # 预处理
gcc -S main.i -o main.s   # 编译
gcc -c main.s -o main.o   # 汇编
gcc main.o -o main         # 链接

# 一步完成
gcc -O2 main.c -o main

4.1 静态链接 vs 动态链接

特性 静态链接 (.a/.lib) 动态链接 (.so/.dll)
链接时机 编译时 运行时
文件大小 较大(包含库代码) 较小
部署 独立,无依赖 需要共享库
更新 重新编译 替换共享库
内存 每进程一份 多进程共享

4.2 头文件与编译单元

// math_utils.h(头文件:声明)
#pragma once  // 防止重复包含
int add(int a, int b);
int multiply(int a, int b);

// math_utils.cpp(编译单元:定义)
#include "math_utils.h"
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }

// main.cpp
#include "math_utils.h"
int main() {
    return add(3, 4);
}
# 分别编译,再链接
g++ -c math_utils.cpp -o math_utils.o
g++ -c main.cpp -o main.o
g++ math_utils.o main.o -o main

5. 现代 C++ 实践建议

原则 旧方式 现代方式
内存管理 new/delete make_unique/make_shared
数组 C 数组 std::vector / std::array
字符串 char* std::string / std::string_view
迭代 下标遍历 范围 for / 迭代器
回调 函数指针 std::function / Lambda
常量 #define constexpr / const
线程 pthread std::thread / std::async

参考资料

  • "The C Programming Language" - Kernighan & Ritchie
  • "Effective Modern C++" - Scott Meyers
  • "C++ Primer" - Stanley B. Lippman
  • CppReference: https://en.cppreference.com

评论 #