C++面向对象整理(5)之继承(基类与派生类)

C++面向对象整理(5)之继承(基类与派生类)

注:整理一些突然学到的C++知识,随时mark一下
例如:忘记的关键字用法,新关键字,新数据结构



提示:本文为 C++ 中 类定义、成员函数 的写法和举例


一、继承的定义和三种方式

  继承( inheritance )是面向对象编程的一个重要特性,它允许一个类(派生类或子类)继承另一个类(基类或父类)的属性和方法。C++提供了三种主要的继承方式:公有继承(public )、私有继承(private)和保护继承(protected )。

子类可以直接继承拿得父类中除了private权限的其他成员拿来用且都能访问,但其权限根据其继承方式可能会发生变化,子类的成员还可以新添自己特有的其他成员,还可以重定义同名的成员。

继承的语法使用冒号,格式是class 子类B :继承方式 基类A {}

1、公有继承(Public Inheritance)

当使用公有继承: public 时,基类的公有(public)和保护(protected)成员在派生类中保持其访问权限不变,即公有成员在派生类中仍然是公有的,保护成员在派生类中仍然是保护的。基类的私有(private)成员在派生类中是不可访问的。

cpp

class Base {  
public:  
    void pub_func() { /* ... */ }  
protected:  
    void prot_func() { /* ... */ }  
private:  
    void priv_func() { /* ... */ }  
};  


  
class Derived : public Base {  
public:  
    void use_base_funcs() {  
        pub_func();  // 可以调用,因为pub_func是公有的  
        prot_func(); // 也可以调用,因为prot_func是受保护的  
        // priv_func(); // 错误:priv_func是私有的,不可在派生类中直接访问  
};

2、私有继承(Private)

在私有继承中,基类的公有和保护成员在派生类中都会变为私有成员。派生类的成员函数可以间接访问它们。基类的私有成员在派生类中仍然不可访问(其实有继承,但编译器给予隐藏)

class Derived : private Base {  
public:  
    void use_base_funcs() {  
        pub_func();  // 可以调用,但pub_func在子类Derived中变成了私有  
        prot_func(); // 可以调用,但prot_func在子类Derived中变成了私有  
        // priv_func(); // 错误:priv_func是私有的,不可访问  
    };  
};

3、保护继承(Protected Inheritance)

保护继承与私有继承类似,但区别在于基类的公有和保护成员在派生类中变为保护成员。这意味着这些成员在派生类内部是可访问的,但在派生类的对象外部是不可访问的。基类的私有成员在派生类中仍然不可访问

class Derived : protected Base {  
public:  
    void use_base_funcs() {  
        pub_func();  // 可以调用,但pub_func在Derived中变成了保护  
        prot_func(); // 可以调用,prot_func在Derived中仍然是保护  
        // priv_func(); // 错误:priv_func是私有的,不可访问  
    };  
};

在选择继承方式时,需要考虑如何最好地封装和保护基类的数据,以及派生类如何与基类和其他派生类交互。公有继承通常用于“是一个从属于”的关系(例如,猫是动物),而私有和保护继承更多地用于实现细节和内部结构的复用。注意,无论选择哪种继承方式,派生类都可以再额外添加自己的新成员,并且可以重定义同名的成员函数,还能重写基类中的虚函数(如果它们被声明为virtual的话)。

二、继承中的构造函数调用

1、子类的构造在最后,析构在最先

结论:先基类、再成员变量(如果要到第三方类)、再自己子类的构造顺序。下面看一个例子:
假设有类B继承自类A,并且类B有一个第三方类对象成员C c时,构造函数的调用会稍微复杂一些。下面是一个简单的例子来说明这个过程:

class A {  
public:  
    A() {  
        std::cout << "A's constructor called" << std::endl;  
    }  
};  
  
class C {  
public:  
    C() {  
        std::cout << "C's constructor called" << std::endl;  
    }  
};  
  
class B : public A {  
private:  
    C c;  
public:  
    B() : c() {  
        std::cout << "B's constructor called" << std::endl;  
    }  
};  
  
int main() {  
    B b;  
    return 0;  
}

在这个例子中,当创建B类的对象时,构造函数的调用顺序如下:

第一,基类A的构造函数:首先调用基类A的构造函数。这是因为B继承自A,所以A的构造函数会在B的构造函数之前被调用。
第二,成员变量C的构造函数:接下来调用类B的成员变量c即类C的构造函数。成员变量的构造函数在B的构造函数体执行之前被调用。
第三,类B的构造函数:最后,调用类B的构造函数。在这个例子中,B的构造函数使用了初始化列表来显式地调用成员变量C的构造函数。
输出将会是:

//输出结果:
A's constructor called  
C's constructor called  
B's constructor called

这个顺序是C++中对象构造的一个基本规则:首先构造基类,然后构造成员变量,最后构造派生类本身。在析构时,顺序相反:首先析构派生类,然后析构成员变量,最后析构基类。

三、继承中子类重定义同名成员

如果子类定义了一个与父类同名的成员(无论是数据成员还是成员函数),那么在子类中,这个成员会覆盖父类的同名成员。这意味着,如果你试图在子类的对象上访问这个成员,那么默认情况下你会得到子类中的成员,而不是父类中的。

但也可以访问父类的,C++提供了作用域解析运算符::,它允许你明确指出你想访问的是哪个作用域中的成员。假设有一个父类BASE和一个子类S1,并且它们都有一个名为same1_的成员,你可以使用作用域解析运算符来访问父类中的same1_成员。

下面是一个简单的例子来演示这个概念:

  
class BASE {  
public:  
    int same1_; // 父类中的成员  
  
    BASE(int val) : same1_(val) {}  
};  
  
class S1 : public BASE {  
public:  
    double same1_; // 子类中的同名成员,类型不同  
  
    S1(int baseVal, double subVal) : BASE(baseVal), same1_(subVal) {}  
  
    void printBoth() {  
        std::cout << "BASE::same1_: " << BASE::same1_ << std::endl; // 访问父类的same1_  
        std::cout << "S1::same1_: " << same1_ << std::endl;         // 访问子类的same1_  也可以直接this->same1_  因为默认是重定义的子类那个
    }  
};  
  
int main() {  
    S1 s1(10, 20.5);  
    s1.same1_;//访问自己的
    s1.BASE::same1_;//访问父类的
    s1.printBoth(); // 输出两个same1_的值 
     
    return 0;  
}

在这个例子中,BASE类有一个整数类型的same1_成员,而S1类有一个双精度浮点类型的same1_成员。在S1类的printBoth成员函数中,我们使用BASE::same1_来明确访问父类中的same1_成员,而直接写same1_则访问的是子类中的同名成员。

当你运行这个程序时,printBoth函数会输出两个值:一个是父类中same1_的值(整数10),另一个是子类中same1_的值(双精度浮点数20.5)。通过使用作用域解析运算符,我们可以消除子类成员对父类同名成员的隐藏(或覆盖),从而能够访问父类中的成员。(成员函数一样的道理)

注意如果是静态同名成员,一样的方法,而且还可以在子类下直接通过::访问

//假设same1_是静态成员
s1.same1_;//访问自己的
s1.BASE::same1_;//访问父类的
S1::same1_;//访问自己的
S1::BASE::same1_;访问父类的

同时注意上面例子中这个子类的初始化列表构造函数的写法:S1(int baseVal, double subVal) : BASE(baseVal), same1_(subVal) {} 这里的BASE(baseVal)基类的构造函数调用,它可以直接列在初始化列表的冒号后完成赋值

总结

继承的public、private、protected三种继承都不能访问基类私有成员

继承同名成员,可以在子类重新定义,但想访问父类时可以用::访问,静态同名成员完全一样道理

子类构造总是最后调用,子类中的第三方类成员的构造在基类后调用

  • 44
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值