More Effective C++学习笔记(1)

这篇具有很好参考价值的文章主要介绍了More Effective C++学习笔记(1)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

条款1:仔细区别pointers和references

  • 在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某个对象,必须有初值。
  • 如果变量指向可修改,且有可能指向null,就把变量设为指针;如果变量总是必须代表一个对象(不可能为null),就把变量设为引用
  • 引用可能比指针更高效,因为不必像指针那样在使用前判断它是否有效(no null)
//使用引用之前不需要测试它的合法性。
void printDouble(const double& rd)
{
    cout << rd;         // 不需要测试rd,它
}                       // 肯定指向一个double值
//相反,指针则应该总是被测试,防止其为空:
void printDouble(const double *pd)
{
  if (pd) {             // 检查是否为NULL
    cout << *pd;
 }
}

条款2:尽量使用C++风格的类型转换

  • 旧式C的转型操作符缺点:(1)不同转型意图不明确;(2)难以辨识,与C++中其他用到“(类型)”地方难以区分。
  • C++通过引进四个新的类型转换操作符克服了C风格类型转换的缺点,这四个操作符是static_cast, const_cast, dynamic_cast, 和reinterpret_cast
  • static_cast:在功能上基本上与C风格的类型转换一样强大,含义也一样;不能从表达式中去除const属性。
int firstNumber, secondNumber;
...
double result = ((double)firstNumber)/secondNumber;
//如果用上述新的类型转换方法,你应该这样写:
double result = static_cast<double>(firstNumber)/secondNumber;
  • const_cast:用于类型转换掉表达式的const或volatileness属性。
class Widget { ... };
class SpecialWidget: public Widget { ... };
void update(SpecialWidget *psw);
SpecialWidget sw;                // sw 是一个非const 对象。
const SpecialWidget& csw = sw;   // csw 是sw的一个引用
                                // 它是一个const 对象 
update(&csw);  // 错误!不能传递一个const SpecialWidget* 变量
               // 给一个处理SpecialWidget*类型变量的函数 
update(const_cast<SpecialWidget*>(&csw));
			// 正确,csw的const被显示地转换掉(
			// csw和sw两个变量值在update
			//函数中能被更新) 
  • dynamic_cast:用于安全地沿着类的继承关系向下进行类型转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。它不能被用于缺乏虚函数的类型上。
Widget *pw;
...
update(dynamic_cast<SpecialWidget*>(pw));
			// 正确,传递给update函数一个指针
			// 是指向变量类型为SpecialWidget的pw的指针
			// 如果pw确实指向一个对象,
			// 否则传递过去的将使空指针。
void updateViaRef(SpecialWidget& rsw);
updateViaRef(dynamic_cast<SpecialWidget&>(*pw));
                         //正确。 传递给updateViaRef函数
                         // SpecialWidget pw 指针,如果pw 
                         // 确实指向了某个对象
                         // 否则将抛出异常

  • reinterpret_cast:最普通的用途就是在函数指针类型之间进行转换;转换函数指针的代码是不可移植的。
typedef void (*FuncPtr)();      // FuncPtr is 一个指向函数
                                // 的指针,该函数没有参数
				// 返回值类型为void
FuncPtr funcPtrArray[10];       // funcPtrArray 是一个能容纳
                                // 10个FuncPtrs指针的数组
int doSomething();
funcPtrArray[0] = &doSomething;     // 错误!类型不匹配 
reinterpret_cast可以让你迫使编译器以你的方法去看待它们:
funcPtrArray[0] =                   // this compiles
  reinterpret_cast<FuncPtr>(&doSomething);

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

  • 数组中难免会遇到数组索引运算符array[ i ],本质上式一个指针算术表达式*(array+i),通过数组原类型的大小计算每个数组元素的地址。如果将派生类对象数组实参传递给原本为基类对象数组的形参(类似多态的方式),那么代码将仍以基类对象占用的内存大小计算对象数组的每个元素地址,问题的关键在于派生类对象几乎总是比基类对象要大,这样计算出来的元素地址就会有问题,行为是未定义的。
class BST { ... }; 
class BalancedBST: public BST { ... };

void printBSTArray(ostream& s,
                   const BST array[],
                   int numElements)
{
  for (int i = 0; i < numElements; ) {
    s << array[i];          //假设BST类
  }                         //重载了操作符<<
}

BalancedBST bBSTArray[10]; 
... 
printBSTArray(cout, bBSTArray, 10); //报错
  • 用delete [ ] 删除数组时,数组元素中每一个析构函数会被调用,依然会涉及到“指针算术表达式”,所以多态与数组不能混用:
delete [] array;
//它会这样生成代码:
// 以与构造顺序相反的顺序来
// 解构array数组里的对象
for ( int i = 数组元素的个数 1; i >= 0;--i)
 {
    array[i].BST::~BST();                     // 调用 array[i]的
  }                                           // 析构函数


条款4:非必要不提供默认构造函数

  • 需要外部数据信息来建立对象的类则不必拥有缺省构造函数。但是,如果类不提供默认构造函数,会在两个情况下遇到困难:(1)建立类对象数组时,大多数情况下需要默认构造函数进行初始化;(2)将不适用于它们无法在许多基于模板(template-based)的容器类里使用。
  • 解决问题(1)的方法1使用非堆数组(non-heap arrays)
int ID1, ID2, ID3, ..., ID10;            // 存储设备ID号的
                                         // 变量
... 
EquipmentPiece bestPieces[] = {          // 正确, 提供了构造
  EquipmentPiece(ID1),                   // 函数的参数
  EquipmentPiece(ID2),
  EquipmentPiece(ID3),
  ...,
  EquipmentPiece(ID10)
};
  • 解决问题(1)的方法2利用指针数组来代替一个对象数组。缺点在于必须记得对指针数组所指的所有对象进行删除,此外,内存总量会变大(指针+对象)
typedef EquipmentPiece* PEP;             //  PEP 指针指向
                                         //一个EquipmentPiece对象
PEP bestPieces[10];                      // 正确, 没有调用构造函数
PEP *bestPieces = new PEP[10];           // 也正确
//在指针数组里的每一个指针被重新赋值,以指向一个不同的EquipmentPiece对象:
for (int i = 0; i < 10; ++i)
  bestPieces[i] = new EquipmentPiece( ID Number );
  • 解决问题(1)的方法3使用placement new方法在内存中构造对象,指定一块大的连续内存中,在指定地址构建对象。避免了方法2中的过度使用内存的问题。缺点在于大部分程序员不熟悉placement new,维护比较困难;当你不想让它继续存在使用时,必须手动调用数组对象的析构函数,然后调用操作符delete[]来释放raw memory(已经有placement delete/delete []操作符了,它会自动调用析构函数)。
// 为大小为10的数组 分配足够的内存
// EquipmentPiece 对象;
// operator new[] 函数
void *rawMemory =
  operator new[](10*sizeof(EquipmentPiece));
// make bestPieces point to it so it can be treated as an
// EquipmentPiece array
EquipmentPiece *bestPieces =
  static_cast<EquipmentPiece*>(rawMemory);
// construct the EquipmentPiece objects in the memory
// 使用"placement new" (参见条款8)
for (int i = 0; i < 10; ++i)
  new (&bestPieces[i]) EquipmentPiece( ID Number );
// 以与构造bestPieces对象相反的顺序
// 解构它。
 - List item

for (int i = 9; i >= 0; --i)
  bestPieces[i].~EquipmentPiece(); 
// deallocate the raw memory
operator delete[](rawMemory);

//如果你忘记了这个要求而使用了普通的数组删除方法,那么你程序的运行将是不可预测的。这是因为:直接删除一个不是用new操作符来分配的内存指针,其结果没有被定义。
delete [] bestPieces;                    // 没有定义! bestPieces
                                    //不是用new操作符分配的。
  • 针对于问题2没有默认构造函数的类将不适用于它们无法在许多基于模板(template-based)的容器类里使用。因为实例化一个模板时,模板的类型参数应该提供一个缺省构造函数,这是一个常见的要求。
template<class T>
class Array {
public:
  Array(int size);
  ... 
private:
  T *data;
}; 
template<class T>
Array<T>::Array(int size)
{
  data = new T[size];                    // 为每个数组元素 
  ...                                    //依次调用 T::T()
}
  • 虚基类应该有一个默认构造函数,因为如果随着继承体系增加,每个派生类都需要记得基类的有参构造并了解其含义是一件痛苦的事情。
  • 添加无意义的默认构造函数有时会影响类class的效率。因为成员函数会需要测试有参和无参的情况下,某些字段是否已经被初始化。

文章来源地址https://www.toymoban.com/news/detail-654183.html

到了这里,关于More Effective C++学习笔记(1)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • effective c++ 笔记 条款18-25

    使用外覆类型(wrapper)提醒调用者传参错误检查,将参数的附加条件限制在类型本身 三个参数类型相同的函数容易造成误用 导入新的类型 限制取值 从语法层面限制调用者不能做的事 operate*的返回类型上加上const修饰,防止无意的错误赋值if (a * b = c) 接口应表现出与内置类型

    2024年02月21日
    浏览(35)
  • 《Effective C++中文版,第三版》读书笔记7

    隐式接口: ​ 仅仅由一组有效表达式构成,表达式自身可能看起来很复杂,但它们要求的约束条件一般而言相当直接而明确。 显式接口: ​ 通常由函数的签名式(也就是函数名称、参数类型、返回类型)构成 ​ 在源码中明确可见。 编译期多态: 在编译时才能确定具体调

    2024年02月09日
    浏览(24)
  • effective c++ 43-处理模板化基类的名称 笔记

    该节主要分析了一个写模板时常常会遇到的一个编译错误。 这里有一个模板基类,有派生类继承了模板基类,并调用了基类中的方法,但是编译器却会报找不该方法,这是怎么回事? 编译输出如下: 从编译的输出也可以看出,原因是编译器觉得 sendClear 含义不明确,编译器

    2024年02月03日
    浏览(42)
  • 《Effective C++ 改善程序与设计的55个具体做法》读书笔记

    条款01 视C++为一个语言联邦 C Object-Oriented C++ Template C++ STL C++ 高效编程守则视情况而变化,取决于你使用 C++ 的哪一部分。 条款02 尽量与const,enum,inline替换#define 对于单纯常量,最好以 const 对象或 enums 替换 #defines 。 对于形似函数的宏( macros ),最好改用 inline 函数替换

    2024年02月12日
    浏览(26)
  • 《Effective Python 编写高质量Python代码的59个有效方法》学习笔记2

    尽量用enumerate取代range 用zip()同时遍历两个迭代器 合理使用try-except-else-finally 如果既要异常向上传播,又要在异常发生时执行清理工作,可使用try/finally结构 try/except/else结构可以清晰描述哪些异常由自己的代码处理,哪些传播到上一级 无论try块是否异常,都可用try/finally复合

    2023年04月25日
    浏览(38)
  • 【Effective C++】让自己习惯C++

    C++由四个次语言组成: C:过程形式,没有模板、没有异常、没有重载 Object-Oriented C++:面向对象形式,类(构造函数和析构函数)、封装、继承、多态 Template:泛型编程、模板元编程 STL:容器、算法、迭代器和函数对象 目标是让编译器来替代预处理器,使用预处理器会存在

    2024年01月21日
    浏览(39)
  • Effective C++ 条款四

    使用未初始化的值会导致不明确的行为 对于内置的数据类型(char,int,float,double等),在使用前必须进行初始化。 对于class来说,在使用对象之前,必须使用构造函数对成员变量进行初始化 但是需要注意赋值和初始化的区别 构造函数中的赋值操作(非初始化) 例如下面的构造函数

    2024年02月05日
    浏览(30)
  • effective c++ 条款2

    尽量用const,enum,inline替换#define 总结就是: 双const:指针不能变(只能指向这一块内存空间),指向的内存单元的值也不能变。 或者可以使用string常量代替: 类的静态成员 在头文件声明,在cpp文件中定义 这样也就完成了常量的定义。 可以在其他函数中,直接调用常量GamePl

    2024年02月15日
    浏览(34)
  • 【Effective C++】4. 设计与声明

    如何创建和销毁 初始化对象和对象的赋值应该有什么差别 以值传递意味着什么 新type的合法值 继承体系 类型转换 操作符重载 继承标准 未声明接口 type一般化(template) 有以下几个优势: 1. 语法一致性:访问class成员时每一个都是函数,不需要记住是否需要小括号 2. 使成员

    2024年01月24日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包