c++继承

参考文章:C++ 成员函数的重载,继承,覆盖和隐藏 - 知乎

C++之继承详解(万字讲解)_c++继承-CSDN博客

#include <iostream> 
using namespace std; 

class Base { 
public: 
    void f(int a){ 
        cout << "Base::f(int a)" << endl; 
    } 
    virtual void g(int a) { 
        cout << "virtual Base::g(int a)" << endl; 
    } 
}; 

class Derived : public Base 
{ 
public: 
    void h(int a) { 
        cout << "Derivd::h(int a)" << endl; 
    } 
}; 

int main() 
{ 
    Base b; 
    b.f(3); 
    b.g(4); 

    Derived d; 
    d.f(3); 
    d.g(4); 
    d.h(3); 
} 

#include <iostream>
using namespace std;

class Base {
public:
 void f(int a){
  cout << "Base::f(int a)" << endl;
 }
 virtual void g(int a) {
  cout << "virtual Base::g(int a)" << endl;
 }
};

class Derived : public Base
{
public:
 void h(int a) {
  cout << "Derivd::h(int a)" << endl;
 }
};

int main()
{
 Base b;
 b.f(3);
 b.g(4);

 Derived d;
 d.f(3);
 d.g(4);
 d.h(3);
}

Base b的对象模型:

 

Derived d的对象模型:

 

public,protected,private

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。(这里也就解决了我们前面遗留的访问限定符的问题)
3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected> private。
4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强

继承中的作用域:

1. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

class Person
{
protected:
    string _name = "张三";
    int _num = 123456;//身份证号
};
class Student :public Person
{
public:
    void Print()
    {
        cout << "姓名:" << _name << endl;
        cout << "学号:" << _num << endl;
        cout << "身份证号:" <<_num << endl;//这种方式是不能输出父类中的_num的信息的
        cout << "身份证号:" << Person::_num << endl;//必须以这种方式找到父类的成员变量
    }
protected:
    int _num = 1001;//学号
};
int main()
{
    Student s;
    s.Print();
    return 0;
}

2. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

 

 

 

基类和派生类对象的赋值转换

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。(也叫切片)

class Person
{
public:
    Person(const char* name = "LiMing")
        :_name(name)
    {
        cout << "Person()" << endl;
    }

    Person(const Person& p)
        :_name(p._name)
    {
        cout << "Person(const Person& p)" << endl;
    }

    Person& operator=(const Person& p)
    {
        cout << "Person& operator=(const Person& p)" << endl;
        if (this != &p)
            _name = p._name;
        return *this;
    }
    
    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name; //姓名
};
class Student :public Person
{
public:
    Student(const char* name,int num)
        //:_name(name)//不能直接以这种方式为成员变量初始化
        :Person(name) //显示调用
        ,_num(num)
    {
        cout << "Student()" << endl;
    }
    Student(const Student& s)
        :Person(s)//显示调用
        ,_num(s._num)
    {
        cout << "Student(const Student & s)" << endl;
    }

    Student& operator=(const Student& s)
    {
        cout << "Student& operator=(const Student& s)" << endl;
        if (this != &s)
        {
            Person::operator=(s);//显示调用父类的函数
            _num = s._num;
        }
        return *this;
    }

    ~Student()
    {
        //我们平时以为的析构函数
        Person::~Person();//调用父类的析构函数,处理父类内的数据
        //但是其实我们不需要显示调用父类的析构函数
        //...    //处理子类内的数据
        cout << "~Student()" << endl;
    }
protected:
    int _num; //学号
};
int main()
{
    Student s1("张三",18);
    Student s2("李四", 2);
    Student s3(s1);  //拷贝构造
    s3 = s1;         //operator=赋值运算运算符重载
    return 0;
}

1. 父类的字段继承给子类,子类需要调用父类的构造器初始化该字段。

2. 子类的析构函数中不需要显示调用父类的析构函数,调用子类析构函数后会自动调用父类析构函数,先调用子类析构函数,在调用父类析构函数。

3. 如果父类有默认的构造函数,子类可以不用显示调用父类的构造函数;如果父类没有默认构造函数,子类必须显示调用父类的构造函数。

 

如何让一个类无法被继承:

private它的构造器

class A
{
    //C++98
    //1.父类构造函数私有---子类是不可见的
    //2.子类对象实例化时,无法调用构造函数
private:
    A()
    {}
protected:
    int _a;
};
class B :public A
{
public:
    B()
    {}
protected:
    int _b;
};

c++11中可以用final字段

//C++11
class A final
{
public:
    A()
    {}
protected:
    int _a;
};
class B :public A
{
public:
    B()
    {}
protected:
    int _b;
};

 

static成员:

类共有的,所有实例化对象共用一个static成员。

 

菱形继承问题:

源于多继承的特性

 

Assistant中Person的成员会有两份,存在数据冗余和二义性的问题。

 普通继承下:

class A
{
public:
    int _a;
};

class B :public A
{
public:
    int _b;
};

class C :public A
{
public:
    int _c;
};

class D :public B, public C
{
public:
    int _d;
};

int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
}

 会有多个成员变量_a

虚拟继承:

class Person
{
public:
    string _name;//姓名
};
//这里有了virtual关键字,
//我们也称这种继承方式为虚拟继承
class Student :virtual public Person
{
protected:
    int _num; //学号
};
//virtual关键字
class Teacher :virtual public Person
{
protected:
    int _id;  //职工编号
};

class Assistant :public Student, public Teacher
{
protected:
    string _majoeCourse; //主修课程
};
int main()
{
    Assistant a1;
    //在这里就可以直接访问了,解决了数据冗余和二义性的问题
    a1._name = "张三";
    return 0;
}

 这里B和C中原来A的位置保存相对于A的偏移量,这样就达到了共享A的目的。

 

待续。。。

 

posted @ 2025-03-21 22:13  横渡大海的神仙鱼  阅读(25)  评论(0)    收藏  举报