如果函数的参数是基类对象或基类对象的引用,函数调用时的实参可以是派生类对象。
#include <iostream>
class Base {
public:
virtual void show() {
std::cout << "Base class show() method" << std::endl;
}
};
class Derived : public Base {
public:
void show() override {
std::cout << "Derived class show() method" << std::endl;
}
};
void display(Base& obj) {
obj.show();
}
int main() {
Base b;
Derived d;
display(b); // 输出:Base class show() method
display(d); // 输出:Derived class show() method
return 0;
}
在上面的代码中,函数 display
的参数是 Base
类的引用。在 main
函数中,display
函数被调用两次,一次传递的是 Base
类对象 b
,一次传递的是 Derived
类对象 d
。由于 Base
类的 show
方法是虚函数(virtual
),在调用 display(d)
时,实际调用的是 Derived
类的 show
方法。这就是多态性的体现。
基类与派生类对象之间具有赋值兼容的关系,可以进行类型间的转换,派生类对象可以直接转换为基类对象,同样基类对象也可以直接转换为派生类对象 。
正确部分
- 派生类对象可以直接转换为基类对象:这部分是正确的。派生类对象可以直接赋值给基类对象,这种转换被称为“向上转换”或“上行转换”,是安全的,并且是自动进行的。
错误部分
- 基类对象可以直接转换为派生类对象:这部分是不正确的。基类对象不能直接转换为派生类对象。这种转换被称为“向下转换”或“下行转换”,需要进行类型检查,通常使用C++的
dynamic_cast
运算符。如果基类对象没有包含派生类对象的实际实例,这种转换是危险的,并且会失败。
将构造函数说明为纯虚函数是没有意义的。
是的,将构造函数说明为纯虚函数是没有意义的。在C++中,纯虚函数用于定义一个抽象类,目的是为了要求派生类必须实现这些函数。而构造函数在对象创建时被调用,它们不能是虚函数,也不能是纯虚函数。
具体原因如下:
-
构造函数的目的:构造函数用于初始化对象的状态。在创建对象时,构造函数按继承链自上而下依次调用。基类的构造函数在派生类的构造函数之前调用,用于初始化基类部分的数据成员。
-
虚函数表的创建:虚函数表(vtable)是在对象创建过程中,由构造函数设置的。在调用构造函数之前,虚函数表还没有被建立,因此不可能调用虚函数。
-
抽象类与对象创建:抽象类不能实例化,因为它至少有一个纯虚函数。纯虚函数要求派生类提供实现,但构造函数是用来创建对象的,所以它不能是纯虚的。
因此,C++语言规范中不允许构造函数为虚函数,更不用说是纯虚函数。
因为静态成员函数不能是虚函数,所以它们不能实现多态。
静态成员函数
静态成员函数是属于类的,而不是类的某个对象。它们可以在没有对象实例的情况下调用,并且只能访问静态成员变量。静态成员函数没有this
指针,因为它们不与任何对象实例相关联。
虚函数
虚函数是为了实现多态性。通过使用虚函数,程序可以在运行时决定调用哪个版本的函数,即使该函数是通过基类指针或引用调用的。虚函数依赖于对象的类型来实现动态绑定,并且需要通过对象的this
指针来访问虚函数表(vtable)。
原因
因为静态成员函数不与任何对象实例相关联,所以它们没有this
指针,也没有虚函数表。这就意味着静态成员函数不能是虚函数,因此不能实现多态性。
关于虚函数的描述中,( )是正确的。
A.虚函数是一个static 类型的成员函数
B.虚函数是一个非成员函数
C.基类中说明了虚函数后,派生类中与其对应的函数可不必说明为虚函数
D.派生类的虚函数与基类的虚函数具有不同的参数个数和类型
A. 虚函数是一个static类型的成员函数
- 这个选项是错误的。虚函数不能是
static
类型的成员函数。static
成员函数属于类,而不属于类的对象,不能与具体对象的虚函数表(vtable)关联。
B. 虚函数是一个非成员函数
- 这个选项是错误的。虚函数必须是成员函数,且属于类。非成员函数不能是虚函数,因为虚函数是为了实现多态,需要类的继承机制。
C. 基类中说明了虚函数后,派生类中与其对应的函数可不必说明为虚函数
-
这个选项是正确的。在基类中声明为虚函数的函数,在派生类中仍然是虚函数,即使在派生类中没有显式地使用
virtual
关键字。例如:class Base { public: virtual void func() { std::cout << "Base" << std::endl; } }; class Derived : public Base { public: void func() override { std::cout << "Derived" << std::endl; } };
-
这里,
Derived
类的func
函数继承了基类Base
的虚函数特性,即使在Derived
类中没有使用virtual
关键字。 -
D. 派生类的虚函数与基类的虚函数具有不同的参数个数和类型
- 这个选项是错误的。派生类的虚函数必须与基类的虚函数具有相同的参数个数和类型才能正确覆盖基类的虚函数。如果参数不同,就不能实现覆盖,只是一个新的函数,而不是虚函数的重写。
关于纯虚函数和抽象类的描述中,( )是错误的。
A.纯虚函数是一种特殊的虚函数,它没有具体的实现
B.抽象类是指具有纯虚函数的类
C.一个基类中说明有纯虚函数,该基类的派生类一定不再是抽象类
D.抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出
A. 纯虚函数是一种特殊的虚函数,它没有具体的实现 - 正确。纯虚函数声明在基类中,但没有具体的实现,必须由派生类实现。
B. 抽象类是指具有纯虚函数的类 - 正确。包含至少一个纯虚函数的类被称为抽象类,不能实例化。
C. 一个基类中说明有纯虚函数,该基类的派生类一定不再是抽象类 - 错误。如果派生类没有实现所有继承的纯虚函数,那么该派生类仍然是抽象类。只有当派生类实现了所有纯虚函数后,它才不是抽象类,才能被实例化。
D. 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出 - 正确。抽象类不能实例化,只能作为基类,具体的纯虚函数实现由派生类提供。
下列描述中,( )是抽象类的特性。
A.可以说明虚函数
B.可以进行构造函数重载
C.可以定义友元函数
D.不能定义该类对象
A. 可以说明虚函数 - 正确。抽象类通常包含虚函数,特别是纯虚函数,这正是抽象类的一个重要特性。
B. 可以进行构造函数重载 - 正确。抽象类可以有多个构造函数,只是这些构造函数不能用于实例化抽象类对象,但它们可以用于初始化派生类对象。
C. 可以定义友元函数 - 正确。抽象类可以定义友元函数,友元函数可以访问类的私有和保护成员。
D. 不能定义该类对象 - 正确。抽象类的一个显著特征是它不能被实例化,不能定义该类的对象。只有派生类实现了所有纯虚函数后,才能实例化派生类对象。
设有如下代码段:
class A {
public:
void func1() {
cout << "A1" << endl;
}
virtual void func2() {
cout << "A2" << endl;
}
};
class B : public A {
public:
void func1() {
cout << "B1" << endl;
}
void func2() {
cout << "B2" << endl;
}
};
int main() {
A *a = new B;
a->func1();
a->func2();
}
则输出结果为:
A.A1 A2
B.A1 B2
C.B1 A2
D.B1 B2
-
A *a = new B;
- 这里,
a
是指向A
类型的指针,但它实际上指向的是一个B
类型的对象。这是向上转换(upcasting),它是安全且允许的。
- 这里,
-
a->func1();
- 由于
func1
不是虚函数,所以调用的是指针a
所指向的A
类的func1
方法。因此,这行代码输出A1
。
- 由于
-
a->func2();
- 由于
func2
是虚函数,因此会发生动态绑定(动态多态性)。程序会根据实际对象的类型来决定调用哪个版本的func2
。因为a
实际上指向一个B
类型的对象,所以会调用B
类中的func2
方法。因此,这行代码输出B2
。
- 由于
A1 B2
关于虚函数,正确的描述是(a)。
A.构造函数不能是虚函数
B.析构函数不能是虚函数
C.虚函数可以是友元函数
D.虚函数可以是静态成员函数
解释:
- 构造函数不能是虚函数,因为在对象创建过程中,虚函数表还没有建立。
- 析构函数可以是虚函数,用于正确删除派生类对象。
- 虚函数不能是静态成员函数,因为静态成员函数不与任何对象实例关联。
不列哪一项不是动态联编实现的条件?
A.要有说明的虚函数
B.调用虚函数操作的是指向对象的指针
C.调用虚函数操作的是对象引用
D.由对象调用虚函数
A. 要有说明的虚函数
这是正确的。动态联编是通过虚函数实现的,因此被动态联编的函数必须是虚函数。
B. 调用虚函数操作的是指向对象的指针
这是正确的。通过指向基类的指针(该指针实际上指向派生类对象)调用虚函数时,会发生动态联编。
C. 调用虚函数操作的是对象引用
这同样是正确的。类似于指针,通过基类引用(该引用绑定到派生类对象)调用虚函数时,也会发生动态联编。
D. 由对象调用虚函数
这是不正确的。当直接通过对象(而不是指针或引用)调用虚函数时,不会发生动态联编。在这种情况下,将使用对象的实际类型(即不是基类,而是具体的派生类)来确定要调用的函数版本,这被称为静态联编或早期绑定。