C++20 高级编程 004:从初始化、内存到const系列关键字

C++20 高级编程 003:从初始化、内存到const系列关键字

  • 前言
  • [Bilibili 同步视频](#Bilibili 同步视频)
  • [一、✨ 指派初始化器:结构体初始化的优雅解法](#一、✨ 指派初始化器:结构体初始化的优雅解法)
  • [二、📦 内存两大阵营:栈帧 VS 自由存储区(堆)](#二、📦 内存两大阵营:栈帧 VS 自由存储区(堆))
    • [1. 栈(栈帧)运行规则](#1. 栈(栈帧)运行规则)
    • [2. 自由存储区 + 裸指针全解](#2. 自由存储区 + 裸指针全解)
      • 开发规范警告(原文摘抄)
      • [① 指针基础与空指针安全](#① 指针基础与空指针安全)
      • [② 对象指针箭头运算符 ->](#② 对象指针箭头运算符 ->)
      • [③ 动态分配堆数组](#③ 动态分配堆数组)
      • [④ nullptr 取代老旧 NULL](#④ nullptr 取代老旧 NULL)
  • [三、🔒 const 全家桶:常量修饰的多层玩法](#三、🔒 const 全家桶:常量修饰的多层玩法)
    • 1. const 修饰普通变量,替代 #define
    • [2. const 与指针的四种组合(原文核心难点)](#2. const 与指针的四种组合(原文核心难点))
    • [3. const 修饰函数参数](#3. const 修饰函数参数)
    • [4. const 类成员函数(const 正确性)](#4. const 类成员函数(const 正确性))
  • [四、⚡ constexpr & consteval:编译期求值双兄弟](#四、⚡ constexpr & consteval:编译期求值双兄弟)
    • [1. constexpr(C++11)](#1. constexpr(C++11))
    • [2. consteval(C++20 立即函数)](#2. consteval(C++20 立即函数))
  • [五、📌 收尾开发避坑清单(原文警告汇总)](#五、📌 收尾开发避坑清单(原文警告汇总))
  • 结语

前言

不少深耕 C++ 的开发者常会陷入一种尴尬:能写业务逻辑,却在底层语法细节栽跟头 ------ 指派初始化分不清、栈堆内存乱释放、const指针看得头皮发麻、constexprconsteval傻傻分不清。

本篇结合《C++20 高级编程(第 5 版)上册》第一章完整知识点,把书中原文重点提炼,搭配可直接运行的代码示例,兼顾趣味性与实用性,帮你一次性吃透底层核心逻辑,告别模糊认知~

Bilibili 同步视频

C++20 高级编程 003:从初始化、内存到const系列关键字

一、✨ 指派初始化器:结构体初始化的优雅解法

原文表述:"使用指派初始化器的好处是,与使用统一初始化语法相比,它更容易理解指派初始化器正在初始化的内容。使用指派初始化器,如果对某些成员的默认值感到满意,则可以跳过它们的初始化。"

三大核心优势(完全贴合原文)

  1. 语义清晰可读:显式标注每个待初始化成员,阅读代码不用对照结构体定义核对参数顺序;

  2. 可跳过成员初始化:跳过的成员若无类内初始化器,自动初始化为 0;若存在类内默认值,则沿用预设值;

  3. 扩展兼容性强:原文提到 "当新成员被添加到数据结构时,使用指派初始化器的现有代码将继续起作用。新的数据成员将使用其默认值进行初始化。"

反观传统统一初始化语法,原文明确限制:"如果使用统一初始化语法,这是不可以的,必须像下面这样指定 employeeNumber 为 0",不能省略中间成员,拓展性极差。

正反代码对比演示(复刻书中 Employee 结构体)

cpp 复制代码
#include <iostream>
using namespace std;

struct Employee {
    char firstInitial;
    char lastInitial;
    int employeeNumber = 0;    // 无显式赋值则默认0
    double salary = 75000;     // 类内初始化默认薪资75000
};

int main() {
    // 书中标准指派初始化写法:打乱顺序、跳过employeeNumber
    Employee anEmployee{
        .lastInitial = 'D',
        .firstInitial = 'J',
        .salary = 80000
    };

    // 统一初始化语法:必须按成员顺序完整传参,不能跳过中间employeeNumber
    Employee emp2{'J','D',0,80000};

    cout << "指派初始化员工工号:" << anEmployee.employeeNumber << ",薪资:" << anEmployee.salary << endl;
    cout << "统一初始化员工工号:" << emp2.employeeNumber << ",薪资:" << emp2.salary << endl;
    return 0;
}

运行输出:

Plain 复制代码
指派初始化员工工号:0,薪资:80000
统一初始化员工工号:0,薪资:80000

对应原文场景:后续给Employee新增成员,指派初始化代码无需改动;但统一初始化代码必须补充参数,否则编译报错。

二、📦 内存两大阵营:栈帧 VS 自由存储区(堆)

原文划分两大内存区域:栈(栈帧)、自由存储区,二者完全隔离,绝大多数内存泄漏、程序崩溃问题都来源于对两块内存规则混淆。

1. 栈(栈帧)运行规则

原文定义:"栈帧很好,因为它为每个函数提供了独立的内存空间。如果在 foo () 栈帧中声明了一个变量,那么除非专门要求,否则调用 bar () 函数不会更改该变量。此外,foo () 函数执行完毕时,栈帧就会消失,该函数中声明的所有变量都不会再占用内存。在栈上分配内存的变量不需要由程序员释放内存 (删除),这个过程是自动完成的。"

配套书中示例代码:

cpp 复制代码
#include <iostream>
using namespace std;

void foo() {
    // 存储在foo函数独立栈帧中
    int i = 7;
    int j = 11;
    cout << "foo栈内变量 i=" << i << " j=" << j << endl;
}

int main() {
    foo();
    // 函数执行结束,i、j内存自动销毁,main无法访问
    return 0;
}

2. 自由存储区 + 裸指针全解

原文描述自由存储区:"自由存储区是与当前函数或栈帧完全独立的内存区域。如果想在函数调用结束之后仍然保存其中声明的变量,可以将变量放到自由存储区中。必须确保释放 (删除) 在自由存储区上分配的任何内存,这个过程不会自动完成,除非使用了智能指针,智能指针将在第 7 章详细讨论。"

开发规范警告(原文摘抄)

这里介绍指针是因为你将会遇到它们,尤其是在遗留代码中,但是,在新代码中,仅在不涉及所有权的情况下,才允许使用此类原始 / 裸指针。否则,应该使用第 7 章介绍的智能指针之一。

① 指针基础与空指针安全

原文强调:"未初始化的指针,因为它们会指向内存中的某个随机位置。使用这种指针很可能使程序崩溃。这就是总是应同时声明和初始化指针的原因。如果不希望立即分配内存,可以把它们初始化为空指针 nullptr"

同时警示:"在解引用之前指针必须有效,对 null 或未初始化的指针解引用会导致未定义的行为。程序可能崩溃,也可能继续运行,却给出奇怪的结果。"

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    // 规范写法:初始化空指针
    int* myIntegerPointer = nullptr;
    if (!myIntegerPointer) {
        cout << "当前指针为空,禁止解引用!" << endl;
    }

    // new分配堆内存
    myIntegerPointer = new int;
    *myIntegerPointer = 8; // *解引用修改堆内数值
    cout << "堆内整数:" << *myIntegerPointer << endl;

    // 使用完成释放内存,后置空避免野指针
    delete myIntegerPointer;
    myIntegerPointer = nullptr;
    return 0;
}

② 对象指针箭头运算符 ->

原文给出两种对象成员访问方式:

cpp 复制代码
// 写法1:解引用后使用点运算符
cout << (*anEmployee).salary << endl;
// 写法2:箭头运算符(推荐,可读性更强)
cout << anEmployee->salary << endl;

同时推荐短路逻辑规避空指针崩溃,原文示例:

cpp 复制代码
bool isValidSalary = ((anEmployee != nullptr) && anEmployee->salary>0);

逻辑规则:仅当anEmployee不为空时,才会执行解引用获取薪资,空指针直接短路,不会触发危险操作。

③ 动态分配堆数组

原文说明:"指针变量仍在栈中,但动态创建的数组在自由存储区中。"

释放强制规则:"delete 后的方括号表明所删除的是一个数组!每次调用 new \[\] 时,都必须相应地调用 delete \[\],以避免内存泄漏。"

示例代码完全复刻书中逻辑:

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int arraySize = 8;
    int* myVariableSizedArray = new int[arraySize];

    myVariableSizedArray[3] = 2;
    cout << "数组下标3的值:" << myVariableSizedArray[3] << endl;

    // 数组必须搭配delete[]释放
    delete[] myVariableSizedArray;
    myVariableSizedArray = nullptr;
    return 0;
}

④ nullptr 取代老旧 NULL

原文点明NULL的设计缺陷:"NULL 只是被简单地定义为常量 0,这会导致一些问题" ,重载场景下会错误匹配 int 形参;

nullptr是 C++11 标准空指针常量,原文案例:

cpp 复制代码
#include <iostream>
using namespace std;

void func(int i) {
    cout << "匹配int类型参数版本" << endl;
}

int main() {
    func(NULL);    // NULL等价整数0,错误调用func(int)
    // func(nullptr); // 编译报错,无指针重载,精准暴露问题
    return 0;
}

三、🔒 const 全家桶:常量修饰的多层玩法

原文开篇定义:"基本上,关键字 const 是 constant 的缩写,它表示某些内容保持不变。编译器通过将任何试图将其更改的行为标记为错误,用来保证此要求。此外,启用优化后,编译器可以利用此知识生成更好的代码。"

1. const 修饰普通变量,替代 #define

原文推荐:"在 C++ 中,鼓励程序员使用 const 取代 #define 定义常量。使用 const 定义常量就像定义变量一样,只是编译器保证代码不会改变这个值。"

书中示例常量定义:

cpp 复制代码
const int versionNumberMajor = 2;
const int versionNumberMinor = 1;
const std::string productName = "Super Hyper Net Modulator";
const double PI = 3.14159265358979323842;

2. const 与指针的四种组合(原文核心难点)

原文核心判断规则:"const 关键字作用于其直接左侧的内容" ,同时补充记忆技巧:"从右向左读复杂声明"

  1. const int* ip; / int const* ip;:指向 const int 的指针,不能修改指向的值,指针本身可重新赋值

    原文验证:ip[4]=5; // DOES NOT COMPILE!

  2. int* const ip = nullptr;:指向 int 的 const 指针,指针地址不可修改,声明必须初始化

    原文验证:ip=new int[10]; // DOES NOT COMPILE!

  3. const int* const ip = nullptr;:双重 const,指针、指向的值均不可修改

完整测试代码:

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int num = 10;
    int num2 = 20;

    // 场景1:值不可改,指针可换指向
    const int* p1 = &num;
    // *p1 = 99; // 编译失败
    p1 = &num2;

    // 场景2:指针固定,值可修改
    int* const p2 = &num;
    *p2 = 99;
    // p2 = &num2; // 编译失败

    // 场景3:全部锁定
    const int* const p3 = &num;
    // *p3 = 99; // 编译失败
    // p3 = &num2; // 编译失败
    return 0;
}

3. const 修饰函数参数

原文说明:"可将非 const 变量转换为 const 变量。为什么想这么做呢?这提供了一定程度的保护,防止其他代码修改变量。"

分为两类参数:

  1. const 值参数:函数内无法修改参数副本
cpp 复制代码
void func(const int param) {
    // param = 100; 编译报错
}
  1. const 指针参数:保护外部原始数据,禁止函数修改外部变量
cpp 复制代码
#include <string>
#include <iostream>
using namespace std;

void mysteryFunction(const string* someString) {
    // *someString = "Test"; // Will not compile
}

int main() {
    string myString = "The string";
    mysteryFunction(&myString);
    return 0;
}

4. const 类成员函数(const 正确性)

原文定义区分两种成员函数:

  • const 成员函数:检查器 (inspector) ,只读,不能修改类数据成员,声明末尾加const

  • 普通非 const 成员函数:赋值函数 (mutator),可修改成员变量。

书中 AirlineTicket 完整示例:

cpp 复制代码
#include <string>
#include <iostream>
using namespace std;

class AirlineTicket
{
public:
    // const只读方法
    double calculatePriceInDollars() const;
    string getPassengerName() const;
    void setPassengerName(string name);
    int getNumberOfMiles() const;
    void setNumberOfMiles(int miles);
    bool hasEliteSuperRewardsStatus() const;
    void setHasEliteSuperRewardsStatus(bool status);
private:
    string m_passengerName = "Unknown Passenger";
    int m_numberOfMiles = 0;
    bool m_hasEliteSuperRewardsStatus = false;
};

string AirlineTicket::getPassengerName() const
{
    // m_passengerName = "测试"; // 编译报错,const函数禁止修改成员
    return m_passengerName;
}

int main() {
    AirlineTicket ticket;
    cout << ticket.getPassengerName() << endl;
    ticket.setPassengerName("开发者");
    cout << ticket.getPassengerName() << endl;
    return 0;
}

四、⚡ constexpr & consteval:编译期求值双兄弟

1. constexpr(C++11)

原文说明:"C++ 一直有常量表达式的概念,即在编译器求值的表达式。在某些情况下,必须使用常量表达式。例如,定义数组时,数组的大小需要为常量表达式。"

普通函数无法作为数组长度,constexpr函数可以在编译期求值,书中示例:

cpp 复制代码
// 普通函数,不能作为数组长度
// const int getArraySize(){return 32;}
// int myArray[getArraySize()]; // Invalid in C++

// constexpr函数,编译期可计算
constexpr int getArraySize(){ return 32;}
int myArray[getArraySize()];// OK
int myArray2[getArraySize()+1];// OK

同时支持constexpr构造函数自定义常量对象:

cpp 复制代码
#include <iostream>
using namespace std;

class Rect
{
public:
    constexpr Rect(size_t width,size_t height) :m_width(width),m_height(height){}
    constexpr size_t getArea() const { return m_width*m_height;}
private:
    size_t m_width=0,m_height=0;
};

int main() {
    constexpr Rect r{8,2};
    int myArray[r.getArea()];// 合法,编译期计算数组长度
    return 0;
}

原文补充特性:constexpr不强制编译期执行,传入运行时动态变量会延迟到运行时计算。

2. consteval(C++20 立即函数)

原文核心区分二者:"constexpr 关键字指定函数可以在编译期执行,但不能保证一定在编译期执行;如果确实希望保证始终在编译时对函数进行求值,则需要使用 C++20 的 consteval 关键字将函数转换为所谓的立即函数 (immediate function)。"

书中单位转换示例:

cpp 复制代码
#include <iostream>
using namespace std;

// 立即函数,强制编译期求值
consteval double inchToMm(double inch){ return inch*25.4;}

int main() {
    constexpr double const_inch = 6.0;
    double mm1 = inchToMm(const_inch);// 正常编译,编译期计算

    double dynamic_inch = 8.0;
    // double mm2 = inchToMm(dynamic_inch); // 编译报错,动态变量无法编译期求值
    return 0;
}

五、📌 收尾开发避坑清单(原文警告汇总)

  1. 内存管理规范:原文明确 "避免使用 C 中的 malloc 和 free,而使用 new 和 delete,或者使用 new \[\] 和 delete \[\]"newdeletenew[]delete[]必须成对,否则内存泄漏;新项目优先智能指针,裸指针仅用于无所有权场景;

  2. 指针安全:禁止使用未初始化、空指针直接解引用,指针释放后赋值nullptr;使用短路逻辑判空,规避程序崩溃;

  3. const 规范:遵循 const 正确性,所有只读参数、只读成员函数统一添加const修饰;

  4. 初始化方案:结构体优先指派初始化,兼顾可读性与代码拓展性;

  5. 空指针常量:抛弃老式NULL,统一使用 C++11 标准nullptr消除重载歧义;

  6. 编译期优化:固定常量计算选用constexpr,需要强制编译执行、零运行时开销场景使用 C++20 consteval

结语

全文知识点全部取自《C++20 高级编程(第 5 版)上册》第一章原文,没有脱离标准教材的基础语法拓展。C++ 基础语法看似简单,但每一条规则、每一处限制都有明确的底层设计原因,线上绝大多数内存错误、编译歧义、性能损耗,根源都是对这些基础细节一知半解。

吃透指派初始化、栈堆内存管理、指针体系、const 修饰体系、编译期关键字constexpr/consteval这几大块内容,既能从容应对面试中高频的语法提问,也能写出规范、安全、高性能的现代化 C++20 工程代码。

相关推荐
雪芽蓝域zzs42 分钟前
Vue3 + JS + pnpm 创建项目
开发语言·javascript·ecmascript
一晌小贪欢43 分钟前
Python办公16:PDF 强力缝合——将几十个 PDF 合并为单个文档并添加页码
开发语言·python·excel·数据可视化·python办公
名剑走天下1 小时前
Kotlin_LiveData
开发语言·kotlin
kyle~1 小时前
点云配准--- 迭代最近点 ICP 求解
线性代数·算法·机器学习
迷迭香yy1 小时前
回测过拟合检测体系从样本内外到组合稳健性评估 IG50免费开源股票数据API接口
服务器·开发语言·数据库·人工智能·python
zmzb01031 小时前
C++课后习题训练记录Day199
开发语言·c++
Zane1994121 小时前
`ClassName()` 只是一步?拆开看 `__new__` 和 `__init__` 各自在干什么
开发语言·python
qq7422349841 小时前
Gradio 极简入门:三分钟为AI模型打造交互界面,并对比Streamlit与Dash如何选型
人工智能·算法·大模型·交互·dash
人邮异步社区1 小时前
如何系统地学习 C++ 语言?
开发语言·c++·学习