面经——C++

只能列表初始化

类成员为const类型、类成员为引用类型、类成员为没有默认构造函数的类类型、如果类存在继承关系、派生类必须在其初始化列表中调用基类的构造函数

动态分配const

空指针&野指针

https://blog.csdn.net/qq_33757398/article/details/81259636

面对对象编程思想

封装:利用抽象数据类型将数据和基于数据的操作封装在一起,尽可能地隐藏内部的细节,只保留一些对外的接口使其与外部发生联系。
继承:继承实现了 IS-A 关系。子类继承父类使得子类对象(实例)具有父类的属性和方法。继承应该遵循里氏替换原则。
多态:多态分为编译时多态和运行时多态,编译时多态主要指方法的重载和模板,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定(虚函数)。

对象的作用

类的作用是为了安全。
类的作用是继承,继承可以实现多态。

虚函数

冠以关键字virtual的成员函数,是实现运行时多态的关键,C++提供的虚函数解释机制让基类指针可以依赖运行时的地址值(基类指针当前指向)调用不同类版本的成员函数从而实现运行时多态。
定义纯虚函数意味着他没有被实现,在抽象基类中定义纯虚函数,抽象基类无法实例化,并要求继承此类的派生类实现函数。
静态函数(static)不能是虚函数(静态函数参数没有隐式的this指针);
构造函数不能是虚函数,因为在调用构造函数时,虚表指针并没有在对象的内存空间中;
内联函数不能是表现多态性时的虚函数;
虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象;
虚函数的调用过程:this -> vptr -> vtable ->virtual function;

https://blog.csdn.net/haoel/article/details/1948051/

new,delete,malloc,free能否混用

malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符。对于用户自定义的对象而言,malloc/free不在编译器控制权限之内,无法对象在创建的同时要自动执行构造函数,以及销毁时自动析构。
当申请的空间是内置类型时,delete和free可以混用
当申请的空间是自定义类型时: 1若没有析构函数,delete和malloc可以混用,有[]和没有[]都相同。2若申请的空间有析构函数时,malloc申请的空间可以用delete和free释放,但是用delete释放时不能加[]。3若申请的空间有析构函数时,new申请的空间不能用free释放,可以用delete释放,但是释放时必须加上[]。

https://blog.csdn.net/weixin_42425594/article/details/82563190

C++构造函数体内初始化与列表初始化的区别

对于成员类型是内置类型的情况,通过上述两种情况去初始化是相同的;
若某个类(下文的class B)有一个类成员是类类型(下文的class A),那么:若类B通过构造函数体内初始化,会先调用类A的默认构造函数(无参构造函数),再调用类A的赋值运算符;类B通过初始化列表去初始化,则只调用类A的拷贝构造函数。

https://blog.csdn.net/weixin_41675170/article/details/93485655
列表初始化在构造函数之前,没有写初始化列表,则是调用成员的默认无参构造函数;写了初始化列表,则是调用初始列表中显示的成员构造方法。
初始化列表的初始化顺序与列表顺序无关,只与声明的顺序有关。

一个函数模板生成的两个模板函数内存空间是否独立?

函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。
编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

深拷贝和浅拷贝

浅拷贝不复制数据,只复制指向数据的指针,因此是多个指针指向同一份数据。 深拷贝会复制数据,每个指针指向一份独立的数据。浅拷贝可能会导致某个指针在类析构时被删除两次,这就是所谓的指针悬挂问题,因此对于申请了堆内存的类而言一定要用深拷贝。

为什么拷贝构造函数要传引用

当一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函数以生成一个复本。如果类A的拷贝构造函数是以值方式传递一个类A对象作为参数的话,当需要调用类A的拷贝构造函数时,需要以值方式传进一个A的对象作为实参; 而以值方式传递需要调用类A的拷贝构造函数;结果就是调用类A的拷贝构造函数导致又一次调用类A的拷贝构造函数,这就是一个无限递归。

为什么要有虚析构函数

这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用,防止指针调用基类的析构函数从而出现内存泄漏的现象。
用派生类类型指针绑定派生类实例,析构的时候,不管基类析构函数是不是虚函数,都会正常析构
用基类类型指针绑定派生类实例,析构的时候,如果基类析构函数不是虚函数,则只会析构基类,不会析构派生类对象,从而造成内存泄漏。

为什么构造函数不能是虚函数

从存储空间角度:虚函数对应一个vtable, vtable其实是存储在对象的内存空间的。如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,这会导致运行错误;
从使用角度来看:虚函数主要用于在信息不全的情况下,使重写的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义。

智能指针

对于编译器来说,智能指针实际上是一个对象,并非指针类型。
对于特定的对象,即某一个对象,同一个时刻只能有一个智能指针可拥有, 比如说当智能指针A指向对象x,当执行完B=A后,原来的指针A就失去了对x的所有权,A会变成空指针,这样只有拥有对象的智能指针的构造函数会删除该对象。unique_ptr和auto_ptr就是采用这种策略。
创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1,。当减为0时才调用delete。这是shared_ptr采用的策略。
如果程序中要使用多个指向同一个对象的指针,那么应该使用shared_ptr;如果程序中不需要使用多个指向同一个对象的指针,则可使用unique_ptr;

面向对象与面向过程的区别?

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

C++的内存管理

在C++中,内存分成4个区,从低地址到高地址分别是常量区、全局/静态数据区、堆区、栈区。
栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
自由存储是C++中通过new与delete动态分配和释放对象的抽象概念,而堆(heap)是C语言和操作系统的术语,是操作系统维护的一块动态分配内存。
new所申请的内存区域在C++中称为自由存储区。藉由堆实现的自由存储,可以说new所申请的内存区域在堆上。堆的空间申请和释放由应用程序来完成。
全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

C++11的新标准

右值引用;智能指针;匿名函数lambda表达式。auto关键字、nullptr关键字、初始化列表:使用初始化列表来对类进行初始化

指针函数和函数指针

(1)指针函数声明:int *fun(int x,int y),指针函数的使用和一般函数的使用相同,只是返回值是一个指针值,即地址。
(2)函数指针:指向函数地址的指针,int(*p)(int, int),可用于回调函数

虚继承

  • 单继承与多继承:派生类有一个or多个直接基类。
  • 菱形继承:两个子类继承同一个父类,而又有子类同时继承这两个子类。在这里插入图片描述
  • 菱形继承的问题:(1)变量名称的二义性(2)空间浪费
  • 加作用域可以解决二义性问题;
//接图片中ABCD的例子
class A
{
public:
string _a ; // 姓名
};
class B : public A
{
protected :
int _b ; //学号
};
class C : public A 
{
protected :
int _c ; // 职工编号
};
class D : public B, public C
{
protected :
string _d ; // 主修课程
};
void Test ()
{
// 显示指定访问哪个父类的成员
D d ;
d.B ::_a = "xxx";
d.C ::_a= "yyy";
}

虚继承可以解决空间浪费

class A
{
public:
	int a;
};

class B:virtual public A   //虚继承
{
public:
	int b;
};
class C :virtual public A   //虚继承
{
public:
	int c;
};
class D:public B, public C
{
public:
	int d;
};
int main()
{
	D d;
	d.B::a = 1;
	d.C::a = 2;
	
	d.b = 3; 
	d.c = 4 ;
    d.d = 5;
}

异常

C++异常是对程序运行过程中发生的异常情况(例如被0除)的一种响应。异常提供了将控制权从程序的一个部分传递到另一部分的途径。对异常的处理有3个组成部分:a.引发异常。b.捕获有处理程序的异常。c.使用try块。
throw语句实际上是跳转,即命令程序跳到另一条语句。throw关键字表示引发异常,紧随其后的值(例如字符串或对象)指出了异常的特征。
(2)catch关键字和异常类型用作标签,指出当异常被引发时,程序应跳到这个位置执行。异常处理程序也被称为catch块。
try块标识其中特定的异常可能被激活的代码块,它后面跟一个或多个catch块。
如果函数引发了异常,而没有try块或没有匹配的处理程序时,将会跳转到调用他的函数中。在默认情况下,程序最终将调用abort()函数。
(5)可使用异常规范对函数定义进行限定,指出它将引发哪些类型的异常。为此,可在函数定义的后面加上异常规范,它由关键字throw和异常类型列表组成
(6)新的C++编译器将异常合并到语言中。例如,为支持该语言,exception头文件(以前为exception.h或except.h)定义了exception类,C++可以把它用作其他异常类的基类。

右值引用

(1)右值引用用来解决C++98/03中遇到的两个问题,第一个问题就是临时对象非必要的昂贵的拷贝操作,第二个问题是在模板函数中如何按照参数的实际类型进行转发。
(2)区分左值和右值的一个简单办法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。
(3)右值引用绑定了右值,让临时右值的生命周期延长了。我们可以利用这个特点做一些性能优化,即避免临时对象的拷贝构造和析构
(4)右值引用独立于左值和右值。意思是右值引用类型的变量可能是左值也可能是右值。会自动类型推断;但右值引用一定不能被左值所初始化,只能用右值初始化;一旦初始化一个右值引用变量,该变量就成为了一个左值,可以被赋值;
(5)右值引用的自动类型推断可以在语法层面识别出临时对象,在使用临时对象构造新对象(拷贝构造)的时候,用户可以将临时对象所持有的资源『转移』到新的对象中,就能消除这种不必要的拷贝,典型的实现是移动构造函数。
(5)移动语义:移动语义是通过右值引用来匹配临时值的,利用右值引用的自动类型推断,对于临时值我们仅仅需要做浅拷贝即可,无需再做深拷贝,从而解决了前面提到的临时变量拷贝构造产生的性能损失的问题,用移动而不是复制来避免无必要的资源浪费,从而提升程序的运行效率;代表函数是std::move,std::move无条件的将它的参数转换成一个右值,把资源从一个对象转移到另一个对象上,而不是复制他们。
(6)完美转发:在函数模板中,完全依照模板的参数的类型(即保持参数的左值、右值特征),将参数传递给函数模板中调用的另外一个函数。一般情况下,当右值作为参数传入时,存储右值的变量已经成为了一个左值类型,也就是右值类型只传递了一次。而完美转发可以识别由右值转换成的左值,并重新将该左值转换为右值。C++11中的std::forward(std::forward仅当参数被右值绑定时,才会把参数转换为右值)正是做这个事情的,他会按照参数的实际类型进行转发。

volatile关键字

(1)volatile 关键字是一种类型修饰符,所以使用 volatile 告诉编译器不应对这样的对象进行优化。
(2)volatile保证可见性:可见性就是指当前线程对变量的操作结果对其他的线程是可见的,当前线程更改某个变量值后,会立马将变量的值刷新到主存,其他线程如果想操作这个变量,必须从主存中读取这个变量最新的值。
(3)volatile保证有序性:对于单线程,在不改变最终执行结果的前提下,处理器会对代码指令进行重排序,以优化执行速度。而volatile可以防止指令重排序。
(4)应用场景:a.作为状态标志,用于指示发生了一个重要的一次性事件;b.用于保证单例模式的双重检查锁定;c. 独立观察: 定期发布观察结果供程序内部使用;

extern关键字

(1)extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
(2)extern “C”:被extern "C"限定的函数或变量是extern类型;被extern "C"修饰的变量和函数是按照C语言方式进行编译。
(3)C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器不要为extern “C”内部的函数生成用于链接的中间函数名。

final关键字

(1)禁用继承:C++11中允许将标记为final,方法时直接在类名称后面使用关键字final,如此,意味着继承该类会导致编译错误。
(2)禁用重写:C++中还允许将方法标记为fianal,这意味着无法再子类中重写该方法。这时final关键字置于方法参数列表后面。

override关键字

(1)当派生类想要覆盖它继承的虚函数,通过在某成员函数后面添加关键字override,显示地注明该成员函数覆盖了它继承的虚函数。
(2)添加override关键字之后,编译器会检查是否重写了该成员函数,没有的话会报错。

explicit(显式)关键字

(1)explicit 修饰构造函数时,可以防止隐式转换和复制初始化;,只有一个参数可以隐式转换。
(2)explicit 修饰构造函数时,可以防止隐式转换;

引用和指针的区别

(1)指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
(2)引用只能在定义时被初始化一次,之后不可变;指针可变;
(3)引用不能为空,指针可以为空;

inline的作用

(1)inline函数不像正常函数在调用时存在压栈和call的操作,它会把程序代码直接嵌入到调用代码段中,也就是说使用inline函数会增大二进制程序的体积,但是会使执行速度加快。
(2)同时,它在编译期间可以对参数进行强类型检查,在运行时可调试,这是它优于宏的一个方面
(3)内联函数不能含有复杂流程控制语句否则inline无效
(4)在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
(5)虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。因为内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码。

this指针的作用

(1)this 指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向调用该成员函数的那个对象。
(2)当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数是一个指向这个成员函数所在的对象的指针。
(3)每次成员函数存取数据成员时,都隐式使用 this 指针。
(4)this 并不是一个常规变量,而是个右值,所以不能取得 this 的地址;

const有哪几种用法?

(1)修饰变量,说明该变量不可以被改变;
(2)修饰指针,分为指向常量的指针(pointer to const)和自身是常量的指针(常量指针,const pointer);指向常量的指针也就是底层const,

https://www.cnblogs.com/happying30/p/9350712.html

(3)修饰引用,指向常量的引用(reference to const),用于形参类型,即避免了拷贝,又避免了函数对值的修改;
(4)在类中将成员函数修饰为const表明在该函数体内, 不能修改对象的数据成员而且不能调用非const函数。
(5)修饰函数参数,表示在函数体内,不能修改其值。
(6)也是用const来修饰返回的指针或引用,保护指针指向的内容或引用的内容不被修改,也常用于运算符重载。归根究底就是 使得函数调用表达式不能作为左值。

static有几种用法

(1)修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间;
(2)修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。
(3)修饰类的成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
(4)修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。
类中一般成员与静态成员区别

空类声明和定义的情况

https://blog.csdn.net/taiyang1987912/article/details/43485569

map和unordered_map

对于map是通过红黑树来实现的,因此map内部所有的数据都是有序的,map的查询、插入、删除操作的时间复杂度都是O(logn)。此外,map的key需要定义operator <,对于一般的数据类型已被系统实现,若是用户自定义的数据类型,则要重新定义该操作符。
unordered_map内部利用开链的哈希表,元素是无序的。key需要定义hash_value函数并且重载operator ==。
set与unordered_set同理

全局变量与局部变量重名

c++里面可以,全局前面加::表示全局作用域。

#include <iostream>
using namespace std;
int c=0;
int main()
{
	int c=10;
   cout << c<<" " <<::c;
   return 0;
}

C++是不是类型安全的?

不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。

malloc/free & new/delete

malloc与 free 是 C++/C 语言的标准库函数,new/delete 是 C++的运算符,c++多了构造和析构,对应增加new、delete功能。
它们都可用于申请动态内存和释放内存。 对于非内部数据类型的对象而言,只用 malloc/free 无法满足动态对象的要求。由于malloc/free 是库函数无法调用构造和析构函数,因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符 new ,以及一个能完成清理与释放内存工作的运算符delete 。

emplace_back 与 push_back

emplace_back用到了移动构造函数,节约资源。
在没有移动构造情况下,push_back会先构造()中的变量,而后执行拷贝构造,最后释放钱构造的内容。

NULL和nullptr

https://www.cnblogs.com/nothx/p/8523191.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清欢_小铭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值