news 2026/4/14 19:53:28

第一章 基础议题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第一章 基础议题

第一章 基础议题

条款一:仔细区分pointer和references

1.区别

1.指针可以为NULL,引用一定要初始化(初值)。

string& rs; // 错误 引用必须被初始化 string str1("Nancy"); string& rs = str1; // 没问题,rs指向str1 string* ps;// 未初始化的指针,有效,但风险高

2.指针可以被重新赋值,指向另一个对象但是引用总是指向它最初获得的哪个对象。

string str1("Nancy"); string str2("Clancy"); string& rs = str1; // rs代表str1 string* ps = &str1; // ps指向str1 rs = str2; // rs仍然代表str1,但是str1的值现在变成了“Clancy” ps = &str2; // ps现在指向str2,str1没有变化

2.使用时机

指针:考虑变量“不指向任何对象“的可能型,或者”不同时间指向不同对象“。

引用:

①确定"总是代表某个对象",而且"一旦代表某个对象就不能够再改变"。

②要实现某些操作符时,例如operator[]。

条款二:最好使用C++转型操作符

C++中4个新的类型转换操作符:static_cast、const_cast、dynamic_cast、reinterpret_cast

4个新型转型操作的作用:

static_cast:和C中的类型转换相同。

int a = 10; double b = static_cast<double>(a);

const_cast:去除某个对象的常量性。

const int a = 10; int b = const_cast<int>(a); // 去掉a的常量性

**dynamic_cast:**用于在继承体系中,“安全的像下转型或跨系转型动作”。

class A { }; class B:public A { }; A* a = new A(); B* b = dynamic_cast<B*>(a);

reinterpret_cast:转换“函数指针”类型、但是与编译器平台系相关。

typedef void (*FuncPtr)(); FuncPtr funcArrPtr[10]; int doSomthing(); funcArrPtr[0] = reinterpret_cast<FuncPtr>(&doSomthing);

条款三:绝对不要以多态方式处理数组

delete[]对数组执行清理时,编译器只会用“静态类型”计算元素大小和偏移,而不会用“动态类型”做多态。
如果通过基类指针删除派生类对象数组,指针算术会错档,导致未定义行为(通常是堆损坏 / 崩溃)。

#include <iostream> class Base { public: virtual ~Base() { std::cout << "~Base\n"; } }; class Derived : public Base { char dummy[32]; // 让 Derived 比 Base 大很多 ~Derived() { std::cout << "~Derived\n"; } }; int main() { const std::size_t n = 5; Derived* dArray = new Derived[n]; // 生成 5 个 Derived 对象 Base* bPtr = dArray; // 向上转型:语法合法,但埋下炸弹 delete [] bPtr; // ★ 按 Base 的步长去析构 + 释放 }

解释:

  1. Derived大小 40 B,Base大小 8 B(仅一个 vptr)。

  2. delete[] bPtr从首地址开始,每次偏移 8 B找下一个元素,实际数组步长却是 40 B → 指针迅速指到“半空中”。

  3. 析构函数因虚表还能正确调到~Derived,但内存回收时地址已对不上,堆管理器直接 abort。

    正确姿势:永远不要“多态数组”

    // 方法一:用 std::vector<std::unique_ptr<Base>> std::vector<std::unique_ptr<Base>> vec; for (std::size_t i = 0; i < n; ++i) vec.emplace_back(std::make_unique<Derived>()); // 方法二:如果非要用数组,也存指针 std::unique_ptr<Base[]> ptrArray(new Base*[n]); for (std::size_t i = 0; i < n; ++i) ptrArray[i] = new Derived(); // 释放时 for (std::size_t i = 0; i < n; ++i) delete ptrArray[i];

    小结:“多态对象”请用指针容器/智能指针管理,绝不要把派生类对象直接放进“Base 数组”后再通过 Base* 删除。

条款四:非必要不提供default contructor

只有在“确实需要”时才给类一个默认构造函数(无参 ctor)。
贸然提供T()会让对象处于不确定/无效状态,后续必须靠.init()open()等二次赋值才能用——这反而增加出错机会并迫使客户端写更多检查代码。
如果对象生来就必须携带有效资源,就让构造函数强制这些资源进来,没有资源就构造失败(抛异常),从而保证“每个对象一出生就有效”。

一、非必要不提供 —— 强制外部传入有效参数

class SerialPort { public: // 没有默认构造!必须给出端口名和波特率 explicit SerialPort(const std::string& port, int baud); ~SerialPort(); void send(const void* data, std::size_t len); std::size_t recv(void* buffer, std::size_t max); private: int fd_; // POSIX 文件描述符 }; SerialPort::SerialPort(const std::string& port, int baud) { fd_ = ::open(port.c_str(), O_RDWR | O_NOCTTY); if (fd_ == -1) throw std::runtime_error("open failed"); // 下面设置 baud、termios … } // 使用端:编译器强制你传入有效参数 SerialPort sp1("/dev/ttyS0", 115200); // OK SerialPort sp2; // ❌ 编译期就报错

1.编译器帮你拦下“忘初始化”的对象;

2.构造要么成功(资源立即可用),要么抛异常,不存在“半吊子”状态

3.后续成员函数无需if (!isOpen()) return;这类重复检查。

二、妥协提供默认构造 —— 带来的额外负担

class SerialPort { public: SerialPort() : fd_(-1) {} // 默认构造:对象处于“无效”态 bool open(const std::string& port, int baud); // 二次初始化 void send(const void* data, std::size_t len); private: int fd_; }; bool SerialPort::open(const std::string& port, int baud) { if (fd_ != -1) return false; // 可能重复打开 fd_ = ::open(port.c_str(), O_RDWR); return fd_ != -1; } // 使用端:很容易写出 bug SerialPort sp; // 1. 对象诞生,但 fd_ == -1 sp.send("hi", 2); // 2. 运行时错误!端口根本没打开

问题

1.每个成员函数都要if (fd_ == -1) throw/return;——代码膨胀;

2.客户端必须先调.open()且不忘检查返回值,把本属于类的不变式推给了用户

3.默认构造后对象处于“无效”期,拷贝、移动语义都要额外处理哨兵值

三、什么时候“必须”提供默认构造?

1.需要放在标准容器(std::vector<SerialPort> v(10);

2.需要作为数组元素(SerialPort buf[8];

小结
“非必要不提供默认构造”就是:
让对象一出生就处于有效、完整、可用的状态;
把“资源获取即初始化”(RAII)做到极致,
把“忘记初始化”这类运行时错误提前到编译期。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/13 8:58:58

Typed.js打字动画库:让网页文字“活“起来的终极解决方案

还在为静态网页缺乏动感而烦恼吗&#xff1f;想让你的网站文字像真人打字一样生动有趣吗&#xff1f;Typed.js正是你需要的解决方案&#xff01;这个轻量级的JavaScript打字动画库&#xff0c;能够为任何网页元素添加逼真的打字效果&#xff0c;让你的内容瞬间"活"起…

作者头像 李华
网站建设 2026/4/13 7:31:03

Path Copy Copy:Windows文件路径复制的最佳解决方案

Path Copy Copy&#xff1a;Windows文件路径复制的最佳解决方案 【免费下载链接】pathcopycopy Copy file paths from Windows explorers contextual menu 项目地址: https://gitcode.com/gh_mirrors/pa/pathcopycopy 你是否曾经因为需要在Windows资源管理器中复制文件路…

作者头像 李华
网站建设 2026/4/14 5:09:18

如何选择适合企业的RFID系统解决方案?

在物联网技术赋能企业数字化转型的进程中&#xff0c;RFID&#xff08;射频识别&#xff09;技术凭借非接触式识别、多标签批量读取、数据实时上传等核心优势&#xff0c;已广泛应用于物流仓储、制造业生产溯源、资产管理、零售库存管控等多个领域。然而&#xff0c;企业在选型…

作者头像 李华
网站建设 2026/4/14 12:24:05

Minecraft基岩版终极启动器:Linux和macOS免费畅玩完整指南

Minecraft基岩版终极启动器&#xff1a;Linux和macOS免费畅玩完整指南 【免费下载链接】mcpelauncher-manifest The main repository for the Linux and Mac OS Bedrock edition Minecraft launcher. 项目地址: https://gitcode.com/gh_mirrors/mc/mcpelauncher-manifest …

作者头像 李华
网站建设 2026/4/11 7:10:30

如何5分钟完成VPS系统重装:极速一键迁移指南

如何5分钟完成VPS系统重装&#xff1a;极速一键迁移指南 【免费下载链接】reinstall 又一个一键重装脚本 项目地址: https://gitcode.com/GitHub_Trending/re/reinstall reinstall是一款功能强大的VPS系统重装工具&#xff0c;能够在短短5分钟内完成从Linux到Windows、W…

作者头像 李华
网站建设 2026/4/11 11:10:31

Linux和macOS系统畅玩Minecraft基岩版终极指南

Linux和macOS系统畅玩Minecraft基岩版终极指南 【免费下载链接】mcpelauncher-manifest The main repository for the Linux and Mac OS Bedrock edition Minecraft launcher. 项目地址: https://gitcode.com/gh_mirrors/mc/mcpelauncher-manifest 还在为Linux或macOS系…

作者头像 李华