C++面试题汇总

编译内存相关

C++编译过程?

编译预处理 处理#开头的指令

编译优化 将源码翻译成汇编代码

汇编 将汇编代码翻译成机器代码

链接 将机器代码链接成一个整体,生成.exe文件

链接分为两种:

静态链接:将静态链接库里的代码拷贝到最终可执行文件中,程序执行时,代码会被装进该进程的虚拟地址空间。

动态链接:动态链接库中的代码不会被拷贝到最终可执行文件中,只是在其中记录了共享对象的名字等信息。程序运行时,动态链接库的全部内容会被映射到运行时相应的虚拟地址空间。

静态链接:

优点:运行速度快      缺点:浪费空间,更新困难(修改静态库文件后需要对目标文件重新编译)

动态链接:

优点:节省内存,更新方便      缺点:运行速度稍慢

内存管理

分区:

栈区:局部变量、函数参数、返回地址。由编译器自动分配和释放

堆区:动态申请内存空间,由程序员控制分配和释放

全局、静态存储区:存放全局变量和静态变量

常量存储区:存放的是常量,程序运行结束自动释放

代码区:存放编译后的二进制文件,不允许修改,可以执行

注:全局变量不应该定义在头文件中,该头文件被多个文件include时,会导致重复定义

堆和栈的区别

1 栈是自动分配,堆是程序员申请

2 栈是连续存储,堆不连续

对象创建限制在堆或栈

静态建立 编译器在栈上分配内存,直接调用类的构造函数创建对象

动态建立 使用new关键字在堆空间上创建对象,首先在堆空间上寻找合适的内存并分配,然后调用类的构造函数创建对象

限制对象只能建立在堆上(禁止使用A a)

class A
{
protected:
    A() {}
    ~A() {}

public:
    static A *create()
    {
        return new A();
    }
    void destory()
    {
        delete this;
    }
};

首先构造函数要设置protected。将构造函数的访问权限设置为private或protected时,类外将无权限调用。但如果设为private,则无法继承

使用一个public静态函数创建对象,使用static的原因是非静态函数只能通过对象调用,静态函数则可以通过类名调用。想调用非静态函数需要先有一个对象,但protected禁止了类外创建对象,所以只能使用静态函数。析构函数和构造函数成对使用。

C++面试题汇总_第1张图片

 

内存对齐

编译器将程序中每个数据单元安排在自己宽度的整数倍上

优点:便于不同平台之间的移植;提高内存的访问效率

类的大小

类的大小是指类的实例化对象的大小,用sizeof对类操作时,结果是该类对象的大小

计算原则:

遵循普通结构体的对齐原则

与普通成员变量有关,与成员函数和静态成员无关。因为静态数据成员被类的对象共享,并不属于哪个具体的对象。

虚函数对类的大小有影响,但虚函数的个数并不影响所占内存的大小,因为内对象中只保存指向虚函数表的指针(8个字节)。

虚继承对类的大小有影响

空类的大小是1个字节

指针占8个字节,int占4个字节,char占1个字节,long占4个字节,string占32个字节(实际测试是40)

内存泄漏

由于疏忽或错误导致程序未能释放已经不再使用的内存

比如指针指向一块申请到的内存空间后被重新赋值,导致申请的那块内存空间无法被找到,造成内存泄漏

如何防止内存泄漏?内存泄漏检测工具的原理?

防止内存泄漏的方法:

1. 内部封装:将内存的分配和释放封装到类中,构造时申请内存,析构时释放内存

注:该方法会导致程序对同一内存空间释放两次

void fun1()
{
    A ex(100);
    A ex1 = ex; 
    char *p = ex.GetPointer();
    strcpy(p, "Test");
    cout << p << endl;
}

ex1和ex指向的是同一块内存空间,相当于拷贝了地址,因此在离开函数作用域时,会调用两次析构函数释放空间。

注意:A(const A &temp) 拷贝构造函数,将一个A类型的别名为temp的对象传进来,创建出一个和temp一样的对象。它和temp都拥有指向同一块内存空间的指针。

拷贝构造函数必须以引用的方式传递参数,且不能修改传入的参数。这是因为通过值传递的方式将实参传递给形参时,会默认调用拷贝构造函数希望创建当前对象的副本,但调用拷贝构造函数时又将当前对象从实参传入形参,又要调用拷贝构造函数,这样无限循环,最终导致内存溢出。采用引用的方式就可以解决这个问题,当我们调用拷贝构造函数时,使用引用传递不会构建一个新的副本,也就不会让拷贝构造函数自己调用自己。

拷贝构造分为浅拷贝和深拷贝,默认的拷贝构造都是浅拷贝,浅拷贝并未重新开辟新的内存空间,只是拷贝了地址。

2. 智能指针

封装在memory头文件中

共享指针shared_ptr    多个指针指向同一个对象

独占指针unique_ptr   同一时刻只有一个指针指向该对象 通过move移交所有权

弱指针weak_ptr   指向shared_ptr指向的对象

使用智能指针可能出现的问题:循环引用

两个类分别定义了指向对方对象的共享指针,因此要释放a对象,首先要释放b对象;而要释放b对象,又要先释放a对象。

weak_ptr拥有临时所有权,当某个对象只有存在时才能被访问,需要获得所有权时将其转化为 shared_ptr,此时如果原来的 shared_ptr 被销毁,则该对象的生命期被延长至这个临时的 shared_ptr 同样被销毁。

语言对比

C++11的新特性

1. auto类型推导,编译器会在 编译期间 通过初始值推导出变量的类型

2. Decltype 声明类型自动推导,通过表达式推断要定义变量的类型,但又不想用该表达式初始化变量,此时使用decltype

3. Lambda表达式

4. for语法,遍历某个序列

5. 右值引用

6. 标准库move()函数

7. Deletedefault

面向对象

什么是面向对象?面向对象的三大特性

对象是指具体的某个事物,事物的抽象就是类,类中包含成员变量和成员函数

面向对象的三大特性

继承 子类继承父类的特征和行为,对于非private方法或成员变量,子类可以重写父类方法,但是当父类中的成员变量、成员函数或者类本身被final修饰时,修饰的类、成员不能被修改

封装 将具体的实现过程和数据封装成一个函数,只能通过接口进行访问,降低耦合性

多态 父类指针指向子类对象,不同继承类的对象,对同一消息作出不同响应。

重载、重写、隐藏

重载:参数列表中类型、个数、顺序不同的同名函数

隐藏:派生类的函数屏蔽了与其同名的基类函数,此时只要同名,无论参数列表是否相同,基类函数均被隐藏

重写(覆盖):派生类的函数名、参数列表、返回值类型都必须同基类一致,只有函数体不同。重写的基类中被重写的函数必须有 virtual 修饰。

什么是多态?多态如何实现?

不同继承类的对象对同一消息做出不同的响应。

实现过程:

通过虚函数实现。虚函数的地址保存在虚函数表中,虚函数表指针保存在对象之中。当基类指针指向派生类对象时,会通过派生类对象的虚标指针找到对应的虚函数表,进而找到对应的虚函数。

关键字库函数

sizeof和strlen的区别

1 strlen是函数,sizeof是运算符

2 strlen得到字符串长度,sizeof得到数组分配空间的大小

3 字符数组名作为函数形参时,sizeof将其当做字符指针处理

4 strlen是库函数,在运行中计算长度,sizeof是运算符,在编译时计算长度

5 sizeof的参数可以是类型也可以是变量,strlen的参数必须是char*类型的变量。

Lambda表达式

sort(arr.begin(), arr.end(), [](int a, int b) { return a > b; }); // 降序排序

explicit的作用

声明类构造函数是显示调用的,可以阻止调用构造函数时进行隐式转换。

A ex=10相当于先将10转化为A类型的对象,然后将该对象赋值给ex。而使用explicit后该构造函数只能显式调用ex(100),不允许先显式调用构造函数再去赋值。

static关键字的作用

改变了变量的生存周期,使得该变量存在于定义后直到程序运行结束。

静态成员函数只能访问静态成员变量,不能将静态成员函数定义为虚函数因为静态成员函数属于类,而虚函数发生在对象上。

静态成员变量可以作为成员函数的参数,而普通成员变量不可以。这是因为静态成员变量的地址在编译期已经确定存在,而普通成员变量在运行期才存在分配地址

静态数据成员的类型可以是所属类的类型,但普通数据成员的类型只能是该类型的指针或引用,因为静态成员属于这个类,普通数据成员属于对象,定义类时并没有产生对象。

static全局变量和普通全局变量的异同

普通全局变量的作用域是整个源程序(包含多个源文件),静态全局变量的作用域是本源文件。

final关键字

在类名后面加final,表示禁止继承

在虚函数后加final,该方法在子类中无法重写

Const作用和用法

作用:

1 修饰成员变量 相当于宏常量

2 修饰函数参数 不能修改传递过来的函数参数的值

3 修饰成员函数,不能修改成员变量,这意味着不能调用非const成员函数,因为非const成员函数可能修改成员变量

const在类中的使用

const修饰成员变量

1 const成员变量只能在类内声明定义,在构造函数初始化列表中初始化

2 const成员变量不能在类的声明中进行初始化,这是因为此时类的对象还没有创建,如果在声明中进行初始化,后面的对象将无法修改const成员变量,这会导致所有对象的const成员变量都是一样的。

const成员函数

1 不能修改成员变量的值

2 不能调用非const成员函数

define和const的区别

区别:

1 difine在编译预处理阶段进行替换,const在编译阶段确定其值

2 define定义的宏常量无数据类型,不会进行类型安全的检查

3 define定义的宏常量在内存中有多个备份,占用代码段的空间,const定义的常量在静态存储区,运行过程中只有一份

4 define定义的宏常量不能调试,因为预编译阶段已经完成了替换,const定义的常量可以调试

const的优点:

1 占内存空间小

2 可以调试

3 安全性高,有类型检查

define和typedef的区别

#define表示直接替换,可以为类型、常量、变量等取别名。没有作用域的限制

typedef用来定义类型的别名

inline作用及使用方法

类内的成员函数默认是内联函数,类外的成员函数需要加inline,在编译阶段,编译器将程序中出现内联函数调用表达式用内联函数的函数体进行替换

宏定义define和内联函数inline的区别

1 内联函数在编译时展开,宏在编译预处理时展开

2 内联函数可以减少调用的开销

3 宏定义是文本替换,不会对参数类型、语句能否正常编译进行检查

new和malloc如何判断是否申请到内存

malloc:申请到内存返回该内存的指针,否则返回null指针

new:申请到内存返回该对象类型的指针,分配失败抛出bad_alloc异常

delete实现原理?delete和delete[]的区别?

delete首先执行该对象所属类的析构函数,进而通过调用operator delete标准库函数释放内存

delete和delete[]的区别

delete释放单个对象所占空间,只调用一次析构函数

delete[]释放数组空间,对数组中每一个成员都调用一次析构函数

new和malloc的区别,delete和free的区别

new和delete搭配,malloc和free搭配

new申请空间时无需指定分配空间的大小,编译器会根据类型自行计算,malloc申请空间时,需要确定所申请空间的大小

new会调用构造函数,delete会调用析构函数

new和delete是关键字,malloc和free是库函数

C和C++中struct的区别

C中struct定义数据,C++定义数据和函数

C++中struct可以有访问权设置

在c语言中struct定义的数据类型,在定义该类型的变量时要加上struct

struct和union的区别

union是联合体,struct是结构体

使用时,联合体只有一个有效成员,对不同成员赋值,会覆盖其他成员的值,对结构体则不会。

联合体的大小为所有变量中的最大值,按最大类型的倍数分配内存,结构体分配内存的大小遵循内存对齐的原则

struct和class的异同

struct默认public,class默认private

volatile的作用?是否具有原子性,对编译器的影响

volatile:当对象的值可能发生改变时,告知编译器不对这样的对象进行优化,即不会将变量从内存缓存到寄存器中

什么情况下一定要用volatile,能否和const一起使用

当多个线程用到某一变量,并且该变量的值可能发生改变,需要volatile进行修饰,不要将变量从内存缓存到寄存器中,导致寄存器里的值和内存中的值不一致

返回函数中静态变量的地址会发生什么

静态局部变量直到程序运行结束才销毁

sizeof(1==1) 在 C 和 C++ 中分别是什么结果?

C中1==1按整数1处理,4个字节

C++按bool处理,1个字节

strcpy函数缺陷

strcpy不检查缓冲区的大小边界,将源字符串逐一赋值给目的字符串起始的一块连续内存空间,同时加上字符串终止符,会导致其他变量被覆盖。

类相关

什么是虚函数?什么是纯虚函数

虚函数要使用virtual修饰,

在虚函数基础上=0就是纯虚函数;

纯虚函数需要子类重写基类纯虚函数,否则不能实例化

如何禁止构造函数的使用

构造函数=delete

构造函数、析构函数是否需要定义成虚函数?为什么

构造函数不能定义为虚函数:因为构造函数是在创建对象之前调用的,而虚函数必须在对象创建完成后获得虚函数指针才能调用,这是矛盾的。

析构函数定义为虚函数可以防止内存泄漏,因为当基类指针指向子类对象时,如果没有将基类的析构函数定义为虚函数,指针会调用基类的析构函数(只有定义为虚函数后,基类指针会找到子类对象中的虚标指针,再通过虚标指针找到虚函数表,最后找到对应的虚函数),那么只能释放掉基类成员所占的空间,子类特有的内存空间无法释放掉。

如何避免拷贝?

delete掉

将基类的拷贝构造函数和赋值构造函数设为private

这样派生类无法调用父类的构造函数和赋值构造函数

如何减少构造函数开销?

在构造函数中使用类初始化列表,因为对于非内置类型,少了一次调用默认构造函数的过程。C++规定,对象的成员变量初始化动作发生在进入构造函数本体之前,要先调用默认构造函数设定初值。也就是说在进入本类的构造函数本体前,要么通过自己定义的构造函数完成成员变量初始化,要么调用默认构造函数完成初始化,调用玩默认构造函数后,在本类的构造函数中还要调用定义的构造函数。

多重继承时会出现什么状况?如何解决

造成命名冲突和数据冗余

解决方案:

1. 声明出现冲突的成员变量来源于哪个类

2. 虚继承,在继承方式前面加上virtual关键字,保证命名冲突的成员变量在派生类中只保留一份

虚继承应用在菱形继承中

空类占多少字节?编译器会给一个空类自动生成哪些函数?

空类占1个字节,会生成6个成员函数:缺省的构造函数、拷贝构造函数、析构函数、赋值运算符、两个取址运算符

为什么拷贝构造函数必须为引用

拷贝构造函数必须以引用的方式传递参数,且不能修改传入的参数。这是因为通过值传递的方式将实参传递给形参时,会默认调用拷贝构造函数希望创建当前对象的副本,但调用拷贝构造函数时又将当前对象从实参传入形参,又要调用拷贝构造函数,这样无限循环,最终导致内存溢出。采用引用的方式就可以解决这个问题,当我们调用拷贝构造函数时,使用引用传递不会构建一个新的副本,也就不会让拷贝构造函数自己调用自己。

C++类对象的初始化顺序

先调用基类的构造函数(按派生列表的顺序构造),在进入派生类构造函数前调用派生类中成员变量所属类的构造函数,最后调用派生类自身的构造函数

如何禁止一个类被实例化

1 在类中定义一个纯虚函数

2 构造函数private

3 delete

实例化一个对象需要几个阶段

1 分配空间,存储在堆上的对象,在运行阶段分配内存

2 初始化

3 赋值

友元函数的作用

在类中将类外的一个普通函数定义为友元函数,则普通函数能够访问类的私有成员

友元类之间共享数据

深拷贝和浅拷贝的区别

深拷贝:新对象和原对象占用不同的内存空间

浅拷贝:新对象和原对象占用相同的内存空间

当类的成员变量有指针变量时,最好使用深拷贝,因为如果是浅拷贝,当一个对象删除后,这个对象内的指针所指内存也将释放,此时另一个对象指向的就是垃圾内存。

深拷贝需要在拷贝构造函数中定义。

编译时的多态和运行时的多态

编译时的多态:函数重载

运行时的多态:父类指针指向子类对象

要求类成员函数不能修改类的成员变量

使用const修饰成员函数,在{}前加const,表示函数体不允许修改成员变量。

语言特性相关

左值和右值得区别,左值引用和右值引用的区别,如何将左值转换为右值

看能不能对表达式取地址,如果能,就是左值,否则就是右值

右值引用可以延长临时值得生命周期

move()函数的实现原理

1 独享指针所有权的转移,unique_ptr

2 左值到右值属性的转移

什么是指针?指针的大小和用法

指向某个类型

大小8个字节空间

指向普通对象的指针

指向常量对象的指针

指向函数的指针

指向对象成员的指针

指向类的当前对象的指针常量this

什么是野指针和悬空指针

野指针和悬空指针都是还存在的指针。野指针是指向内存被释放的指针

悬空指针是不确定指向的指针

 

Nullptr比NULL的优势

Nullptr本身是指针类型,而NULL本身是0;

指针和引用的区别

引用相当于取别名,给一个对象取别名后,不能将这个别名给另一个对象。

引用不占内存空间,

指针可以为空,但引用一定要绑定对象

指针可以由多级,但引用只能一级

常量指针和指针常量的区别

const在*左边 常量指针,定值,值不能改变

const在*右边,指针常量 定向 指向不能改变

函数指针和指针函数

指针函数本质是函数,返回值是指针

函数指针本质是指针,这个指针指向一个函数

强制类型转换

static_cast 良性转换 原有的自动类型转换

const_cast 强制去掉常量属性

reinterpret_cast 改变指针或引用的类型 两个具体类型指针的转换

dynamic_cast 在类的继承层次之间进行类型转换 要求有虚函数

如何判断结构体是否相等?能否用memcmp函数判断结构体相等

需要重载操作符==,不能使用memcmp判断,因为memcmp是逐个字节进行比较的,而结构体在内存中存在字节对齐,补的字节是随机的

什么是模板?如何实现?

模板是创建类或者函数的蓝图,分为函数模板和类模板

template 

函数模板和类模板的区别

实例化方式不同,函数模板自动推导类型,类模板需要显式

#include

using namespace std;

template 
T add_fun(const T & tmp1, const T & tmp2){
    return tmp1 + tmp2;
}

int main(){
    int var1, var2;
    cin >> var1 >> var2;
    cout << add_fun(var1, var2); // 显式调用

    double var3, var4;
    cin >> var3 >> var4;
    cout << add_fun(var3, var4); // 隐式调用
    return 0;
}

什么是模板特化,为什么特化?

某些情况下,通用模板的定义对特定类型不合适,所以进行模板特化

#include 
#include 

using namespace std;
//函数模板
template 
bool compare(T t1, T t2)
{
    cout << "通用版本:";
    return t1 == t2;
}

template <> //函数模板特化
bool compare(char *t1, char *t2)
{
    cout << "特化版本:";
    return strcmp(t1, t2) == 0;
}

int main(int argc, char *argv[])
{
    char arr1[] = "hello";
    char arr2[] = "abc";
    cout << compare(123, 123) << endl;
    cout << compare(arr1, arr2) << endl;

    return 0;
}
/*
运行结果:
通用版本:1
特化版本:0
*/

<> 包含的头文件为系统自带的头文件库;而 "" 包含的头文件为用户自定义的函数库

你可能感兴趣的:(大数据,c++,学习)