• 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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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;
    }
    
    
    • 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
    • 31

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

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

    1、封装

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

    2、继承

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

    常见的继承有三种方式:

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

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

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

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

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

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

    重载(运行时多态):

    • 是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
    • 例如:基类是一个抽象对象——人,那教师、运动员也是人,而使用这个抽象对象既可以表示教师、也可以表示运动员。
  • 相关阅读:
    计网第六章(应用层)(三)(文件传输协议FTP)
    Oracle之ADG与DG的区别?
    【三剑客+JSP+Mysql+Tomcat】从前到后搭建简易编程导航小网站(期末作业)
    C++11标准模板(STL)- 算法(std::stable_sort)
    BERT预训练模型学习笔记
    3天快速入门python机器学习(黑马xxx)
    Java编程练习题Demo41-Demo50
    【C进阶】指针笔试题解析
    了解Netty,从IO开始
    深度学习-优化器
  • 原文地址:https://blog.csdn.net/Edward_LF/article/details/125503990