面向对象程序设计(Object-oriented programming)的核心思想是数据抽象,继承,和动态绑定。
1. 继承
在C++语言中,基类将类型相关的函数与派生类不做改变直接继承的函数区分对待。对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时基类就将这些函数声明成虚函数(virtual function)。
1 class Quote { 2 public: 3 std::string isbn() const; 4 virtual double net_price(std::size_t n) const; 5 };
派生类必须通过使用派生列表(class derivation list)明确指出它是从哪个(哪些)基类继承而来的:
如下:
1 class Bulk_quote : public Quote { 2 public: 3 double net_price(std::size_t) const override; 4 };
派生类必须在其内部对所有重新定义的虚函数进行声明。派生类可以在这样的函数之前加上virtual关键字,但是并不是非得这么做。并且,C++11新标准允许派生类显式地注明它将使用哪个成员函数改写基类的虚函数,具体措施是在改函数的形参列表之后增加一个override关键字。
1.1 访问控制和继承


1 #include<iostream> 2 class A { 3 public: 4 int p = 0; 5 virtual void test(); 6 private: int p2 = 1; 7 protected: int p3 = 2; 8 }; 9 10 void A::test() 11 { 12 std::cout << this->p << this->p2 << this->p3 << std::endl; 13 } 14 15 class B:public A { 16 public: 17 int b = 3; 18 void test() { 19 std::cout << this->b << this->b2 << this->b3 << std::endl; 20 } 21 22 void test2() { 23 std::cout << this->p3 << std::endl; // 派生类可以访问基类的protect和public 24 } 25 26 friend void test3() { 27 std::cout << this-> << std::endl; 28 } 29 private: int b2 = 4; 30 protected: int b3 = 5; 31 }; 32 33 int main() 34 { 35 A a; 36 std::cout << a.p << std::endl;// 只能访问自己的public 37 a.test(); 38 39 B b; 40 std::cout << b.b << b.p << std::endl;// 派生类 只能访问自己的puiblic和基类的public 41 b.test(); 42 43 }
1.2 定义基类和派生类
1.定义基类。
Note:基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此。
1 class Quote { 2 public: 3 Quote() = default; 4 Quote(const std::string &book, double sales_price): 5 bookNo(book), price(sales_price){} 6 std::string isbn()const {return bookNo;} 7 //返回给定数量的书籍的销售总额 8 //派生类负责改写并使用不同的折扣计算算法 9 virtual double net_price(std::size_t n) const 10 {return n*price;} 11 virtual ~Quote() = default; //对析构函数进行动态绑定 12 private: 13 std::string bookNo; 14 protected: 15 double price = 0.0; //代表普通状态下不打折的价格 16 };
- 基类必须将它的两种成员函数区分开来:一种是基类希望其派生类进行覆盖的函数,基类通常将其定义为虚函数;另一种是基类希望派生类直接继承而不要改变的函数。
- 任何构造函数之外的非静态函数都可以是虚函数,关键字virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。
- 如果基类把一个函数声明为虚函数,则该函数在派生类中隐式地也是虚函数。
- 成员函数如果没被声明为虚函数,则其解析过程发生在编译时而非运行时。
- 派生类能访问公有成员,而不能访问私有成员。
- 不过在某些时候,基类中还有一种成员,基类希望它的派生类有权访问该成员,同时禁止其他用户访问。我们用受保护的(protected)访问运算符说明这样的成员。
2. 定义派生类
1 class Bulk_quote : public Quote { 2 public: 3 Bulk_quote() = default; 4 Bulk_quote(const std::string&, double, std::size_t, double); 5 //覆盖基类的函数版本以实现基于大量购买的折扣政策 6 double net_price(std::size_t) const override; 7 private: 8 std::size_t min_qty = 0; //使用折扣政策的最低购买量 9 double discount = 0.0; //以小数表示的折扣额 10 };
- 派生类经常(但不总是)覆盖它继承的虚函数。如果派生类没有覆盖其基类中的某个虚函数,则该虚函数的行为类似于其他的普通成员,派生类会直接继承其在基类中的版本。
- C++新标准允许派生类显式地注明它使用某个成员函数覆盖了它继承的虚函数。具体做法是在函数后面加上关键字override。
- 在派生类对象中含有与其基类对应的组成部分,所以我们能把派生类的对象当成基类对象来使用,而且我们也能将基类的指针或引用绑定到派生类对象中的基类部分上。如下:
1 Quote item; //基类对象 2 Bulk_quote bulk; //派生类对象 3 Quote *p = &item; //p指向Quote对象 4 p = &bulk; //p指向bulk的Quote部分 5 Quote &r = bulk; //r绑定到bulk的Quote部分
3. 派生类构造函数
派生类可以继承基类的成员,但是不能直接初始化这些成员(每个类控制它自己的成员初始化过程)。
1 Bulk_quote(const std::string &book, double p, std::size_t qty, 2 double disc):Quote(book, p), min_qty(qyt), discount(disc) { }
4.派生类使用基类的成员
派生类可以访问基类的公有成员,和受保护成员
5.继承与静态成员
如果基类中有静态变量,则不论派生出多少类,对每个静态成员来说都只存在唯一实例。如果基类中静态成员是private,则派生类无权访问,假设派生类可以访问,则我们既能通过基类也能通过派生类使用它。
6.防继承
C++11新标准提供了一种防止继承发生的方法,即在类名后跟一个关键字final
1 class NoDerived final { /* */}; //NoDerived不能作为基类
7.不存在从基类向派生类的隐式类型转换
之所以存在派生类向基类的类型转换是因为每个派生类都包含了基类的一部分,而基类引用或者指针可以绑定到该基类部分上。但是因为一个基类对象可能是派生类对象的一部分,也可能不是,所以不存在从基类向派生类的自动类型转换。
1 Quote base; 2 Bulk_quote* blukP = &base; //不合法 3 Bulk_quote& blukRef = base; //不合法
下面这种情况也是不允许的
1 Bulk_quote bulk; 2 Quote* itemP = &bulk; //合法,基类绑定派生类 3 Bulk_quote* blukRef = itemP; //不合法
如果基类中含有一个或多个虚函数,我么可以使用 dynamic_cast 请求类型转换。
8.对象之间不存在类型转换
派生类向基类的自动类型转换只对指针或引用类型有效,在派生类类型和基类类型之间不存在这样的转换。很多时候,我们确实希望将派生类对象转换成基类类型,但是这种转换往往与我们所期望的不一样。
请注意,当我们初始化或赋值一个类类型的对象时,实际上实在调用某个函数。当执行初始化时,我们调用构造函数,而当执行赋值操作时,我们调用赋值运算符。
因为这些成员接受引用作为参数,多以派生类向基类的转换允许我们给基类的拷贝和移动操作传递一个派生类的对象。这些操作不是虚函数,当我们给基类的构造函数传递一个派生类对象时,实际运行的构造函数是基类中定义的那个,显然,该运算符只能处理基类自己的成员。
1 Bulk_quote bulk; // 派生类对象 2 Quote item(bulk);// 使用Quote::Quote(const Quote&)构造函数 3 item = bulk; // 调用Quote::opertator=(const Quote&)。同时忽略掉派生类中的部分成员
2. 动态绑定
在C++语言中,当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定。明晰派生类调用到底调用谁的print方法。
比如:
1 #include<iostream> 2 class A { 3 public: 4 A() = default; 5 virtual void print() { 6 std::cout << "a" << std::endl; 7 } 8 virtual ~A() { 9 std::cout << "destroy A" << std::endl; 10 }; 11 }; 12 13 class B :public A { 14 public: 15 B() = default; 16 void print() { 17 std::cout << "b" << std::endl; 18 } 19 ~B() { 20 std::cout << "destroy B" << std::endl; 21 } 22 23 }; 24 25 int main() 26 { 27 A a; 28 B b; 29 a.print(); 30 b.print(); 31 32 /// 动态绑定.如果基类中 print 方法不是虚函数,则以下结果均为a 33 A *a2 = &a; 34 A *b2 = &b; 35 a2->print();// a 36 b2->print();// b 37 38 A &a3 = a; 39 A &b3 = b; 40 a3.print();// a 41 b3.print();// b 42 43 /// 强行调用基类 44 //b.A::print(); // a 45 //b2->A::print(); // a 46 //b3.A::print(); // a 47 48 }