Effective C++(1~4)

1 视C++为一个语言联邦

View C++ as a federation of languages

众所周知,C++是由C语言发展而来的。因此,C++语言需要兼容C语言,而C语言是面向过程语言,相比其它面向对象语言C++还继承了C语言的缺陷。
C++语言虽然存在缺陷,但是它仍然是面向对象语言,受到其它面向对象语言的“激励”,C++语言也在不断变化,C++语言已经不是C with class那个时代了。《Effective C++》一书中说道,C++是一门“联邦式的语言”。现今强大的C++语言大体分以下几个部分:

  1. C:区块、语句、预处理、内置数据类型等,缺陷是C语言没有模板,没有重载,没有异常。
  2. Object-Oriented C++:这部分是C with classes 的诉求,继承、多态、封装、virutal函数(动态绑定)
  3. Template C++:泛型编程部分,高效的程序是适用于多种场景,多种参数类型的程序。template功能很强大,它带来了崭新的编程范型,也就是template metaprogramming(TMP模板元编程)。
  4. STL:作为C++程序员必须对STL有一定掌握,STL对容器、算法、迭代器以及函数对象的规约有极佳的配合和协调。
  • C++高效编程守则视情况而定,需要考虑所写程序对应的部分。

2 尽量以const、enum、inline替换#define

Prefer consts,enum,and inline to #defines

对于宏,我们知道,宏不是函数,它只是简单的替换,表达式替换过程可能因为运算符优先级问题造成不可预料的错误,宏替换发生在预处理阶段。

# define PI 3.14

遇到数学问题的圆周率,C语言时候我们经常会使用上面的宏定义。从这句代码开始,我们都是PI来代替3.14进行计算,后续代码中就会出现多份3.14。而如果我们使用const定义一个常变量,使用常变量来计算,就不会存在冗余的数据。

const float PI=3.14;

在class内部的成员,我们也需要const来定义一个常变量,#define没有作用域限制,一旦宏被定义,他就在其后的编译过程有效。#define不仅不能用来定义class专属常量,也不能提供封装性,const就不会存在这些问题。

class A{
private:
	static const float PI;
};
static const float A::PI=3.14; 

假如,我们的class专属常量还需要保持一份,是的,我们需要加static,static成员必须在类外进行初始化,这时候采用enum hack就可以实现在类内对常量成员进行初始化。

class A{
private:
	enum{PI=3.14};
};

这种状况就不能对enum对象取地址,取地址就会出错。
实际上,使用宏还需要注意是否会存在副作用导致错误。这里是关于宏的总结

  • 对于单纯常量,最好以const对象或者enums替换#define
  • 对于形似函数的宏,最好改用inline函数替换#define

3 尽可能使用const

Use const whenever possible

  1. 常指针、常变量、常指针常变量
#include<iostream>
int main()
{
	int a = 10;
	const int* p1 = &a;//指向常变量的指针p1,不能通过p1改变a的值,p1可以指向其它地方
	int* const p2 = &a;//常指针p2,只能指向b
	const int* const p3 = &a;//指向常变量的常指针,p3只能指向b,不能通过p3改变b的值
}
  1. const_iteraor给出const T*类型的指针,不能通过const_iterator修改元素内容。声明常量迭代器,T* const指针,迭代器不能移动(不能++或者–)。
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string str({"I love C plus plus..."});
	const string::iterator it1 = str.end();//it1只能指向str的尾元素位置,不能移动
	string::const_iterator it2 = str.begin();//it2可以指向其它位置,it2具有只读权限
	while (it2 != it1)
	{
		cout << *it2;
		it2++;
	}
	cout << endl;
	return 0;
}
  1. 当函数返回值不希望被赋值时候,可声明为const类型。这样可以避免一些外界意外发生。
#include<iostream>
using namespace std;

class Rational{
public:
	const Rational operator*(const Rational& r){}
};
int main()
{
	Rational a, b, c;
	//(a*b) = c;这种暴行就会报错
	return 0;
}
  1. 两个成员函数,如果只是常量属性不同,可以被重载。
#include<iostream>
#include<string>
using namespace std;

class Text{
public:
	char& operator[](size_t pos)//non-const对象
	{
		return str[pos];
	}
	const char& operator[](size_t pos)const//const对象专属
	{
		return str[pos];
	}
private:
	string str;
};
int main()
{
	Text text;
	const char c1 = text[0];
	char c2 = text[0];
	c2 = text[2];
	return 0;
}
  1. bitwise constness和logical constness
  • bitwise constness又称physical constness,认为成员函数只有在不更改对象之任何成员变量时才可以说是const,也就是一个bit位都不会被改变。const成员函数不可以更改对象内任何non-static成员变量。所以const成员函数中不能存在=,否则就会报错。
  • 实例中,在const成员函数中,我们知道=不会改变对象内容时候,我们需要将被赋值的变量,给与它mutable属性。这个时候我们所遵循的就是logical constness,即就是逻辑上的常量性。
  1. non-const调用const版本,减少代码冗余
class Text{
public:
	const char& operator[](size_t pos) const		//const对象专属,不会改变
	{
		return str[pos];
	}
	char& operator[](size_t pos)
	{
		return const_cast<char&>(static_cast<const Text&>(*this)[pos]);
	}
private:
	string str;
};

(static_cast<const Text&>(*this)将this指针转换成const类型,调用[ ]会调用const成员函数,类型为const char&,使用const_cast去除const属性。

为何不使用const成员函数调用non-const成员函数? const成员函数承诺自己不会改变对象内容,但是它却使用了non-const成员函数,如果non-const成员函数改变对象内容,那么const成员函数就犯错了。

  • 编译器强制实施bitwise constness,编写程序时候应该使用“概念上的常量性”。
  • const和non-const成员函数有着实质等价时,令non-const调用const版本。

4 使用对象前确定其已经被初始化

Make sure that objects are initialized before they’re used
C part of C++(STL的array)不保证初始化,non-C of C++(STL的vector)会保证初始化。
因此为避免错误,最好在对象定义时候直接初始化。

  1. 编译器初始化顺序是按照对象声明顺序初始化的。 栗子:array初始化前需要先初始化size。
  2. 构造函数最好将所有对象按照声明顺序在初始化列表初始化,防止遗漏。不在构造函数内赋值的原因是,构造函数在进行函数体之前,会执行初始化列表进行初始化,为了提高效率,避免多余操作。
class First_name{};
class Last_name{};
class  Person
{
public:
	Person()
		:first_()
		, last_()
		, age_(20)
	{}

private:
	First_name first_;
	Last_name last_;
	int age_;
};
  1. 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local对象。

函数内的static对象称为local static对象,作用域仅在函数体内;其它static对象称之为non-local static对象,它们会在main函数结束时候被销毁。

当编译单元存在两个以上函数non-local static对象时候,其中一个non-local static对象初始化依赖另一个non-local static对象时候,由于C++对“定义于不同的编译单元内的non-local static 对象”的初始化相对次序并无明确定义。就可以能存在错误,避免错误的方法就是使用local static代替non-local static对象,典型例子:Singleton模式。

  • 对内置对象进行手动初始化,因为C++不保证初始化它们。
  • 构造函数最好使用初始化列表按照声明顺序初始化,而不是构造函数内赋值。
  • 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local对象。

猜你喜欢

转载自blog.csdn.net/Vickers_xiaowei/article/details/93408811