433-C++基础语法(51-60)

51、将字符串“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

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

  • 拷贝构造函数的作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。

a 值传递:

  • 对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);
  • 对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);

b 引用传递:

  • 无论对内置类型还是类类型传递引用或指针最终都是传递的地址值
  • 地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用

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

53、静态函数能定义为虚函数吗?说说你的理解

不可以定义为虚函数!

  • static成员不属于任何类对象或类实例,所以即使给此函数加上virutal也是没有任何意义的。
  • 静态与非静态成员函数之间有一个主要的区别,那就是静态成员函数没有this指针。

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

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

54、虚函数的代价是什么?

  • 带有虚函数的类,每一个类会产生一个虚函数表,用来存储指向虚成员函数的指针,增大类的内存!
  • 带有虚函数的类的每一个对象,都会有有一个指向虚表的指针,会增加对象的空间大小;
  • 不能再是内联的函数,因为内联函数在编译阶段进行替代,而虚函数在运行阶段才能确定到底是采用哪种函数,虚函数不能是内联函数

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

背景:

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

  • 拷贝构造函数中,对于指针,我们一定要采用深层复制而移动构造函数中,对于指针,我们采用浅层复制;
  • C++引入了移动构造函数,专门处理这种,用a初始化b后,就将a析构的情况;
  • 与拷贝类似,移动也使用一个对象的值设置另一个对象的值
  • 但是,又与拷贝不同的是,移动实现的是对象值真实的转移(源对象到目的对象),源对象将丢失其内容其内容将被目的对象占有
  • 移动操作的发生的时候,是当移动值的对象未命名的对象的时候。这里未命名的对象就是那些临时变量,甚至都不会有名称
  • 典型的未命名对象就是函数的返回值或者类型转换的对象。使用临时对象的值初始化另一个对象值,不会要求对对象的复制:因为临时对象不会有其它使用,因而,它的值可以被移动到目的对象。
  • 做到这些,就要使用移动构造函数和移动赋值:当使用一个临时变量对象进行构造初始化的时候,调用移动构造函数。类似的,使用未命名的变量的值赋给一个对象时,调用移动赋值操作
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;
}

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

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

  1. 对一个对象做显示的初始化操作,X xx = x;
  2. 当对象被当做参数交给某个函数时,即传参时
  3. 函数返回一个类对象时;
  4. 如果一个类没有拷贝构造函数,但是含有一个类类型的成员变量,该类型含有拷贝构造函数,此时编译器会为该类合成一个拷贝构造函数;
  5. 如果一个类没有拷贝构造函数,但是该类继承自含有拷贝构造函数的基类,此时编译器会为该类合成一个拷贝构造函数;
  6. 如果一个类没有拷贝构造函数,但是该类声明或继承了虚函数,此时编译器会为该类合成一个拷贝构造函数;
  7. 如果一个类没有拷贝构造函数,但是该类含有虚基类,此时编译器会为该类合成一个拷贝构造函数;

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

  • 在派生类构造函数中,先执行所有的虚基类及上一层基类的构造函数调用;
  • 对象的vptr被初始化;
  • 如果有成员初始化列表,将在构造函数体内扩展开来,这必须在vptr被设定之后才做;
  • 执行程序员所提供的代码;

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

  1. 构造函数,虚函数对应一个vtable(虚函数表),类中存储一个vptr指向这个vtable。如果构造函数是虚函数,就需要通过vtable调用,可是对象没有初始化就没有vptr,无法找到vtable,所以构造函数不能是虚函数。
  2. 内联函数内联函数表示在编译阶段进行函数体的替换操作,而虚函数意味着在运行期间进行类型确定,所以内联函数不能是虚函数
  3. 静态函数,静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义。
  4. 友元函数,**友元函数不属于类的成员函数,不能被继承。**对于没有继承特性的函数没有虚函数的说法。
  5. 普通函数普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数。

59、什么是纯虚函数,与虚函数的区别?

虚函数和纯虚函数区别?

虚函数:

  • 虚函数是为了实现动态编联产生的,目的是通过基类类型的指针指向不同派生类/基类对象时,自动调用相应的、和基类同名的函数(使用同一种调用形式,既能调用派生类又能调用基类的同名函数)。
  • 虚函数需要在基类中加上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;
}

60、介绍面向对象的三大特性,并且举例说明

三大特性: 封装、继承和多态。

1、封装

  • 数据和代码捆绑在一起,避免外界干扰和不确定性访问。
  • 封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏,例如:将公共的数据或方法使用public修饰,而不希望被访问的数据或方法采用private修饰。

2、继承

  • 让某种类型对象获得另一个类型对象的属性和方法;
  • 它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

常见的继承有三种方式:

  • 实现继承:指使用基类的属性和方法而无需额外编码的能力;
  • **接口继承:**指仅使用属性和方法的名称、但是子类必须提供实现的能力(抽象类)

3、多态
同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为。

重载实现编译时多态,虚函数实现运行时多态。

允许将子类类型的指针赋值给父类类型的指针。

实现多态有二种方式:覆盖(override),重载(overload)。

覆盖(编译时多态): 是指子类重新定义父类的虚函数的做法。

重载(运行时多态):

  • 是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
  • 例如:基类是一个抽象对象——人,那教师、运动员也是人,而使用这个抽象对象既可以表示教师、也可以表示运动员。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

liufeng2023

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

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

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

打赏作者

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

抵扣说明:

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

余额充值