大纲
范围 for
范围 for 概念
C++ 的 范围 for 循环(Range-based for loop) 是 C++ 11 引入的一种简洁的遍历容器或数组的语法。
特点:
- 简洁,代码量少
- 自动推导类型(结合
auto 使用更方便) - 无法直接获取索引(如果需要索引,还是用普通
for)
适用:
- 数组
vector、list、map 等 STL 容器- 自定义支持
begin() / end() 函数的类
范围 for 使用
1 2 3
| for (元素类型 变量名 : 容器或数组) { }
|
1 2 3 4
| int arr[] = {1, 2, 3, 4, 5}; for (int item : arr) { std::cout << item << " "; }
|
1 2 3 4
| std::vector<int> v = {6, 7, 8, 9, 10}; for (int item : v) { std::cout << item << " "; }
|
- 引用遍历(避免拷贝)- 如果元素是大对象,或需要修改元素,建议用引用
1 2 3 4
| std::vector<int> v = {6, 7, 8, 9, 10}; for (int &item : v) { std::cout << item << " "; }
|
- 引用遍历(避免拷贝)- 如果不想修改元素但又不想拷贝,可以用
const&
1 2 3 4
| std::vector<int> v = {6, 7, 8, 9, 10}; for (const int &item : v) { std::cout << item << " "; }
|
范围 for 适用场景
| 适用场景 | 建议用法 |
|---|
| 简单遍历 | for (auto x : container) |
| 遍历时需要修改元素 | for (auto& x : container) |
| 遍历时保证不修改元素 | for (const auto& x : container) |
范围 for 底层原理
- C++ 编译器会自动把范围 for 循环转换成类似以下形式:
1 2 3 4
| for (auto it = std::begin(obj); it != std::end(obj); ++it) { auto x = *it; }
|
- 所以要求容器或对象:
- 是原生数组
- 或者支持
begin() 和 end() 函数(可以是成员函数,也可以是 std::begin() 和 std::end())
auto 关键字
auto 是 C++ 中的一个关键字,用于让编译器自动推导变量的类型(自动类型推导),从而使代码更简洁、灵活,减少重复声明类型的麻烦。它在 C++11 中首次引入,并在 C++14、C++17 中得到了进一步增强。
auto 核心概念
auto 的基础语法: auto 变量名 = 初始值;auto 可以根据初始化表达式,自动推导变量的类型,无需显式写出类型。auto 必须在声明时初始化,因为编译器需要通过初始值推导具体的类型。auto 的自动类型推导发生在编译期间,因此使用 auto 不会影响程序的运行效率。auto 支持泛型、模板编程等现代 C++ 风格,常用于范围 for 和 Lambda 表达式中。
auto 使用场景
1 2 3
| std::map<std::string, std::vector<int>>::iterator it = myMap.begin();
auto it = myMap.begin();
|
1 2 3 4 5
| std::vector<int> vec = {1, 2, 3};
for (auto val : vec) { std::cout << val << std::endl; }
|
1
| auto add = [](int a, int b) { return a + b; };
|
auto 功能增强
- C++14 的增强:
auto 可以用于函数返回类型
1 2 3
| auto add(int a, int b) { return a + b; }
|
- C++20 的增强:
auto 可以用于范围推导(简化结构绑定)
1 2
| std::pair<int, std::string> p = {1, "hello"}; auto [id, name] = p;
|
auto 使用注意事项
1 2 3 4 5
| int x = 5; int& ref = x; auto a = ref;
auto& b = ref;
|
1 2 3 4
| const int ci = 100;
auto a = ci; auto& b = ci;
|
constexpr 关键字
constexpr 简介
constexpr 是 C++ 11 引入的一个关键字,表示 编译时常量表达式。它用于指示编译器:一个变量、函数或构造函数的值可以在编译期间求值,从而实现更高效的代码(例如:避免运行时计算、提升性能、在模板中使用等)。
constexpr 变量
constexpr 变量表示这个变量的值在编译时就已知,类似 const,但更强:
1 2
| constexpr int size = 10; int arr[size];
|
1 2 3 4
| const int a = rand();
constexpr int b = rand(); constexpr int c = 3 + 4;
|
constexpr 函数
constexpr 函数表示用于定义可以在编译期执行的函数,在 C++ 11 中其定义规则:(1) 函数体必须只有一个 return 表达式; (2) 所有参数和调用都必须是常量表达式;
1 2 3 4 5
| constexpr int square(int x) { return x * x; }
int arr[square(4)];
|
- 从 C++ 14 开始,
constexpr 函数可以有:多条语句、条件判断(if)、循环(for、while)
1 2 3 4 5 6 7 8
| constexpr int factorial(int n) { int res = 1; for (int i = 1; i <= n; ++i) { res *= i; } return res; }
|
constexpr 构造函数
从 C++ 11 开始,可以将一个类的构造函数定义为 constexpr,使得该类可以在编译期构造对象:
1 2 3 4 5 6 7 8 9 10 11 12
| class Point { public: constexpr Point(int x_, int y_) : x(x_), y(y_) {
}
private: int x, y; };
constexpr Point p1(1, 2);
|
constexpr 应用场景
| 场景 | 用途 |
|---|
| 定义静态数组大小 | constexpr int size = 100; int arr[size]; |
| 作为模板参数 | template<int N> struct Array {} |
| 提高性能 | 避免运行时计算,提升速度 |
switch 中使用 | case square(3): |
| 元编程 | 与 template、type_traits 等结合实现复杂编译期计算 |
constexpr 使用注意事项
constexpr 不等于 “只在编译期计算”,它也可以在运行期使用,只是如果传入常量,它可以在编译期计算。- 从 C++ 20 起,
constexpr 支持的功能更强,几乎可用于所有逻辑控制(包括 try/catch 限制性支持)。 constexpr 与 consteval、constinit 是不同的概念(见下表)。
constexpr 与相关关键字对比
| 关键字 | 引入版本 | 含义 |
|---|
const | C++ 98 | 值不可变(可用于编译期或运行期常量) |
constexpr | C++ 11 | 编译期可求值的常量或函数(从 C++ 14 开始支持更复杂的函数体) |
consteval | C++ 20 | 必须在编译期计算的函数(调用时就会被立即求值) |
constinit | C++ 20 | 保证用于初始化 static 或 thread_local 变量时没有静态初始化顺序问题 |
动态内存分配
野指针、悬空指针、裸指针
野指针(Wild Pointer):指向一块未初始化或非法内存区域的指针,一旦访问就有可能触发未定义行为。悬空指针(Dangling Pointer):指针仍然指向一块已被释放或无效的内存区域,一旦访问就有可能触发未定义行为。裸指针(Raw Pointer):普通的 C/C++ 指针,直接指向有效的内存,但不具备自动内存管理能力(非智能指针),也没有生命周期约束,比如平常写的这种指针 int *p = new int(42); 就是裸指针。
C/C++ 五大存储区
C/C++ 程序的存储区有以下几个:
程序代码区(Text Segment)
- 作用:
- 特点:
- 只读(防止程序自修改)
- 多个进程可以共享(操作系统支持代码段共享)
常量存储区(RODATA Segment)
- 存放:
- 字符串常量
const 修饰的全局常量(在一些编译器实现中)
- 特点:
- 只读(通常有内存保护)
- 与
.data 段分开,防止误修改 - 注意:局部
const 变量通常在栈上分配,但全局 const 变量可能分配在常量区
全局 / 静态存储区(Data Segment)
- 已初始化全局 / 静态区(
.data 段)- 存放:已初始化的全局变量、静态变量
- 生命周期:程序运行整个过程,程序结束时由系统自动释放
- 未初始化全局 / 静态区(
.bss 段)- 存放:未初始化的全局变量、静态变量
- 系统会自动初始化为 0 或 NULL
堆(Heap)
- 用途:
- 分配:
- C 语言:
malloc() / free() - C++:
new / delete
- 特点:
- 程序员手动分配和释放内存(忘记释放会造成内存泄漏)
- 灵活,但效率比栈稍低
栈(Stack)
- 存放:
- 局部变量(非
static) - 函数参数、返回地址、临时变量
- 特点:
- 由编译器自动分配和释放内存
- 后进先出(LIFO)
- 每个线程有独立栈空间
- 栈空间有限,过大可能导致 栈溢出
C/C++ 程序各个存储区的布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 地址低 ↓ +-----------------------------+ | 程序代码区(.text) | +-----------------------------+ | 常量区(.rodata) | +-----------------------------+ | 已初始化全局变量(.data) | +-----------------------------+ | 未初始化全局变量(.bss) | +-----------------------------+ | 堆 | | (向上增长,从低地址开始) | +-----------------------------+ | 栈 | | (向下增长,从高地址开始) | +-----------------------------+ ↑ 地址高
|
C/C++ 程序各个存储区的对比
| 区域 | 主要内容 | 生命周期 | 典型分配方式 |
|---|
| 程序代码区 | 可执行代码 | 程序运行期间 | 编译生成 |
| 常量区(RODATA) | 字符串字面量、常量 | 程序运行期间 | 编译生成 |
| 静态区(Data / BSS) | 全局变量、静态变量 | 程序运行期间 | 编译生成 |
| 栈 | 局部变量、函数调用信息 | 函数调用时分配,调用结束时释放 | 自动管理 |
| 堆 | 动态分配内存 | 程序员控制,包括 new/delete、malloc()/free() | 手动管理 |
提示
C 语言和 C++ 程序都有上面五大存储区,但它们的使用方式和细节略有不同。
malloc 与 free 使用
基础语法
malloc() 动态分配内存
- 函数原型:
void* malloc(size_t size); - 参数:
size 是要分配的字节数 - 返回:返回
void* 指针,指向新分配的内存 - 在 C++ 中,返回值需要强制类型转换为目标类型
free() 释放内存
- 函数原型:
void free(void* ptr); - 参数:
ptr 是 malloc() 返回的指针 - 作用:释放指定的内存,防止内存泄漏
使用案例
1 2 3 4 5 6 7
| int *p = nullptr; p = (int *) malloc(sizeof(int)); if (p != nullptr) { *p = 5; std::cout << *p << std::endl; free(p); }
|
1 2 3 4 5 6 7 8 9
| char *c = nullptr; const int size = sizeof(char) * 20; c = (char *) malloc(size); if (c != nullptr) { memset(c, 0, size); strcpy(c, "hello world"); std::cout << c << std::endl; free(c); }
|
new 与 delete 使用
基础语法
new 的基础语法
- (1)
指针变量名 = new 类型标识符,比如 int *p = new int;,分配一个 int 类型的内存,但未初始化,值是未定义的。 - (2)
指针变量名 = new 类型标识符(初始值),比如 int *p = new int(3);,分配一个 int 类型的内存,并将值初始化为 3。 - (3)
指针变量名 = new 类型标识符[内存单元个数],比如 int *arr = new int[5];,分配一个包含 5 个 int 类型的数组,未初始化(值未定义)。
delete 基础语法
- (1)
delete 指针变量名,释放指针内存。 - (2)
delete[] 指针变量名,释放数组内存。
特别注意
new 与 delete 必须结对使用;使用 new[] 申请的内存,必须使用 delete[] 释放内存。delete 不能重复调用,否则会引发未定义行为;正确做法是调用 delete 后将指针设为 nullptr,当 ptr == nullptr 时,delete ptr; 是安全的,不会做任何操作。
使用案例
1 2 3 4 5
| int *p = new int; *p = 5; std::cout << *p << std::endl; delete p; p = nullptr;
|
1 2 3 4
| int *p = new int(3); std::cout << *p << std::endl; delete p; p = nullptr;
|
1 2 3 4 5 6 7 8 9 10
| class A { public: A(int x) { std::cout << "A constructor: " << x << std::endl; } };
A *obj = new A(10); delete obj; obj = nullptr;
|
1 2 3 4 5 6 7 8 9 10 11 12
| int *arr = new int[5];
for (int i = 0; i < 5; ++i) { arr[i] = i * 10; }
for (int i = 0; i < 5; ++i) { std::cout << arr[i] << " "; }
delete[] arr; arr = nullptr;
|
malloc 和 new 的区别
new
- C++ 中的运算符(
operator new),用于按类型动态分配内存。 - 底层同样是调用
malloc() 函数开辟内存,但还会调用类对象的构造函数(如果是类对象)进行初始化。 - 返回值是对应类型的指针,不需要强制类型转换。
- 开辟内存失败时,会抛出
std::bad_alloc 异常。
malloc
- C 语言中的标准库函数,用于按字节动态分配内存。
- 不会初始化分配的内存,也不会调用类对象的构造函数(如果分配的内存是用于类对象)。
- 返回值是
void *,需要强制类型转换。 - 开辟内存失败时,返回
nullptr(或 C 中返回 NULL),需要手动检查。
| 区别点 | malloc | new |
|---|
| 所属语言 | C / C++ | C++ 专用 |
| 本质 | 库函数(在 <cstdlib> 中) | 运算符(可重载) |
| 内存分配方式 | 按字节动态分配内存 | 按类型动态分配内存 |
| 返回值 | 返回 void *,需强制类型转换 | 返回对应类型的指针,无需类型转换 |
| 是否调用构造函数 | ❌ 不会调用构造函数 | ✅ 会调用构造函数 |
| 是否调用析构函数 | ❌ free 不会调用析构函数 | ✅ delete 调用析构函数 |
| 失败时的行为 | 返回 nullptr(或 C 中返回 NULL),需手动检查 | 抛出 std::bad_alloc 异常 |
| 是否可重载 | ❌ 不可重载 | ✅ 可重载 operator new、operator delete |
| 对象初始化 | ❌ 不支持 | ✅ 自动调用构造函数来初始化对象 |
| 释放方式 | 使用 free(ptr) 释放内存 | 使用 delete ptr 或 delete[] ptr 释放内存 |
free 和 delete 的区别
delete 的概述
delete 操作符用于释放通过 new 分配的内存。- 在释放内存之前,
delete 会先调用类对象的析构函数,以确保资源(如文件句柄、网络连接等)正确释放。 - 如果分配的是数组,必须使用
delete[],否则可能会导致未定义行为。
free 的概述
free 是 C 标准库函数,用于释放通过 malloc/calloc/realloc 分配的内存。- 它只会释放内存,不会执行任何其他操作,例如对象的析构。
delete 与 free 不能混用
- 通过
new 分配的内存必须用 delete 释放,不能用 free,否则可能会导致未定义行为。 - 通过
malloc 分配的内存必须用 free 释放,不能用 delete,否则可能会导致未定义行为。
delete 与 free 的内存布局和管理差异
new 和 delete 是 C++ 的操作符,它们了解对象的类型,并能为复杂类型的构造和析构做出正确的处理。malloc 和 free 是 C 的函数,它们只分配和释放内存,不了解对象的类型。
| 特性 | delete | free |
|---|
| 适用语言 | C++ 专用 | C 和 C++ |
| 适用对象 | 动态分配的对象(通过 new 分配) | 动态分配的内存块(通过 malloc/calloc/realloc 分配) |
| 是否调用析构函数 | 会调用类对象的析构函数 | 只释放内存,不调用类对象的析构函数 |
| 分配与释放的匹配要求 | 必须和 new 成对使用 | 必须和 malloc/calloc/realloc 成对使用 |
| 数组释放 | 使用 delete[] 释放动态数组 | 没有专门的数组释放功能 |
| 底层机制 | C++ 的运行时库负责,处理更高级的资源管理 | C 的运行时库负责,直接释放内存 |