面试题总结(二) – 面向对象篇(封装、继承、多态)
<1> 请阐述 C++ 中面向对象的三大特性(封装、继承、多态)。
- 封装是将数据和操作数据的方法封装在一个类中,隐藏内部实现细节。
- 继承允许创建新类基于现有类,复用其属性和方法。
- 多态则通过基类指针或引用调用派生类的函数,实现同一接口不同实现。
封装(Encapsulation)
概念:封装是将数据和操作数据的方法封装在一个类中,对外隐藏内部的实现细节,只提供公共的接口供外部访问。通过封装,可以保护数据的安全性和完整性,防止外部直接访问和修改内部数据,同时也提高了代码的可维护性和可扩展性。
实现方式:
使用访问修饰符(如public、private和protected)来控制类成员的访问权限。private成员只能在类内部访问,public成员可以在类外部访问,protected成员可以在类内部和派生类中访问。
提供公共的成员函数作为接口,外部代码通过调用这些接口来操作类的内部数据。例如:
class MyClass {
private:
int privateData;
public:
void setData(int value) {
privateData = value;
}
int getData() const {
return privateData;
}
};
作用:
- 数据隐藏:保护类的内部数据不被外部直接访问,防止意外的修改和破坏。
- 代码模块化:将数据和操作封装在一个类中,使得代码更加模块化,易于理解和维护。
- 提高可维护性:如果内部实现需要修改,只需要修改类的内部代码,而不会影响外部代码的使用。
继承(Inheritance)
概念:继承是一种建立类之间层次关系的机制,允许一个类(派生类)继承另一个类(基类)的属性和方法。派生类可以扩展和修改基类的功能,同时继承基类的特性。通过继承,可以实现代码的复用,减少重复代码的编写,提高开发效率。
实现方式:使用冒号(:)后跟基类名称来表示继承关系。例如:
class Base {
public:
void baseFunction() {
std::cout << "Base function." << std::endl;
}
};
class Derived : public Base {
public:
void derivedFunction() {
std::cout << "Derived function." << std::endl;
}
};
作用:
- 代码复用:派生类可以继承基类的成员变量和成员函数,避免重复编写相同的代码。
- 层次结构:建立类之间的层次关系,使得代码更加清晰和易于理解。
- 多态性基础:继承是实现多态性的基础,通过基类指针或引用可以指向派生类对象,从而实现不同的行为。
多态(Polymorphism)
概念:多态是指同一个操作作用于不同的对象可以有不同的表现形式。在 C++ 中,多态主要通过虚函数和函数重载来实现。通过多态,可以提高代码的灵活性和可扩展性,使得程序能够根据不同的对象类型自动选择合适的函数进行调用。
实现方式:
1)动态多态
通过基类指针或引用调用虚函数时,会根据实际指向的对象类型来调用相应的函数。例如:
class Base {
public:
virtual void virtualFunction() {
std::cout << "Base virtual function." << std::endl;
}
};
class Derived : public Base {
public:
void virtualFunction() override {
std::cout << "Derived virtual function." << std::endl;
}
};
2)静态多态
函数重载:在同一个作用域内,可以有多个函数具有相同的函数名,但参数列表不同。根据不同的参数类型,编译器会自动选择合适的函数进行调用。例如:
void print(int value) {
std::cout << "Integer: " << value << std::endl;
}
void print(double value) {
std::cout << "Double: " << value << std::endl;
}
作用:
- 灵活性:多态使得程序能够根据不同的对象类型自动选择合适的函数进行调用,提高了代码的灵活性。
- 可扩展性:可以在不修改现有代码的情况下,通过添加新的派生类来扩展程序的功能。
- 代码复用:通过虚函数和函数重载,可以实现代码的复用,减少重复代码的编写。
<2> 什么是类的构造函数和析构函数?它们的作用分别是什么?
在 C++ 中,构造函数是一种特殊的成员函数,用于在创建对象时进行初始化操作。析构函数也是特殊的成员函数,在对象销毁时执行清理工作,释放资源。
构造函数
定义:构造函数是一种特殊的成员函数,它的名称与类名相同,没有返回类型。构造函数可以有参数,用于在创建对象时初始化对象的成员变量。
作用:
- 对象初始化:构造函数在对象创建时被自动调用,用于初始化对象的成员变量,确保对象在使用之前处于一个合理的状态。
- 参数传递:构造函数可以接受参数,允许在创建对象时传递初始值,以便根据不同的情况创建具有不同状态的对象。
- 资源分配:构造函数可以负责分配对象所需的资源,如动态内存分配、打开文件、建立数据库连接等。
例如:
class MyClass {
private:
int data;
public:
MyClass() : data(0) {
std::cout << "Default constructor called." << std::endl;
}
MyClass(int value) : data(value) {
std::cout << "Constructor with parameter called." << std::endl;
}
};
在这个例子中,MyClass
类有两个构造函数。一个是默认构造函数,它没有参数,用于创建一个初始状态为data = 0
的对象。另一个是带参数的构造函数,它接受一个整数参数,用于创建一个具有特定初始值的对象。
析构函数
定义:析构函数也是一种特殊的成员函数,它的名称是在类名前加上波浪线(~)。析构函数没有参数,也没有返回类型。析构函数在对象被销毁时自动调用。
作用:
资源释放:析构函数负责释放对象在生命周期中分配的资源,如释放动态内存、关闭文件、断开数据库连接等。
清理工作:析构函数可以执行一些清理工作,如保存数据、释放锁等,确保对象在销毁时不会留下任何未处理的状态。
例如:
class MyClass {
private:
int* data;
public:
MyClass() : data(new int[10]) {
std::cout << "Constructor called." << std::endl;
}
~MyClass() {
delete