加载中...

面向对象期末复习大纲


前言

​ 本文重点在理清简答题的概念。根据老师发的课件整理了一下,因为我们学校简答题占60%,所以这里全部整理了出来,其他学校的同学可以根据需要看一看。实际上这门语言还是很重实战的,有时候看很久课本或网课不如上手打一个类效果来的好。

复习大纲:

1.用代码说明在标准C++中如何进行输入输出,并解释各语句的含义是什么?

cout<<"hello!"<<"world";
cin>>a>>b;
  • 在输入时,从键盘输入的数据先放在键盘缓冲区中,当按回车键时,键盘缓冲区中的数据输入到程序中的输入缓冲区,形成cin,然后用流提取运算符“>>”从输入缓冲区中提取数据送给程序中的有关变量。
  • 当用**cout和流插入运算符“<<”向显示器输出数据时,先将这些数据送到程序中的输出缓冲区保存,直到缓冲区满了或遇到endl,就将缓冲区中的全部数据送到显示器显示出来。**

2.阐述C++中函数三种调用方式的实现机制、特点及其实参、形参格式,最好用代码说明。试比较三种方式的执行效率。(提示:传址、传值、引用传递)

  • 这里所说的执行效率,是指在被调用的函数体内执行时的效率。 因为传值调用时,当值被传到函数体内,临时对象生成以后,所有的执行任务都是通过直接寻址的方式执行的,而指针和大多数情况下的引用则是以间接寻址的方式执行的,所以实际的执行效率会比传值调用要低。如果函数体内对参数传过来的变量进行操作比较频繁,执行总次数又多的情况下,传址调用和大多数情况下的引用参数传递会造成比较明显的执行效率损失。
  • 总结:传值调用后,函数体内的对象通过直接寻址方式执行,而传址和引用调用则是通过间接寻址方式执行,执行效率不如传值调用,但具体的执行效率要具体来看,我们推荐大部分传递优先采用引用传递。

3.什么是内联函数?为什么要使用内联函数?

  • 在编译时将所调用函数的代码直接嵌入到主调函数中。
  • 实现代码的重用,提高开发效率。

4.什么是类的前向声明?使用类的前向声明时,要注意什么?

  • 引用未定义的类之前,声明该类。
  • 在没有定义完整的类之前,不能使用这个类创建对象。

5.什么是先验条件(Precondition),什么是后置条件(Postcondition)?

  • **先验条件是在执行某段代码或正式规范操作之前必须始终为真的条件或谓词。**比如输入一个时间必须小于24。
  • **后置条件是在执行某段代码或正式规范操作之后必须始终为真的条件或谓词。**比如计算输入数字的平方根程序可能具有结果为数字的后置条件,且其平方等于输入。

6.什么是名称空间(namespace)?它的主要作用是什么?要使用一个名称空间中声明的标识符,方式有哪些?

  • 名字空间实质上是一种作用域。名字空间是一种描述逻辑分组的机制,是为了解决C++中的变量、函数命名冲突而服务的。

  • 直接指定标识符

    #include <iostream>
    std::cout << "hello!!"<< std::endl;
  • 使用using关键字进行声明

    #include <iostream>
    using std::cout;
    using std::endl;
  • 最方便的就是使用指令using namespace std;

    #include <iostream>
    using namespace std;

7.什么是重载(Overloading),解释并举例说明?能否根据返回值不同,对函数进行重载,为什么?

  • C++允许用同一函数名定义多个函数,这些函数的参数个数和参数类型不同。

    int max(int a,int b, int c);

    double max(double a,double b,double c);

    long max(long a,long b,long c);

  • 不能根据返回值不同进行重载。

    因为调用时不能指定类型信息,编译器不知道你要调用哪个函数。

8.如何防止一个头文件被多重包含?举例说明。

  • 如果a.hb.h都包含了一个头文件x.h。那么x.h在此也同样被包含了两次,只不过它的形式不是那么明显而已。

    #include "a.h"
    #include "b.h"
  • 可以使用条件编译。

    #ifndef _X_H
    #define _X_H
    ..//(头文件内容)
    #endif
  • 当头文件第一次被包含时,它被正常处理,符号_X_H被定义为1。如果头文件被再次包含,通过条件编译,它的内容被忽略。

9.dynamic-cast的作用是什么?试举例说明。

  • dynamic_cast < Type-id > ( expression ) 该运算符把expression转换为type-id类型,并且可以在运行期间检测类型转换是否安全。dynamic_cast要求转型的目的类型必须是指针或者引用。将基类指针转换为派生类指针,将基类引用转换为派生类引用;

  • 转换是有条件的

    如果指针(或引用)所指对象的实际类型与转换的目的类型兼容,则转换成功进行;

    否则如执行的是指针类型的转换,则得到空指针;如执行的是引用类型的转换,则抛出异常。

10.叙述面向对象编程的特点是什么?(提示:封装、继承、多态。)

  • 对比面向过程具有抽象、封装、继承和多态的特点。

    封装是将抽象得到的数据和行为相结合,形成了一个有机整体,使得一部分成员充当类与外部的接口,而将其他成员隐藏了起来达到了对成员访问权限的合理控制,使得不同类之间的影响最小,加强数据安全,简化编程。

    继承允许在保持原有类特性的基础上,进行更具体、更详细的说明,能够很好反映出特殊概念和一般概念之间的关系,是代码复用的基础机制。

    多态使得一段程序能够具有处理多种类型对象的能力,相同的消息在不同的对象下会有不同的动作,增强了编程的灵活性。

11.什么是封装?其作用是什么?

  • 封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的函数代码进行有机结合,形成类。

  • 作用:使一部分成员充当类与外部的接口,而将其他成员隐藏起来,这样就达到了对成员访问权限的合理控制,使不同类之间的相互影响减少到最低限度,进而保护数据增强数据的安全性和简化程序编写工作。

12.什么是类?通常一个类中,包含什么样的内容?定义一个类的语法是什么,试举例说明。

  • 类是逻辑上相关的函数与数据的封装,描述了所创建对象共同的属性和方法。
  • 类中声明或定义的变量和函数称为成员,类的成员包括数据成员和函数成员,数据成员描述问题的属性,函数成员描述问题的行为。

13.什么是对象?什么是类?类与对象的关系是什么?

  • 类是逻辑上相关的函数与数据的封装,它是对问题的抽象描述。
  • 对象是类的某一特定实体。
  • 类对象的关系:类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。

14.类中的成员可以用public / protected / private分别进行修饰,这三种成员在什么情况下是可以被访问的?类中没有用public / protected / private修饰的成员,其可访问性是什么,结构体中没有用public / protected / private修饰的成员,其可访问性是什么?

  • public修饰的成员可以在任何地方被访问
  • private修饰的成员只能由该类中的函数、其友元函数访问;不能被任何其他访问,该类对象也不能访问。
  • protected修饰的成员可以被该类中函数、子类函数、友元函数访问;但不能被该类对象访问。
  • 类的默认为privatestruct默认为public

15.什么是类作用域(Class scope)、文件作用域(file scope)、函数作用域(function scope)?

  • 类作用域:类是有名成员的集合,类X的成员m具有类作用域,对成员m的访问方式有如下三种:
    • 如果X的成员函数中没有声明同名的局部作用域标识符,那么可以直接使用成员m
    • 通过表达式x.mX::m(访问静态成员)
    • 通过ptr->m,其中ptr为指向X类的一个对象的指针
  • 文件作用域:
    • 在函数外部声明的变量只在当前文件范围内(包括文件内所有定义的函数)可用
    • 在其他文件不可用。要使变量具有文件作用域,必须在变量的声明前加static关键字。
    • 当多个源文件链接成一个程序时,static可以避免一个文件中的全局变量与其它文件中的变量同名而发生冲突。
  • 函数作用域:指在函数定义或者复合语句中,从标识符的定义点开始到函数或者一对花括号之间的程序段。
    • 在同一个局部作用域内不能出现相同名字的两个局部变量(包括形参)。
    • 一个函数内的复合语句又是一个局部作用域,也就是在函数内有某个变量时,复合语句中可以有另外一个同名字的变量。

16.什么是this指针,其作用是什么?

  • this指针是一个隐含于每一个成员函数中的特殊指针。它是一个指向正在被该成员函数操作的对象,也就是要操作该成员函数的对象。通过this指针可以访问当前对象的所有成员。

17.在一个类中,为什么静态成员函数( static member function )中不能使用 this 指针?

  • 静态成员函数并不是针对某个类的实例对象,而是属于整个类的,为所有的对象实例所共有。他在作用域的范围内是全局的,独立于类的对象之外的。他只对类内部的静态成员变量做操作。当实例化一个类的对象时候,里面不存在静态成员的。this指针是相当于一个类的实例的指针,this是用来操作对象实例的内容的,既然静态成员函数和变量都是独立于类的实例对象之外的,他就不能用this指针。也不能操作非静态成员。

18.什么是构造函数?构造函数有返回值吗?构造函数如何命名?构造函数可以重载吗?什么是缺省构造函数(default constructor)?什么情况下,类中会有缺省构造函数?

  • 构造函数主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值。
  • 构造函数没有返回值。
  • 构造函数是一个与其所在的类同名的函数。
  • 构造函数可以重载。但是, 每个构造函数必须有不同的函数签名。
  • 如果构造函数没有参数,或者构造函数的所有参数都有默认值,就可以称其为缺省构造函数。一个类中,只能有一个缺省构造函数。
  • 当没有定义构造函数或者定义的构造函数没有参数时,类中会有缺省构造函数。

19.构造函数的作用是什么?什么时候会被调用?构造函数的执行顺序是什么(父类与子类的构造函数、类自身与其数据成员的构造函数)?

  • 构造函数主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值。
  • 当类被创建时,自动调用。
  • 执行构造函数的顺序:
    • 1.父类的构造函数
    • 2.数据成员的初始化(成员中有类,执行该类的构造函数)
    • 3.子类的构造函数

20.为什么拷贝构造函数(copy constructor)的参数必须是按引用传递(by reference)而不能是按值传递(by value)?

  • 无限递归调用:当一个对象需要以值方式传递时编译器会生成代码调用它的拷贝构造函数以生成一个复本。如果类 A的拷贝构造函数是以值方式传递一个类 A 对象作为参数的话,当需要调用类 A 的拷贝构造函数时,需要以值方式传进一个 A 的对象作为实参;而以值方式传递需要调用类 A 的拷贝构造函数;结果就是调用类 A 的拷贝构造函数导致又一次调用类A的拷贝构造函数,这就是一个无限递归。
  • 在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那 A 中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时 A 内的指针就是野指针了,出现运行错误。

21.拷贝构造函数(复制构造函数)的作用是什么?什么是浅拷贝?什么是深拷贝?

  • 复制构造函数由编译器调用来完成一些基于同一类的其他对象的构建及初始化。
  • 浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

22.全局对象(Global scope objects)的构造函数、析构函数分别是什么时候被调用的?自动局部对象(Automatic local objects)的构造函数、析构函数分别是什么时候被调用的?静态局部对象(static local objects)的构造函数、析构函数分别是什么时候被调用的?

  • 全局变量构造函数程序运行前被调用,在main()函数返回后全局对象才被销毁,析构函数在程序结束前最后被调用。
  • 自动局部变量,当程序执行到对象定义时,调用自动局部对象的构造函数。该对象的析构函数在对象离开范围时调用(即离开定义对象的块时)。自动对象的构造函数与析构函数在每次对象进人和离开范围时调用。
  • 静态局部对象的构造函数只在程序执行首次到达对象定义时调用一次,对应的析构函数在main终止或调用exit函数时调用。

23.什么是初始化列表(Initialization Sections)?它的作用是什么?(提示:一般数据成员的初始化、常量成员的初始化,对象成员构造函数的选择、父类构造函数的选择等)。

  • 构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。

    class A
    {
    public:
        int a;
        float b;
        A(): a(0),b(9.9) {} //构造函数初始化列表
    };
  • 初始化列表作用:一般数据成员的初始化、常量成员的初始化,对象成员构造函数的选择、父类构造函数的选择。

24.什么是析构函数?析构函数有返回值吗?析构函数如何命名?析构函数可以重载吗?

  • 与构造函数相反,当对象结束其生命周期,如果对象所在的函数已调用完毕时,系统会自动执行析构函数。
  • 析构函数没有返回值。
  • 名字与类名相同,在前面加‘ ~ ’。
  • 析构函数不返回任何值,没有函数类型,也没有函数参数,因此它不能被重载。

25.析构函数的作用是什么?什么时候会被调用?为什么析构函数通常是虚函数,如果不是虚函数,会如何?

  • 作用:清空并释放对象先前创建或者占用的内存资源。

  • 析构函数对象消亡时即自动被调用。

  • 如果析构函数不被声明成虚函数,则编译器采用的绑定方式是静态绑定,在删除基类指针时,只会调用基类析构函数,而不调用派生类析构函数,这样就会导致基类指针指向的派生类对象析构不完全。

  • 若是将析构函数声明为虚函数,不管派生类的析构函数前是否加virtual(可以理解为编译器优化),都构成重写。基类的指针指向派生类的对象,然后调用重写虚函数——析构函数,构成了多态,而多态与类型无关,只与对象有关,所以就能够调用的就是派生类的析构函数了。

26.如果要编写一段程序,跟踪类A所创建的实例的个数,请叙述编写程序的大体思路。

#include<iostream>
#include<string.h>
#include<stdio.h>

using namespace std;

class A
{
public:
  A(){ i++; }
  ~A(){ i--; }
  int get(){ return i; }
private:
  static int i;
};

int A::i(0);

int main()
{
  A c;
  A b;
  A e;
  cout<<c.get()<<endl;
  A *p=new A;
  cout<<c.get()<<endl;
  delete p;
  cout<<c.get()<<endl;
  return 0;
}

27.什么是C++中的三大函数(The Big Three)?

  • Big Three: 是指 如果一个类要实现下面某一个成员函数,一般都要一起实现另外两个:
    • Desconstructor 析构函数
    • copy constructor 拷贝构造函数
    • operator = 赋值函数

28.使用const定义常量与用使用define定义常量相比,有什么优点?

  • const常量有数据类型,而宏常量没有数据类型。编译器可以对const常量进行类型安全检查,而对宏常量只能字符替换
  • 有些集成化的调试工具能对const常量进行调试,对宏常量不能调试
  • const定义的常量在程序运行的过程中只有一份拷贝,而define定义的常量在内存中有若干拷贝。

29.C++中如何进行静态类型转换,解释并举例说明。

  • 用于类层次结构中基类和派生类之间指针或引用的转换。进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。

    class Base { };
    class Derived:public Base {};
    
    int main()
    {
      Derived D;
      Base* B = static_cast<Base*> (&D);
      return 0;
    }
  • 将派生类型的指针转化为基类型的指针。

  • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

    double a;
    int b=100;
    a = static_cast<double> (b);

30.关键字const的用法有哪些?

  • 定义常量

    const int val = 5int const val = 5;
  • 修饰指针

    const int* p;     //指针p指向的内容是常量,不可改变。
    int* const p;      //指针本身是一个常量,不可改变。
    const int* const p;  //指针本身和指向的内容都是常量,都不可以改变。
  • 修饰函数参数 void function(const int Var); 表明参数在函数体内不能被修改,但此处没有任何意义,Var本身就是形参,在函数内不会改变。

    • 使用引用参数,可以防止创建副本,减少内存开销,同时可以在函数中对引用参数修改,函数结束后,引用参数的修改仍然存在。
    • 如果为了防止对引用参数进行修改,可以对该参数加上const关键字。
  • 修饰函数返回值:与修饰普通变量和指针意义差不多,而在传引用时,如果不希望函数返回值被改变,就可以添加关键字 const

在类中使用const

  • 修饰类成员变量

    class A{
      const int nValue;
    }
    • 成员常量不可被修改。只能在初始化列表中被赋值。
  • 修饰类成员函数

    class A{
      void function()const;
    }
    • 常成员函数, 它不改变对象的成员变量. 代表只读函数,增加程序的可读性。

    • 常成员函数不能调用类中任何非const成员函数。

31.C++中,static静态变量的初始化和取值问题?

  • 初始化:不能在main()函数和类的构造函数中定义,必须在类外定义。

32.操作符new的作用是什么?如何申请单个空间?如何申请动态数组?用new创建类的对象时,会发生哪些操作?必要时,请用代码说明。

  • 作用:在堆中申请一段空间,动态分配内存。

  • 申请单个空间 int *i = new int;

  • 申请动态数组 int *a = new int[10];

  • new创建类对象需要指针接收,一处初始化,多处使用,作用域是全局,且需要手动释放空间,在堆中动态分配内存,调用构造函数。

33.操作符delete的作用是什么?如何删除单个用new申请的空间?如何删除申请的动态数组?用delete删除一个类的对象时,会发生哪些操作?必要时,请用代码说明。

  • 作用:释放所申请的空间
  • 释放单个空间delete i;
  • 释放动态数组delete []a;
  • 释放在堆中分配的内存,调用析构函数。

34.什么是悬挂指针(又称为野指针,Dangling Pointers),其危害是什么?

  • 指针指向非法的内存地址,那么这个指针就是悬挂指针,也叫野指针。意为无法正常使用的指针。
  • 野指针造成的危害程度和危害时间未知,因为野指针指向的内存空间,有可能是某个重要的数据或其他程序。严重的情况下会造成程序崩溃。

35.什么是切片(Slicing)?(注意参考讲义)

  • 派生类的存储结构与基类的存储结构存在着“粘接(splice)”关系:当子类对象拷贝到父类对象时,父类对象中只存在父类定义的成员,而不会出现任何子类中的成员。

36.若父类中没有缺省构造函数,则对派生类的构造函数有什么要求?

  • 如果父类是一个无缺省参数的构造函数,那么对于派生类一旦没有构造函数,那么就不会自动的先构造父类的构造函数,这是不允许的。
  • 派生类中一定要有构造函数。
  • BaseballTeam(const string s[], int si) : Team(si)
  • 派生类的构造函数通过初始化列表,对基类进行初始化。

37.父类成员中的publicprotectedprivate成员,哪些在子类中是可以访问的?在公有继承、私有继承、受保护继承三种继承方式下,父类成员中的publicprotectedprivate成员被继承到子类后,其可访问性分别是什么?派生类是否可以继承父类的构造函数和析构函数?

  • publicprotected是可以访问的,private不可访问。
  • 公有继承:publicprotectedprivate
  • 私有继承:privateprivateprivate
  • 保护继承:protectedprotectedprivate
  • 派生类不能继承父类的构造函数和析构函数。

38.多重继承会带来什么问题?在C++中是如何解决的?

  • 问题1:类DC的对象中存在多个同名成员 x, 应如何使用?

  • 解决方案:在DC对象调用x时,用作用域标识符说明 x 是属于哪个类的。

    class BC0
    {
    public:
    	int K;
    };
    
    class BC1 : virtual public BC0
    {
    public:
    	int x;
    };
    
    class BC2 : virtual public BC0
    {
    public:
    	int x;
    };
    
    class DC : public BC1,public BC2
    {
    };
    
    void main()
    {
    	DC d;    //虚继承使得BC0仅被DC间接继承一份
    	d.K = 13;   // OK
    	d.BC1::x = 1;
        d.BC2::x = 2;
    }
  • 问题2:类DC的对象中,存在两份来自类 BC0 的成员 K,如何区分?

  • 解决方案:在BC1类和BC2类继承BC0时,其前面加上virtual关键字就可以实现虚拟继承,使用虚拟继承后,当系统碰到多重继承的时候就会先自动加一个BC0的拷贝,当再次请求一个BC0的拷贝时就会被忽略,以保证继承类成员函数的唯一性。

    class BC0
    {
    public:
      int K;
    };
    
    class BC1 : virtual public BC0
    {
    public:
      int x;
    };
    
    class BC2 : virtual public BC0
    {
    public:
      int x;
    };
    
    class DC : public BC1, public BC2
    {
    };
    
    void main( )
    {
      DC d;    //虚继承使得BC0仅被DC间接继承一份
      d.K = 13;   // OK
    }

39.什么是纯虚函数?什么是抽象数据类型(ADT)?抽象类的作用是什么?抽象类是否可实例化?抽象类的什么样子类可以实例化?

  • 纯虚函数是没有函数体的虚函数,它的实现留给该基类的派生类去做,这就是纯虚函数的作用。
  • 抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。
  • 抽象类不可实例化,只可以派生。
  • 抽象类派生的子类必须重置基类的纯虚函数才能实现实例化。
  • 抽象数据类型是具有类似行为的特定类型的数据结构的数学模型:或者具有类似语义的一种或者多种程序设计语言的数据类型。
  • 定义:一个数学模型以及定义在该模型上的一组操作。

40.对于函数调用,什么是前期绑定(Early Binding,又称为静态联编)?什么是后期绑定(Late Binding,又称为动态联编)?重载函数是后期绑定吗,如果不是为什么?

  • 绑定:程序自身彼此关联的过程,确定程序中的操作调用与执行该操作的代码间的关系。例如把一个标示符名和一个存储地址联系在一起的过程。

  • 用面向对象的术语讲,就是把一条消息和一个对象的方法相结合的过程。

  • 按照绑定进行的阶段的不同,可以分为静态绑定和动态绑定两种。

  • 静态绑定:绑定工作在编译连接阶段完成。

    因为绑定过程是在程序开始执行之前进行的,因此也称为早期绑定或前绑定。

    在编译、连接过程中,系统就可以根据类型匹配等特征确定程序中操作调用与执行该操作代码的关系,即确定了某一个同名标识到底是要调用哪一段程序代码。

  • 动态绑定:和静态绑定相对应,绑定工作在程序运行阶段完成的。

    class A
    {
    public:
        virtual void Get( );
    };
    
    class B : public A
    {
    public:
        virtual void Get( );
    };
    
    void MyFunction( A * pa )
    {
    	pa->Get( );
    }

    pa->Get( ) 调用的是 A::Get( )还是B::Get( ),编译时无法确定,因为不知道MyFunction被调用时,形参会对应于一个 A 对象还是B对象。

    所以只能等程序运行到 pa->Get( )了,才能决定到底调用哪个Get()

  • 重载函数是静态绑定。

41.要让一个函数调用表现出多态特征,必须满足哪些条件?

  • 必须存在继承关系;
  • 子类重写父类的方法。继承关系中必须有同名的虚函数,并且它们是覆盖关系(重载不行)。
  • 存在基类的指针,通过该指针调用虚函数。

42.什么是隐藏(hiding)、覆盖(overriding)、重载(overloading)?对比它们的异同?以C++代码为例进行说明。

  • 若基类 B 定义了非虚方法 m,同时其派生类 D 也定义了方法m,此时,我们说派生类方法 D::m 隐藏了继承自基类的同名方法 B::m 。由于函数签名不同,所以二者不构成重置。故D::m隐藏了B::m

    class B
    {
    public:
        void m(int x)
        {
        }
    };
    
    class  D : public B
    {
    public:
        void m ( )//由于函数签名不同,所以二者不构成重置。
        {
        }
    };
    
    int main( )
    {
        D  d1 ;
        d1.m();  // invokes D::m()
        //d1.m(10); // ERROR
        d1.B::m(10);  // OK
        return 0;
    }
  • 覆盖(override)是指派生类中存在重新定义的函数,其函数名、参数列、返回值类型必须同父类中的相对应被覆盖的函数严格一致,覆盖函数和被覆盖函数只有函数体不同,当派生类对象调用派生类中该同名函数时会自动调用派生类中的覆盖版本,而不是父类中的被覆盖函数版本,这种机制就叫做覆盖。

    class B
    {
    public:
        virtual void m()
        {
        }
    };
    
    class  D : public B
    {
    public:
        void m ()//重置了基类方法,仍然为虚函数
        {
        }
    };
    
    int main()
    {
        B*p= new D;
        p -> m();  // 动态绑定 D::m()
        return 0; 
    }
  • 重载:如果顶层函数如果同一类中的函数有不同的签名,则函数名可以相同。编译过程中绑定函数调用和对应的函数体。

    class C
    {
    public:
        C()
        {
        }     // default constructor
        C(int x)
        {
        }  // convert constructor
    };
    void  print( double d );
    void  print( char * );
    int  main( )
    {
        C c1,c2(26);
        print(100.123);
        print( "100.123" );
    }

43.什么是多态?

  • 多态是一种运行时绑定机制(run-time binding) ,通过这种机制,实现将函数名绑定到函数具体实现代码的目的。

44.什么是运算符重载?为什么要使用运算符重载?如何进行运算符重载,举例说明。

  • 运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

  • 扩展C++中提供的运算符的适用范围,以用于类所表示的抽象数据类型。同一个运算符,对不同类型的操作数,所发生的行为不同。

  • 运算符重载的函数一般地采用如下两种形式:成员函数形式和友元函数形式。这两种形式都可访问类中的私有成员。

    class Complex
    {
    public:
        Complex( )
        {
        	real=0;
        	imag=0;
        }
        Complex(double r,double i)
        {
       	 	real=r;
        	imag=i;
        }
        Complex operator+( const Complex & ) const;       //重载为成员函数
        friend Complex operator+(Complex &c1,Complex &c2);  //重载为友员函数
        void display( );
    private:
        double real;
        double imag;
    };

45.为什么重载为全局函数的运算符通常要比重载为成员函数的运算符多一个参数?举例说明。

  • 当重载为成员函数时,会有一个this指针,指向当前的类,所以只需要一个参数就可以了。

  • 而当重载为全局函数时,将没有隐含的参数this指针,这样将会多一个参数。

    Complex operator +(Complex&);
    friend Complex operator+(Complex &c1,Complex &c2);

46.什么是友元(friend)函数?为什么要使用友员函数?

  • 友元函数是在类声明中由关键字friend修饰说明的非成员函数或其它类的成员函数,在它的函数体中能够通过对象名访问 privateprotected成员
  • 友元函数可以访问这个类中的私有成员,增加灵活性,使程序员可以在封装和快速性方面做合理选择。
  • 友元是C++提供的一种对数据封装和数据隐藏的破坏机制。

47.什么是UML?如何画UML中的类图?什么是类与类之间依赖关系、关联关系、包含关系?试举例说明这三种类之间的关系。这三种关系如何和UML图表示?

  • UML统一建模语言,UML语言是一种可视化的的面向对象建模语言,描述软件模型的静态结构、动态行为及模块组织与管理。
  • 依赖关系:一个事物的变化可能会影响到使用它的另一个事物。举例:驾驶员(源)开车(目标)。
  • 关联关系:一个类的对象和另一个类的对象之间相互作用。举例:老师和学生,小明的语文老师是张老师,张老师的学生有小明。
  • 包含关系:聚集和组合
  • 聚集表示类之间的关系是整体与部分的关系。举例:班级成员和学生。
  • 组合是指整体拥有各个部分,整体和部分共存,整体不存在了,部分也会随之消失。举例:打开一个视窗口,它由标题、外框和显示区域组成,视窗口是一个整体,它消失了,部分也就随之消失了。
  • 继承关系:在UML中称为泛化。举例:鸭子和鸟,鸭子是一种鸟,继承了鸟的特性。

48.常见的类与类之间的关系有哪几种,举例说明每种关系的对应UML图如何画?两个什么样的类可以实现为继承关系?

  • 依赖关系、关联关系、包含关系、继承关系。
  • 具有共同属性的两个类可以实现继承关系。

49.分别举例说明用于算法抽象的函数模板和用于数据抽象的类模板。

  • 函数模板:

    template<class T>
    T add( T a, T b )//函数模板
    {
        return a + b;
    }
    add<int> ( 10, 17 );//模板实例
    complex c1, c2;
    add<complex> ( c1, c2 );
  • 函数模板是 对算法相似,但支持的数据类型不同的一组操作的提炼,以提高程序的重用性。

  • 函数模板的实例就是一个用于特定类型的普通函数。

  • 通常,编译器可根据实参类型确定模板参数;

    add ( 10, 17 ); // add<int> (10,17);
    complex c1, c2;
    add ( c1, c2 ); // add<complex>(c1,c2);
  • 类模板:

  • 使用类模板使用户可以为类定义一种模式,使得类中的某些数据成员、某些数据成员函数的参数、返回值和局部变量能够取任意类型(包括系统预定义和用户自定义)

  • 有时候,有两个或多个类,其功能是相同的,仅仅是数据类型不同,可以使用模板类。

    template<class T>//声明模板
    class Array
    {
        T*  array;
        int size;
    publicArray( int );
        T& operator[ ]( int );
    };
    class charArray
    {
        char *array;
        int size;
    public:
        charArray( int );
        char& operator[](int);
    };
      
    class intArray
    {
        int *array;
        int size;
    public:
        intArray( int );
        int& operator[](int);
    };

50.使用异常处理机制的好处是什么?

  • 将常规代码与错误处理代码的分离
  • 实现在调用栈中传播异常
  • 实现对不同的错误类型进行分类

51.简述C++中的异常处理机制。要捕获某段代码中的所有异常,应该如何编写代码?

  • C++trycatch进行异常处理,当try块出现异常,则catch中匹配相应的异常处理,若catch块中没有匹配该异常对象的语句,则转向外一层的trycatch语句,若一直退回到主函数都无法处理异常,则由系统调用terminate()函数终止程序。
  • 用异常规格(exception specification)列出函数可能会抛出所有异常的类型。

52.简述虚函数动态绑定的实现原理。

  • 构造函数中为对象的虚指针赋值,通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址,通过该入口地址调用虚函数。

文章作者: 心意
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 心意 !
评论
  目录