C++ 学习笔记(15)面向对象程序设计(类、继承、虚函数、抽象类、using命令、容器与继承)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l773575310/article/details/79326229

C++ 学习笔记(15)面向对象程序设计(类、继承、虚函数、抽象类、using命令、容器与继承)

参考书籍:《C++ Primer 5th》
API:


15.1 OOP:概述

  • 面向对象程序设计(object-oriented programming)的核心思想:数据抽象、继承、动态绑定。
  • 使用基类的引用(或指针)调用一个虚函数时,发生动态绑定(运行时绑定)。

15.2 定义基类和派生类

15.2.1 定义基类

  • 关键字virtual只能出现在类内部的声明语句之前,而不能用于类外部的函数定义。

15.2.2 定义派生类

  • 每个类负责定义各自的接口。要想与类的对象交互必须使用该类的接口,即使这个对象是派生类的基类部分。即派生类最后不要直接初始化基类的成员(而是使用基类的接口)。
  • final:防止类被继承:class A final { };

15.2.3 类型转换与继承

  • 静态表达式:编译时已知,即声明时的类型或表达式生成的类型。
  • 动态表达式:运行时才知道,是变量或表达式表示的内存中的对象的类型。
  • 不能隐式从基类转换成派生类。可以用static_cast强制转换。
  • 如果基类中含有一个或多个虚函数,可以用dynamic_cast请求类型转换。
  • 派生类向基类转换只对指针或引用类型有效,如果是对象之间转换,会调用拷贝赋值操作(实现对象的类型转换)。

15.3 虚函数

  • 基类中的虚函数在派生类中隐含地也是一个虚函数。当派生类覆盖了虚函数,函数形参需要严格匹配,但是返回值是相同或者可以隐式转换。
class A
{
public:
    virtual A* function() { return new A(); };
};

class B : public A
{
public:
    B* function() { return new B(); };      // 覆盖了A类的虚构函数,返回值不一定是相同的,但是形参必须匹配
};

15.4 抽象基类

  • 抽象类:含有纯虚函数。不能创建抽象类的对象。
  • 纯虚函数:标明=0,无须定义,如果需要,不能在类的内部定义,但可以在类的外部定义。
class A
{
public:
    virtual int func() = 0;     // 纯虚函数。A类就是抽象类了
};
  • 重构(refactoring):负责重新设计类的体系,以便操作数据从一个类移动到另一个类中(如继承类构造函数调用了基类构造函数)。

15.5 访问控制与继承

  • 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。
class C
{
protected:
    int protMem;
};

class D : public C
{
public:
    void sd() {}
    friend void func(C& c) { c.protMem = 2; }   // 错误,友元不能访问该类的基类的成员。
    friend void func(D& d) { d.protMem = 3; }   // 正确,友元可以访问本类所有成员(包括继承过来的成员)。
};
  • 只有派生类public继承基类,才能使用派生类转换成基类。
  • 派生类的成员函数和友元,可以用派生类转换成基类。
  • 派生类的成员变量和友元,在public或protected继承类时,可以转换成基类,private不能。
  • 友元关系不能传递,同样也不能继承。即父类有个友元,但对于子类来说不是友元。
  • 可以改变个别成员的访问性,使用using声明。
  • 默认情况下,使用class定义的派生类是私有继承的,用struct关键字定义的派生类是公有继承的。
class A
{
public:
    int pub;
protected:
    int pro;
}

class B : private A     // private继承也可以修改成员访问性
{
public :
    using A::pub;
    // using A::pro;    // 但不能修改原有访问性,即使语法正确,但实际还是原来的访问性
protected :
    using A::pro;
}

15.6 继承中的类作用域

  • 如果需要覆盖重载合集(一系列重载虚函数)的一部分,可以使用using语句来覆盖。

15.7 构造函数与拷贝控制

15.7.1 虚析构函数

  • 如果基类的析构函数不是虚函数,则delete一个指向派生对象的基类指针将产生未定义行为。
  • 基类通常需要析构函数,而且设定为虚函数。

15.7.2 合成拷贝控制与继承

  • 定义基类的方式可能导致派生类成员变成删除的函数:
    • 基类的默认构造函数、拷贝操作、析构函数定义为删除或不可访问(private),派生类对应成员也是删除的。因为编译器无法用基类来构造。
    • 基类中有删除或不可访问析构函数,派生类的合成默认构造函数、拷贝构造函数、移动构造函数是删除的。因为编译器无法销毁基类部分。
    • =default请求移动操作时,如果基类对应操作是删除或不可访问,派生类定义为删除。

15.7.3 派生类的拷贝控制成员

class Base { ... };
class D : public Base
{
public:
    D(const D& d) : Base(d) { ... }     // 拷贝基类成员
    D(D&& d) : Base(std::move(d)) { ... }   // 移动基类成员
}

15.7.4 继承的构造函数

class B : A
{
public:
    using A::A;     // 继承A的构造函数。
}
  • 当一个基类构造函数含有默认实参,这些默认实参不会被继承。而是获得多个构造函数,每个构造函数分别省略掉一个有默认实参的形参。

15.8 容器与继承

class A
{
public:
    virtual void Output() { cout << "AAA" << endl; }
};

class B : public A
{
public:
    void Output() { cout << "BBB" << endl; }
};

void main()
{
    vector<shared_ptr<A>> vec;      // 也可以是普通指针
    vec.push_back(make_shared<A>());
    vec.push_back(make_shared<B>());
    vec[0]->Output();   // 输出AAA
    vec[1]->Output();   // 输出BBB

    vector<A> vec2;
    vec2.push_back(A());
    vec2.push_back(B());
    vec2[0].Output();   // 输出AAA
    vec2[1].Output();   // 输出AAA,派生部分被忽略
}

猜你喜欢

转载自blog.csdn.net/l773575310/article/details/79326229
今日推荐