目录
一、栈内存与堆内存
C++程序中的栈(Stack)和堆(Heap)是RAM(物理内存)中实际存在的两个区域,它们都用于存储运行程序所需的数据。
-
栈 (Stack) -- 自动管理
-
函数内的局部变量、函数参数存放在栈上。
-
分配与释放由编译器自动完成:进入作用域分配,离开作用域自动销毁。
-
分配速度极快:仅需移动栈指针(一条 CPU 指令)。
-
空间有限:通常 1~2 MB,存储大对象容易栈溢出。
-
-
堆 (Heap) -- 手动管理
-
使用
new/malloc分配,需用delete/free手动释放。 -
生命周期完全由程序员控制,对象可跨函数存在。
-
分配速度较慢:需要遍历空闲链表、处理内存碎片。
-
空间较大:受限于操作系统/物理内存,可存储大对象。
-
-
选择原则
-
优先使用栈:小对象、生命周期明确、作用域内使用。
-
必要时使用堆:大对象(>1 MB)、生命周期不确定、需要动态大小。
-
使用堆时推荐智能指针(
unique_ptr/shared_ptr)避免手动delete。
-
二、宏
-
本质
-
预处理器指令(
#define),在编译前进行纯文本替换。 -
不属于 C++ 类型系统,没有作用域概念。
-
-
常见用法
-
头文件保护(
#pragma once或#ifndef HEADER_H)。 -
条件编译(
#ifdef DEBUG控制调试代码)。 -
简化字面常量(不推荐,用
constexpr代替)。 -
宏函数(有严重缺陷,现代 C++ 应避免)。
-
-
主要陷阱
-
运算符优先级 :
#define SQUARE(x) x*x调用SQUARE(2+3)得 11 而非 25。 -
多次求值 :
#define MAX(a,b) ((a)>(b)?(a):(b))调用MAX(++x, y)导致++x执行两次。 -
调试困难:宏在预处理阶段展开,断点、单步无法看到宏代码。
-
逗号问题 :
LOG(std::min(5,3))若宏定义为#define LOG(x) std::cout << x,逗号被解释为参数分隔。
-
-
现代 C++ 替代方案
-
常量 →
constexpr变量。 -
函数式宏 →
inline/constexpr函数或模板。 -
条件编译 → 仍可用
#ifdef,部分场景可用constexpr if(C++17)。 -
最佳实践:仅在不得不使用预处理器时使用宏(如头文件保护、平台相关代码)。
-
三、auto
-
本质
-
C++11 引入的编译时类型推导,根据初始化表达式推断变量类型。
-
不是动态类型,推断后类型固定。
-
-
主要用途
cpp
auto it = vec.begin(); // 代替 std::vector<int>::iterator
-
简化冗长类型名:迭代器、Lambda 表达式、模板类型。
-
保证类型一致性:避免手动写错类型。
-
泛型 Lambda (C++14):
auto作为参数类型。 -
返回类型推导 (C++14):函数返回值用
auto。
3.类型退化规则
auto会丢弃const、volatile和引用(值语义)。- 数组退化为指针:
auto a = arr;→int*。 - 保留引用/
const需使用auto&、const auto&或decltype(auto)。
4. 常见陷阱
- 代理类问题 :
std::vector<bool>的operator[]返回代理对象,auto推导为代理而非bool。 - 过度使用降低可读性 :基础类型(
int、double)显式写出更清晰。 - 不适用于需要类型转换的场景。
四、std::array
-
定义与本质
-
std::array是 C++11 标准库提供的固定大小数组容器 ,大小在编译时确定(通过模板参数N)。 -
存储在栈上(或作为其他对象的成员),不涉及堆内存分配,性能与 C 风格数组几乎相同。
-
需要包含头文件
<array>。
-
-
主要特点
-
大小固定 :声明后无法改变元素个数,不支持
push_back/resize。 -
类型安全 :提供
.at()成员函数进行边界检查(越界抛出std::out_of_range)。 -
支持 STL 算法 :具有
.begin()/.end()迭代器,可直接用于<algorithm>。 -
可拷贝/赋值 :支持深拷贝(
array<int,5> a2 = a1;),C 风格数组无法直接赋值。 -
内存布局连续 :元素在内存中连续存储,与 C 风格数组完全兼容(可用
.data()获取指针)。
-
-
与 C 风格内置数组的对比
| 特性 | std::array<T,N> |
C 风格数组 T arr[N] |
|---|---|---|
| 大小获取 | .size() 成员函数 |
sizeof(arr)/sizeof(arr[0])(易错) |
| 传递函数 | 传值或引用,保留类型信息 | 退化为指针,丢失长度 |
| 边界检查 | .at(i) 提供检查 |
无(越界未定义行为) |
| 拷贝/赋值 | 支持(a2 = a1) |
不支持(需 memcpy) |
| 与 STL 算法 | 直接使用 .begin()/.end() |
需传入 arr 和 arr+N |
| 性能 | 与 C 风格数组相同 | 相同(零开销抽象) |
4.使用示例
cpp
#include <array>
#include <algorithm>
#include <iostream>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// 安全的边界访问
try {
int val = arr.at(10); // 抛出 std::out_of_range
} catch (...) {}
// 支持范围 for
for (int& x : arr) x *= 2;
// 使用 STL 算法
std::sort(arr.begin(), arr.end(), std::greater<>());
// 获取大小
std::cout << "Size: " << arr.size() << '\n'; // 5
// 与 C API 交互(获取底层指针)
int* ptr = arr.data();
}
与 std::vector 的选择
- 大小固定、编译时已知 →
std::array(栈上,无堆分配开销)。 - 大小动态、运行时改变 →
std::vector(堆上,灵活但略慢)。