面试题补充
今天有同学问到豆芽一个问题:为什么微信不能像qq一样打开多个。豆芽突然想到看样子还得补充一些面试题。豆芽尽我所能帮助到大家。
对于C++软件求职要求确实是比较高的,《蒋豆芽的秋招打怪之旅》专刊包含近400道高频面试题,今天再补充一些设计模式面试题和场景题。
设计模式面试题
说说面对对象中的设计原则
SRP(Single Responsibility Principle):单一职责原则,就是说一个类只提供一种功能和仅有一个引起它变化的因素。
OCP(Open Close Principle):开放封闭原则,就是对一个类来说,对它的内部修改是封闭的,对它的扩展是开放的。
DIP(Dependence Inversion Principle):依赖倒置原则,就是程序依赖于抽象,而不依赖于实现,它的主要目的是为了降低耦合性,它一般通过反射和配置文件来实现的。
LSP(Liskov Substitution Principle):里氏替换原则,就是基类出现的地方,通过它的子类也完全可以实现这个功能
ISP(Interface Segregation Principle):接口隔离原则,建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
CRP(Composite Reuse Principle):合成复用原则,多用组合设计类,少用继承。
单一职责原则和接口隔离原则的区别
- 单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离。
- 单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节; 而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。
说说你了解的设计模式
单例模式(要求学会手撕)
单例模式只允许创建一个活动的对象(实例),提供了对唯一实例的受控访问。
比如Windows的任务管理器,就是一个很典型的单例模式实现。
单例实现原理是,将能够创建对象的函数都设置为private,通过静态成员返回一个实例。
有两种方式,一个是懒汉式,一个是饿汉式。懒汉式需要考虑加锁。
实现代码如下:
#include <iostream> #include <pthread.h> using namespace std; class singleInstance{ public: static singleInstance* GetsingleInstance(){ if (instance == NULL){ pthread_mutex_t mutex;//mutex mlock; 加锁互斥 pthread_mutex_lock(&mutex);//mlock.lock(); if (instance == NULL){ instance = new singleInstance(); } pthread_mutex_unlock(&mutex);//mlock.unlock(); } return instance; }; ~singleInstance(){}; private:// 涉及创建对象的函数都设置为private singleInstance(){}; singleInstance(const singleInstance& other){}; singleInstance& operator=(const singleInstance& other){ return *this; }; static singleInstance* instance; }; //懒汉式 singleInstance* singleInstance::instance = nullptr; int main(){ // 因为没有办法创建对象,就得采用静态成员函数的方法返回静态成员变量 singleInstance *s = singleInstance::GetsingleInstance(); //singleInstance *s1 = new singleInstance(); // 报错 cout << "Hello World"; return 0; }
下面是饿汉式:
#include <iostream> #include <pthread.h> using namespace std; class singleInstance{ public: static singleInstance* GetsingleInstance(){ // 饿汉式,直接创建一个对象,不需要加锁 static singleInstance instance; return &instance; }; ~singleInstance(){}; private:// 涉及创建对象的函数都设置为private singleInstance(){}; singleInstance(const singleInstance& other){}; singleInstance& operator=(const singleInstance& other){ return *this; }; }; int main(){ // 因为没有办法创建对象,就得采用静态成员函数的方法返回 singleInstance *s = singleInstance::GetsingleInstance(); //singleInstance *s1 = new singleInstance(); // 报错 cout << "Hello World"; return 0; }
工厂模式(要求学会手撕)
就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。
#include <iostream> #include <pthread.h> using namespace std; //产品类(抽象类,不能实例化) class Product{ public: Product(){}; virtual void show()=0; //纯虚函数 }; class productA:public Product{ public: productA(){}; void show(){ cout << "product A create!" << endl; }; ~productA(){}; }; class productB:public Product{ public: productB(){}; void show(){ cout << "product B create!" << endl; }; ~productB(){}; }; class simpleFactory{ // 工厂类 private: Product* m; public: simpleFactory(){}; Product* product(const string str){ if (str == "productA") m = new productA(); if (str == "productB") m = new productB(); return m; }; }; int main(){ simpleFactory obj; // 创建工厂 Product* pro; // 创建产品 pro = obj.product("productA"); pro->show(); // product A create! pro = obj.product("productB"); pro->show(); // product B create! delete pro; return 0; }
工厂模式为的就是代码解耦,如果我们不采用工厂模式,如果要创建产品A、B,我们通常做法是不是用switch...case语句?那麻烦了,代码耦合程度高,后期添加更多的产品进来,我们不是要添加更多的case吗?这样就太麻烦了,而且不符合设计模式中的开放封闭原则。
为了进一步解耦,在简单工厂的基础上发展出了抽象工厂模式,即连工厂都抽象出来,实现了进一步代码解耦。代码如下:
#include <iostream> #include <pthread.h> using namespace std; //产品类(抽象类,不能实例化) class Product{ public: Product(){} virtual void show()=0; //纯虚函数 }; //产品A class ProductA:public Product{ public: ProductA(){} void show(){ cout<<"product A create!"<<endl; }; }; //产品B class ProductB:public Product{ public: ProductB(){} void show(){ cout<<"product B create!"<<endl; }; }; class Factory{//抽象类 public: virtual Product* CreateProduct()=0; }; class FactorA:public Factory{//工厂类A,只生产A产品 public: Product* CreateProduct(){ Product* _Product = nullptr; _Product = new ProductA(); return _Product; } }; class FactorB:public Factory{//工厂类B,只生产B产品 public: Product* CreateProduct(){ Product* _Product = nullptr; _Product = new ProductB(); return _Product; } }; int main(){ Product* _Product = nullptr; auto MyFactoryA = new FactorA(); _Product = MyFactoryA->CreateProduct();// 调用产品A的工厂来生产A产品 _Product->show(); delete _Product; auto MyFactoryB=new FactorB(); _Product=MyFactoryB->CreateProduct();// 调用产品B的工厂来生产B产品 _Product->show(); delete _Product; getchar(); return 0; }
观察者模式
观察者模式的作用是:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
比如拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。
代码如下:
#include <iostream> #include <list> using namespace std; class Observer{ // 观察者抽象 public: virtual void Update(int) = 0; }; class Subject{ // 被观察者抽象 public: virtual void Attach(Observer *) = 0; virtual void Detach(Observer *) = 0; virtual void Notify() = 0; }; class ConcreteObserver1:public Observer{ // 第一个观察者 public: ConcreteObserver1(Subject *pSubject):m_pSubject(pSubject){} void Update(int value){ cout << "ConcreteObserver1 get the update. New State:" << value << endl; } private: Subject *m_pSubject; }; class ConcreteObserver2 : public Observer{ // 第二个观察者 public: ConcreteObserver2(Subject *pSubject):m_pSubject(pSubject){} void Update(int value){ cout << "ConcreteObserver2 get the update. New State:" << value << endl; } private: Subject *m_pSubject; }; class ConcreteSubject:public Subject{ // 被观察者 public: void Attach(Observer *pObserver); void Detach(Observer *pObserver); void Notify(); void SetState(int state){ m_iState = state; } private: std::list<Observer *> m_ObserverList; int m_iState; }; void ConcreteSubject::Attach(Observer *pObserver){ // 添加观察者 m_ObserverList.push_back(pObserver); } void ConcreteSubject::Detach(Observer *pObserver){ // 删除观察者 m_ObserverList.remove(pObserver); } void ConcreteSubject::Notify(){ // 通知观察者 std::list<Observer *>::iterator it = m_ObserverList.begin(); while (it != m_ObserverList.end()){ (*it)->Update(m_iState); ++it; } } int main(){ // Create 被观察者 ConcreteSubject *pSubject = new ConcreteSubject(); // Create 观察者 Observer *pObserver1 = new ConcreteObserver1(pSubject); Observer *pObserver2 = new ConcreteObserver2(pSubject); // 改变状态 pSubject->SetState(2); // 注册观察者 pSubject->Attach(pObserver1); pSubject->Attach(pObserver2); pSubject->Notify();// 通知观察者 // 删除观察者 pSubject->Detach(pObserver1); pSubject->SetState(3); pSubject->Notify(); delete pObserver1; delete pObserver2; delete pSubject; }
输出如下:
ConcreteObserver1 get the update. New State:2 ConcreteObserver2 get the update. New State:2 ConcreteObserver2 get the update. New State:3
代码很容易理解,大家仔细看看。
适配器模式
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
代码如下:
#include <iostream> #include <list> using namespace std; //目标抽象类Robot(机器人接口) class Robot{ public: virtual void eat() = 0; virtual void sleep() = 0; }; //适配者类Douya(Douya类) class Douya{ public: void eat(){ cout << "豆芽吃饭" << endl; } void sleep(){ cout << "豆芽睡觉" << endl; } }; //适配器类DouyaAdapter(DouyaAdapter类) class DouyaAdapter : public Robot, public Douya{ public: void eat(){ cout << "机器人模仿:" ; Douya::eat(); } void sleep(){ cout << "机器人模仿:" ; Douya::sleep(); } }; //客户端测试类Client int main(void){ Robot *robot = (Robot*)new DouyaAdapter(); robot->eat(); // 机器人模仿:豆芽吃饭 robot->sleep(); // 机器人模仿:豆芽睡觉 delete robot; return 0; }
装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
动态地给一个对象添加一些额外的职责。
#include <iostream> #include <list> #include <memory> using namespace std; //抽象构件类Transform(变形金刚) class Transform{ public: virtual void move() = 0; }; //具体构件类Car class Car : public Transform{ public: Car(){ cout << "变形金刚是一辆车!" << endl; } void move(){ cout << "在陆地上移动。" << endl; } }; //抽象装饰类 class Changer : public Transform{ public: Changer(shared_ptr<Transform> transform){ this->transform = transform; } void move(){ transform->move(); } private: shared_ptr<Transform> transform; }; //具体装饰类Robot class Robot : public Changer{ public: Robot(shared_ptr<Transform> transform) : Changer(transform){ cout << "变成机器人!" << endl; } void say(){ cout << "说话!" << endl; } }; //具体装饰类AirPlane class Airplane : public Changer{ public: Airplane(shared_ptr<Transform> transform) : Changer(transform){ cout << "变成飞机!" << endl; } void say(){ cout << "在天空飞翔!" << endl; } }; //客户端测试 int main(void){ shared_ptr<Transform> camaro = make_shared<Car>(); camaro->move(); cout << "--------------" << endl; shared_ptr<Robot> bumblebee = make_shared<Robot>(camaro); bumblebee->move(); bumblebee->say(); return 0; }
上面的代码没有改变Car类的内部结构,还为其增加了新的功能,这就是装饰器模式的作用
策略模式
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。该模式使得算法可独立于使用它的客户而变化。
策略模式为了适应不同的需求,只把变化点封装了,这个变化点就是实现不同需求的算法,但是,用户需要知道各种算法的具体情况。
#include <iostream> using namespace std; // The abstract strategy class Strategy{ // 策略抽象 public: virtual void AlgorithmInterface() = 0; }; class ConcreteStrategyA : public Strategy{ public: void AlgorithmInterface(){ cout<<"I am from ConcreteStrategyA."<<endl; } }; class ConcreteStrategyB : public Strategy{ public: void AlgorithmInterface(){ cout<<"I am from ConcreteStrategyB."<<endl; } }; class ConcreteStrategyC : public Strategy{ public: void AlgorithmInterface(){ cout<<"I am from ConcreteStrategyC."<<endl; } }; class Context{ public: Context(Strategy *pStrategyArg) : pStrategy(pStrategyArg){}; void ContextInterface(){ pStrategy->AlgorithmInterface(); } private: Strategy *pStrategy; }; int main(){ // Create the Strategy Strategy *pStrategyA = new ConcreteStrategyA; Strategy *pStrategyB = new ConcreteStrategyB; Strategy *pStrategyC = new ConcreteStrategyC; Context *pContextA = new Context(pStrategyA); Context *pContextB = new Context(pStrategyB); Context *pContextC = new Context(pStrategyC); pContextA->ContextInterface(); // 这里就实现了使用不同的策略 pContextB->ContextInterface(); pContextC->ContextInterface(); if (pStrategyA) delete pStrategyA; if (pStrategyB) delete pStrategyB; if (pStrategyC) delete pStrategyC; if (pContextA) delete pContextA; if (pContextB) delete pContextB; if (pContextC) delete pContextC; }
好了,设计模式还有很多,但是豆芽觉得上面几种应该是最常问的了。
场景题
接下来的问题就比较考察思考能力了,往往是面试官突然想到或者是他自己平时思考过的问题,突然拿出来问,这种问题首先不要慌,基于自己的知识架构的情况下冷静分析,可能无法一下子拿出答案,但是分析思考接近答案,也是面试官希望看到的,有的面试官比较好,可能还会逐渐引导我们。
这种题呢回答不出来不要紧,重在参与,嘻嘻。当然了,也别重在参与哈,努力回答出来最好。
《蒋豆芽的秋招打怪之旅》专刊把知识体系框架给大家构建起来了,今天我们就相当于实战一下。
故事背景
蒋 豆 芽:小名豆芽,芳龄十八,蜀中人氏。卑微小硕一枚,科研领域苟延残喘,研究的是如何炒好一盘豆芽。与大多数人一样,学习道路永无止境,间歇性踌躇满志,持续性混吃等死。会点编程,对了,是面对对象的那种。不知不觉研二到找工作的时候了,同时还在忙论文,豆芽都秃了,不过豆芽也没头发啊。
隔壁老李:大名老李,蒋豆芽的好朋友,技术高手,代码女神。给了蒋豆芽不少的人生指导意见。
导 师:蒋豆芽的老板,研究的课题是每天对豆芽嘘寒问暖。
对上百万数量的手机号码进行排序怎么做?
隔壁老李:来,豆芽你说说吧,你有什么思路?
蒋 豆 芽:(手一挥)我上来就是一顿梭哈,冒泡排序、插入排序挨个用一遍。
隔壁老李:(惊呼)好家伙。
蒋 豆 芽:(嘻嘻)开玩笑的,好歹我也是参加过秋招的人。新人没有实际经验,上来就是排序啊,冒泡排序、插入排序。这种回答基本就凉了。时间复杂度为O(n^2)。大家以后要注意,笔试面试题不允许出现O(n^2)以上的解法,除非真的没有更好的解法了。
我有点经验,知道有更快的排序方法,可以使用快排或归并排序。时间复杂度为O(nlogn)。
隔壁老李:时间是满足了,但是要考虑到“上百万”数量,说明量很大,快排和归并是基于数组的基础上排序的,什么意思呢?就是先要把数据读入到数组内,也就是读入我们的内存中。问题来了,我们的内存有那么大吗?能够容纳“上百万”数量的数据吗?
蒋 豆 芽:(挠头)意思是内存没那么多是吧,我想想哈。。。既然内存不够,那我们就借助外存,那具体应该怎么做呢?
隔壁老李:豆芽,你再想想,其实已经接近这个方向了。
蒋 豆 芽:老李,经过我深思熟虑之后,不知道。
隔壁老李:没事,豆芽,我们具体来讲讲。所以依赖内存不行,我们就需要借助外存,我们试想一下,我们可以先排序一部分数据,然后把这部分数据写入文件中,再接着排一部分数据,又写入另一个文件中。然后就可以合并,把两个有序的数组合并成一个有序的数组,你想到了什么?
蒋 豆 芽:(牛客题霸)NC22.合并两个有序的数组!原来这道题实际应用场景在这里,平时刷题我都没有注意到。
隔壁老李:也正常哈,知识学到一定程度就会突然悟道,没悟道之前有人点拨一下也可以有所领悟。
我们接着讲。那当然了,两个文件没办法同时读入内存,那我们就每一百行每一百行读,然后两个数组进行归并,成功归并的元素立马写入新的文件中,这样按步骤下来,不就完成了两个文件的排序了吗?
如果是“亿万”数量的数据量呢?那就多分几个文件,这就是k路归并排序算法,对应(牛客题霸)NC33.合并k个升序链表。豆芽你可以做做这两道题。
参考答案:
这个题呢,采用暴力排序肯定是不行的,如冒泡排序、插入排序。时间复杂度在O(n^2)。所以要考虑更快的排序方法。
如果内存允许的话,就直接采用快排或归并排序。
如果内存不允许的话,就借助外存,采用k路归并排序算法。具体是将这些数据拆分成k组,分别写入k个文件中,每个文件的数据足以读入内存中,然后对每一个文件分别完成排序,最后再k路归并,完成整体排序。
隔壁老李:豆芽你注意了没,我们在回答面试官问题的时候,不要一上来就给出准确答案,而是采用一种递进式的思考方式去接近答案,这是面试官想看到的。
请说说k路归并排序算法思路
蒋 豆 芽:我们有k个链表,各自是排好序的,接下来我们拿出每个链表的第一个节点参与一轮比较,找出最小的元素,剔除最小元素,接着把最小元素所处的节点的下一个节点又拿出来参与下一轮比较,找出最小元素。这样按步骤进行,完成k路归并为一个完整排序的链表。(参考答案)
隔壁老李:你说说复杂度吧。
蒋 豆 芽:我们拿出每个链表的第一个节点参与一轮比较,那么就是k个节点,每一轮比较时间复杂度为O(k^2),每一个链表可能包含n个节点,总的时间复杂度为O(n*k^2),因为我们没用到辅助空间,空间复杂度为O(1)。
隔壁老李:那你能再优化一下吗?
蒋 豆 芽:没问题,我们在进行每一轮比较时,既然需要的是最小元素,那么我们可以采用优先队列,每次弹出最小元素,然后新的元素再入队列,重新调整堆的时间复杂度为O(logk),k个节点调整k次,时间复杂度为O(klogk),总的时间复杂度为O(n*klogk)。因为用到优先队列,空间复杂度为O(k)。
过程如下:
隔壁老李:怎么样,豆芽,平时一直都会使用归并排序,但是归并真正的思想是不是通过这道题有所感悟了。
蒋 豆 芽:嗯嗯,有味道。
从百万数据中如何挑选出前100个高频数据
蒋 豆 芽:这个我知道了。对于百万数据,全部读入内存排序肯定是不行的,只需要前100个高频数据,可以考虑使用优先队列,我们只需要构建一个包含k个节点的最大堆,接下来n个数据都入队列进行比较,时间复杂度为O(nlogk),空间复杂度为O(k)。
从百万数据中去掉重复的数据
蒋 豆 芽:还是一样的!不能读入内存,所以我们要借助外存,将百万数据分成k组,写入k个文件中,对每个文件分别去重,怎么样,老李,我是个天才吧?
隔壁老李:好家伙,你自己想想对吗?虽然每个文件中没有重复的数据了,但是文件与文件之间数据可能重复啊!
蒋 豆 芽:(挠挠头)还真是,这可怎么办呢?
隔壁老李:你想想我们《蒋豆芽的秋招打怪之旅》专刊里面有讲过,如何将一个固定的数据存入数组中固定的下标位置?(参考哈希表的原理)
蒋 豆 芽:(恍然大悟)哈希啊!数据就如同我们的键值对,文件就像我们的数组下标,通过哈希映射,就可以保证相同的数据只会写入固定的文件中,然后再针对每个文件进行去重。
我悟了了呀!老李。原来哈希思想使用这么普遍,太有意思了!
隔壁老李:不错,豆芽,孺子可教也。你来总结一下参考答案吧。
蒋 豆 芽:遵命!我们可以考虑暴力去重,但是时间复杂度较高,为O(n^2)。如果内存足够的话,可以采用哈希表去重。如果内存不足的话,我们就借助外存,设置k个文件,然后将所有数据哈希映射至文件中,通过哈希映射,可以保证相同的数据只会写入固定的文件中。最后再对每个文件进行去重。就完成整体数据去重了。
给定一个数组,求取第k大的值。(内存可以读入)
蒋 豆 芽:我就不客气了。可以直接排序,挑选出第k大的数,时间复杂度O(nlogn),空间复杂度O(1)。
可以使用堆排序。时间复杂度O(nlogk),空间复杂度O(k)。
很简单嘛!
隔壁老李:现在我需要你进一步降低时间复杂度。
蒋 豆 芽:what!还有更好的解法?
隔壁老李:想想快排是怎么实现的?
蒋 豆 芽:快排原理是设置一个基准元素,比基准元素小的放左边,比基准元素大的放右边。这样基准元素的下标我们就知道了,好家伙!我悟了!
基准元素的下标我们知道了,基准元素是第几大我们就清楚了啊!如果基准元素的下标为Index,
while (index == k){ if (index > k) 抛弃index右边所有的元素,对左边的元素继续排序 else if (index < k) 抛弃index左边所有的元素,对右边的元素继续排序 }
这样整个排序的过程就特别像二分查找,算法的时间复杂度为:O(N)--详情参考算法导论,空间复杂度O(1)。好家伙!
为什么微信不能像qq一样打开多个?
蒋 豆 芽:肯定啊,微信团队马小龙说不能就是不能,我管那么多干嘛!
隔壁老李:我问你的是,技术上怎么实现?
蒋 豆 芽:我思考一下哈。(偷偷打开百度搜索,好家伙,竟然没搜到,好吧,自己思考一下)
因为《蒋豆芽的秋招打怪之旅》专刊里面有讲过,一个可执行程序通过“包装”成为一个进程,一个可执行程序通过“包装”成为多个进程也没有问题,可执行程序和进程是一对多的关系,这就是为什么qq可以打开多个。
而微信只能打开一个,那么说明我们需要对进程做限定,保证进程唯一性。
那么这个时候如何保证进程唯一性呢?专刊里面又讲过互斥锁,通过互斥锁我们就可以保证了。但是是哪一个类型的互斥锁呢?
隔壁老李:豆芽,你能思考到这一步,已经很不错了,面试官看到我们的思考,也是很有意义的。
这个时候你再查查百度搜索基本就可以找到答案了。
那就是文件锁。
我们在微信源码里面加上文件锁,那么就只有一个进程对微信读写,即使多开几个进程也获取不了微信的可执行程序了,开了等于没开。
蒋 豆 芽:我悟了。基础扎实,很多场景题都是基于扎实的基础啊!
计算类的大小
#include<iostream> using namespace std; class A{}; class B{ public: B() {} ~B() {} }; class C{ public: C() {} virtual ~C() {} }; int main(){ cout << "空类的大小:"<< sizeof(A) << endl; //1 cout << "带构造函数和析构函数的类的大小:" << sizeof(B) << endl; //1 cout << "带虚函数的类的大小:" << sizeof(C) << endl; //32位机为4,64位机为8 return 0; }
- 空类也要占一个字节,这是C++的规定,为什么呢?因为有一个字节,才能保证类的地址独一无二。
- 函数是不占空间的。
- 类存在虚函数时,编译器会分配虚函数表指针,那么指针变量当然会占用空间了。32位机为4,64位机为8(虚函数表与表指针在专刊里有详细讲解,不再赘述)。
#include<iostream> using namespace std; class D { char c; void func() {} }; class E { char c1; //占用1字节 char c2; //占用1字节 virtual void func() {} }; class F { int in; virtual void func() {} }; int main(){ cout << "D类的大小"<< sizeof(D) << endl; //1 cout << "E类的大小" << sizeof(E) << endl; //32位机为8,64位机为16 cout << "F类的大小" << sizeof(F) << endl; //32位机为8,64位机为16 }
- 有了变量,就要占用空间,比如D类,char c占用一个字节的空间
- 同样遵循字节对齐的原则。E类中,一个指针变量占8字节,那么前面要补齐8字节,一共16个字节(字节对齐内容在专刊里有详细讲解,不再赘述)。
- 同理2
#include<iostream> using namespace std; class A{}; class B{ public: B() {} ~B() {} }; class C{ public: C() {} virtual ~C() {} }; class D { char c; void func() {} }; class G :public A, public C {}; class H :public A, public B, public C {}; class I :public A, public B, public D {}; class J :public A, public C, public D {}; int main(){ cout << "G的大小" << sizeof(G) << endl; //8 cout << "H的大小" << sizeof(H) << endl; //8 cout << "I的大小" << sizeof(I) << endl; //1 cout << "J的大小" << sizeof(J) << endl; //16 }
- 存在继承关系时,空类不占用空间
- 子类的成员变量同样占用空间
- 同样遵循内存对齐原则
针对于非科班和基础不太好的同学,建议学习本人专刊文章《蒋豆芽的秋招打怪之旅》,该专刊文章对每一个知识点进行了详细解析。本专刊采用讲故事的形式为大家串联、呈现每一个知识点,做到有趣有干货、通俗易懂,春秋招本是痛苦难捱的过程,我希望通过自己讲故事能缓解大家的压力与焦虑,并将知识轻松学到手。适合C++软件开发和嵌入式软件开发的同学们。
网址: https://www.nowcoder.com/tutorial/10078/index
全部评论
(5) 回帖