前言
基类和派生类的赋值对象转换、派生类与基类成员的函数隐藏、派生类中的默认成员函数、继承与友元、继承与静态成员函数、菱形继承、菱形虚拟继承等的介绍
一、基类和派生类的赋值对象转换
派生类可以赋值给基类,因为会是使用切片、切割的方式将派生类中基类所拥有的成员赋值给基类
基类不可以直接赋值给派生类
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
string _name;
string _sex;
int _age;
};
class Student : public Person
{
public:
int _No;
};
int main()
{
Student s;
s._name = "zhangsan";
s._sex = "nan";
s._age = 18;
s._No = 999;
Person p;
p = s;
return 0;
}
二、派生类与基类成员的函数隐藏
当派生类中的成员函数与基类中的成员函数同名时,就会默认隐藏基类中的成员函数,只能访问到派生类的成员函数,若需要访问基类中的成员函数,需要加类的访问限定符。
#include <iostream>
using namespace std;
class Person
{
public:
void fun()
{
cout << "name:" << _name << endl;
}
protected:
string _name = "peter";
int _num = 100;
};
class Student : public Person
{
public:
void fun(int i)
{
// 派生类找成员变量的顺序,默认从局部域开始找,若没有去自己的类中找,
// 若没有再去基类中找,最后再去全局域中找,没找到报错
//int _num = 0;
cout << "num:" << _num << " i:" << i << endl;
cout << "Person::num:" << Person::_num << endl;
}
protected:
int _num = 999;
};
int main()
{
Student s;
s.fun(1);
s.Person::fun();
return 0;
}
三、派生类中的默认成员函数
-
派生类中的构造函数,会默认调用基类的默认构造函数,若基类中没有默认构造函数,则需要手动调用。
-
派生类中的拷贝构造,在调用基类的拷贝构造函数时,只要传入派生类对象即可,因为派生类会自动切片赋值给基类
-
派生类中的赋值运算符重载函数,与基类中的赋值运算符重载函数名相同,会自动隐藏,在调用基类中的赋值运算符重载时,要加类访问限定符调用基类的赋值运算符重载函数。
-
派生类中的析构函数,在析构函数调用结束时会自动调用基类中的析构函数,因为必须先析构派生类,再析构基类(防止析构完基类,但再次用到基类的成员)
// 派生类的默认成员函数
#include <iostream>
using namespace std;
class Person
{
public:
Person(const char* name)
:_name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
:_name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
if (this != &p)
{
_name = p._name;
}
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name;
};
class Student : public Person
{
public:
// 派生类的构造函数会在初始化列表中先调用基类的默认构造函数,在对自己的成员变量初始化
// 如果基类中没有默认构造函数,需要在派生类的初始化列表调用基类的构造函数
// 派生类继承基类,基类成员的声明一定是在派生类中的,所以初始列表一般应先初始化基类成员,与声明保持一致
Student(const char* name = "zhangsan", int num = 99)
:Person(name)
,_num(num)
{}
Student(const Student& s)
:Person(s)
,_num(s._num)
{}
Student& operator=(const Student& s)
{
if (this != &s)
{
// 直接写operator=会调用派生类的赋值运算符重载
// 此处应该调用基类的赋值运算符重载,应该加类域
Person::operator=(s);
_num = s._num;
}
return *this;
}
~Student()
{}
protected:
int _num;
};
int main()
{
Student s1("knowledge", 100);
Student s2(s1);
Student s3;
s3 = s2;
return 0;
}
四、继承与友元
友元关系不能继承
五、 继承与静态成员函数
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
所以,我们可以计算一个基类以及它的派生列共实例化了多少个对象。
因为每一个派生类构造时都会调用基类的默认构造,所以我们用静态成员变量在基类的默认构造中计数
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person() { ++_count; }
protected:
string _name;
public:
static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _num;
};
class Teacher : public Person
{
protected:
int _id;
};
int main()
{
Person p;
Student s1;
Student s2;
Teacher t;
cout << p._count << endl; // 4
return 0;
}
六、菱形继承
#include<iostream>
#include <string>
using namespace std;
class Person
{
public:
string _name;
};
class Student : public Person
{
protected:
int _num;
};
class Teacher : public Person
{
protected:
int _id;
};
class Assistant : public Student, public Teacher
{
protected:
int _age;
};
int main()
{
Assistant a;
// 二义性的问题,_name指向不明确
//a._name = "zhangsan";
// 可以解决二义性的问题,但是无法解决数据冗余的问题
a.Student::_name = "zhang";
a.Teacher::_name = "Mr.zhang";
return 0;
}
七、菱形虚拟继承
在腰部的类的继承方式前面加 virtual
虚拟继承会改变类对象模型的结构,比如: D会改变B和C的结构,B中除了存储_b外,还存除了,当前位置到A的偏移量。有了偏移量可以让每个类都有很好的访问方式
#include<iostream>
#include <string>
using namespace std;
class A
{
public:
int _a;
};
class B : virtual public A
{
public:
int _b;
};
class C : virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d._a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
总结
基类和派生类的赋值对象转换、派生类与基类成员的函数隐藏、派生类中的默认成员函数、继承与友元、继承与静态成员函数、菱形继承、菱形虚拟继承等的介绍