C++面经总结之《Effective C++》(一)_面试总结

面试感触

最厉害的面经,往往是如此的朴实无华(没别的意思,我的意思是,在座的面经,要成体系啊、)。
当时老师跟我说:你有多少水平,面试官是面的出来的。
所以刷那么多面经干嘛呢?还不如找套成体系的书或者课程,把自己真的会的东西拾掇拾掇来的实在。

我还是觉得,学习一项新技术,最难的不是学习本身,最难的是知道这个新技术。

学习能力摆在这里,目前有心仪的目标了。踏踏实实继续学吧。


条款

以下为我自己的读书感悟,如果各位有时间,建议自行搜索或购买这本书阅读,封面在开头有了、

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

C
标准C++
模板C++
STL

一千个哈姆雷特有一千个读者,大家自行体会吧、

我觉得,我得真的去接触一下模板编程了、
STL源码剖析也要再看一遍了,毕竟一年多了。


2、以编译器替换预处理器

首先第一点,就是你很可能在报错的时候找不到是什么情况。
这是我自己遇到的,但是我想不起来了。

碧如书中给的栗子:#define ASPECT_RATIO 1.653
报错的时候可能只给你个1.653, 你去哪里找这个1.653是个什么鬼、

我是想不起来当时的场景了,但是我知道当时使用了排除法排查了很久才找到。

解决之道:const double ASPECT_RATIO = 1.653

可能会有人说:define可以用来做宏定义函数,不会带来函数调用的开销。我不做评判,我只说我个人感觉,就是麻烦,而且不确定因素存在,最根本的原因就是我不了解它,而且也没那个欲望。


3、尽可能使用const

我就不吭声、
有多少人(还没参加工作的)知道写代码的时候应该尽可能使用const,有多少人真的在做?

好,我不多说,我自己也没在用这个。
之后会尽量记得把const添上的。

有个小细节需要注意的:

const void* a,这个时候,被const的是a
void * const a,这个时候,被const的是指针
const void* const a,这个时候,a和指针都被const

对于const的成员函数可以这样写:

const char& operator[](std::size_t position) const

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

我们是否有写过这种代码:

int a,b,c;
a = 0;
b = 0;

成员变量有时候会初始化为0(或null),有时候不会。

记的不是很清楚,那时候没有写博客。我在做培训班的毕业项目的时候,曾经遇到过这么一个问题:数据包传输数据时不时的乱码。

当时就觉得很奇葩,你要传就传,不传就不传呗,传过去了你乱码是几个意思?
最后排查下来,问题出在了封装数据所用的char*没有在声明的时候被置空,出现了脏数据。后来给它初始化的时候手动置空,解决了问题。

反正呢,养成好习惯,在对象声明的时候给它一个身份。


5、了解C++空类默认构造函数

什么时候空类不再是一个空类呢?当C++处理过它之后。

如果你自己没声明,编译器会为它声明:一个拷贝构造函数、一个默认构造函数、一个析构函数、以及一个赋值操作符。

这个当时有问过,没想起来。


6、若不想使用编译器自动生成的函数,就明确拒绝

如果你不希望class支持某一特定机能,只要不声明对应函数就是了。
但这个策略对上面那几个不起作用的。

那怎么办?
其实吧,你可以将它们初始化为私有的,并且不去定义它们。


7、在多态虚基类中声明析构函数

这个就不提啦,会有内存泄漏。

8、别让异常逃离析构函数

这个上次也被问到了,但是没答上来。像这种知识点,看过就答得上来了。

C++并不禁止析构函数吐出异常,但它不鼓励你这样做。这是有理由的。
考虑以下代码:

class Widget{
public:
	···
	~Widget(){···}
};

void dosomething(){
	std::vector<Widget> v;
}	//v在这里被自动销毁

当vector被销毁,它有责任带走所有的Widget。但是呢,如果在析构过程中,有个异常被抛出,那后面还没被析构的Widget怎么办?

好,继续。再被抛出异常,这也不是什么很奇怪的事情,有一就有二嘛。
在两个异常同时存在的情况下,程序要么强制结束,要么就是导致不明确行为。那就很尴尬了。


以下情况一直存在于我自己的代码中:将close函数置于析构函数中,并祈求不会出现问题(其实有时候我自己也不知道是不是真的被close了)。

只要调用成功,那就万事大吉、
但是如果该调用导致异常,那析构函数就会传播该异常,造成难以预料的问题。


解决办法有,之前看大佬的代码里有写,我也模仿过,但终究是不知道什么意思,放出来,现在知道了:

void DB::DB_close(){
	db.close();
	this->closed = true;
}

DB::~DB(){
	if(!this->closed){
		try(db,close();)
		catch(···){	//Python的异常抛出用的可六了,C++的倒是没有体验过
			日志记录
		}
	}|
}

一般而言,将异常吞掉是一个坏主意,因为它压制了某些动作失败的重要信息。但是“不明确行为”也不是什么好事儿。

所以,就做一个接口,给客户端自行去调用关闭函数吧。

记住,析构函数一定不要吐出异常!!!
实在没办法,那咱也要像个硬汉,吞下去!!!


9、绝不在构造和析构过程中调用(自身)virtual函数

这个我看了很久。因为从第一眼,我就想到了线程池。
线程创建的时候,那不就得要传一个任务父类的run函数进去嘛?

后来看了半天,是不要在构造函数和析构过程中调用自身的虚函数,因为在这两个过程中,都会从最原始的那个父类开始往下执行,但是那时候它并不会去下降到子类的虚函数实现当中去。
或者可以这么说:在base class构造(销毁)期间,virtual函数不是virtual函数。