Effective C++学习笔记(一)

条款02:尽量以const,enum,inline替换#define

因为使用#define会出现如下的问题:

问题一:

#define ASPECT_RATIO = 1.653
以上语句中的记号名称ASPECT_RATIO也许从未被编译器看见;也许在编译器开始处理源码之前它就被预处理器移走了。于是在你运用此常量时,可能获得一个编译错误信息,原因是你所使用的名称可能并没有进入记号表(symbol table)。

解决之道是以一个常量替换上述的宏:

const double AspectRatio = 1.6653;

当我们以常量替换#defines,有两种特殊情况值得注意。第一是定义常量指针(constant pointers)。由于常量定义式通常被放在头文件内(以便被不同的源码含入),因此有必要将指针(而不是指针所指之物)声明为const。第二个值得注意的是class专属常量。为了将常量的作用域(scope)限制于class内,必须让它成为class的一个成员;而为了确保此常量至多只有一份实体,你必须让它成为一个static成员:

class GamePlayer {
private:
      static const int NumTurns = 5;  //常量声明式
      int scores[NumTurns];              //使用该常量
};
而上述变量NumTurns是声明式而非定义式。通常C++要求对所使用的任何东西提供一个定义式,所以对该变量必须另外提供定义式:

const int GamePlayer::NumTurns;
由于class常量已在声明时获得初值,因此定义时不可以再设初值。然而旧式编译器也许不支持上述语法,它们不允许static成员在其声明式上获得初值。此外所谓的“in-class初值设定”也至允许对整数常量进行。如果不支持这种声明时初始化,可以把初始值放在定义式。

不过这里有个例外,当你在class编译期间需要一个class常量值,例如上述的GamePlayer::scores的数组声明式中。这时候万一你的编译器不允许“static整数型class常量”完成“in-class初值设定”,可以改为“the enum hack”补偿做法。其理论基础是:“一个枚举类型的数值可以权充ints被使用”,于是GamePlayer可定义如下:

class GamePlayer {
private:
    enum{  NumTurns = 5 };
    
    int scores[NumTurns];
};

问题二:

另一个常见的#define误用情况是以它实现宏(macros)。宏看起来像函数,但不会招致函数调用带来的额外开销。下面这个宏夹带着宏实参,调用函数f:

int a = 5, b = 0;
CALL_WITH_MAX(++a, b);       //a被累加二次
CALL_WITH_MAX(++a, b);       //a被累加一次
在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”!无论何时你写出这种宏,你必须记住为宏中的所有实参加上小括号,否则就会出现上述的问题。

解决上述问题可以使用内联函数,你可以获得宏带来的效率以及一半函数的所有可预料行为和类型安全性:

template<typename T>
inline void callWithMax(const T& a, const T& b)
{
      f(a>b?a:b);
}
小结

(1)对于单纯常量,最好以const对象或enum替换#define。

(2)对于形似函数的宏,最好改用inline函数替换#define。


条款03:尽可能使用const
令函数返回一个常量值,往往何以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。如下的例子:

class Rational {……};
const Rational operator* (const Rational& lhs, const Rational& rhs);
返回一个const对象可以避免如下的暴行:

Rational a, b, c;
...
(a*b) = c;
在a*b的成果上调用operator=,将返回类型设置为const,而const类型对象不允许被复制,所以就可以避免上述的错误。

const成员函数
将const实施于成员函数:

一可以使class接口比较容易被理解。因为得知哪个函数可以改动对象内容而哪个函数不行,很重要;

二是使操作const对象成为可能。因为只有const成员函数才能操作const对象。

注意:两个成员函数如果只是常量性(constness)不同,可以被重载,如下class所示:

class TextBlock {
public:
...
const char& operator[](std::size_t position) const  // operator[] for
{ return text[position]; } // const objects
char& operator[](std::size_t position) // operator[] for
{ return text[position]; }    // non-const objects
private:
std::string text;
}; 
这里需要弄清楚一个问题:成员函数如果是const意味着什么?有两种流行概念:bitwise constness  (也叫 physical constness) 和 logical constness。

bitwise constness认为:成员函数只有在不更改对象的任何成员变量(static除外)时才可以说是const。也就是说它不更改对象内的任何一个bit。这正是C++对常量性的定义,因此const成员函数不可以更改对象内任何non-static成员变量。不过,一个更改了“指针所指物”的成员函数虽然不算是const,但如果只有指针隶属于对象,那么称此函数为bitwise const不会引发编译器异议。

logical constness认为:一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才可以。

在const和non-const成员函数中避免重复

class TextBlock {
public:
...
const char& operator[](std::size_t position) const
{
...  // do bounds checking
... // log access data
... // verify data integrity 
return text[position];
}
char& operator[](std::size_t position )
{
... // do bounds checking
... // log access data
... // verify data integrity
return text[position];
}
private:
std::string text;
};
如上两个重载函数实际上都在做一些相同的动作,这将严重影响系统的优化,可以令non-const operator[]调用其const兄弟是一个避免代码重用的安全方法——该过程需要一个转型动作:

class TextBlock {
public:
...
const char& operator[](std::size_t position) const // same as before
{
...
...
...
return text[position];
}
char& operator[](std::size_t position) // now just calls const op[]
{
return
const_cast<char&>( // cast away const on op[]'s return type;
static_cast<const TextBlock&>(*this) // add const to *this's type;
[position] // call const version of op[]
);
}
...
};
这里先将*this从原始类型TextBlock转换为const TextBlock(这里使用static_cast),然后是从const operator[]的返回值中移除const(使用const_cast)。

小结

(1)将某些东西声明为const可帮助编译器侦测出错误用法。

(2)当const和non-const成员函数有着实质等价的实现时,令non-const调用const版本可避免代码重复。

条款04:确定对象被使用前已先被初始化

注意别混淆赋值(assignment)和初始化(initialization)这两个概念。

class PhoneNumber { ... };
class ABEntry { // ABEntry = "Address Book Entry"
public:
ABEntry(const std::string& name, const std::string& a ddress,
const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones; 
int num TimesConsulted;
};
ABEntry::ABEntry(const std::string& name, const std::string& 
address,
const std::list<PhoneNumber>& phones)
{
theName = name;  	// 这些都是赋值
theAddress = address;  // 而不是初始化
thePhones = phones
numTimesConsulted = 0;
}
上述构造函数内的的操作是赋值而不是初始化的原因是: C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。可以通过使用成员初值列(member initialization list)替换赋值动作。注意要在初始列中列出所有成员变量,以免出现因对内置类型未初始化,而直接使用它的值,而造成“不明确行为”。






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值