交易系统开发技能及面试题之c++基础特性

本文聚焦C++在交易系统开发中的核心特性,通过一系列面试问题探讨C++的内存管理、多态、智能指针、STL和Boost库等关键知识点,帮助开发者深入理解C++并应对面试挑战。
摘要由CSDN通过智能技术生成

概要:

C++当前在交易系统开发技能中还占主要核心位置,其核心特性在于保障低时延,以及提供系统处理能力。当然其也有缺点:写好c++真的很难,经常容易因为内存泄漏,操作越界等原因导致系统崩溃。程序的稳健性也是系统的考量目标之一,所以在掌握语言特性的本身,也可以学习其他新的语言,比如Rust。回到正题,本章内容主要列了关于c++面试的常见问题,除此之外,还希望大家掌握 STL、Modern C++、Boost库等。

Q1 空类实例化对象的sizeof

class Test {
   
};
Test t;
Test t2;

一个对象的大小大于等于所有非静态成员大小的总和,这里没有任何成员,那么是不是应该为0呢?

其实不然,如果值为0的话,那么t 与 t2 就应该是同个实例吧。其实每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器会往隐藏一个字节

Q2 指针操作优先级 *p++

const char* p = "Test";

c++中, 运算符优先级高,*p++ 等同于 *(p++), p++会返回旧值,*(p) 输出T后,p指向e字符

Q3 c-string作为map key时会有什么问题

以上代码定义了key为const char* 的map,其key的默认比较函数参数为两个 char 指针,而两个不同的指针可以指向同样的字符串内容,那么违背了key的唯一性,我们需要自定义比较函数,来处理C-字符串。

struct StrCmp {
   
	bool operator()(char const *a, char const *b) const {
   
		//是遍历c字符串比对
		return std::strcmp(a, b) < 0;
	}
}
//
map<const char *, int , StrCmp> strMap;

Q4 虚函数表的Sizeof大小

class Test {
   
public:
 virtual ~Test() {
   
 }
};

Test t;

可以看到Test类包含了虚函数,为了实现多态,编译器采用了虚函数表的方式,即t对象包含了一个指针,这个指针指向了虚函数表,即sizeof(t)统计了指针的大小(32位系统指针大小为 4, 64位系统指针大小为8)

Q5 new/new[]/malloc 对应 delete/delete[]/free

int *p = new int[10];
p++;
delete[] p;

以上代码是不安全的,p++ 将指针移动到指向p+1的地址,该地址并不是new[] 分配的,所以使用delete[] 不是合法行为。

再看以下代码:

int main() {
   
	int x = 5;
	delete &x;
}

x是一个栈变量,并不是通过new创建的,所以使用delete也是不合法行为。

又比如以下代码:

class Test {
   };
Test *p = new Test();
free(p);

因为p是通过new创建的,所以不能通过free释放,而应该通过delete,只有malloc分配的内存由free关键字释放

Q6 统计对象的sizeof

class Test {
   
public:
	static int x;
	char c;
};
int Test::x = 3;
Test t;

在前文讲到过,sizeof统计的是对象非静态成员的大小,那么sizeof(t) 只计算了char c,即 sizeof(t) 输出 1个字节,因为Test已经不是一个空类了, 所以编译器没必要隐含一个字节。

Q7 类的默认构造函数

class Test {
   
public:
	Test(const Test& obj) {
   
	}   
};
Test t;

以上代码不能正常编译,因为我们自定义了Test的构造函数,所以编译器不会创建默认构造函数,这个时候 t的实例化因为没有合适的构造函数而失败。

通常我们会自定义构造函数来组织编译器这种默认行为,默认构造函数会初始化成员变量,如果某成员无法初始化(没有默认构造函数时),会编译失败。

当需要自定义构造函数又需要默认构造函数时,可以这样定义:

Test() = default

Q8 类的构造函数

class Test{
private:
	int *p;
	
public: 
	Test() {
		p = new int(10);
	}
	~Test(){
		delete p;
	}

	//拷贝构造函数 Test t; Test t1(t);
	Test(const Test& obj) {
		this->p = obj.p;
	}

	//赋值构造函数 Test t; Test t1 = t;
	Test& operator=(const Test& obj) {
		this->p = obj.p;
		return *this;
	}

	//modern c++ 移动构造函数
	Test(Test &&obj) noexcept {
		this->p = obj.p;
		obj.p = nullptr;
	}
};

因为Test类包含了指针的成员变量,如果使用编译器默认的构造函数,会引发c++中的深拷贝、浅拷贝问题,所以需要我们自定义赋值、拷贝构造函数来屏蔽编译器默认的构造函数。
深拷贝和浅拷贝主要是针对类中的指针和动态分配的空间来说的,浅拷贝只是简单的复制指针的地址,并不是实例化对象,在没有自定义赋值构造函数情况下:
Test t; Test t1(t); 构造两个实例化对象 t跟t1时, t1的p指针与t的指针地址一致(浅拷贝复制指针值),当t析构时会释放t的指针p,而t1进行析构时,因为t1.p指向的地址内存已经被t释放了,造成了double free的情况。

Modern c++ 引入了左值、右值的概念,但存在有所有权要转移情况下,优化临时变量的创建,我们可以定义移动构造函数。
拷贝构造函数里 使用了&符号,其表示的是一个左值引用,可使用&&来表示右值,右值表示所引用的对象将要被销毁,该对象没有其他的用户,可以被当前使用者窃取数据,类似于rust所讲的所有权转移。这里的函数使用了noexcept关键字,表示当前函数不会抛出异常,即通知标准库不需要对异常处理做额外的工作,可以提升效率。

因为是窃取obj的数据,而obj表示即将销毁的对象,那么它会调用析构函数,同样会释放obj的p指针,所以这里将其值设置为nullptr,避免窃取过来的内存被obj给释放。

如果不需要自定义构造函数又不想让编译器默认生成构造函数时,我们可以使用delete关键字

class Test{
   
private:
	int *p;
	
public: 
	Test() {
   
		p = new int(10);
	}
	~Test(){
   
		delete p;
	}
	
	Test(const Test& obj) delete;
	
	Test& operator=(const Test& obj) = delete;
};

Q9 构造函数、析构函数是否可抛出异常

因为构造函数不像其他的函数有返回值(比如返回错误码),而当需要在构造函数做资源准备等一些逻辑操作时,可以抛出异常来处理实例化的错误情况。当然了,也可以像windows api一样,通过getlasterror来查看是否有错误,可使用threalocal的存储技术。

析构函数在编程规范上不建议抛出异常,而是在析构函数里将所有的错误情况处理完再正常退出实例,保证实例的生命周期是完整的。

Q10 虚函数表

class
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值