C++学习笔记——类继承

一、类继承

  从已有的类(基类)派生出新的类,派生类继承了原有类的数据成员和方法。

二、公有派生

  使用公有派生,基类的公有成员将成为派生类的公有成员,基类的私有成员也会成为派生类的一部分,但派生类只能通过使用基类的公有和保护方法访问基类的私有成员。派生类需要自己的构造函数,同时派生类可以根据需要添加额外的数据成员和成员函数。

1 class drivedClass : public baseClass{
2 private:
3   ...
4 public:
5   ...
6 };

三、派生类访问权限的考虑

  创建派生类对象时,程序首先创建基类对象。意味着基类对象要在程序进入派生类的构造函数之前被创建。由于派生类不能直接访问基类的私有成员。所以必须使用基类的公有方法来完成基类对象的创建。

  通过在派生类的构造函数中使用成员初始化列表,来调用基类的构造函数完成基类对象的创建。之后,程序进入派生类的构造函数体,将派生类中的rating 成员初始化为 r ,完成派生类对象的创建。

  注:成员初始化列表只能用于构造函数,非构造函数不能使用成员初始化列表,但派生类方法可以调用公有的基类方法。

  如果没有通过成员初始化列表显式调用基类构造函数,程序将使用默认的基类构造函数。除非要使用基类的默认构造函数完成对基类对象部分的创建,否则应显式调用正确的基类构造函数。

1 drivedClass::drivedClass(unsigned int r, const string& fn,
2         const string& ln, bool ht) : baseClass(fn, ln, ht){
3     rating = r;
4 }

  有关派生类构造函数的要点:

    1、首先要创建基类对象;

    2、派生类构造函数应通过成员初始化列表将基类信息传递给基类的构造函数;

    3、派生类构造函数应显示初始派生类中新增的数据成员;

  创建派生对象时,程序先调用基类构造函数,然后再调用派生类构造函数。派生类对象过期时,程序先调用派生类的析构函数,再调用基类的析构函数。  

四、派生类和基类之间的特殊关系

  1、派生类对象可以使用基类的方法(前提是基类方法不能为私有的)

  2、基类指针可以在不进行显示类型转换的情况下指向派生类对象,基类引用同理。但基类指针或引用只能调用基类方法,不能调用派生类的方法。 

五、继承:is-a 关系

  C++有3种继承方式:公有继承,保护继承,私有继承。公有继承建立一种is-a关系,即派生类对象也是一个基类对象,对于基类对象执行的任何操作也可以对派生类执行。

六、多态公有继承

  多态:类方法的行为取决于调用该方法的对象,即同一个方法的行为随上下文而异。

  两种实现多态公有继承的机制:1、在派生类中重新定义基类的方法。  2、使用虚方法。

  通过 virtual 关键字来使用虚方法(只用于类声明的方法原型中)。

  在方法是通过引用或指针而不是对象调用的情况下,如果没有使用 virtual 关键字,程序将根据引用类型或指针类型选择方法;若使用了 virtual 关键字,程序将根据引用或指针指向的对象的类型来选择方法。

  注:如果要在派生类中重新定义基类方法,通常应将基类方法声明为虚的。

  如果析构函数不是虚的,则只调用对应于指针类型的析构函数。若析构函数是虚的,则先调用相应对象类型的析构函数,然后自动调用基类的构造函数。

复制代码
 1 class Brass{
 2 private:
 3     std::string fullName;
 4     long acctNum;
 5     double balance;
 6 public:
 7     Brass(const std::string & s = "Nullbody", long an = 01, double bal = 0.0);
 8     void Deposit(double amt);
 9     virtual void Withdraw(double amt); //虚方法
10     double Balance() const;
11     virtual void ViewAcct() const;
12     virtual ~Brass();
13 };
复制代码

七、静态联编和动态联编

  函数名联编,将源代码中的函数调用解释为执行特定的代码块。 C++中由于函数重载的缘故,编译器必须查看函数参数和函数名才能确定使用哪个函数,在编译过程进行联编被称为静态联编(早期联编)。又因为虚函数的存在,使用哪一个函数是不能在编译时确定的,编译器必须生成能够在程序运行时选择正确的虚方法的代码,这称为动态联编(晚期联编)。

  动态联编和通过指针与引用调用方法相关。公有继承建立 is-a 关系的一种方法是:如何处理指向对象的指针和引用。

  指向基类的引用或指针可以引用派生类对象,将派生类引用或指针转换为基类引用或指针称为向上强制转换,该规则是 is-a 关系的一部分。将基类引用或指针转换为派生类指针或引用称为向下强制转换(通常不允许,如果不使用显示类型转换的话,因为 is-a 关系通常不可逆)。

  隐式向上强制转换使得基类指针或引用可以指向基类对象或派生类对象,这种情况需要动态联编。c++使用虚成员函数满足这种需求。

1 BrassPlus ophelia;  //derived-class object
2 Brass * bp;         //base-class pointer
3 bp = &ophelia;    //Brass pointer to BrassPlus object
4 bp->ViewAcct();

  如果在基类(Brass)中,没有将ViewAcct()声明为虚函数,bp->ViewAcct()根据指针类型(Brass *)调用 Brass::ViewAcct();如果ViewAcct()被声明为虚的,bp->ViewAcct()调用BrassPlus::ViewAcct();有时只有在运行时程序才能确定对象的类型,编译器对虚方法使用动态联编。

  为何有两种类型的联编以及为何默认采用静态联编?

    效率差别,如果采用动态联编,编译器需要采用一些方法跟踪基类指针或引用指向的对象类型,增加额外的处理开销。如果类不会作为基类,或者派生类不会重新定义基类的方法,则不需要动态联编。

八、虚函数的工作原理

  编译器处理虚函数的方法是:给每个对象添加一个隐藏成员,隐藏成员存储一个指向函数地址数组(虚函数表)的指针。虚函数表存储为类对象进行声明的虚函数的地址。

  例如:基类对象包含一个指针,指向基类的虚函数表;派生类对象也有自己独立的指针指向派生类对象的虚函数表。如果派生类没有重新定义虚函数,该虚函数表保存函数原始版本的地址,否则保存新函数地址。如果派生类定义了新的函数(基类没有定义),则该函数的地址也将被添加到派生类的虚函数表中。

 

  有关虚函数的注意事项:

  1、构造函数不能为虚函数,派生类不继承基类的构造函数。

  2、析构函数应当为虚函数,除非类不为基类。

  3、友元不是类成员,所以友元也不能为虚函数。

  4、在派生类中重新定义函数不会生成函数的两个重载版本,只会隐藏同名的基类方法。如果重新定义继承的方法,应确保函数原型与原来的相同,如果返回类型是基类引用或指针,可以修改为指向派生类的引用或指针(返回类型协变)。

复制代码
 1 class Dwelling{
 2 public:
 3     virtual void showperks(int a) const;
 4 ...
 5 };
 6 
 7 class Hovel : public Dwelling{
 8 public:
 9     virtual void showperks() const;
10 }
复制代码

  5、如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。若只定义一个版本,则另外两个版本将被隐藏。

九、访问控制:Protected

  protect与private类似,在类外都只能用公有类成员来访问。它们之间的区别体现在继承之中:派生类成员可以访问基类的保护成员,但是不能

访问基类的私有成员。

十、抽象基类

  从多个类中抽象出它们的共性,将这些特性放在一个抽象基类(ABC)中,再从这个ABC中派生出多个类。此时可以用基类指针数组同时管理派生出的多个派生类对象。

  由于对于不同的派生类来说,相同方法的实现是不同的(用到的数据成员可能也不同)。C++使用纯虚函数提供未实现的函数,纯虚函数在声明的结尾处为 =0 。例如下面的Area()函数。

复制代码
 1 class BaseEllipse{
 2 private:
 3     double x;
 4     double y;
 5     ...
 6 public:
 7     BaseEllipse(double x0 = 0, double y0 = 0) : x(x0),y(y0) {}
 8     virtual ~BaseEllipse() {};
 9     void Move(int nx,int ny) { x = nx, y = ny;}
10     virtual double Area() const =0 ; //a pure virtual function
11 }
复制代码

  若类声明中包含纯虚函数时,不能创建该类的对象,只能用作基类。类要为ABC则必须至少包含一个纯虚函数。在原型中使用 =0 指出类是一个抽象基类,在类中可以不定义该函数。

  可以将ABC看成是一种必须实现的接口,具体派生类覆盖其纯虚函数,使得派生类遵循ABC设置的接口规则。确保从ABC派生的类至少都支持ABC指定的功能。

十一、继承和动态内存分配

  假设基类使用了动态内存分配,声明中包含了构造函数使用new时需要的特殊方法:析构函数、复制构造函数、重载赋值运算符。

  1、派生类不使用new:若派生类没有使用new来进行动态内存分配,也没有包含其他不常用的、需要处理的特性,派生类不需要定义显式析构函数、复制构造函数和赋值运算符。

  2、派生类使用new:若派生类使用new,则必须为派生类显式定义析构函数、赋值构造函数和赋值运算符。派生类的显式赋值运算符需要负责所有继承的基类对象的赋值,通过显式调用基类赋值运算符完成(使用base::operator=()的方法调用,避免使用 *this = drivedClass,会导致编译器使用drivedClass::operator=(), 从而形成递归调用)。

  派生类如何使用基类的友元呢?

  通过使用基类的友元函数,但是友元不是成员函数,不能通过作用域解析运算符指出要使用哪个函数,所以使用强制类型转换,以便匹配原型时能够选择正确的函数。

十二、编译器生成的成员函数

  1、默认构造函数。默认构造函数要么没参数,要么所有的参数有默认值。如果没有定义默认构造函数和构造函数,编译器自动生成默认构造函数,该默认构造函数会调用基类的默认构造函数(如果有)以及调用本身是对象的数据成员的所属类的构造函数。如果定义了某个构造函数,编译器不会定义默认构造函数。

  2、复制构造函数。调用复制构造函数的情况:

  •   将新对象初始化为一个同类对象(区别赋值);
  •   按值将对象传递给函数;
  •   函数按值返回对象;
  •   编译器生成临时对象;

  在某些情况下,例如使用new初始化的成员指针通常需要执行深复制,或者类包含需要修改的静态变量。此时需要定义自己的复制构造函数。

  3、赋值运算符。赋值运算符用于处理同类对象之间的赋值(区别初始化),默认赋值为成员赋值(浅复制)。

 十三、其他类方法

  1、构造函数,构造函数创建新的对象,而其他类方法被现有的对象调用,因此构造函数不能被继承。继承意为着派生类对象可以使用基类的公有方法,但是在构造函数完成初始化之前,对象不存在。

  2、析构函数,需要使用显式析构函数释放类构造函数中使用new分配的所有内存。

  3、转换,使用只有一个参数的构造函数完成从参数类型到类类型的转换。将可转换的类型传递给以类为参数的函数时,将调用转换构造函数。

1 Star north;
2 north = "polaris"; //假设没有定义将char * 赋给Star的赋值运算符

    第二行代码调用Star::operator=(const Star *)函数,在此之前先调用Star:Star(const char *)生成一个Star对象。可以使用explicit禁止进行隐式转换,但仍允许显式转换。要将类对象转换为其他类型,应定义转换函数。

  4、按值传递对象与传递引用,对象作为函数参数时,为提高效率通常按引用传递对象,按值传递涉及生成临时拷贝(调用复制构造函数、析构函数),花费更多时间。在继承使用虚函数时,按引用传递可以使函数参数为基类引用时,可以接受派生类。函数返回对象还是引用与返回的对象是否是临时副本有关。

 十四、公有继承考虑的因素

  1、is-a关系,如果派生类不是一种特殊的基类,则不要使用公有继承。表示is-a关系的方式之一:无需进行显式类型转换,基类指针可以指向派生类对象,反之不可以。

  2、构造函数、析构函数和赋值运算符是不能被继承的。

  3、赋值运算符默认或隐式版本采用浅复制,如果对象为派生类,编译器使用基类赋值运算符来处理派生类对象中基类的赋值。若将派生类对象赋给基类对象,则基类对象的赋值运算符只处理基类成员,忽略派生类中的其他成员。

  4、析构函数应该是虚的。

  5、友元函数并非类成员,因此不能被继承。 若要使派生类中的友元函数可以使用基类的友元函数,可以通过强制类型转换的方式。

  6、派生类方法可以使用作用域解析运算符来调用公有的和受保护的基类方法。

posted @   owmt  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示