C++学习笔记——栈内存与堆内存、宏、auto、std::array

目录

一、栈内存与堆内存

二、宏

三、auto

四、std::array


一、栈内存与堆内存

C++程序中的栈(Stack)和堆(Heap)是RAM(物理内存)中实际存在的两个区域,它们都用于存储运行程序所需的数据。

  1. 栈 (Stack) -- 自动管理

    • 函数内的局部变量、函数参数存放在栈上。

    • 分配与释放由编译器自动完成:进入作用域分配,离开作用域自动销毁。

    • 分配速度极快:仅需移动栈指针(一条 CPU 指令)。

    • 空间有限:通常 1~2 MB,存储大对象容易栈溢出。

  2. 堆 (Heap) -- 手动管理

    • 使用 new / malloc 分配,需用 delete / free 手动释放。

    • 生命周期完全由程序员控制,对象可跨函数存在。

    • 分配速度较慢:需要遍历空闲链表、处理内存碎片。

    • 空间较大:受限于操作系统/物理内存,可存储大对象。

  3. 选择原则

    • 优先使用栈:小对象、生命周期明确、作用域内使用。

    • 必要时使用堆:大对象(>1 MB)、生命周期不确定、需要动态大小。

    • 使用堆时推荐智能指针(unique_ptr / shared_ptr)避免手动 delete

二、宏

  1. 本质

    • 预处理器指令(#define),在编译前进行纯文本替换

    • 不属于 C++ 类型系统,没有作用域概念。

  2. 常见用法

    • 头文件保护(#pragma once#ifndef HEADER_H)。

    • 条件编译(#ifdef DEBUG 控制调试代码)。

    • 简化字面常量(不推荐,用 constexpr 代替)。

    • 宏函数(有严重缺陷,现代 C++ 应避免)。

  3. 主要陷阱

    • 运算符优先级#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,逗号被解释为参数分隔。

  4. 现代 C++ 替代方案

    • 常量 → constexpr 变量。

    • 函数式宏 → inline / constexpr 函数或模板。

    • 条件编译 → 仍可用 #ifdef,部分场景可用 constexpr if (C++17)。

    • 最佳实践:仅在不得不使用预处理器时使用宏(如头文件保护、平台相关代码)。

三、auto

  1. 本质

    • C++11 引入的编译时类型推导,根据初始化表达式推断变量类型。

    • 不是动态类型,推断后类型固定。

  2. 主要用途

cpp 复制代码
auto it = vec.begin();  // 代替 std::vector<int>::iterator
  • 简化冗长类型名:迭代器、Lambda 表达式、模板类型。

  • 保证类型一致性:避免手动写错类型。

  • 泛型 Lambda (C++14):auto 作为参数类型。

  • 返回类型推导 (C++14):函数返回值用 auto

3.类型退化规则

  • auto 会丢弃 constvolatile 和引用(值语义)。
  • 数组退化为指针:auto a = arr;int*
  • 保留引用/const 需使用 auto&const auto&decltype(auto)

4. 常见陷阱

  • 代理类问题std::vector<bool>operator[] 返回代理对象,auto 推导为代理而非 bool
  • 过度使用降低可读性 :基础类型(intdouble)显式写出更清晰。
  • 不适用于需要类型转换的场景

四、std::array

  1. 定义与本质

    • std::array 是 C++11 标准库提供的固定大小数组容器 ,大小在编译时确定(通过模板参数 N)。

    • 存储在栈上(或作为其他对象的成员),不涉及堆内存分配,性能与 C 风格数组几乎相同。

    • 需要包含头文件 <array>

  2. 主要特点

    • 大小固定 :声明后无法改变元素个数,不支持 push_back/resize

    • 类型安全 :提供 .at() 成员函数进行边界检查(越界抛出 std::out_of_range)。

    • 支持 STL 算法 :具有 .begin()/.end() 迭代器,可直接用于 <algorithm>

    • 可拷贝/赋值 :支持深拷贝(array<int,5> a2 = a1;),C 风格数组无法直接赋值。

    • 内存布局连续 :元素在内存中连续存储,与 C 风格数组完全兼容(可用 .data() 获取指针)。

  3. 与 C 风格内置数组的对比

特性 std::array<T,N> C 风格数组 T arr[N]
大小获取 .size() 成员函数 sizeof(arr)/sizeof(arr[0])(易错)
传递函数 传值或引用,保留类型信息 退化为指针,丢失长度
边界检查 .at(i) 提供检查 无(越界未定义行为)
拷贝/赋值 支持(a2 = a1 不支持(需 memcpy
与 STL 算法 直接使用 .begin()/.end() 需传入 arrarr+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 的选择

  1. 大小固定、编译时已知 → std::array(栈上,无堆分配开销)。
  2. 大小动态、运行时改变 → std::vector(堆上,灵活但略慢)。
相关推荐
努力努力再努力FFF2 小时前
医生对AI辅助诊断感兴趣,作为临床人员该怎么了解和学习?
人工智能·学习
OBiO20132 小时前
Cell | 突破AAV载体容量限制!路中华/姜玉武/刘太安团队开发AAVLINK系统实现大基因递送
笔记
智者知已应修善业3 小时前
【51单片机2个按键控制流水灯运行与暂停】2023-9-6
c++·经验分享·笔记·算法·51单片机
sakiko_3 小时前
UIKit学习笔记5-使用UITableView制作聊天页面
笔记·学习·swift·uikit
Alice-YUE4 小时前
【js高频八股】防抖与节流
开发语言·前端·javascript·笔记·学习·ecmascript
云泽8084 小时前
C++11 核心特性全解:列表初始化、右值引用与移动语义实战
开发语言·c++
北山有鸟5 小时前
修改源码法和插件法
嵌入式硬件·学习
richxu202510015 小时前
嵌入式学习之路->stm32篇->(14)通用定时器(上)
stm32·单片机·嵌入式硬件·学习
AI进化营-智能译站5 小时前
ROS2 C++开发系列12-用多态与虚函数构建可扩展的ROS2机器人行为模块
开发语言·c++·ai·机器人
Morwit5 小时前
QML组件之间的通信方案(暴露子组件)
c++·qt·职场和发展