More Effective C++ 基础议题(条款1-4)总结
More Effective C++ 基础议题(条款1-4)总结
条款1:仔细区别pointers和references
- 如果有一个变量,其目的是用来指向(代表)另一个对象,但是也有可能它不指向(代表)这个变量,那么应该使用
pointer
,因为可将pointer
设为null
,反之设计不允许变量为null
,那么使用reference
- 以下这是有害的行为,其结果不可预期(C++对此没有定义),编译器可以产生任何可能的输出
char *pc = 0; // 将 pointer 设定为null
char& rc = *pc; // 让 refercence 代表 null pointer 的 解引值
- 没有
null reference
, 使用reference
可能比pointers
更有效率,在使用reference
之前不需要测试其有效性
void printDouble(const double& rd)
{
cout < < rd; // 不需要测试rd,它
} // 肯定指向一个double值
//相反,指针则应该总是被测试,防止其为空:
void printDouble(const double *pd)
{
if (pd) // 检查是否为NULL
{
cout < < *pd;
}
}
pointers
可以被重新赋值,指向另一个对象,reference
却总是指向(代表)它最初获得的哪个对象- 实现某些操作符。如operator[],操作符应返回某种“能够被当作assignment赋值对象”
总结
当你知道你需要指向某个东西,而且绝不会改变指向其他东西,或是当你实现一个操作符而其语法需求无法由pointers达成,你就应该选择reference。任何其他时候,请采用pointers
条款2:最好使用C++转型操作符
- 旧式的C转型方式,它几乎允许你将任何类型转换为任何其他类型,这是十分拙劣的
旧式转型存在的问题:
- 例如将
pointer-to-const-object
转型为一个pointer-to-non-const-object
(只改变对象的常量性),和将一个pointer-to-base-class-object
转型为一个pointer-to-derived-class-object
(完全改变一个对象的类型),其间有很大的差异。但是传统的C转型动作对此并无区分 - 难以辨识,旧式转型由一小对小括号加上一个对象名称(标识符)组成,而小括号和对象名称在C++的任何地方都有可能被使用
staic_cast:
static_cast
基本上拥有与 C 旧式转型相同的威力与意义,以及相同的限制(如不能将struct转型为int)。- 不能移除表达式的常量性,由
const_cast
专司其职 - 其他新式 C++ 转型操作符适用于更集中(范围更狭窄)的目的
(type) expression // 原先 C 的转型写码形式
static_cast<type>(expression) // 使用 C++ 转型操作符
const_cast:
const_cast
用来改变表达式的常量性(constness)或变易性(volatileness),使用const_cast
,便是对人类(编译器)强调,通过这个转型操作符,你唯一打算改变的是某物的常量性或变易性。这项意愿将由编译器贯彻执行。如果将const_cast
应用于上述以外的用途,那么转型动作会被拒绝
#include <iostream>
using namespace std;
class Widget {};
class SpecialWidget : public Widget {};
void update(SpecialWidget* psw);
SpecialWidget sw; // sw是个 non-const 对象
const SpecialWidget& csw = sw; // csw 确实一个代表sw的 reference
// 并视之为一个const对象
update(&csw); // 错误!不能及那个const SpecialWidget*
// 传给一个需要SpecialWidget* 的函数
update(const_cast<SpecialWidget*>(&csw)); // 可!&csw的常量性被去除了
update((SpecialWidget*)&csw); // 可!但较难识别 C 旧式转型语法
const_cast
最常见的用途就是将某个对象的常量性去除掉
dynamic_cast:
- 用来转型继承体系重“安全的向下转型或跨系转型动作”。也就是说你可以利用
dynamic_cast
,将“指向base ckass objects
的pointers
或references
”转型为“指向derived(或sibling base)class objects
的pointers
或references
”,并得知转型是否成功。如果转型失败,会以一个null
指针或一个exception
(当转型对象是reference
)表现出来:
Widget *pw;
update(dynamic_cast<SpecialWidget*>(pw)); // 很好,传给update()一个指针,指向pw所指的
// pw所指的SpecialWidget--如果pw
// 真的指向这样的东西;否则传过去的
// 将是一个 null 指针
void updateViaRef(SpecialWidegt& rsw);
updateViaRef(dynamic_cast<SpecialWidegt&>(*pw)); // 很好,传给updateViaRef()的是
// pw所指的SpecialWidget--如果
// pw真的指向这样的东西;否则
// 抛出一个exception
dynamic_cast
只能用来协助你巡航于继承体系之中。它无法应用在缺乏虚函数(请看条款24)的类型身上,也不能改变类型的常量性(constness)- 如果不想为一个不涉及继承机制的类型执行转型动作,可使用
static_cast
;要改变常量性(constness),则必须使用const_cast
reinterpret_cast:
- 最后一个转型操作符是
reinterpret_cast
。这个操作符的转换结果几乎总是与编译平台息息相关。所以reinterpret_cast
不具移植性 reinterpret_cast
的最常用用途是转换"函数指针"类型。
typedef void (*FuncPtr)(); // FuncPtr是个指针,指向某个函数
// 后者无须任何自变量,返回值为voids
FuncPtr funcPtrArray[10]; // funcPtrArray 是个数组
// 内有10个FuncPtrs
假设由于某种原因,希望将以下函数的一个指针放进funcPtrArray中
int doSomething();
如果没有转型,不可能办到,因为doSomething
的类型与funcPtrArray
所能接受的不同。funcPtrArray
内各函数指针所指函数的返回值是void
,但doSomething
的返回值却是int
funcPtrArray[0] = &doSomething; //错误!类型不符
funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); //这样便可通过编译
某些情况下这样的转型可能会导致不正确的结果(如条款31),所以你应该尽量避免将函数指针转型。
补充:
More Effective C++
没有过多的对reinterpret_cast
操作符进行解释,但我觉得应该对它进行更多说明,因为它实在是太强大了,也应该对使用规则做出足够多的说明- reinterpret_cast通过重新解释底层位模式在类型之间进行转换。它将
expression
的二进制序列解释成new_type
,函数指针可以转成void*再转回来。reinterpret_cast
很强大,强大到可以随便转型。因为他是编译器面向二进制的转型,但安全性需要考虑。当其他转型操作符能满足需求时,reinterpret_cast
最好别用。 - 更多了解可看cpp reference reinterpret_cast
总结:
在程序中使用新式转型法,比较容易被解析(不论是对人类还是对工具而言),编译器也因此得以诊断转型错误(那是旧式转型法侦测不到的)。这些都是促使我们舍弃C旧式转型语法的重要因素
条款3:绝对不要以多态(polymorphically)方式处理数组
假设你有一个class BST
及一个继承自BST的class BalancedBST
;
class BST {};
class BalancedBST : public BST {};
现在考虑有个函数,用来打印BSTs数组中的每一个BST的内容
void printBSTArray(ostream& s, const BST array[], int numElements)
{
for (int i = 0 ; i < numElements; ++i)
{
s << array[i]; // 假设BST objects 有一个
// operator<< 可用
}
}
当你将一个由BST对象组成的数组传给此函数,没问题:
BST BSTArray[10];
printBSTArray(cout, BSTArray, 10); // 运行良好
然而如果你将一个BalancedBST
对象所组成的数组交给printBSTArray
函数,会发生什么事?
BalancedBST bBSTArray[10];
printBSTArrat(cout, bBSTArray, 10); // 可以正常运行吗?
- 此时就会发生错误,因为array[i]代表的时
*(array+i)
,编译器会认为数组中的每个元素时BST对象,所以array和array+i之间的距离一定是i*sizeof(BST) - 然后当传入由
BalancedBST
对象组成的数组,编译器会被误导。它仍假设数组中每一元素的大小是BST的大小,但其实每一元素的大小是BalancedBST的大小。因此当BalancedBST
的大小不等于BST
的大小时,会产生未定义的行为 - 当尝试通过一个·base class·指针,删除一个由derived class objects组成的数组,上述的问题还会再次出现,下面是你可能做出的错误尝试
void deleteArray(ostream& os,BST array[])
{
os << "Delete array,at address" <<
static_cast<void*>(array) << 'n';
delete []array;
}
编译器看到这样的句子
delete[] array;
会产生类似这样的代码,问题也就跟之前一样出现了
for(int i = the number of elements in the array-1; i >= 0; --i)
{
array[i].BST::~BST(); // 调用array[i]的 destructor
}
总结:
- 多态和指针算术不能混用,数组对象几乎总是涉及指针的算术运算,数组和多态不要混用
条款4:非必要不提供default constructor
后续看过条款43,再回头来补充
总结:
- 添加无意义的default constructors,也会影响classes的效率。如果class constructors可以确保对象的所有字段都会被正确地初始化,为测试行为所付出的时间和空间代价都可以免除。如果default constructors无法提供这种保证,那么最好避免让default constructors出现。虽然这可能会对classes的使用方式带来某种限制,但同时也带啦一种保证:当你真的使用了这样的classes,你可以预期它们所产生的对象会被完全地初始化,实现上亦富有效率
More Effective C++ 基础议题(条款1-4)总结的更多相关文章
- MoreEffectiveC++Item35(基础议题)(条款1-4)
条款1:区别指针和引用 条款2:最好使用C++转换操作符 条款3: 绝对不要以多态的方式处理数组 条款4: 避免无用的缺省构造函数 条款1:区别指针和引用 1.指针(pointer) 使用[*/-&g ...
- ###《More Effective C++》- 基础议题
More Effective C++ #@author: gr #@date: 2015-05-11 #@email: forgerui@gmail.com 一.仔细区别pointers和refere ...
- More Effective C++ - 章节一 : 基础议题
1. 仔细区分 pointers 和 references references和pointers的差别描述如下: pointer:当需要考虑"不指向任何对象"时,或者是考虑&qu ...
- More Effective C++: 01基础议题
01:仔细区别 pointers 和 references 1:没有所谓的null reference,但是可以将 pointer 设为null.由于 reference 一定得代表某个对象,C++ ...
- 《effective C++》:条款37——绝不重新定义继承而来的缺省参数值
引子: 阿里的一道题: #include <IOSTREAM> using namespace std; class A{ public: ) { cout<<"a~ ...
- Effective Modern C++ 条款1:理解模板型别推导
成百上千的程序员都在向函数模板传递实参,并拿到了完全满意的结果,而这些程序员中却有很多对这些函数使用的型别是如何被推导出的过程连最模糊的描述都讲不出来. 但是当模板型别推导规则应用于auto语境时,它 ...
- 《Effective C++》:条款46-条款47
条款46请输入转换的时候,需要定义非模板成员函数 条款47请使用traits class表现类型信息 条款46:须要类型转换时请为模板定义非成员函数 条款 24提到过为什么non-member函数才有 ...
- 《Effective C++》:条款48:理解力template
元编程
Template metaprogramming(TMP,模板元编程)这是写template-based C++规划.编译过程.template metaprogramming随着C++写模板程序,化 ...
- Effective C++ 笔记:条款 32 确定你的public继承塑造出正确的is-a关系
32 : Make sure public inheritance models "is-a." 0 引言 Inheritance and Object-Oriented Desi ...
随机推荐
- Codeforces 1010F - Tree(分治 NTT+树剖)
Codeforces 题面传送门 & 洛谷题面传送门 神仙题. 首先我们考虑按照这题的套路,记 \(t_i\) 表示 \(i\) 上的果子数量减去其儿子果子数量之和,那么对于一个合法的放置果子 ...
- 11.13python第一周周末练习
2.请输出你的基本个人信息 3.结合逻辑判断,写一个不同学生分数,输出良好,优秀,分数不及格 循环输出 字符串的替换. 以什么开头startwith 以什么结尾endwith 列表转为字符串 字符串转 ...
- Spark集群环境搭建——服务器环境初始化
Spark也是属于Hadoop生态圈的一部分,需要用到Hadoop框架里的HDFS存储和YARN调度,可以用Spark来替换MR做分布式计算引擎. 接下来,讲解一下spark集群环境的搭建部署. 一. ...
- 零基础学习java------day4------流程控制结构
1. 顺序结构 代码从上往下依次执行 2. 选择结构 也叫分支结构,其会根据执行的结果选择不同的代码执行,有以下两种形式: if 语句 switch 语句 2.1 if 语句 2.1.1 if语 ...
- 如何启动、关闭和设置ubuntu防火墙 (转载)
引自:http://www.cnblogs.com/jiangyao/archive/2010/05/19/1738909.html 由于LInux原始的防火墙工具iptables过于繁琐,所以ubu ...
- 如何从 100 亿 URL 中找出相同的 URL?
题目描述 给定 a.b 两个文件,各存放 50 亿个 URL,每个 URL 各占 64B,内存限制是 4G.请找出 a.b 两个文件共同的 URL. 解答思路 每个 URL 占 64B,那么 50 亿 ...
- oracle中分组中的ROLLUP和CUBE选项
在进行多列分组统计时,如果直接使用GROUP BY子句指定分组列,则只能生成基于所有分组列的统计结果.如果在GROUP BY子句中使用ROLLUP语句或CUBE语句,除了生成基于所有指定列的分组统计外 ...
- 时间同步之pxe,cobbler,dhcp
ntpdate 时间同步 同步方法 ntpdate ntp服务器IP 例: ntpdate 192.168.37.11 自动运行同步时间脚本 crontab -e * */1 * * * /usr/s ...
- BDD自动化测试框架cucumber(1): 最基本的demo
BDD(Behavior Driven Development),行为驱动开发, 对应自动化测试框架,python有behave,java有cucumber, 这次记录cucumber+springb ...
- Mysql资料 存储索引