C++ OOP 继承与多态

动态绑定允许同一代码处理基类和派生类对象,主要涉及虚函数的使用。在C++中,基类的虚函数通过引用或指针调用时会发生动态绑定。基类应定义虚析构函数以确保正确删除对象。派生类可以继承基类的虚函数,如果未覆盖,则使用基类版本。抽象基类包含纯虚函数,不能实例化。继承访问控制影响派生类对基类成员的访问权限,而using语句用于引入基类成员到派生类作用域。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态绑定


通过使用动态绑定,我们可以用同一段代码分别处理基类和派生类的对象,函数的运行版本由实参决定。因此动态绑定又称为运行时绑定

在C++语言中,当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定。

定义基类


基类通常都应该定义一个 虚析构函数,即使该函数不执行任何实际操作也是如此。
原因:确保delete基类指针时将运行正确的析构函数版本。(动态绑定)
例如一个Base指针可能指向一个Derived类型对象。

成员函数与继承

基类通过在其成员函数的声明语句前加上关键字virtual使得该函数执行动态绑定。

任何构造函数之外的非静态函数都可以是虚函数。

成员函数如果没有被声明为虚函数,则其解析过程发生在编译时而非运行时。

访问控制与继承

派生类可以访问基类的公有成员,但不能访问基类的私有成员。如果基类希望它的派生类有权访问该成员,同时禁止其他用户访问,则使用protected访问运算符说明。

定义派生类


如果派生类没有覆盖基类中的某个虚函数,则派生类会继承其在基类中的版本。

在派生类对象中含有与其基类对应的组成部分,因此可以实现派生类和基类之间的类型转换。

派生类构造函数

尽管在派生类对象中含有从基类继承而来的成员,但派生类不能直接初始化这些成员,而必须使用基类的构造函数来初始化它的基类部分。

class Quote{
    //...
};
class Bulk_quote: public Quote{
    Bulk_quote(...):Quote(...),(..)..{};
}

C++11可以使用using声明语句继承直接基类名的构造函数。

class Bulk_quote: public Quote{
   using Quote::Quote;//继承Quote的构造函数
}

对于基类的每个构造函数,编译器都在派生类中生成一个形参列表完全相同的构造函数。

这些编译器生成的构造函数形如:

derived(params): base(params) { }

一个构造函数的using声明不会改变该构造函数的访问级别。

静态成员

如果基类定义了一个静态成员,则在整个体系中只存在该成员的唯一定义。

防止继承

在形参列表(包括const或引用修饰符)以及尾置返回类型之后。

类型转换与继承


内置指针和智能指针都支持将基类的指针或引用绑定到派生类对象上

静态类型与动态类型

静态类型:编译时已知,变量声明时的类型或表达式生成的类型

动态类型:运行时才知道

如果表达式既不是引用也不是指针,则动态类型永远与静态类型一致

不存在从基类向派生类的自动类型转换,即使一个基类指针或引用绑定在一个派生类对象上,这是因为编译器编译时只能通过静态类型来推断推断转换是否合法。

如果在基类中有一个或多个虚函数,可以使用dynamic_cast请求一个类型转换,该转换的安全检查将在运行时执行。同样,如果我们已知某个类型向派生类的转换是安全的,则我们可以使用static_cast来强制覆盖掉编译器的检查工作。

我们通常能够将一个派生类对象拷贝、移动或赋值给一个基类对象,但这种操作只处理派生类对象的基类部分。

虚函数


override

当派生类覆盖了某个虚函数时,该函数在基类中的形参必须与派生类中的形参严格匹配,否则没有覆盖基类的版本。为了调试并发现这样的错误,可以使用override关键字来说明派生类中的虚函数,使程序员的意图更加清晰同时使编译器可以发现错误。

纯虚函数

纯虚函数无需定义,只需要在函数体的位置书写=0就可以将一个虚函数说明为纯虚函数。

也可以为纯虚函数提供定义,但函数体必须定义在类的外部,内部书写=0。

含有纯虚函数的类是抽象基类,我们无法创建抽象基类的对象。

访问控制与继承


protected成员

派生类的成员和友元只能访问派生类对象中的基类部分的受保护成员,对于普通的基类对象中的成员不具有特殊的访问权限。

继承

public、private、protected继承:派生访问说明符

派生访问说明符对于派生类的成员及友元能否访问直接基类的成员没有什么影响,其目的是控制派生类用户(包括派生类的派生类)对于基类成员的访问权限。

class B{
public:
    void pub_mem();
protected:  
    int prot_mem;
private:
    char priv_mem;
};
struct Pub_derv:public Base{
//...
}
struct Priv_derv:private Base{
//...
}
Pub_derv d1;
Prive_derv d2;
d1.pub_mem(); //正确,pub_mem在派生类中是public
d2.pub_mem();//错误,pub_mem在派生类中是private

派生访问控制符还可以控制继承自派生类的新类的访问权限。

struct Derived_from_private: public Priv_derv{
    int use_base(){
    return prot_mem;//错误,Base::prot_mem在Priv_derv中为private
    }
}

总结:

protected继承:基类的所有公有成员和保护成员都成为派生类的保护成员,基类的私有成员仍然是私有的。

private继承:基类的所有成员都作为派生类的私有成员。

public继承:保持原有的状态。

派生类向基类的转换的可访问性

  • 只有当D公有地继承B时,用户代码才能使用派生类向基类的转换。

  • 不论D以什么方式继承B,D的成员函数和友元都能使用派生类向基类的转换。

  • 如果D继承B的方式为公有或受保护,则D的派生类的成员函数和友元可以使用D向B的类型转换。

using

  • 可以在类的内部通过using语句改变个别成员的可访问性。

派生类只能为那些它可访问的名字提供using声明。

  • using声明语句指定一个名字而不指定形参列表,可以把基类函数的所有重载实例添加到派生类作用域中。

struct和class

  • struct定义派生类默认是公有继承,class默认为私有继承。

  • struct 默认的访问权限是public,而class 默认的访问权限是private。

继承中的类作用域


派生类的作用域位于基类作用域之中。

编译时我们能使用哪些成员由静态类型决定,即使静态类型与动态类型不一致。

名字冲突与继承

如果派生类(内层作用域)的成员与基类(外层作用域)的某个成员同名,则派生类将在其作用域中隐藏该基类成员,即使派生类成员和基类成员的形参列表不一致。

好的,下面是使用C++实现的oop租车系统多重继承)的代码示例: ```cpp #include <iostream> #include <string> #include <vector> using namespace std; // 定义车辆类 class Vehicle { public: Vehicle(const string& make, const string& model, int year, int rent_per_day) : make_(make), model_(model), year_(year), rent_per_day_(rent_per_day) {} // 打印车辆信息 void print_info() const { cout << make_ << " " << model_ << " (" << year_ << ") - $" << rent_per_day_ << "/day" << endl; } private: string make_; // 品牌 string model_; // 型号 int year_; // 年份 int rent_per_day_; // 租金/天 }; // 定义客户类 class Customer { public: Customer(const string& name, int age) : name_(name), age_(age) {} // 打印客户信息 void print_info() const { cout << name_ << " (" << age_ << " years old)" << endl; if (rented_vehicle_) { cout << "Rented vehicle: "; rented_vehicle_->print_info(); } } // 租用车辆 void rent_vehicle(Vehicle* vehicle) { rented_vehicle_ = vehicle; cout << name_ << " rented "; rented_vehicle_->print_info(); } private: string name_; // 姓名 int age_; // 年龄 Vehicle* rented_vehicle_ = nullptr; // 租用的车辆 }; // 定义租车公司类 class CarRentalCompany { public: // 初始化车辆和客户 CarRentalCompany() { // 添加车辆 vehicles_.push_back(new Vehicle("Make1", "Model1", 2020, 50)); vehicles_.push_back(new Vehicle("Make2", "Model2", 2020, 60)); vehicles_.push_back(new Vehicle("Make3", "Model3", 2020, 70)); // 添加客户 customers_.push_back(new Customer("Customer1", 25)); customers_.push_back(new Customer("Customer2", 30)); } // 打印所有车辆信息 void print_all_vehicles() const { cout << "All vehicles:" << endl; for (const auto& vehicle : vehicles_) { vehicle->print_info(); } } // 打印所有客户信息 void print_all_customers() const { cout << "All customers:" << endl; for (const auto& customer : customers_) { customer->print_info(); } } private: vector<Vehicle*> vehicles_; // 所有车辆的数组 vector<Customer*> customers_; // 所有客户的数组 }; int main() { // 创建租车公司 CarRentalCompany company; // 打印所有车辆和客户信息 company.print_all_vehicles(); company.print_all_customers(); // 客户1租用第2辆车 company.print_all_customers(); company.print_all_vehicles(); company.print_all_customers(); company.print_all_vehicles(); return 0; } ``` 该代码定义了三个类:Vehicle、Customer 和 CarRentalCompany。其中,Vehicle 和 Customer 分别表示车辆和客户,而 CarRentalCompany 则表示租车公司。使用多重继承,CarRentalCompany 类同时继承了 Vehicle 和 Customer 类的属性和方法。 使用该代码,我们可以创建一个租车公司,并初始化其中的车辆和客户。然后,我们可以打印所有车辆和客户的信息,以及让某个客户租用某辆车。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值