高级专栏系列:
- 嵌入式面经解析大全专栏链接 :嵌入式/C++面试题解析大全
- 嵌入式面经解析大全目录详情 : 嵌入式面经111道面试题全解析C/C++可参考
- 嵌入式项目交流分享链接:嵌入式项目交流分享(附源码)
- 安卓高频面经解析大全专栏链接:150道安卓高频面试题全解析
- 安卓高频面经解析大全目录详情 : 安卓面经_anroid面经_150道安卓常见基础面试题全解析
- 安卓系统Framework面经专栏链接:Android系统Framework面试题解析大全
- 安卓系统Framework面经目录详情:Android系统面经_Framework开发面经_150道面试题答案解析
- Android进阶知识体系解析专栏链接:Android进阶知识体系解析
- Android进阶知识体系解析目录详情:Android进阶知识体系解析_20大安卓进阶必备知识点
本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业嵌入式和安卓开发经验,该专栏整理本人对常见嵌入式高频开发面试题的理解;
网上嵌入式资料千千万,笔者将继续维护专栏,一杯奶茶价格不止提供答案解析,承诺提供专栏内容免费技术答疑,直接咨询即可。助您提高嵌入式面试准备效率,为您面试保驾护航!
本专栏内容主要是面试过程口头提问的问题答案汇总,订阅后送C++资料和笔试真试题,欢迎嵌入式或者安卓交流哈!
正文开始⬇
文章目录
第二章 C/C++高频面试题 (本文讲2.2.1-2.2.4)
2.1 c和c++区别、概念相关面试题
2.1.1 new和malloc的区别⭐⭐⭐⭐⭐
2.1.2 malloc的底层实现⭐⭐⭐⭐
2.1.3在1G内存的计算机中能否malloc(1.2G)?为什么?⭐⭐
2.1.4指针与引用的相同和区别;如何相互转换?⭐⭐⭐⭐⭐
2.1.5 C语言检索内存情况 内存分配的方式⭐⭐⭐
2.1.6 extern”C” 的作用⭐⭐⭐
2.1.7 extern容易忽略的知识点⭐⭐⭐⭐
2.1.8函数参数压栈顺序,即关于__stdcall和__cdecl调用方式的理解⭐⭐⭐
2.1.9重写memcpy()函数需要注意哪些问题⭐⭐
2.1.10数组到底存放在哪里⭐⭐⭐
2.1.11 struct和class的区别 ⭐⭐⭐⭐⭐
2.1.12 char和int之间的转换;⭐⭐⭐
2.1.13 static的用法(定义和用途)⭐⭐⭐⭐⭐
2.1.14 cosnt的用法(定义和用途)⭐⭐⭐⭐⭐
2.1.15const常量和#define的区别(编译阶段、安全性、内存占用等) ⭐⭐⭐⭐
2.1.16 volatile作用和用法 ⭐⭐⭐⭐⭐
2.1.17有常量指针 指针常量 常量引用 没有 引用常量⭐⭐⭐
2.1.18没有指向引用的指针,因为引用是没有地址的,但是有指针的引用⭐⭐⭐
2.1.19c/c++中变量的作用域⭐⭐⭐⭐⭐
2.1.20 c++中类型转换机制?各适用什么环境?dynamic_cast转换失败时,会出现什么情况?⭐⭐⭐⭐
2.2 继承、多态相关面试题 ⭐⭐⭐⭐⭐
2.2.1继承和虚继承 ⭐⭐⭐⭐⭐
2.2.2多态的类,内存布局是怎么样的 ⭐⭐⭐⭐⭐
2.2.3被隐藏的基类函数如何调用或者子类调用父类的同名函数和父类成员变量 ⭐⭐⭐⭐⭐
2.2.4多态实现的三个条件、实现的原理 ⭐⭐⭐⭐⭐
2.2.5对拷贝构造函数 深浅拷贝 的理解 拷贝构造函数作用及用途?什么时候需要自定义拷贝构造函数?⭐⭐⭐
2.2.6析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?⭐⭐⭐
2.2.7什么情况下会调用拷贝构造函数(三种情况)⭐⭐⭐
2.2.8析构函数一般写成虚函数的原因⭐⭐⭐⭐⭐
2.2.9构造函数为什么一般不定义为虚函数⭐⭐⭐⭐⭐
2.2.10什么是纯虚函数⭐⭐⭐⭐⭐
2.2.11静态绑定和动态绑定的介绍⭐⭐⭐⭐
2.2.12 C++所有的构造函数 ⭐⭐⭐
2.2.13重写、重载、覆盖的区别⭐⭐⭐⭐⭐
2.2.14成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?⭐⭐⭐⭐
2.2.15如何避免编译器进行的隐式类型转换;(explicit)⭐⭐⭐⭐
2.2.1继承和虚继承
解析:因为C++支持多重继承,那么在这种情况下会出现重复的基类这种情况,也就是说可能出现将一个类两次作为基类的可能性。比如像下面的情况
为了节省内存空间,可以将DeriverdA、DeriverdB对Base的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如 下:
class Base
class DeriverdA:public virtual Base; //虚继承
class DeriverdB:public virtual Base; //虚继承
class D:public DeriverdA,DeriverdB; //普通继承
虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在C++中,多重继承是不推荐的,也并不常用,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要因为这样只会降低效率和占用更多的空间。
注意:不要全部都使用虚继承,因为虚继承会破坏继承体系,不能按照平常的继承体系来进行类型转换(如C++提供的强制转换函数static_cast对继承体系中的类对象转换一般可行的,这里就不行了)。所以不要轻易使用虚继承,更不要在虚继承的基础上进行类型转换,切记切记!
2.2.2多态的类,内存布局是怎么样的
解析:关于类的内存布局主要是考某个类所占用的内存大小,以下通过几个案例加以分析。
(1)虚继承:如果是虚继承,那么就会为这个类创建一个虚表指针,占用4个字节
#include <stdio.h>
class A {
public:
int a;
}; //sizeof(A)=4,因为a是整形,占用4字节
class B : virtual public A {
public:
int b;
};//sizeof(B)=4(A副本)+4(虚表指针占用4字节)+4(变量b占用4字节)=12
class C : virtual public B {
};//sizeof(c)= 12(B副本)+4(虚表指针) = 16,如果这里改为直接继承,那么sizeof(c)=12,因为此时就没有虚表指针了
(2)多重继承:如果是以虚继承实现多重继承,记得减掉基类的副本
#include <stdio.h>
class A {
public:
int a;
};//sizeof(A) = 4
class B : virtual public A {
};// sizeof(B) =4+4=8
class C : virtual public A {
};//sizeof(C) =4+4=8
class D : public B, public C{
};
//sizeof(D)=8+8-4=12这里需要注意要减去4,因为B和C同时继承A,只需要保存一个A的副本就好了,sizeof(D)=4(A的副本)+4(B的虚表)+4(C的虚表)=12,也可以是8(B的副本)+8(c的副本)-4(A的副本)=12
(3)普通继承(含有:空类、虚函数)
class A //result=1 空类所占空间的大小为1
{
};
class B //result=8 1+4 字节对齐后为 8
{
char ch;
virtual void func0() { }
};
class C //result=8 1+1+4 字节对齐后为 8,没有继承的,此时类里即使出现多个虚函数,也只有一个虚指针
{
char ch1;
char ch2;
virtual void func() { } //也只有一个虚指针
virtual void func1() { } //也只有一个虚指针
};
class D: public A, public C //result=12 8(C的副本)+4(整形变量d占用4字节)=12
{
int d;
virtual void func() { } //继承了C,C里已经有一个虚指针,此时D自己有虚函数,
virtual void func1() { } //也不会创建另一个虚指针,所以D本身就变量d需要4字节
};
class E: public B, public C //result=20 8( B的副本)+8(C的副本)+4(E本身)=20
{
int e;
virtual void func0() { } //同理,E不会创建另一个虚指针,所以E本身就变量e需
virtual void func1() { } //要4字节
};
(4)虚继承(多重继承和虚函数)
class CommonBase
{
int co;
};// size = 4
class Base1: virtual public CommonBase
{
public:
virtual void print1() { }
virtual void print2() { }
private:
int b1;
};//4(父类副本)+4(自己有虚函数,加1个虚指针空间)+4(自身变量b1)+4(虚继承再加1个虚指针空间)=16
class Base2: virtual public CommonBase
{
public:
virtual void dump1() { }
virtual void dump2() { }
private:
int b2;
};//同理16
class Derived: public Base1, public Base2
{
public:
void print2() { }
void dump2() { }
private:
int d;
};//16+16-4+4=32
前辈总结说:如果不是虚继承的类,即便有虚函数也不会因此增加存储空间,如果是虚继承的类,没有虚函数就添加一个虚指针空间,有虚函数不论多少个,就添加两个虚指针空间。
本人将前辈总结归纳为:如果此时类里有一个或多个虚函数,那么需要加1个虚指针空间,如果还是虚继承,那么需要再加1个虚指针空间,最多就2个虚指针空间。
(5)虚继承与虚函数
class A
{
public:
virtual void aa() { }
virtual void aa2() { } //如果此时类里有一个或多个虚函数,那么需要加1个虚指针空间
private:
char ch[3];
}; // 1+4 =补齐= 8
class B: virtual public A //如果还是虚继承,那么需要再加1个虚指针空间,最多就2个虚指//针空间。
{
public:
virtual void bb() { }
virtual void bb2() { }
}; // 8(副本)+4(虚继承)+4(虚指针)= 16
【小结】重要的事情讲三遍!!!
如果此时类里有一个或多个虚函数,那么需要加1个虚指针空间,如果还是虚继承,那么需要再加1个虚指针空间,最多就2个虚指针空间。
2.2.3被隐藏的基类函数如何调用或者子类调用父类的同名函数和父类成员变量
解析:父类的同名函数和父类成员变量被隐藏不代表其不存在,只是藏起来而已,C++有两种方法可以调用被隐藏的函数:
1)用using关键字:使用using后,父类的同名函数就不再隐藏,可以直接调用,如下:
class Child:public Parent
{
public:
Child(){};
using Parent::add;
int add(void){};
};
2)用域操作符,可以调用基类中被隐藏的所有成员函数和变量。
如子类child和父类father都有add()函数,可以通过下面代码实现子类对象调用父类的add()函数:
Child c;
c.Parent::add(10);
2.2.4 C++如何实现多态(讲明多态实现的三个条件、实现的原理)
解析:只要是有涉及到c++的面试,面试官百分百会问到多态相关的问题,尤其是让你解释下多态实现的原理,此时首先要知道多态实现的三个条件:
1)要有继承
2)要有虚函数重写
3)要有父类指针(父类引用)指向子类对象
答:
编译器发现一个类中有虚函数,便会立即为此类生成虚函数表vtable。虚函数表的各表项为指向类里面的虚函数的指针。编译器还会在此类中隐含插入一个指针vptr(对 vc 编译器来说,它插在类的内存地址的第一个位置上)指向虚函数表。调用此类的构造函数时,在类的构造函数中,编译器会隐含执行vptr 与 vtable 的关联代码,即将vptr 指向对应的 vtable,将类与此类的vtable 联系了起来。
另外在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的this 指针,这样依靠此 this 指针即可得到正确的 vtable,如此才能真正与函数体进行连接,这就是动态联编,实现多态的基本原理。
上面这段话可能有点难以理解,我本人当初也是理解好一会哈,我们直接看个例子:
#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
using namespace std;
class Father
{
public:
void Face()
{
cout << "Father's face" << endl;
}
virtual void Say()
{
cout << "Father say hello" << endl;
}
};
class Son:public Father
{
public:
void Say()
{
cout << "Son say hello" << endl;
}
};
void main()
{
Son son;
Father *pFather=&son; //隐式类型转换
pFather->Say();
}
我们重点来看这行代码Father *pFather=&son;
此时指向基础类的指针pFather已经变成指向具体的类son的this指针,那么我们调用这个pFather父类指针,就相当于调用了等号右边的类即子类son的this指针,这个this所能调用的函数,自然就是子类son本身的函数。即pFather->Say();这行代码调用的是子类的Say()函数。因此我们就成功的实现了用父类指针pFather调用子类函数,也就是实现了多态。