《More Effective C++》 笔记1

条款1:区别指针和引用

  1. 没有所谓的null reference。
  • reference总是指向一个对象。所以如果有一个变量,既可以指向一个对象,又可以不指向任何对象,那使用指针。
  • 如果必须指向一个对象,不能是null,那就用引用。
  • reference的使用可能会比指针高效,因为引用不需要测试其有效性,而指针一般都需要先检查是否指向null、
  1. pointers可以被重新赋值,而reference总是指向最初获得的那个对象。
string s1("Jeff");
string s2("Jack");

string& rs = s1;
string* ps = s1;

rs = s2; 	//现在rs仍指向s1,但是s1的内容变成Jack
ps = &s2 ;	// ps指向s2

一旦代表了该值,便不再改变的,一般选用引用。

  1. 有些情况需要使用引用。

比如某些操作符 , 如[ ] 操作符,就需要返回引用。

条款2:最好使用C++转型操作符

在C语言中,经常使用(type)value的方式去强制转换一个变量。但这种转换类型一是不够精确,二是很难识别。

C++中转型操作符:

名称 作用
static_cast 基本拥有与C就是转换符相同的威力和意义,但是也有相同限制
const_cast 改变表达式中的常量性以及变易性
dynamic_cast 用来执行继承体中“安全向下转型或跨系转型动作”,也就是可以将基类的引用或指针转换为派生类引用或指针。如果转型失败返回null
reinterpret_cast 转换函数指针

//static_cast<type>(value)

int x = 10, y = 4;
cout << x / y << endl;
cout << static_cast<double>(x) / y << endl;

在这里插入图片描述
(啊第一次用visual studio 2019…)

static_cast的功能有限,比如不能将struct转为int,不能将double转为pointer,甚至不能移除变量的常量性。

//const_cast<type>(value)

class Base
{
    
    
	int x;
public:
	Base():x(10){
    
    }
	void show() {
    
     cout<<x<<endl; }
	void modify(int i) {
    
     x = i; }
};

class Derived :public Base
{
    
    
};

void update(Derived* pdr)
{
    
    
	pdr->modify(5);
}
int main()
{
    
    
	int x = 10, y = 4;
	//cout << x / y << endl;
	//cout << static_cast<double>(x) / y << endl;
	
	Derived d;
	const Derived& r = d;
	const_cast<Derived&>(r).show();
	//update(r);

	update(const_cast<Derived*>(&r));
	const_cast<Derived&>(r).show();
	return 1;
}

切记切记: 如果你声明了引用为const,但是类是non-const类型的,则你不能用此引用去调用类的所有成员函数,因为类型不符合,所以你只能麻烦的一个个去转换,多此一举啦。

在这里插入图片描述
//dynamic_cast<type>()

将基类指针或引用转换成派生类指针或引用,不要跟基类指针可以指向派生类而派生类指针不可以指向基类对象的这个概念混淆

	Derived d;
	Base* pb = &d;
	Base& rb = d;
	pb->show();
	update(dynamic_cast<Derived*>(pb));
	//update(dynamic_cast<Derived*>(&rb)); 将引用转换,然后指针
	pb->show();

ATTENTION: 其实上面的//const_cast部分也犯了一个小错误,有派生类就应该把基类的析构函数设置为virtual的。这样可以避免错误。
这一部分如果没有把析构函数设置为virtual会发生error

条款3:不要以多态方式处理数组

// eg1.假如有一个打印Base数组中每一个元素的函数:

class Base
{
    
    
	....
};

class Derived : public Base
{
    
    
	...
};

void printBase(ostream& os, const Base arr[], int Size)
{
    
    
	for (int i = 0; i < Size; i++)
		os << arr[i] << " ";
}
  1. 传入Base 数组
  2. 传入Derived 数组(编译器也不会报错)
Base ba[10];
printBase(cout, ba, 10);
Derived da[10];
printBase(cout, da, 10);

但是我们知道循环的原理,arr[i]是指针算术表达式简写,represent*(arr+i),其中arr是个指针指向数组开始位置,arr+iarr相差了i*sizeof(数组中对象)个距离,而数组中对象就是Base,而不是Derived.
至少编译器是这么想,但是一般情况下,Derived都是要比Base大的,所以可能会发生错误。

// eg2. 尝试用Base指针删除一个Derived数组

void deleteDerived(ostream& os, Base arr[])
{
    
    
	os << "Deleting array at address " 
	<< static_cast<void*>(arr) << "\n";
	delete[] arr;
}

Derived* d = new Derived[50];
deleteDerived(cout, d);

其中delete[] arr会产生这样的代码

for(int i = Size-1; i>=0;i--)
	arr[i].Base::~Base()	//调用arr[i]的析构函数

通过Base指针来删除Derived数组,其结果是未定义的.

Summary: 多态和指针算术不能混用。且如果你能避免让一个具体类继承自另一个具体类,可以带来许多好处。

条款4:非必要不提供默认构造函数

default constructor就是在没有任何外来信息的情况下将对象初始化。但是有许多对象,没有外来信息就没有办法执行一个完全的初始化动作。比如学生类,没有名字你初始化个锤子。

但不提供默认构造函数会带来一些麻烦:
1.当你的class缺乏一个默认构造函数,你使用此class会有一些限制 比如:

错误情况因为没有初始化
class Student
{
    
    
	private:
		string name;
	public:
		Student(string n):name(n){
    
    
			cout<<name<<" is created\n";
		}
};

int main()
{
    
    
	// 产生数组时,会error
	Student class_1 [10] ;
	Student* class_2 = new Student [20]; 

解决方法:
① 使用non-heap数组

string s1("Jeff"),s2("Jack"),s3("Jason");
	Student class_1[]{
    
    
		Student(s1),
		Student(s2),
		Student(s3)
	};

缺点:无法延伸至heap数组

②使用指针数组而非对象数组

Student* class_3[10];
Student** class_2 = new Student* [10];
	for(int i=0;i<10;i++)
		class_2[i] = new Student("Jeff");

缺点:必须将此数组所指的所有对象删除。而且你需要的内存总量比较大,因为你需要一些空间用来放置指针,还需要一些空间放类对象。

③分配raw memory后使用placement new。

	void* rawMemory = operator new[](10* sizeof(Student));
	Student* class_1 = static_cast<Student*>(rawMemory) ;
	for(int i=0;i<10;i++)
		new(&class_1[i]) Student("Jeff");

这里先不对operator new[ ]做详细说明,先知道用法:
首先rawMemory指向分配好的内存空间,然后Student指针指向这片内存空间。然后给每个Student指针所指的类new.

缺点:你必须在数组内对象结束生命时,手动调用析构函数。最后还得调用operator delete[ ]

for(int i=0;i<10;i++)
	class_1[i].~Student();
operator delete []

不可以写delete [ ] class_1,因为不是来自new operator,而是placement new.

2.它们将不适合于许多模板类

3.最后,如果是 virtual base classes,则一定要提供默认狗杂函数
因为virtual base class constructors的自变量必须由最深层次的派生类提供。如果没有默认构造函数,则最深层次的派生类则必须了解并且提供基类的构造函数。

但是真的不是什么情况都要提供默认构造函数,

  1. 有时候提供一个默认构造函数会起到画蛇添足的作用。比如一个必须要被初始化有姓名的Student类,你搞一堆无名怪有啥用。
  2. 提供默认构造函数可能会影响效率。你可能要检测一个对象是否被初始化了。如果你能够保证默认构造函数能给你带来正确的初始化,那无疑是提高效率的。

猜你喜欢

转载自blog.csdn.net/ZmJ6666/article/details/108731144
今日推荐