More Effective C++ 条款1,2

发信人: tsbob (差一点被鲜花插上), 信区: VisualC
标  题: More Effective C++ 条款1(转载)
发信站: 北大未名站 (2001年11月02日18:47:04 星期五), 站内信件

【 以下文字转载自 ICST 讨论区 】
【 原文由 scarto 所发表 】

条款一:指针与引用的区别

指针与引用看上去完全不同(指针用操作符’*’和’->’,引用使用操作符’.’),
但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什
么时候使用指针,在什么时候使用引用呢?
首先,要认识到在任何情况下都不能用指向空值的引用。一个引用必须总是指向某些对
象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指
向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,
如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明
为引用。
“但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?”
char *pc = 0;          // 设置指针为空值
char& rc = *pc;        // 让引用指向空值
这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事
情都有可能发生),应该躲开写出这样代码的人除非他们同意改正错误。如果你担心这
样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的
程序员去做。我们以后将忽略一个引用指向空值的可能性。
因为引用肯定会指向一个对象,在C里,引用应被初始化。
string& rs;             // 错误,引用必须被初始化
string s("xyzzy");
string& rs = s;         // 正确,rs指向s
指针没有这样的限制。
string *ps;             // 未初始化的指针
                        // 合法但危险
不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在
使用引用之前不需要测试它的合法性。
void printDouble(const double& rd)
{
    cout << rd;         // 不需要测试rd,它
}                       // 肯定指向一个double值
相反,指针则应该总是被测试,防止其为空:
void printDouble(const double *pd)
{
  if (pd) {             // 检查是否为NULL
    cout << *pd;
 }
}
指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是
引用则总是指向在初始化时被指定的对象,以后不能改变。
string s1("Nancy");
string s2("Clancy");
string& rs = s1;         // rs 引用 s1
string *ps = &s1;        // ps 指向 s1
rs = s2;                 // rs 仍旧引用s1,
                         // 但是 s1的值现在是
                         // "Clancy"
ps = &s2;                // ps 现在指向 s2;
                         // s1 没有改变
总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(
在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象
(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对
象后就不会改变指向,那么你应该使用引用。
还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符
[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。
vector<int> v(10);       // 建立整形向量(vector),大小为10;
                         // 向量是一个在标准C库中的一个模板(见条款35)
v[5] = 10;               // 这个被赋值的目标对象就是操作符[]返回的值
如果操作符[]返回一个指针,那么后一个语句就得这样写:
*v[5] = 10;
但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这
有一个有趣的例外,参见条款30)
当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必
要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针。

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

仔细想想地位卑贱的类型转换功能(cast),其在程序设计中的地位就象goto语句一样
令人鄙视。但是它还不是无法令人忍受,因为当在某些紧要的关头,类型转换还是必需
的,这时它是一个必需品。
不过C风格的类型转换并不代表所有的类型转换功能。一来它们过于粗鲁,能允许你在任
何类型之间进行转换。不过如果要进行更精确的类型转换,这会是一个优点。在这些类
型转换中存在着巨大的不同,例如把一个指向const对象的指针(pointer-to-const-ob
ject)转换成指向非const对象的指针(pointer-to-non-const-object)(即一个仅仅去
除cosnt的类型转换),把一个指向基类的指针转换成指向子类的指针(即完全改变对象
类型)。传统的C风格的类型转换不对上述两种转换进行区分。(这一点也不令人惊讶,
因为C风格的类型转换是为C语言设计的,而不是为C++语言设计的)。
 二来C风格的类型转换在程序语句中难以识别。在语法上类型转换由圆括号和标识符组
成,而这些可以用在C++中的任何地方。这使得回答象这样一个最基本的有关类型转换
的问题变得很困难,“在这个程序中是否使用了类型转换?”。这是因为人工阅读很可
能忽略了类型转换的语句,而利用象grep的工具程序也不能从语句构成上区分出它们来

C++通过引进四个新的类型转换操作符克服了C风格类型转换的缺点,这四个操作符是,
static_cast, const_cast, dynamic_cast, 和reinterpret_cast。在大多数情况下,对
于这些操作符你只需要知道原来你习惯于这样写,
(type) expression
而现在你总应该这样写:
static_cast<type>(expression)
例如,假设你想把一个int转换成double,以便让包含int类型变量的表达式产生出浮点
数值的结果。如果用C风格的类型转换,你能这样写:
int firstNumber, secondNumber;
...
double result = ((double)firstNumber)/secondNumber;
如果用上述新的类型转换方法,你应该这样写:
double result = static_cast<double>(firstNumber)/secondNumber;
这样的类型转换不论是对人工还是对程序都很容易识别。
static_cast 在功能上基本上与C风格的类型转换一样强大,含义也一样。它也有功能上
限制。例如,你不能用static_cast象用C风格的类型转换一样把struct转换成int类型或
者把double类型转换成指针类型,另外,static_cast不能从表达式中去除const属性,
因为另一个新的类型转换操作符const_cast有这样的功能。
其它新的C++类型转换操作符被用在需要更多限制的地方。const_cast 用于类型转换掉
表达式的const或volatileness属性。通过使用const_cast,你向人们和编译器强调你通
过类型转换想做的只是改变一些东西的constness 或者 volatileness属性。这个含义被
编译器所约束。如果你试图使用const_cast来完成修改constness 或者 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
//函数中能被更新)
update((SpecialWidget*)&csw);
                         // 同上,但用了一个更难识别
//的C风格的类型转换
Widget *pw = new SpecialWidget;
update(pw);         // 错误!pw的类型是Widget*,但是
                    // update函数处理的是SpecialWidget*类型
update(const_cast<SpecialWidget*>(pw));
                    // 错误!const_cast仅能被用在影响
                    // constness or volatileness的地方上。,
                    // 不能用在向继承子类进行类型转换。
到目前为止,const_cast最普通的用途就是转换掉对象的const属性。
第二种特殊的类型转换符是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
                         // 确实指向了某个对象
                         // 否则将抛出异常
dynamic_casts在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上
(参见条款24),也不能用它来转换掉constness:
int firstNumber, secondNumber;
...
double result = dynamic_cast<double>(firstNumber)/secondNumber;
                         // 错误!没有继承关系
const SpecialWidget sw;
...
update(dynamic_cast<SpecialWidget*>(&sw));
                         // 错误! dynamic_cast不能转换
                         // 掉const。
如你想在没有继承关系的类型中进行转换,你可能想到static_cast。如果是为了去除c
onst,你总得用const_cast。
这四个类型转换符中的最后一个是reinterpret_cast。这个操作符被用于的类型转换的
转换结果几乎都是实现时定义(implementation-defined)。因此,使用reinterpret_
casts的代码很难移植。
reinterpret_casts的最普通的用途就是在函数指针类型之间进行转换。例如,假设你有
一个函数指针数组:
typedef void (*FuncPtr)();      // FuncPtr is 一个指向函数
                                // 的指针,该函数没有参数
                                 // 也返回值类型为void
FuncPtr funcPtrArray[10];       // funcPtrArray 是一个能容纳
                                // 10个FuncPtrs指针的数组
让我们假设你希望(因为某些莫名其妙的原因)把一个指向下面函数的指针存入funcPt
rArray数组:
int doSomething();
你不能不经过类型转换而直接去做,因为doSomething函数对于funcPtrArray数组来说有
一个错误的类型。在FuncPtrArray数组里的函数返回值是void类型,而doSomething函数
返回值是int类型。
funcPtrArray[0] = &doSomething;     // 错误!类型不匹配
reinterpret_cast可以让你迫使编译器以你的方法去看待它们:
funcPtrArray[0] =                   // this compiles
  reinterpret_cast<FuncPtr>(&doSomething);
转换函数指针的代码是不可移植的(C++不保证所有的函数指针都被用一样的方法表示)
,在一些情况下这样的转换会产生不正确的结果(参见条款31),所以你应该避免转换
函数指针类型,除非你处于着背水一战和尖刀架喉的危急时刻。一把锋利的刀。一把非
常锋利的刀。
如果你使用的编译器缺乏对新的类型转换方式的支持,你可以用传统的类型转换方法代
替static_cast, const_cast, and reinterpret_cast。也可以用下面的宏替换来模拟新
的类型转换语法:
#define static_cast(TYPE,EXPR)       ((TYPE)(EXPR))
#define const_cast(TYPE,EXPR)        ((TYPE)(EXPR))
#define reinterpret_cast(TYPE,EXPR)  ((TYPE)(EXPR))
你可以象这样使用使用:
double result = static_cast(double, firstNumber)/secondNumber;
update(const_cast(SpecialWidget*, &sw));
funcPtrArray[0] = reinterpret_cast(FuncPtr, &doSomething);
这些模拟不会象真实的操作符一样安全,但是当你的编译器可以支持新的的类型转换时
它们可以简化你把代码升级的过程。
没有一个容易的方法来模拟dynamic_cast的操作,但是很多函数库提供了函数,安全地
在派生类与基类之间的进行类型转换。如果你没有这些函数而你有必须进行这样的类型
转换,你也可以回到C风格的类型转换方法上,但是这样的话你将不能获知类型转换是否
失败。当然,你也可以定义一个宏来模拟dynamic_cast的功能,就象模拟其它的类型转
换一样:
#define dynamic_cast(TYPE,EXPR)     (TYPE)(EXPR)
请记住,这个模拟并不能完全实现dynamic_cast的功能,它没有办法知道转换是否失败

我知道,是的,我知道,新的类型转换操作符不是很美观而且用键盘键入也很麻烦。如
果你发现它们看上去实在令人讨厌,C风格的类型转换还可以继续使用并且合法。然而正
是因为新的类型转换符缺乏美感才能使它弥补了在含义精确性和可辨认性上的缺点,并
且使用新类型转换符的程序更容易被解析(不论是对人工还是对于工具程序),它们允
许编译器检测出原来不能发现的错误。这些都是放弃C风格类型转换方法的强有力的理由
,还有第三个理由:也许让类型转换符不美观和键入麻烦是一件好事。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值