C++之其余问题

如果你想在校招中顺利拿到更好的offer,阿秀建议你多看看前人的经验 ,比如准备简历实习上岸经历校招总结阿里、字节、腾讯、美团等一二线大厂真实面经也欢迎来一起参加秋招打卡活动 等;如果你是计算机小白,学习/转行/校招路上感到迷茫或者需要帮助,可以点此联系阿秀;免费分享阿秀个人学习计算机以来的收集到的好资源,点此白嫖;如果你需要《阿秀的学习笔记》网站中求职相关知识点的PDF版本的话,可以点此下载

# 21、什么情况会自动生成默认构造函数?

  1. 带有默认构造函数的类成员对象,如果一个类没有任何构造函数,但它含有一个成员对象,而后者有默认构造函数,那么编译器就为该类合成出一个默认构造函数。

不过这个合成操作只有在构造函数真正被需要的时候才会发生;

如果一个类A含有多个成员类对象的话,那么类A的每一个构造函数必须调用每一个成员对象的默认构造函数而且必须按照类对象在类A中的声明顺序进行;

  1. 带有默认构造函数的基类,如果一个没有任务构造函数的派生类派生自一个带有默认构造函数基类,那么该派生类会合成一个构造函数调用上一层基类的默认构造函数;

  2. 带有一个虚函数的类

  3. 带有一个虚基类的类

  4. 合成的默认构造函数中,只有基类子对象和成员类对象会被初始化。所有其他的非静态数据成员都不会被初始化。

# 22、抽象基类为什么不能创建对象?

抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。

1、抽象类的定义: 称带有纯虚函数的类为抽象类。

2、抽象类的作用: 抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。

3、 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。

抽象类是不能定义对象的。一个纯虚函数不需要(但是可以)被定义。

4、纯虚函数定义 纯虚函数是一种特殊的虚函数,它的一般格式如下:

class <类名>  {  virtual <类型><函数名>(<参数表>)=0;  …  }; 
1

在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。  纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做抽象类。这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。

5、纯虚函数引入原因 1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。 2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔 雀等子类,但动物本身生成对象明显不合常理。  为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;)。若要使派生类为非抽象类,则编译器要求在派生类中,必须对纯虚函数予以重载以实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。 例如,绘画程序中,shape作为一个基类可以派生出圆形、矩形、正方形、梯形等, 如果我要求面积总和的话,那么会可以使用一个 shape * 的数组,只要依次调用派生类的area()函数了。如果不用接口就没法定义成数组,因为既可以是circle ,也可以是square ,而且以后还可能加上rectangle,等等.

6、相似概念 1、多态性

指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。  a.编译时多态性:通过重载函数实现  b.运行时多态性:通过虚函数实现。 2、虚函数  虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载。 3、抽象类  包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。

# 23、模板类和模板函数的区别是什么?

函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指

定。即函数模板允许隐式调用和显式调用而类模板只能显示调用。在使用时类模板必须加<T>,而函数模板不必

# 24、多继承的优缺点,作为一个开发者怎么看待多继承

  1. C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。

  2. 多重继承的优点很明显,就是对象可以调用多个基类中的接口;

  3. 如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性

  4. 加上全局符确定调用哪一份拷贝。比如pa.Author::eat()调用属于Author的拷贝。

  5. 使用虚拟继承,使得多重继承类Programmer_Author只拥有Person类的一份拷贝。

# 25、模板和实现可不可以不写在一个文件里面?为什么?

因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的CPP文件时才会去找对应的模板声明和实现,在这种情况下编译器是不知道实现模板类或函数的CPP文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接程序找地址。

但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。 《C++编程思想》第15章(第300页)说明了原因:模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,

它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。

# 26、将字符串“hello world”从开始到打印到屏幕上的全过程?

1.用户告诉操作系统执行HelloWorld程序(通过键盘输入等)

2.操作系统:找到helloworld程序的相关信息,检查其类型是否是可执行文件;并通过程序首部信息,确定代码和数据在可执行文件中的位置并计算出对应的磁盘块地址。

3.操作系统:创建一个新进程,将HelloWorld可执行文件映射到该进程结构,表示由该进程执行helloworld程序。

4.操作系统:为helloworld程序设置cpu上下文环境,并跳到程序开始处。

5.执行helloworld程序的第一条指令,发生缺页异常

6.操作系统:分配一页物理内存,并将代码从磁盘读入内存,然后继续执行helloworld程序

7.helloword程序执行puts函数(系统调用),在显示器上写一字符串

8.操作系统:找到要将字符串送往的显示设备,通常设备是由一个进程控制的,所以,操作系统将要写的字符串送给该进程

9.操作系统:控制设备的进程告诉设备的窗口系统,它要显示该字符串,窗口系统确定这是一个合法的操作,然后将字符串转换成像素,将像素写入设备的存储映像区

10.视频硬件将像素转换成显示器可接收和一组控制数据信号

11.显示器解释信号,激发液晶屏

12.OK,我们在屏幕上看到了HelloWorld

# 27、为什么拷贝构造函数必须传引用不能传值?

  1. 拷贝构造函数的作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。
  2. 参数传递过程到底发生了什么? 将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)! a 值传递: 对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量); 对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);

如void foo(class_type obj_local){}, 如果调用foo(obj); 首先class_type obj_local(obj) ,这样就定义了局部变量obj_local供函数内部使用

b 引用传递: 无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值!而地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型). 上述1) 2)回答了为什么拷贝构造函数使用值传递会产生无限递归调用,内存溢出。

拷贝构造函数用来初始化一个非引用类类型对象,如果用传值的方式进行传参数,那么构造实参需要调用拷贝构造函数,而拷贝构造函数需要传递实参,所以会一直递归。

# 28、静态函数能定义为虚函数吗?常函数呢?说说你的理解

1、static成员不属于任何类对象或类实例,所以即使给此函数加上virutal也是没有任何意义的。

2、静态与非静态成员函数之间有一个主要的区别,那就是静态成员函数没有this指针。

虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable.对于静态成员函数,它没有this指针,所以无法访问vptr。

这就是为何static函数不能为virtual,虚函数的调用关系:this -> vptr -> vtable ->virtual function。

# 29、虚函数的代价是什么?

  1. 带有虚函数的类,每一个类会产生一个虚函数表,用来存储指向虚成员函数的指针,增大类;

  2. 带有虚函数的类的每一个对象,都会有有一个指向虚表的指针,会增加对象的空间大小;

  3. 不能再是内联的函数,因为内联函数在编译阶段进行替代,而虚函数表示等待,在运行阶段才能确定到低是采用哪种函数,虚函数不能是内联函数。

# 30、说一说你了解到的移动构造函数?

  1. 有时候我们会遇到这样一种情况,我们用对象a初始化对象b后对象a我们就不在使用了,但是对象a的空间还在呀(在析构之前),既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么为什么我们不能直接使用a的空间呢?这样就避免了新的空间的分配,大大降低了构造的成本。这就是移动构造函数设计的初衷;

  2. 拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制;

  3. C++引入了移动构造函数,专门处理这种,用a初始化b后,就将a析构的情况;

  4. 与拷贝类似,移动也使用一个对象的值设置另一个对象的值。但是,又与拷贝不同的是,移动实现的是对象值真实的转移(源对象到目的对象):源对象将丢失其内容,其内容将被目的对象占有。移动操作的发生的时候,是当移动值的对象是未命名的对象的时候。这里未命名的对象就是那些临时变量,甚至都不会有名称。典型的未命名对象就是函数的返回值或者类型转换的对象。使用临时对象的值初始化另一个对象值,不会要求对对象的复制:因为临时对象不会有其它使用,因而,它的值可以被移动到目的对象。做到这些,就要使用移动构造函数和移动赋值:当使用一个临时变量对象进行构造初始化的时候,调用移动构造函数。类似的,使用未命名的变量的值赋给一个对象时,调用移动赋值操作;

Example6 (Example6&& x) : ptr(x.ptr) 
  {
    x.ptr = nullptr;
  }

  // move assignment
  Example6& operator= (Example6&& x) 
  {
   delete ptr; 
   ptr = x.ptr;
   x.ptr=nullptr;
    return *this;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 31、 什么情况下会合成构造函数?都说一说,你知道的都说一下

  1. 如果一个类没有任何构造函数,但他含有一个成员对象,该成员对象含有默认构造函数,那么编译器就为该类合成一个默认构造函数,因为不合成一个默认构造函数那么该成员对象的构造函数不能调用;

  2. 没有任何构造函数的类派生自一个带有默认构造函数的基类,那么需要为该派生类合成一个构造函数,只有这样基类的构造函数才能被调用;

  3. 带有虚函数的类,虚函数的引入需要进入虚表,指向虚表的指针,该指针是在构造函数中初始化的,所以没有构造函数的话该指针无法被初始化;

  4. 带有一个虚基类的类

还有一点需要注意的是:

  1. 并不是任何没有构造函数的类都会合成一个构造函数

  2. 编译器合成出来的构造函数并不会显示设定类内的每一个成员变量

# 32、那什么时候需要合成拷贝构造函数呢?

有三种情况会以一个对象的内容作为另一个对象的初值:

  1. 对一个对象做显示的初始化操作,X xx = x;

  2. 当对象被当做参数交给某个函数时;

  3. 当函数传回一个类对象时;

  4. 如果一个类没有拷贝构造函数,但是含有一个类类型的成员变量,该类型含有拷贝构造函数,此时编译器会为该类合成一个拷贝构造函数;

  5. 如果一个类没有拷贝构造函数,但是该类继承自含有拷贝构造函数的基类,此时编译器会为该类合成一个拷贝构造函数;

  6. 如果一个类没有拷贝构造函数,但是该类声明或继承了虚函数,此时编译器会为该类合成一个拷贝构造函数;

  7. 如果一个类没有拷贝构造函数,但是该类含有虚基类,此时编译器会为该类合成一个拷贝构造函数;

# 33、构造函数的执行顺序是什么?

  1. 在派生类构造函数中,所有的虚基类及上一层基类的构造函数调用;

  2. 对象的vptr被初始化;

  3. 如果有成员初始化列表,将在构造函数体内扩展开来,这必须在vptr被设定之后才做;

  4. 执行程序员所提供的代码;

# 34、一个类中的全部构造函数的扩展过程是什么?

  1. 记录在成员初始化列表中的数据成员初始化操作会被放在构造函数的函数体内,并与成员的声明顺序为顺序;

  2. 如果一个成员并没有出现在成员初始化列表中,但它有一个默认构造函数,那么默认构造函数必须被调用;

  3. 如果class有虚表,那么它必须被设定初值;

  4. 所有上一层的基类构造函数必须被调用;

  5. 所有虚基类的构造函数必须被调用。

# 35、哪些函数不能是虚函数?把你知道的都说一说

  1. 构造函数,构造函数初始化对象,派生类必须知道基类函数干了什么,才能进行构造;当有虚函数时,每一个类有一个虚表,每一个对象有一个虚表指针,虚表指针在构造函数中初始化;

  2. 内联函数,内联函数表示在编译阶段进行函数体的替换操作,而虚函数意味着在运行期间进行类型确定,所以内联函数不能是虚函数;

  3. 静态函数,静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义。

  4. 友元函数,友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。

  5. 普通函数,普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数。

# 36、什么是纯虚函数,与虚函数的区别

虚函数和纯虚函数区别

  • 虚函数是为了实现动态编联产生的,目的是通过基类类型的指针指向不同对象时,自动调用相应的、和基类同名的函数(使用同一种调用形式,既能调用派生类又能调用基类的同名函数)。虚函数需要在基类中加上virtual修饰符修饰,因为virtual会被隐式继承,所以子类中相同函数都是虚函数。当一个成员函数被声明为虚函数之后,其派生类中同名函数自动成为虚函数,在派生类中重新定义此函数时要求函数名、返回值类型、参数个数和类型全部与基类函数相同。

  • 纯虚函数只是相当于一个接口名,但含有纯虚函数的类不能够实例化。

纯虚函数首先是虚函数,其次它没有函数体,取而代之的是用“=0”。

既然是虚函数,它的函数指针会被存在虚函数表中,由于纯虚函数并没有具体的函数体,因此它在虚函数表中的值就为0,而具有函数体的虚函数则是函数的具体地址。

一个类中如果有纯虚函数的话,称其为抽象类。抽象类不能用于实例化对象,否则会报错。抽象类一般用于定义一些公有的方法。子类继承抽象类也必须实现其中的纯虚函数才能实例化对象。

举个例子:

#include <iostream>
using namespace std;

class Base
{
public:
	virtual void fun1()
	{
		cout << "普通虚函数" << endl;
	}
	virtual void fun2() = 0;
	virtual ~Base() {}
};

class Son : public Base
{
public:
	virtual void fun2() 
	{
		cout << "子类实现的纯虚函数" << endl;
	}
};

int main()
{
	Base* b = new Son;
	b->fun1(); //普通虚函数
	b->fun2(); //子类实现的纯虚函数
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 参考文献

《C++中的虚函数》https://blog.csdn.net/wuchuanpingstone/article/details/6742465

《C++虚函数简介》https://blog.csdn.net/xiaoyanilw/article/details/108575035

《C++ Map用法》https://blog.csdn.net/sevenjoin/article/details/81943864

《C++中堆(heap)和栈(stack)的区别》:https://blog.csdn.net/qq_34175893/article/details/83502412

《虚函数表存放在哪里》:https://blog.csdn.net/u013270326/article/details/82830656

C语言与C++有什么区别? (opens new window)》https://www.cnblogs.com/ITziyuan/p/9487760.md

《C++和java的区别和联系》:https://www.cnblogs.com/tanrong/p/8503202.md

《struct结构在C和C++中的区别》:https://blog.csdn.net/mm_hh/article/details/70456240

《C++ 顶层const与底层const总结》:https://www.jianshu.com/p/fbbcf11100f6

《C++的顶层const和底层const浅析》:https://blog.csdn.net/qq_37059483/article/details/78811231

《C++:override和final》:https://www.cnblogs.com/whlook/p/6501918.md

C++的直接初始化与复制初始化的区别:https://blog.csdn.net/qq936836/article/details/83450218

《extern "C"的功能和用法研究》:https://blog.csdn.net/sss_369/article/details/84060561

《类和函数模板特例化》:https://blog.csdn.net/wang664626482/article/details/52372789

《C++实现多态的原理》:https://blog.csdn.net/qq_37954088/article/details/79947898

《浅谈C++中的几种构造函数》:https://blog.csdn.net/zxc024000/article/details/51153743

《C++面试题之浅拷贝和深拷贝的区别》:https://blog.csdn.net/caoshangpa/article/details/79226270

《构造函数、析构函数、虚函数可否内联,有何意义》:https://www.cnblogs.com/helloweworld/archive/2013/06/14/3136705.md

《auto和decltype的用法总结》:https://www.cnblogs.com/XiangfeiAi/p/4451904.md

《C++11新特性中auto 和 decltype 区别和联系》:https://www.jb51.net/article/103666.htm

《写程序判断系统是大端序还是小端序》:https://www.cnblogs.com/zhoudayang/p/5985563.md

《C++拷贝构造函数详解》:https://www.cnblogs.com/alantu2018/p/8459250.md

《【C++】几种类型的new介绍》:https://www.jianshu.com/p/9b57e769c3cb

《NULL和nullptr区别》:https://blog.csdn.net/qq_39380590/article/details/82563571

《C/C++内存管理详解》:https://chenqx.github.io/2014/09/25/Cpp-Memory-Management/

《C++异常处理(try catch throw)完全攻略》:http://c.biancheng.net/view/422.md

《C++STL 》https://www.bilibili.com/video/BV1db411q7B8?p=12

《C++内存管理》https://www.bilibili.com/video/BV1Kb411B7N8?p=25

《C++中的 trivial destructor》:https://blog.csdn.net/wudishine/article/details/12307611

《C++封装继承多态总结》:https://blog.csdn.net/IOT_SHUN/article/details/79674293

《C++类对象成员变量和函数内存分配的问题》:https://blog.csdn.net/z2664836046/article/details/78967313

《为什么用成员初始化列表会快一些?》:https://blog.csdn.net/JackZhang_123/article/details/82590368

《为什么C++不能有虚构造函数,却可以有虚析构函数》:https://dwz.cn/lnfW9H6m

《构造函数或者析构函数中调用虚函数会怎么样?》:https://dwz.cn/TaJTJONX

《智能指针的原理及实现》:https://blog.csdn.net/lizhentao0707/article/details/81156384

《C++构造函数的default和delete》:https://blog.csdn.net/u010591680/article/details/71101737

《C/C++函数调用过程分析》:https://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601204.md

《C/C++函数调用的压栈模型》:https://blog.csdn.net/m0_37717595/article/details/80368411

《C++临时变量不能作为函数的返回值?》:https://www.wandouip.com/t5i204349/

《C++中this指针的用法详解》http://blog.chinaunix.net/uid-21411227-id-1826942.md

《C++ 智能指针(及循环引用问题)》:https://blog.csdn.net/m0_37968340/article/details/76737395

《C++中的静态绑定和动态绑定》:https://www.cnblogs.com/lizhenghn/p/3657717.md

《C++经典面试题》:https://www.cnblogs.com/yjd_hycf_space/p/7495640.md

《C++ 虚函数表解析》:https://blog.csdn.net/haoel/article/details/1948051/

《操作系统(三)》:https://www.nowcoder.com/tutorial/93/675fd4af3ab34b2db0ae650855aa52d5

《互斥锁、读写锁、自旋锁、条件变量的特点总结》:https://blog.csdn.net/RUN32875094/article/details/80169978

《c++右值引用以及使用》:https://www.cnblogs.com/likaiming/p/9045642.md

《从4行代码看右值引用》:https://www.cnblogs.com/likaiming/p/9029908.md

《Free的前世今生》https://blog.csdn.net/YMY_me/article/details/811801

《友元函数和友元类》:https://www.cnblogs.com/zhuguanhao/p/6286145.md

《程序员求职宝典》王道论坛

《STL源码解析》侯捷