Effective C++ 之《让自己习惯C++》

条款01:视C++为一个联邦语言

  一开始C++只是在C的基础上加了一些面向对象 的特性。随着语言的逐渐成熟。我们发现它相比其他语言更加的灵活和自由。有时候甚至对其产生迷惑。我在使用的时候也会经常遇到这样的疑惑,好像它的每一个“适当用法”都是例外,这样会让人产生纠结。
  条款一告诉我们,我们应该视其为一个语言联邦而非单一语言,正因为它的约束规则相比其他语言少了很多,它才给了开发者更多的自用,来创造不同的使用方法。根据条款一的说法,我们为了更好的理解C++这门语言,其实只要读懂它的4个语言:

1. C。 C++仍是以C为基础,它的区块、语句、预处理、内置数据类型、数组、指针等都来自C++,这是C的语法基础,也是C++的语法基础。
2. Object-Oriented C++。 这部分就是C with Classes,它包括类、封装、继承、多态、虚函数,这是面向对象语言的基础。
3. Template C。 这是C++的泛型编程部分。掌握好这部分,可以使你的工作效率事半功倍,这也是大部分C++程序员(包括我)所缺乏的部分。
4. STL。 STL是一个模板程序库。在C++项目工程中,我们会很常见。它对容器、算法、迭代器、函数对象的规约有极佳的紧密配合和协调。
  这四个次语言都有各自的规约,如果我们将C++视为一个语言联邦,当我们在C++中切换使用这四种次语言时,就不会感到奇怪了。

总结

C++ 高效编程守则视状况而变化,取决于你使用C++的哪一部分。

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

1. 使用const替换#define
   通常情况下,我们会在头文件中使用#define定义一些常量,考虑如下情况:

#define ASPECT_RATIO 1.653

   这样使用确实没有问题。但是,会有如下几个小问题:
   (1). 如果在使用该常量的地方出现编译错误,我们可能不会第一时间知道它错误的地方。编译器提示的错误可能是1.653而不是ASPECT_RATIO,这是因为ASPECT_RATIO在预处理器的   时候已经被替换成了1.653,编译器并不知道记号ASPECT_RATIO的存在。
   (2). 使用 ASPECT_RATIO可能会导致更多的目标码。这是因为预处理器会将所有使用ASPECT_RATIO的地方替换成1.653,而使用const则不会出现这种情况。
   (3). 使用ASPECT_RATIO时,没有安全类型检查,与const相比,它时不安全的。
   所以,我们可以在头文件中替换它为:

const double AspectRatio = 1.653;

  当然在使用const的时候需要注意如下几点:
  (1)对于指针,我们使用const,保证指针不被改变,以及指针所指向的内容不被改变。

const char* const authorName = "edwin";

或者使用标准库:

const std::string authorName = "edwin";

  (2)如果需要在类中定义const成员,需要使用static进行修饰。(当然这种情况很少使用),通常在类中,我们会使用enum替代#define。
   2. 使用enum替换#define
  在实际项目中,我们常常会使用一些序列值表示类型。如读写操作。这时,又不想该类型为全局的,所以就需要在类中定义枚举来替代#define的使用:

class MyFile{
	private:
		enum {
			Read_Only = 0,
			Write_Only = 1,
			Read_Write = 2
		}
		...
}

   3. 使用inline替换#define
  Effective C++提供了如下使用#define的例子,这也是大多时候我们使用的方式:

#define CALL_WITH_MAX(a, b) f((a) > f(b) ? (a) : (b))

  这是比较两个数的大小的宏函数。犹豫宏时预处理器自动替换的语法糖。所以如果不加下括号,在使用过程中很可能出现错误的输出。但即使加了这么多括号,也会出现我们意想不到的情况:

int a = 5, b = 0;
CALL_WITH_MAX(++a, b);						// a被累加了两次
CALL_WITH_MAX(++a, b + 10);				//a被累加了一次

  如上,a的累加次数,取决于它的比较对象。当它的比较对象小于它时,它被累加了两次,这显然不是我们想要的结果。
  所以对于这种宏函数,我们最好使用inline函数来替换:

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

总结

  1. 对于单纯常量,最好以const对象或enums替换#define;
  2. 对于形似函数的宏(macros),最好使用inline函数替换#define;

条款03:尽可能使用const

   const在c++中经常使用。它指定了一个语义约束–指定一个不被改动的对象。const可以修饰全局变量、命名空间内的变量、类中的静态和非静态成员变量、指针、函数等。
   1. const指针
   考虑如下的关于const对于指针的修饰:

char cP[] = "hello"; 
const char* p1 = cP;					//const 指针所指向的数据
char* const p2 = cP;					//const 指针本身
const char* const p3 = cP;				//const指针且const指针所指向的数据

  这里区分它们的方法也很简单,如果const在*左边这修饰的时指针所指向的数据,反之修饰的是指针本身。
   2. const迭代器
  c++标准库提供一套迭代器的使用。

std::vector<int> vec;
...
const std::vector<int>::iterator iter1 = vec.begin(); //const修饰的是迭代器iter1,所以iter不可以改变
std::vector<int>::const_iterator iter2 = vec.begin(); //const修饰的是迭代器iter2所指向的内容

   3. 函数返回const常量
  设置函数返回值为const常量,可以避免用户的错误使用而造成的意外。这样做即保证了其安全性,也保证了其安全性。

class Rational{...}
const Rational Add operator* (const Rational& a, const Rational& b);

  如上所示,如果用户使用了重载操作符*,做如下的操作:

Rational a, b, c;
if(a * b = c) {...};

  这个时候编译时,编译器会提示错误,因为重载*函数返回的时const常量,值不能被改变。如果我们不使用const修饰,这样编译会通过,但是运行会得到意想不到的结果。
   5. const成员函数
   const成员函数的作用于const对象上,即const成员函数内部不能对成员变量进行改写。
   (1). c++中的一个重要特性。

两个成员函数如果只是常量性不同,可以被重载。

   所以就会出现如下的情况:

class TextBlock {
public:
	...
	const char& operator[](std::size_t position) const {
		return text[position];
	}
	char& operator[](std::size_t position) {
		return text[position];
	}
private:
	std::string text;
}

   那么该如何调用以上的重载函数呢。

void print(const TextBlock& ctb, TextBlock& tb) {
	std::cout << tb[0];
	tb[0] = 'x';
	std::cout << ctb[0];
	ctb[0] = 'x';				//错误,对const TextBlock对象进行改写。
}

   上面的注释是因为operator[]的返回类型是const,另外non_const的operator[]返回类型是char&,如果是char的话,程序是无法编译过的。
   (2). const成员函数修改成员变量。
  如果我们需要在const成员函数中写成员变量,则需要使用mutable修饰成员变量。这个是logical constness的主张者。
   (3). 在const 和non-const成员函数中避免重复
  考虑当两个重载函数中内容一样,且代码比较多时,如果分别实现了两个函数后,就会出现很多的重复代码。所以如何去解决这种问题呢。

class TextBlock {
public:
	...
	const char& operator[](std::size_t position) const {
		...
		return text[position];
	}
	char& operator[](std::size_t position) {
		return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
	}
private:
	std::string text;
}

  如上,在non-const版本中将this先转换成const对象,然后调用const的重载,然后再将返回的const对象转换成非const引用类型。需要注意的是,我们不能用const成员函数调用non-const的版本,这是因为const成员函数绝不改变其对象的逻辑状态。
总结

将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
编译器强制实施bitwise constness ,但你编写程序时应该使用“概念上的常量性”(conceptual constness)。
当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

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

  该条款相对好理解。主要有如下:

  1. 为内置型对象进行手工初始化,因为C++不保证初始化它们。
  2. 构造函数最好使用成员初值列,而不要再构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们再class中的声明次序相同。
  3. 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。

  关于第三条,因为non-local static对象不能保证初始化的次序,所以使用local static替换non-local static对象。这样就可以保证第二条的初始化次序。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

非正经程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值