More Effective C++ 35个改善编程与设计的有效方法笔记与心得 2

二. 操作符

条款5: 对定制的“类型转换参数”保持警觉

‌‌‌‌  在编程中,类型转换是将一种数据类型变为另一种数据类型。这个过程在很多情况下都是非常有用的,比如从浮点数向整数转换,或者从基类向派生类转换。然而,如果不正确使用,可能会产生非预期的副作用,例如数据丢失,或者转换失败

‌‌‌‌  当你看到"对定制的类型转换参数保持警觉"的建议时,这通常是指在设计和使用自定义的类型转换函数时需要特别小心。类型转换运算符是C++允许你定义的一种特殊成员函数,可将一个类类型的值转换为另一种类型。它们在使用上非常方便,但是如果在设计和使用上没有控制得当,就可能带来很多问题。

‌‌‌‌  例如,自定义的类型转换函数可能在没有明确要求的情况下被隐式调用,可能导致意外的转换和潜在的错误。另一个问题是,如果类型转换函数不清楚,阅读和维护代码的人可能无法理解代码的目的和效果,这可能会导致错误

‌‌‌‌  所以,"对定制的类型转换参数保持警觉"这个建议的含义是:在设计和使用类型转换函数时,应该注意明确、谨慎并尽量避免使用隐式类型转换,以减少潜在的错误

条款6: 区别 increment/decrement 操作符的前置(prefix)和后置(profix)形式

‌‌‌‌  在C++和其他一些编程语言中,我们经常会遇到增量(increment)和减量(decrement)这两类操作符,它们分别被记作 ++ 和 --。当它们作用在一个变量上时,可以用来增加或减少变量的值。

‌‌‌‌  这两个操作符有两种使用形式:前置(prefix)形式和后置(postfix)形式。

1. 前置(prefix)形式

‌‌‌‌  前置形式先进行计算,然后再进行赋值。例如,++i--i。这种形式先改变i的值,然后再进行其他的计算。比如在表达式int a = ++i;中,先把i增加1,然后再把新值赋给a。
‌‌‌‌

2. 后置(postfix)形式

‌‌‌‌  后置形式先进行赋值,然后再进行计算,例如,i++i--。在一个表达式中使用后置增量或减量操作符,会先进行其他的计算,然后再改变i的值。比如在表达式int a = i++;中,首先把i的当前值赋给a,再把i加1。

‌‌‌‌  所以"区别 increment/decrement 操作符的前置(prefix)和后置(postfix)形式"的意思就是让我们理解和区分这两种操作符形式的不同。在实践中,我们往往需要根据具体的需求和场景来选择使用哪种形式。

条款7: 千万不要重载&& , || 和 , 操作符

请记住:
‌‌‌‌  在C++编程中,重载操作符是允许的,允许你为自定义类型(例如类和结构体)定义自己的行为。不过,并不是所有操作符都适合被重载,特别是像&&||,(逗号)这些操作符。

&&|| 操作符

  1. 短路求值(Short-circuit Evaluation):

    • 逻辑与(&&)逻辑或(||) 操作符在C++中具有短路求值的特性。如果第一个操作数能够确定整个表达式的值,第二个操作数就不会被求值。例如:

      if (a && b) { /* ... */ }

      如果 afalse,则 b 不会被求值。这种特性在逻辑判断中非常重要,有助于避免不必要的计算,甚至可以防止潜在的运行时错误(例如 b 可能是一个需要非空检查的指针)。

  2. 重载后可能失去短路求值的特性:

    • 如果重载了这些操作符,编译器将无法再使用这种短路特性,因为重载的版本会被认为是普通函数调用,所有参数都会被求值。这不仅会影响程序的性能,还可能引入潜在的bug和不可预见的行为。

, 操作符

  1. 顺序求值(Sequencing):

    • 逗号操作符在C++中的作用是顺序求值,其表达式中左侧的操作数先被求值,然后右侧的操作数被求值,整个表达式的结果是右侧操作数的结果。例如:

      int x = (a++, b++);

      在这个例子中,a 被先增加然后是 bx 最终被赋值为 b++ 的结果。

  2. 重载后的复杂性和不明确性:

    • 重载逗号操作符会使代码变得难以理解和维护。通常,开发者不会期望逗号操作符有不同的行为,而这种不一致性可能导致程序的逻辑难以跟踪和调试。

总结

‌‌‌‌  重载这些操作符会带来很多潜在的问题,使代码的行为不再符合直觉,并且可能引入性能问题和bug。因此,建议避免重载这些操作符,以保持代码的可读性和可靠性。

条款8: 了解各种不同意义的new和delete

‌‌‌‌  在C++中,newdelete 操作符有多种不同的用途和意义。了解这些不同的用法有助于更好地管理内存,并编写高效、健壮的代码。以下是对各种不同意义的 newdelete 的解释:

new 操作符

  1. 常规动态内存分配:

    • 用于从堆中分配单个对象的内存。

    int* p = new int; // 分配一个 int 类型的对象 *p = 5;

    • 用于从堆中分配数组的内存。

    int* pArray = new int[10]; // 分配一个 int 类型的数组,包含 10 个元素

  2. 带参数的构造函数:

    • 可以使用 new 操作符调用带参数的构造函数。

    class MyClass { public: MyClass(int x) : value(x) {} private: int value; }; MyClass* obj = new MyClass(10); // 使用带参数的构造函数创建对象

  3. 定位 new (placement new):

    • 定位 new 允许在预先分配的内存上构造对象。常用于自定义内存管理。

    char buffer[sizeof(MyClass)]; MyClass* p = new(buffer) MyClass(10); // 在 buffer 上构造 MyClass 对象

    • 使用定位 new 时,需要包含头文件 <new>

delete 操作符

  1. 常规动态内存释放:

    • 用于释放使用 new 分配的单个对象的内存。

    delete p; // 释放单个对象的内存

    • 用于释放使用 new 分配的数组的内存。

    delete[] pArray; // 释放数组的内存

  2. 定位 delete (placement delete):

    • 对于定位 new 构造的对象,应该显式调用析构函数,因为定位 new 不分配内存,只是在指定位置上构造对象。

    p->~MyClass(); // 显式调用析构函数

    • 定位 delete 并不真正释放内存,因为内存的分配和释放是由程序员自己管理的。

特殊情况和运用

  1. 自定义 new 和 delete:

    • 类可以重载全局的 newdelete 操作符,以实现自定义的内存管理策略。

    void* operator new(size_t size) { // 自定义内存分配逻辑 } void operator delete(void* p) { // 自定义内存释放逻辑 }

    • 可以在类内重载,也可以在全局范围重载。
  2. 避免内存泄漏和野指针:

    • 在使用 newdelete 时,必须确保每个 new 都有对应的 delete,否则会导致内存泄漏。
    • 释放指针后,建议将其设置为 nullptr,避免成为悬挂指针(dangling pointer)。

    delete p; p = nullptr;

总结

‌‌‌‌  了解和正确使用 newdelete 及其各种不同的形式和用法,对于C++程序员来说是非常重要的。这不仅有助于有效管理内存资源,还能提高程序的健壮性和性能。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值