C++_继承

本文详细阐述了面向对象编程中的继承概念、不同类型的继承(public、protected、private)、子类对象赋值转换、作用域规则、默认成员的构造与析构、友元与继承的关系、静态成员继承、菱形继承及其问题以及虚继承的解决方案。
摘要由CSDN通过智能技术生成

1. 继承的概念及定义

1.1 继承的感念

继承机制是面向对象程序设计使代码可以复用的重要手段,它可以保持原有类特征的基础上进行扩展.
继承呈现了面向对象程序设计的层次结构.继承是类的设计层次的复用.

//父类
class Person
{
public:
    void print()
    {
        cout << _name << endl;
        cout << age << endl;
    }

protected:
    string _name = "pear";
    int age = 23;
};
// 子类
class student : public Person
{
protected:
    int _stuid;
};

1.2 继承的定义

类成员修饰符:

  • public:公共的,可外访.
  • private:私有的,防外部访问以及子类访问.(可调用父类函数,可以调用父类private成员)
  • protected:保护,外部不可访问,但子类可以访问.

以1.1代码示例:
public继承:

class student : public Person
{
protected:
    int _stuid;
};

image.png

子类public继承,将继承除**private**修饰外的成员与函数,父类public修饰的函数与成员可以通过子类外部访问,子类继承后还是**public**属性.protected则只能在继承后子类内访问,子类继承后则还是**protected**属性.

protected继承

class student : protected Person
{
public:
    void test()
    {
        print();
        cout1();
    }
protected:
    int _stuid;
};

image.png
image.png

protected继承会将父类的除private修饰的成员变量及函数外,其他修饰的成员与函数,子类继承后则具有protected属性.

private继承

//父类
class Person
{
public:
    void print()
    {
        cout << _name << endl;
        cout << age << endl;
    }

private:
    void cout1()
    {
        cout<< "Person::cout1()" << endl; 
    }
    string _name = "pear";
    int age = 23;
};
// 子类
class student : private Person
{
public:
    void test()
    {
        print();
    }
protected:
    int _stuid;
};

image.png

可以看到,父类的除private成员变量与函数外,其他成员都继承到子类当中,并在子类中赋与**private**属性.子类无法访问父类中**private**修饰的成员变量与函数,但可以通过调用继承到父类中的函数来进行访问.

总结:

  1. 父类的private修饰的成员或函数,无法在子类直接访问.
  2. protected保护修饰,则是为了继承而生的,外部无法访问,但子类继承后可以访问.
  3. class默认的继承方式是private,struct的默认继承方式是public.

2. 父类与子类对象赋值转换

以1.1代码为例:
image.png
父类与子类实例内存图

int main()
{
    student s1;
    Person a = s1;
    Person& b = s1;
    Person* c = &s1;
    a.print();
    b.print();
    c->print();
    return 0;
}

image.png
子类赋值给父类操作:
image.png

Person a = s1操作会将子类中含有父类的部分(这部分操作,可称为切割)赋值给a中.这分操作并不会产生随机变量而是调用父类中的拷贝构造函数,

引用或指针操作
image.png

引用其底层则是指针操作,这里则是强制类型转换赋值给子类的指针或引用操作访问这部分的内存地址.

总结:

  1. 子类对象赋值给父类对象,操作称为切割.
  2. 子类对象可以赋值给父类对象,但父类对象并不能赋值给子类对象.
  3. *&则是强制类型转换子类对象指针进行操作.

3. 继承中的作用域

  1. 在继承体系中父类和子类都有独立的作用域.
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也可以叫重定义.(在子类成员中可以使用 ,父类::父类成员来进行访问.)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏.
  4. 注意在实际中在继承体系里面最好不要定义同名的成员.

(函数重载的要求:必须在同一个作用域内.)

//父类
class Person
{
public:
    void print()
    {
        cout << "Person.print()" << endl;
    }

private:
    string _name = "pear";
    int age = 23;
};
// 子类
class student : public Person
{
public:
    void print()//与父类函数同名,父类函数隐藏
    {
        cout << "student.print" << endl;
        Person::print();//通过指定域空间来调用隐藏的函数,
    }
protected:
    int _stuid;
};

int main()
{
    student s1;
    s1.print();
    return 0;
}

image.png

4. 子类中的默认成员

  1. 父类的成员,必须调用父类构造函数成员初始化.(不写则在初始化列表调用.)子类构造,必须得调用父类的构造函数.
  2. 析构函数会被处理成Destructor.(父类析构不能显式调用,为了保存析构顺序).子类析构函数完成时,会自动调用父类析构函数,(根本原因是栈帧规则)保证先析子再析构父.

image.png

4.1 构造与析构:

//父类
class Person
{
public:
    Person()
    {
        cout << "Person()" << endl;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
private:
    string _name = "pear";
    int age = 23;
};
// 子类
class student : public Person
{
public:
    student()
    {
        cout << "student()" << endl;
    }
    ~student()
    {
        cout << "~student()" << endl;
    }
protected:
    int _stuid;
};

int main()
{
    student s1;
    return 0;
}

image.png
**注:**对于多继承来说,默认构造符合初始化列表语法规则,谁先继承谁先声明.

class D: public B,public C // 这里先继承B再继承C
{
    D(const char& a)
    :C(a)
    ,B(a) //这里先初始化B,再初始化C.
};

4.2 赋值重载与拷贝构造

//父类
class Person
{
public:
    Person()
    {
        cout << "Person()" << endl;
    }
    Person(const Person& p)
    :_name(p._name)
    ,age(p.age)
    {
        cout << "Person(const Person& p)" << endl;
    }
    Person& operator=(const Person& p)
    {
        cout << "Person& operator=(const Person& p)" << endl;
        return *this;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
private:
    string _name = "pear";
    int age = 23;
};
// 子类
class student : public Person
{
public:
    student()
    {
        cout << "student()" << endl;
    }
    student(const student& s)
    :Person(s)
    ,_stuid(s._stuid)
    {
        cout << "student(const student& s)" <<endl;
    }
    student& operator=(const student& s)
    {
        if (&s != this)
        {
            Person::operator=(s);
            cout << "studnet& operator=(const student& s)" << endl;
        }
        return *this;
    }
    ~student()
    {
        cout << "~student()" << endl;
    }
protected:
    int _stuid;
};

image.png

5. 友元与继承

友元关系不能被继承,(父类友元关系,子类不会继承)

6. 静态成员继承

静态成员存在于静态区,属于整个类,子类也可访问.但只能初始化一次. (所有的子类共享)

7. 菱形继承

  1. 单继承 : 一个子类只有一个直接父类时,这个继承关系叫单继承.
  2. 多继承: 一个子类有两个或以上直接父类时,这个继承关系叫多继承.
  3. 菱形继承 : 凌形继承 是多继承的一种特殊情况.

菱形继承可能导致的问题: 数据冗余(本质空间浪费)和二义性.

int main()
{
    Assistant a;
    //二义性可指定作用域解决.
    a.Student::_name = "小张";
    a.Teacher::_name = "老张";
}

image.png
菱形继承示意图

7.1 虚继承virtual

解决方式: 子类继承可用虚继承(关键字virtual)的方式解决.

class D: virtual public B,virtual public C
{ };

底层原理:则是多继承而来的共同属性存放在子类内存里,父类对象上开批一个内存地址,该内存地址所指向的空间则记载该父类地址到共同属性的对象的地址之间的漂移量.
image.png

注意:

  1. 使用虚拟继承,子类对象中会被占用8个字节(32位系统下)或以上空间,用于存放记载漂移量的内存地址.
  2. 所记载漂移量的内存地址类似于静态成员.属于整个子类,多次实例化子类对象,并不会多次开辟漂移量空间,只会开辟一次.

题外

创建一个不能被继承的类,(可以私有构造函数或析构函数即可)

class A
{
pubilc:
	static A CreateObj()
	{
        return A();
    }
private:
	A()
	{}
};

class B : public A
{};

int main()
{
    
}
  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值