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(堆上,灵活但略慢)。
相关推荐
WBluuue2 小时前
数据结构与算法:二项式定理和二项式反演
c++·算法
yashuk2 小时前
C语言 vs. C++ ,哪个更适合初学者?
c语言·c++·面向对象编程·初学者·学习路径
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,在生产环境中设置MongoDB(21)
数据库·学习·mongodb
L.fountain2 小时前
图像自回归生成(Auto-regressive image generation)实战学习(六)
学习·数据挖掘·回归
-许平安-2 小时前
MCP项目笔记十(客户端 MCPClient)
c++·笔记·ai·raii·mcp·pluginapi·plugin system
一只旭宝3 小时前
【C++ 入门精讲2】函数重载、默认参数、函数指针、volatile | 手写笔记(附完整代码)
c++·笔记
weixin_443478513 小时前
Flutter组件学习之图表
学习·flutter·信息可视化
旖-旎3 小时前
哈希表(存在重复元素||)(4)
数据结构·c++·算法·leetcode·哈希算法·散列表
John.Lewis3 小时前
C++进阶(8)智能指针
开发语言·c++·笔记