C++中的继承

面向对象的三大特性

  1. 封装
  2. 继承
  3. 多态

继承的概念和定义

继承的本质就是类层次的复用。

继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段.
它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类, 称派生类
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
继承是类设计层次的复用

基类(父类) 派生类(子类)

父类就是被继承的类。
子类就是继承的类。
子类复用了父类

class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "tongtong"; // 姓名 
    int _age = 15; // 年龄
};

class Student : public Person
{
protected:
    int _stuid; // 学号 
};
int main()
{
    Student s;
    //如果共有子类可以用父类的函数
    //私有就不可以
    s.Print();
	return 0;
}

用上面的定义来说,
person就是父类,student就是子类,子类会复用父类

看打印的结果,我们发现,虽然studen没有名字和年龄,但是却可以打印,原因就是继承了
C++中的继承_第1张图片

继承关系和访问限定符

class Student : public Person
在定义student的时候其中pubilc就是继承方式。

继承关系和访问限定符都是有三种,public,protected,private
所以继承关系和访问限定符有9种组合方式,具体如下图。

类成员/继承方式 pubilc继承 protected继承 private继承
父类的pubilc成员 子类的pubilc成员 子类的protected成员 子类的private成员
父类的protected成员 子类的protected成员 子类的protected成员 子类的private成员
父类的private成员 在子类中不可见 在子类中不可见 子类中不可见

当在继承的时候,可以不写继承关系。
但是class默认的继承方式是private,struct的继承方式是public。

总结
访问方式==父类访问限定符和限定方式小的。pubic>protected>private.
protectedprivate的区别
在父类中,这两个访问限定符没有区别,都是在类中可以访问,在类外不可访问。
但是在子类中,他们两个有区别。
protected在子类中可见,不可用。
private在子类中不可见,不可用。

父类和子类的赋值转换

将子类转换为父类,不需要发生类型转换,不会产生临时变量,天然支持的。
可以这样理解,父类是特殊的子类。

int main()
{   
    //发生隐式类型类型转换
    double d = 1.5;
    int i = d;
    //必须用const,临时变量具有常性
	const int& i2 = d;
	
    //天然支持的,不存在类型转换发生
    Student s;
    Person p = s;
    //没有产生临时对象,所以不用加const
    Preson p2& = s;
    Preson* ptr = &s;
	return 0;
}

如果不理解,可以形象的看这个图。C++中的继承_第2张图片

那么问一个问题?
父能不能给给子?
不可以,父类的东西比子类的东西少。

继承中的作用域

  1. 在继承体系中基类和派生类都有独立的作用域。
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类::基类成员 显示访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系里面最好不要定义同名的成员。
问题:既然子类和父类有自己的作用域,那么能不能有同名变量或同名函数?(隐藏/重定义)
可以,独立的作用域当然可以拥有同名的变量或函数。
就近原则,优先访问自己作用域,
但是如果想访问同名的其他作用域,指定作用域就可以。

做下面这个练习题

class A
{
public:
    void fun()
    {
        cout << "func()" << endl;
    }
};
class B : public A
{
public:
    void fun(int i)
    {
        A::fun();
        cout << "func(int i)->" << i << endl;
    }
};
//A:两个fun构成函数重载
//B:两个fun构成隐藏
//C:编译报错
//D:以上说法都不对

B,
在不同的作用域,不可能构成函数重载,这两个类也没有问题,他们构成隐藏。
有人会想,他们同名,参数不同还构成隐藏吗?
如果是成员函数的隐藏,只需要保证函数名相同就可以。不用考虑参数,和返回值。

如果想用A的fun怎么用?
B().A::fun();
加上域作用限定符就可以,我用的是匿名对象,大家也可以不用。

子类的默认成员

先考虑默认成员函数

C++中的继承_第3张图片

先给大家一个父类,写一个student子类

class Person
{
public:
    Person(const char* name = "peter")
        : _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; // 姓名 
};

1、子类的构造函数

父类的成员必须调用父类的构造进行初始化。如果在子类中不写构造和析构函数,调用父类的构造函数和析构函数。

父类的成员调用父类的构造函数初始化,子类的自己的成员自己初始化,
用到方法就是在初始化列表中,按照这个方式初始化person(name),调用父类的构造,用我们自己传的参数构造。
如果不写person(name),也会调用父类的构造函数,用缺省参数构造。

我们可以这样理解,将父类当成子类的成员。

class Student : public Person
{
    Student(const char* name,int num)
        :Person(name)//规定这么写,调用父类的构造函数初始化
        //如果不写person(name)也可以调用父类的构造函数。
        ,_num(num)
    {}
protected:
    int _num;
};
int main()
{
	Student s("tongtong",20);
	return 0;
}

2、子类的拷贝构造

如果不写子类的拷贝构造,会调用父类的默认的拷贝构造。

class Student : public Person
{
public:
    Student(const Student& s)
    {}
protected:
    int _num;
};
int main()
{
	Student s("tongtong",20);
	Student s2(s);
	return 0;
}

对于上面这种写了拷贝构造但不做处理,编译器不会调用父类的拷贝构造。只进行了一次构造,还是因为传参的时候调用。

初始化父类,就传父类的拷贝构造

class Student : public Person
{
public:
    Student(const Student& s)
    	//直接将子类切片。调用父类的拷贝构造
        :Person(s)_num(s._num)
    {}
protected:
    int _num;
};

3、赋值 重载

 Student& operator =(const Student& s)
 {
     if (this != &s)
     {
         Person::operator =(s);
         _num = s._num;
     }
     return *this;
 }

4、析构函数

 //析构函数会被处理为Destructor,构成隐藏所以用作用域
 ~Student()
 {
     Person::~Person();
 }
int main()
{
    Student s1("tongtong", 20);
}

如果这样我们的析构会调用三回,本来只需要调用两回,多调用了一会的原因是Person::~Person();这个多调用了一会。

析构函数不需要我们自己显示调用,他自己调用父类的析构和子类的析构。
所以为了保证析构的顺序,先析构子再析构父。
如果我们自己操作不能保证这个顺序,
所以编译器帮我们做,子类析构函数完成时,会自动调用父类析构函数,保证先析构子再析构父。

因为要保证顺序,
构造的顺序肯定是先构造父类在构造子类。

四个子类的默认函数调用,除了析构函数,顺序都是先父后子。

5、继承和友元

友元关系不能继承。

下面代码有错误,因为友元函数不能继承,所以我们再有友元函数中不能访问子类的成员,没有继承了父类的友元。

class Student;
class Person
{
public:
    friend void Display(const Person& p, const Student& s);
protected:
    string _name; // 姓名 
};
class Student : public Person
{
protected:
    int _stuNum; // 学号 
};
void Display(const Person& p, const Student& s)
{
    cout << p._name << endl;
    //错误的代码下一句,不可访问
    cout << s._stuNum << endl;
}
void main()
{
    Person p;
    Student s;
    Display(p, s);
}

但是如果想要访问,就在子类加上友元函数的声明就可以了

class Student;
class Person
{
public:
    friend void Display(const Person& p, const Student& s);
protected:
    string _name; // 姓名 
};
class Student : public Person
{
	//加上友元就可以使用了。
    friend void Display(const Person& p, const Student& s);
protected:
    int _stuNum; // 学号 
};
void Display(const Person& p, const Student& s)
{
    cout << p._name << endl;
    cout << s._stuNum << endl;
}
void main()
{
    Person p;
    Student s;
    Display(p, s);
}

6、继承与静态成员

父类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例
C++中的继承_第4张图片

.7、复杂的菱形继承及菱形虚拟继承

一、单继承

一个子类只有一个直接父类时称这个继承关系为单继承
C++中的继承_第5张图片

二、多继承

一个子类有两个或以上直接父类时称这个继承关系为多继承
C++中的继承_第6张图片

三、多继承会引起菱形继承

菱形继承是多继承的一种特殊情况。
C++中的继承_第7张图片
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。

使用虚继承解决数据冗余和二义性

8、虚继承继承

现在有一个菱形继承(A B C D)
C++中的继承_第8张图片

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;
	return 0;
}

上面没有虚继承,就是单纯的多继承,会有数据冗余和二义性,先观察一下
C++中的继承_第9张图片

使用虚继承
C++中的继承_第10张图片
内存窗口
C++中的继承_第11张图片
将公共继承的A放到一块空间,
B通过第一个地址找到偏移量20,向下走5个地址,从而找到A,就可以改变A。
C通过第一个地址找到偏移量12,向下走3个地址,从而找到A,就可以改变A。
这样就只有一块A,解决数据冗余和二义性

9、继承和组合

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。 C++中的继承_第12张图片

组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
C++中的继承_第13张图片

你可能感兴趣的:(C++,c++,java,c#)