你是如何看待C++和C的关系?
最初C++是C语言加上一些面向对象的特性,但随着语言的发展,C++支持更多概念和特性,变得比C语言更具弹性和灵活性。现在C++是一个语言联邦,包含一下四个部分
1、C语言
2、面向对象C++
3、泛型编程
4、STL
C++是在C语言基础上,包含了其他特性而发展而来的。
介绍一下static和const
const允许我们定义一个语义约束,告诉编译器某个对象不应该被改变,编译器会强制帮助我们实施这一约束。如果我们定义某个值不能被改变,就应该使用const,来让编译器帮助我们保证这个条件不被违反。
可以修饰全局变量,局部变量
修饰函数返回值,函数参数
修饰指针本身,指针所指对象
修饰类的成员变量,类的成员函数
static
静态局部变量:在函数内声明的静态变量,只初始化一次,每次使用会维持前值。
静态全局变量:全局作用域内声明的静态变量,不能在其他源文件中访问。
静态函数:只能在本源文件内使用
静态成员变量:是类的所有对象共享的成员变量,它不属于特定对象而属于类,它在类的所有对象之间只有一个实例。
静态成员函数:不依赖于类的任何特定对象的成员函数,没有this指针,所以无法对类中的非静态成员访问。
程序在内存中的分布情况
高地址
内核空间 存储和运行操作系统内核的代码和数据 |
栈 系统自动分配释放,函数参数,局部变量等 |
文件映射区 |
堆 程序员管理,new创建出来的对象 |
.bss 未初始化,初始化为0的静态变量和全局变量 |
.data(数据段) 初始化了的静态变量和全局变量 |
.test(代码段)rodata(只读数据段)包含代码区和常量区 |
低地址
C++是如何实现动态多态的?(虚函数实现原理?)
C++是通过虚函数来实现动态多态的,动态多态的核心是程序在运行时才根据对象的实际类型决定调用哪个类的成员函数。它的实现原理是对每一个包含虚函数的对象分配一个虚函数指针,这个虚函数指针指向一个放在常量区的虚函数表。在运行时运行到某个虚函数时,根据虚函数指针来找对应的虚函数表,然后在虚函数表中找到对应的函数完成调用,从而实现动态多态。
一个类有几个虚函数表?一个类的对象有几个虚函数指针?
一个类没有继承父类,内部有虚函数,就有一个虚函数表。
一个类继承几个父类,就有几个虚函数表。
一个类有几个虚函数表,一个类的对象就有几个虚函数指针。
一个类对象的虚函数指针在类对象的内存布局?
类的对象虚函数指针按照继承父类的顺序,从对象的首地址开始依次排布。
虚函数表中函数的排布原则
1、虚函数按照其声明顺序存放在表中。
2、单继承中:父类的虚函数在子类的虚函数前面,依次按照声明顺序排列,共同形成一张表。
3、多继承中:子类的续表和第一个继承的父类合并成一张表。剩下的父类单独形成多个虚函数表。
如何让对象只能产生于栈中?(这个问题有争议)
堆上分配:Cat* cat = new Cat(); new operator分配内存 → 构造函数
栈上分配:Cat cat; 自动调用构造函数和析构函数
重写operator new,将operator new设置为私有的。这时候使用new分配运算尝试在堆上分配内存就无法编译通过,就将内存的分配限制了只能分配在栈上。
当禁用了operator new,程序员还可否通过其他方式“强行”在堆上创建对象?
可以,通过placement new,或者用更底层的方式“手工”创建一个堆对象
如何让对象只能产生于堆中?
将对象的析构函数设为私有(构造函数也可以,但构造函数可能有多个,不方便),因为在栈上分配对象时,编译器自动调用对象的构造函数和析构函数,因此此时如果栈上分配内存会编译报错,就将内存的分配限制了只能分配在堆上。
什么是智能指针,分哪几种?
智能指针是对裸指针的封装,让程序员无需手动释放内存,避免内存泄漏,本质是模板类。
常见的指针指针auto_ptr unique_ptr shared_ptr weak_ptr
这几种智能指针的区别和优缺点
auto_ptr在C++11已经废弃,取而代之的是unique_ptr,相比之下,unique_ptr语义更清晰,更加安全,不允许复制,更加高效,更好的支持数组和C++11特性(std::move, std::forward)
unique_ptr主要的特点是对资源是独占的,它不可以复制。它通过在析构函数中释放资源来管理对象的生命周期,来自动管理资源。可以防止多个智能指针指向同一个对象,更加方便于管理。相比shared_ptr它的优点是高效,避免了循环引用的问题。
当一个资源需要被多个对象共享的时候,就使用shared_ptr。它通过引用计数的方式来对资源进行管理,存在循环引用的问题,需要结合weak_ptr来避免。
weak_ptr本身不具备内存管理能力,它主要是为了解决shared_ptr可能导致的循环引用的问题。weak_ptr的原则是指向某个资源时,但它不会增加这个资源的引用计数。
具体讲一下shared_ptr自动管理内存的原理/引用计数的具体原理/shared_ptr引用计数什么时候增加,什么时候减少?
引用计数的核心原理:
shared_ptr的内部维护了一个计数器,来跟踪有多少个shared_ptr对象指向了某个资源。计数器的值减少到0时,shared_ptr就会调用析构函数来释放资源。
引用计数器何时增加:
新建一个shared_ptr并指向一个资源时。
拷贝构造函数创建一个新shared_ptr时。
拷贝赋值运算符将一个shared_ptr给另一个shared_ptr赋值时。
引用计数器何时减少:
当一个shared_ptr对象被销毁时,比如局部变量离开作用域,或者类成员变量析构时。
当一个shared_ptr对象不在指向一个资源,例如拷贝赋值运算符,通过reset。
循环引用如何发生,如何解决?
两个或多个对象互相引用,导致资源无法被释放掉。需要使用weak_ptr来打破循环引用,因为weak_ptr指向某一个资源时,不会增加这个资源的引用计数。
#include <iostream>
#include <memory>
using namespace std;
/*weak_ptr是为了配合shared_ptr引入的,它指向一个由shared_ptr管理的资源但不影响资源的生命周期*/
class AA {
public:
string m_name;
AA() { cout << m_name << "调用构造函数AA()。" << endl; }
AA(const string& name) : m_name(name) {
cout << "调用构造函数AA(" << m_name << ")" << endl;
}
~AA() { cout << "调用析构函数~AA(" << m_name << ")" << endl; }
shared_ptr<AA> m_sp; // 如果循环引用,引用计数永远无法归0,资源不会被释放
weak_ptr<AA> m_wp; // weak_ptr不影响引用计数
};
// 如果循环引用,引用计数永远无法归0,资源不会被释放
void test01() {
shared_ptr<AA> pa(new AA("西施a"));
shared_ptr<AA> pb(new AA("西施b"));
cout << "pa.use_count() = " << pa.use_count() << endl; // 1
cout << "pb.use_count() = " << pb.use_count() << endl; // 1
// pa->m_sp = pb;
// pb->m_sp = pa;
pa->m_wp = pb;
pb->m_wp = pa;
cout << "pa.use_count() = " << pa.use_count() << endl;
cout << "pb.use_count() = " << pb.use_count() << endl;
}
int main() {
test01();
return 0;
}
weak_ptr是弱引用,“弱”在哪了?
弱在weak_ptr本身不具备内存管理能力,指向某个资源时,不会增加这个资源的引用计数。
shared_ptr是线程安全的吗?
shared_ptr的引用计数的增加和减少是线程安全的,在多线程同时访问同一个智能指针,会导致数据竞争和未定义行为