语言基础 / C++11
sizeof:测量对象表示,而非运行时内容
sizeof 查询静态类型的对象表示大小,结果以 C++ 字节为单位,包含必要的填充。它通常看起来像函数调用,实际是操作数不求值的运算符;数组、引用与指针的结果必须分别理解。
THE FIELD GUIDE / 80 LESSONS
先看结论,再读原理。运行例子,最后独立回答练习。
共 80 篇 · 阅读进度仅保存在当前浏览器
从第一篇开始 →80 篇文章
语言基础 / C++11
sizeof 查询静态类型的对象表示大小,结果以 C++ 字节为单位,包含必要的填充。它通常看起来像函数调用,实际是操作数不求值的运算符;数组、引用与指针的结果必须分别理解。
语言基础 / C++11
sizeof 测量类型占用,strlen 沿字符序列寻找第一个零字符,两者回答不同问题。字符串字面量包含结尾零字符,嵌入零字符会提前结束 strlen;指针本身不记录容量,也不保证指向合法字符串。
语言基础 / C++11
数组并不是指针,但数组形式的函数形参会被调整成指针类型,普通调用中数组实参也常转换为首元素指针。边界不会自动跟随;要保留长度,使用数组引用、容器或 C++20 的 span。
语言基础 / C++11
const 限制经由某个类型进行的修改,但只读引用不意味着底层对象永不变化。理解顶层与底层 const、成员函数的只读承诺,以及 const 与 constexpr 的区别,才能写出可靠而不过度承诺的接口。
语言基础 / C++11
static 的意义取决于出现位置:局部变量获得静态存储期,命名空间实体可以获得内部链接,类的静态成员不属于某个实例。局部静态初始化的线程安全,不代表后续读写也自动线程安全。
语言基础 / C++11
volatile 告诉实现相关访问具有可观察效果,不能按普通内存访问随意消除。它不提供原子性、线程间顺序或 happens-before,也不承诺绕过 CPU 缓存;线程通信应使用原子操作或锁。
语言基础 / C++11
对齐规定对象可放置的地址条件,sizeof 包含为合法布局所需的填充。alignof 查询类型要求,alignas 提高声明的对齐要求;成员大小相加不能代替布局计算,打包结构也不能代替可移植序列化。
语言基础 / C++11
大小端描述多字节标量在存储中的字节排列,不改变数值本身。可移植协议应显式定义字段宽度和编码顺序,再用无符号运算编解码;不要通过未对齐强转或读取未激活的联合体成员猜测端序。
语言基础 / C++11
C++ 的 class 和 struct 都定义类类型,都支持构造、析构、继承、虚函数与模板。语言差异主要是默认成员访问和默认继承访问;用 struct 表达简单数据、用 class 维护不变量,是设计惯例而不是功能限制。
语言基础 / C++11
函数式宏在预处理阶段替换记号,不提供参数类型、正常作用域或单次求值保证。inline 函数具有完整函数语义,主要关联多翻译单元定义规则,并不强制机器码内联;常量和计算通常优先使用 constexpr 或模板。
语言基础 / C++11
typedef 和 using 都为已有类型提供名字,不创建独立类型,也不是宏替换。using 的语法更适合复杂类型和别名模板;给指针别名添加 const 会限定整个指针类型,而不是自动限定它指向的对象。
语言基础 / C++11
explicit 阻止构造函数或转换函数参与某些隐式转换,但仍允许直接初始化和显式转换。它适合保护单位、容量和资源包装类型;explicit operator bool 还能支持条件判断而避免意外数值转换。
语言基础 / C++11
extern 常用于声明在别处定义的变量,让多个翻译单元引用同一个实体。必须区分声明和定义、作用域与链接;带初始化器的 extern 变量声明通常就是定义,头文件中的重复定义也不会被包含保护消除。
语言基础 / C++11
extern "C" 为适用的函数和变量声明指定 C 语言链接,用于连接兼容的 C 接口。它不是把函数体切换为 C 语言,也不让任意 C++ 类型获得跨语言 ABI;名字、调用约定、布局和资源责任必须分别处理。
语言基础 / C++11
mutable 允许特定非静态数据成员在所属对象为 const 时仍被修改,适合缓存、互斥量等实现细节。它不会自动提供同步,也不应掩盖逻辑状态变化;lambda 的 mutable 则控制按值捕获副本的可修改性。
语言基础 / C++11
static_cast、dynamic_cast、const_cast 与 reinterpret_cast 分别表达不同转换意图,但关键字本身不保证运行安全。选择前先证明数值范围、动态类型、实际可修改性,以及对象生命期、对齐和类型访问规则。
类与对象 / C++11
封装维护对象的不变量,公开继承表达可替换的接口关系,组合表达拥有或使用关系。不要因为能复用几行代码就建立继承层次;先定义合法状态与调用契约,再决定是否需要运行期多态。
类与对象 / C++11
public、protected、private 控制名字的可访问性;继承方式还控制基类接口及向上转换对外是否可用。访问检查不会替你过滤重载候选,protected 也不允许派生类通过任意基类对象访问受保护成员。
类与对象 / C++11
重载在编译期从候选函数中选择签名,覆盖决定虚调用最终执行哪个实现,隐藏则发生在名字查找阶段。三者可以同时出现;使用 override 检查覆盖意图,用 using 恢复需要的基类重载集合。
类与对象 / C++11
最派生类先初始化虚基类,再按声明顺序初始化直接基类和成员,最后执行构造函数体。初始化列表的书写顺序不能改变规则;析构反向清理已完成的子对象,构造失败也依赖这一确定顺序。
类与对象 / C++11
析构负责可靠清理,不适合承担必须报告失败的业务提交。析构函数通常隐式不抛出;异常越过 noexcept 边界会终止程序,即使允许抛出,在栈展开时再次让异常逃出析构也会调用 terminate。
类与对象 / C++11
默认拷贝逐成员处理,是否共享底层资源取决于成员类型,而不是统一的深拷贝或浅拷贝标签。优先让标准资源类型管理所有权;只有确需自定义复制语义时,才同时设计拷贝、移动、赋值和析构。
类与对象 / C++11
模板让调用按编译期类型组合,虚函数让同一基类接口在运行期选择实现。按值复制为基类会切片,丢失派生部分;RTTI 能安全查询活着的多态对象,但不能修复悬空指针或替代合理的接口设计。
类与对象 / C++11
this 是隐式对象成员函数所操作对象的指针表达式,其指向类型随成员函数的 const 限定变化。返回 *this 可建立链式接口,但不会延长对象生命周期;静态成员函数没有 this,捕获 this 也不代表拥有对象。
类与对象 / C++11
标准规定虚调用应选择哪个最终覆盖者,却不规定虚表数量、虚指针位置或对象内存图。常见 ABI 用虚表、调整桩和 RTTI 元数据实现这些语义;理解它们有助于调试,但不可用未经保证的内存读取验证。
类与对象 / C++11
抽象类不能创建完整对象,但可以拥有状态、构造函数和普通实现。纯虚表示具体派生类必须提供非纯的最终覆盖者,不等于函数不能有定义;纯虚析构在实际销毁派生对象时仍然需要可用定义。
类与对象 / C++14
构造函数不能声明为虚函数;创建表达式已经决定要建立的具体类型,不能靠尚未完成的对象反向选择构造类型。运行期选型应使用工厂,按现有动态类型复制则用虚 clone,构造期间的虚调用也有阶段限制。
类与对象 / C++14
如果允许通过基类指针拥有并删除派生对象,基类应提供 public virtual 析构;如果基类只是不可独立销毁的接口视图,可用 protected nonvirtual 析构阻止这种删除。选择依据是销毁契约,不是见到继承就一律加 virtual。
类与对象 / C++11
成员函数模板不能声明为虚函数,它的某个特化也不会自动覆盖基类虚函数。但类模板里的普通非模板成员可以是虚函数。需要兼顾泛型入口与运行期扩展时,可把模板转换层和固定签名的虚接口分开。
类与对象 / C++20
空类的完整对象仍具有非零大小,以支持对象身份和数组寻址;空基类子对象则可能不占额外空间。C++20 的 no_unique_address 将重叠布局机会扩展到成员,但不保证具体 sizeof,也不取消同类型对象的身份规则。
内存与所有权 / C++20
一块地址可用的存储,不等于其中已经存在可访问的对象。判断访问是否合法,要同时检查大小、对齐、对象生存期、访问类型和边界;构造与析构决定对象的阶段,分配与释放决定底层存储的阶段。
内存与所有权 / C++11
指针是有类型的值,可能指向对象、函数、尾后位置,也可能为空或无效。非空不是可解引用的证明;正确使用依赖对象仍存活、类型匹配和访问未越界,裸指针本身也不表达释放责任。
内存与所有权 / C++11
读复杂声明要从名字出发,按括号与声明符的结合关系分层分析。数组指针、指针数组、函数指针并不等价;const 修饰哪一层也决定能否改指针或目标,用类型别名可以让接口更清晰。
内存与所有权 / C++14
C++ 规定自动、静态、线程和动态四种存储期,不要求固定的栈、堆或可执行文件分段。变量名的作用域、名字的链接属性、对象的生存期和操作系统映射分别回答不同问题,不能混成一张地址图。
内存与所有权 / C++11
引用提供已有对象的别名,不是可重新绑定的普通指针,也不自动拥有目标。const 引用可以在特定初始化场景延长临时对象的生存期,但这种延长不会沿参数、返回值或另一个引用任意传递。
内存与所有权 / C++14
选参数类型时先决定函数只是读取、需要修改,还是接收所有权,再比较复制成本。小值按值,大型只读对象用 const 引用,可空借用用指针,独占所有权按值传 unique_ptr;这些类型传达的是不同契约。
内存与所有权 / C++14
RAII 把资源的获取与释放封装进拥有型对象,用确定的析构时机处理正常返回和异常展开。关键不是“资源必须在栈上”,而是每份资源有明确拥有者,构造失败不泄漏,析构不把失败继续向外抛。
内存与所有权 / C++14
先考虑直接值成员和容器;确需动态生存期时,默认用 unique_ptr 表达独占拥有,仅在多个参与者都必须延长生存期时使用 shared_ptr。观察者采用引用、裸指针或 weak_ptr,不要把所有访问都升级为拥有。
内存与所有权 / C++11
shared_ptr 共享的是一组销毁责任,通常通过控制块记录拥有者、删除器等信息。控制块允许不同 shared_ptr 实例并发管理同一对象,但不保护对象内部数据,也不允许无同步地修改同一个 shared_ptr 变量。
内存与所有权 / C++11
weak_ptr 观察已有 shared_ptr 所有权组而不增加强引用数量,适合回指、订阅者和非拥有缓存。访问前直接 lock,成功得到的 shared_ptr 会保护这次使用的生存期;expired 检查本身不能预留对象。
内存与所有权 / C++11
malloc 提供未初始化存储,失败返回空指针,必须与 free 配对;它不运行 C++ 构造函数。分配器可能缓存、分桶或向系统申请映射,具体实现并非语言保证,申请字节与构造对象应当分层理解。
内存与所有权 / C++11
new 表达式通常包含取得存储和初始化对象,delete 表达式通常包含析构和释放存储;operator new/operator delete 是其中的底层分配函数,不等同于完整表达式。理解这层区别才能正确处理异常、数组和原位构造。
内存与所有权 / C++14
泄漏首先是资源没有在约定时间被正确回收,不是简单观察进程内存有没有下降。用 RAII 覆盖异常路径,再结合 LeakSanitizer 的分配栈与重复工作负载区分失联分配、引用环、无界缓存以及分配器保留。
内存与所有权 / C++11
未初始化指针没有可依赖的指针值;悬空指针则曾经指向有效对象,但目标已经销毁或失效。初始化为 nullptr 只解决起始状态,不能跟踪对象生存期;根本办法是约束借用范围并正确安排拥有关系。
内存与所有权 / C++11
把内存错误拆成空间越界、时间失效、初始化缺失、释放协议错误和并发竞争,再选择对应证据。崩溃地点往往只是后果;应追踪第一次非法访问、分配与释放栈,并以明确边界和拥有型接口修复根因。
现代 C++ / C++11
nullptr 是具有独立类型的空指针字面量,不是整数零,也不是某种通用指针。它让重载选择和模板传参保留空指针语义,但不会让解引用变安全,更不能代替对象生命周期管理。
现代 C++ / C++14
auto 让编译器推导静态类型,并不引入动态类型。是否保留引用与 const 取决于声明形式;decltype(auto) 则直接采用 decltype 规则,括号也能改变结果,尤其影响函数返回值与生命周期。
现代 C++ / C++11
左值、将亡值与纯右值描述表达式,而不是变量永久拥有的标签。std::move 只把表达式转换为可参与移动重载的形式;实际是否转移资源、源对象留下什么状态,取决于被调用类型的契约。
现代 C++ / C++11
转发引用配合模板推导记录调用者传入的值类别,std::forward 再把这份信息用于下一次调用。它适合透明包装,不是所有 T&& 的别名,也不是比 std::move 更高级的通用替代品。
现代 C++ / C++14
Lambda 创建带调用运算符的闭包对象,捕获决定它保存值还是借用外部状态。按值、按引用与初始化捕获有不同的所有权后果;把回调存起来之前,应先证明它依赖的对象能活到调用结束。
现代 C++ / C++11
模板是一组由参数生成的声明与实现,不是把任意实参强行转换成同一种类型。理解按值与按引用推导、非类型参数以及定义可见性,能更快解释调用失败、数组退化和依赖名称错误。
现代 C++ / C++11
全特化为确定的模板实参提供替代定义,偏特化为一类实参模式提供实现。类模板可以偏特化,函数模板不能;函数行为的分流通常优先用重载或约束,避免误判特化参与选择的方式。
现代 C++ / C++17
Type traits 把类型性质表达为编译期值或类型变换;if constexpr 在模板实例化时丢弃不适用的分支。它解决的是不同类型需要不同合法表达式的问题,不会把运行期条件变成编译期事实。
现代 C++ / C++20
constexpr 表达常量求值的能力与变量约束,consteval 要求立即调用满足常量表达式条件,constinit 约束静态或线程存储期变量的初始化。三者并不等价,尤其 constinit 不会使变量只读。
现代 C++ / C++17
可变参数模板保留每个实参的类型,折叠表达式把参数包组合为一个表达式。可靠的设计需要先决定空包结果、初始值类型与求值顺序,不能把省略号当成自动安全的循环或任意长度数组。
现代 C++ / C++17
optional 表达一个值可能不存在,variant 表达有限候选类型中的一个。二者都管理所含对象的生命周期;安全使用的关键是先确定当前状态,再访问正确分支,而不是用魔法数或裸 union 偷渡状态。
现代 C++ / C++20
string_view 与 span 把一段连续数据的地址和范围交给调用者,不拥有数据。它们能减少复制并统一接口,但原对象销毁、重新分配或失效后,视图也会失效;只读视图同样不能逃离生命周期约束。
现代 C++ / C++20
C++20 concepts 把模板所需的类型性质和表达式条件写到接口上。requires 表达式检查是否存在合法操作,requires 子句控制候选资格;它们改善诊断与重载选择,但不能证明运行期输入或业务语义正确。
现代 C++ / C++20
C++20 ranges 把范围作为算法输入,views 可组合筛选、映射等惰性操作。创建管道通常没有计算全部结果,重复遍历也不承诺复用结果;理解底层所有权、迭代能力与最终物化边界比管道语法更重要。
现代 C++ / C++20
C++20 协程让函数暂停后恢复,但不会自动创建线程、事件循环或后台任务。最小可用抽象也必须明确管理协程帧、完成状态与异常;下面的同步生成器用独占 RAII 所有权保证提前结束时仍能释放资源。
现代 C++ / C++23
std::expected<T,E> 把成功值与失败原因作为同一个返回类型交付,适合预期内、需要调用者处理的失败。启用 C++23 模式不代表整个标准库已实现,应同时检查编译器、标准库版本和对应特性宏。
标准库与算法 / C++11
vector 提供连续存储和常数时间下标访问,但容量不等于已构造元素数。理解 reserve、resize 与重分配的区别,才能正确估算追加成本,并避免把旧指针、迭代器和尾后位置带过修改操作。
标准库与算法 / C++11
deque 支持常数时间随机访问和两端单元素插入,却不保证连续存储。它的关键细节是引用稳定不代表迭代器稳定:两端插入保留已有元素引用,但会使迭代器失效,删除规则还要区分发生位置。
标准库与算法 / C++11
list 擅长已知位置的插入、删除和节点转移,而不是快速寻找位置。splice 可以保留元素身份与迭代器,但分配器和范围有前置条件;跨列表的范围转移也不能一概声称为常数时间。
标准库与算法 / C++11
map、set 按比较器维护有序键,键是否重复由比较等价性决定,而不必由 operator== 决定。理解对数查找、成员边界查询和不可随意修改的键,比记住某种树的实现名称更重要。
标准库与算法 / C++11
unordered 容器用哈希定位候选桶,再用相等谓词识别键。平均常数查找不等于最坏常数;预留元素规模可以减少 rehash,但引用稳定、迭代器失效和桶数策略仍须分别理解。
标准库与算法 / C++20
迭代器类别描述可以执行哪些操作以及相应复杂度,不负责延长对象生命周期。区分单趟输入、多趟前向、双向、随机访问和连续迭代器,再单独检查容器修改导致的失效,才能正确组合算法。
标准库与算法 / C++11
标准算法操作范围而非容器所有权。排序需要合法的严格弱序,二分边界查找依赖分区条件,remove 只改变逻辑末尾而不缩小容器;理解这些前置条件比背函数名更能避免错误。
标准库与算法 / C++11
stack、queue 和 priority_queue 用受限接口表达访问纪律。优先队列不是有序数组:比较器定义谁排在谁之前,而 top 取比较顺序中的最大项;理解这个方向才能正确实现小顶堆和多字段优先级。
标准库与算法 / C++17
分配器把容器的存储获取策略与元素管理分开。C++17 pmr 允许运行时选择内存资源,但资源并不由容器共享拥有;缓冲区、资源和使用它们的对象必须按正确顺序生存与销毁。
并发与同步 / C++20
线程对象管理执行资源,不等于线程正在运行。先确定谁负责等待结束,再确定共享对象活多久;C++20 的 jthread 自动请求停止并等待,但停止仍需要工作函数合作,不能强制终止阻塞操作。
并发与同步 / C++17
互斥锁保护的是一组共享状态的不变量,而不是变量名字。用 RAII 绑定锁的生命周期;同时更新多个对象时,一次获取所需锁。避免锁顺序环路,也避免在持锁期间等待线程或调用未知代码。
并发与同步 / C++11
条件变量只负责让线程等待和重新检查,不保存事件。把真实条件存进共享状态,在同一互斥锁下修改与检查,并使用带谓词的 wait;这样通知提前发生或出现虚假唤醒,都不会破坏业务逻辑。
并发与同步 / C++11
atomic 解决单个原子对象访问不可分割的问题,不会自动把多步业务变成事务。计数更新需要 fetch_add 等读改写操作;原子类型也不承诺一定无锁,更不保证任何基于它的算法都无等待。
并发与同步 / C++20
内存序描述原子操作如何约束周围访问。relaxed 保留原子性却不发布普通数据;读取到对应 release 写入的 acquire 才建立跨线程同步。判断正确性要画出先行发生链,不能依赖机器上看似稳定的执行顺序。
并发与同步 / C++11
future 是一次性结果的接收端,不等于后台线程。async 的启动策略决定独立执行还是延迟求值;promise 负责手动提交值或异常。既要处理结果,也要明确等待、异常传播以及关联任务何时结束。
构建与诊断 / C++11
编译器能看见声明,不等于链接器能找到定义。按预处理、编译、汇编、链接定位错误,再用一个本地三文件项目理解静态库、动态库与 CMake 目标依赖;这些构建方式属于工具链约定,不是语言规定的文件格式。
构建与诊断 / C++17
头文件可以被多个翻译单元包含,但其中的定义必须满足单一定义规则。inline 主要解决跨翻译单元的定义与实体身份,不是强制展开调用;模板可见性、名字查找和构建宏的一致性同样属于接口正确性。
构建与诊断 / C++11
异常安全关心失败以后对象还剩什么,而不是代码里有没有 catch。用 RAII 保住资源,用先准备再提交实现强保证,并让 noexcept 只描述真正不会向外传播异常的操作;copy-and-swap 是有成本和前提的策略,不是万能答案。
构建与诊断 / C++11
让编译警告、运行时插桩和调试器分别回答不同的问题,再以可重复的输入检验修复。必须区分未定义、未指定和实现定义行为;Sanitizer 没有报告只说明本次执行未触发已启用的检查,性能结论也必须来自独立测量。
没有匹配的文章。换个关键词,或点击“重置”。