目录
一、继承的概念
继承是指,在原有类的基础上扩展出新的功能,产生新的类,叫做派生类
二、继承的格式
继承方式和访问限定符不同时,继承基类成员的变化
总结:
1、当以 public 方式继承时,基类中的 public 成员还是派生类的 public 成员,protected 成员还是 protected 成员,private 在派生类中不可见。(以 public 方式继承时,与其在基类中限定符一样,除 private 是不可见)
2、当以 protected 方式继承时,基类的 public 和 protected 成员在派生类中都是 protected 成员,基类中的 private 成员依旧是不可见。(基类中比 protected 等级低的在派生类中都是 protected成员,除 private 不可见)
3、当以 private方式继承时,基类的 public 和 protected 成员在派生类中都是 private成员,基类中的 private 成员依旧是不可见。(基类中比 private等级低的在派生类中都是 private成员,除 private 不可见)
三、基类和派生类的对象赋值兼容转换
派生类可以赋值给基类的 对象/指针/引用。这种方式也叫做切割或切片。
class Person
{
protected:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
private:
string _id;// 学号
};
在进行赋值时,父类只拿到子类中属于父类的那部分,而丢弃了只属于子类中的部分,就像切割了一样,因此这种赋值也叫做切割或切片。(这个赋值是天然的,并不会产生临时变量)
Student s("la", "女", 17, "10201");
Person& rp = s;
在赋值给父类的引用时,rp 改变了成员变量,s 中的成员变量也会跟着改变(因为赋值只是把子类 s 中父类的地址给 rp 即可)
四、隐藏
如果基类和派生类中有同名的成员,就构成隐藏,子类将屏蔽父类的同名成员。
class Person
{
public:
Person(string name = "lw", string sex = "男", int age = 18)
:_name(name),
_sex(sex),
_age(age)
{}
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
Student(string name, string sex, int age, string id)
:Person(name, sex, age),
_id(id),
_age(age)
{}
string _id;// 学号
int _age;
};
int main()
{
Student s("la", "女", 17, "10201");
s._age = 100;
return 0;
}
上述代码有同名成员变量,将 _age 改为 100,默认改的是子类中的 _age 。
可以指定使用 父类 的作用域,来访问 父类中的 _age。
Student s("la", "女", 17, "10201");
s.Person::_age = 100;
同样,成员函数也可以构成隐藏。
class Person
{
public:
Person(string name = "lw", string sex = "男", int age = 18)
:_name(name),
_sex(sex),
_age(age)
{}
void func()
{
cout << "Person::func()" << endl;
}
public:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
Student(string name, string sex, int age, string id)
:Person(name, sex, age),
_id(id),
_age(age)
{}
void func(int x)
{
cout << "Student::func(int x)" << endl;
}
string _id;// 学号
int _age;
};
上述代码中的 func 函数就是构成隐藏,只要是同名成员函数就构成隐藏。与返回值和参数无关
五、派生类的默认成员函数
1、构造函数
派生类的成员分为:
①、派生类的成员:自定义类型和内置类型分别处理
②、父类的成员:调用父类的构造函数
class Person
{
public:
Person(string name = "lw", string sex = "男", int age = 18)
:_name(name),
_sex(sex),
_age(age)
{
std::cout << "Person::Person()" << endl;
}
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
Student(string id = "2024593")
:_id(id)
{
std::cout << "Student::Student()" << endl;
}
string _id;// 学号
};
int main()
{
Student s;
return 0;
}
运行结果:
从上述代码可以看出,子类会先调用父类的构造函数,再调用自己的构造函数。
如果父类没有默认构造函数可以使用 Person(...) 在初始化列表显示调用。(如 四 隐藏中的代码)
2、拷贝构造:
class Person
{
public:
Person(string name = "lw", string sex = "男", int age = 18)
:_name(name),
_sex(sex),
_age(age)
{
cout << "Person::Person()" << endl;
}
Person(const Person& p)
:_name(p._name),
_sex(p._sex),
_age(p._age)
{
cout << "Person::Person(const Person& p)" << endl;
}
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
Student(string id = "2024593")
:_id(id)
{
cout << "Student::Student()" << endl;
}
Student(const Student& s)
:Person(s), // 父类调用父类的拷贝构造
_id(s._id)
{
cout << "Student::Student(const Student& s)" << endl;
}
string _id;// 学号
};
int main()
{
Student s;
Student s_copy = s;
return 0;
}
3、赋值重载
#include <iostream>
using namespace std;
class Person
{
public:
Person(string name = "lw", string sex = "男", int age = 18)
:_name(name),
_sex(sex),
_age(age)
{
cout << "Person::Person()" << endl;
}
Person(const Person& p)
:_name(p._name),
_sex(p._sex),
_age(p._age)
{
cout << "Person::Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
if(&p != this) // 如果不是自己给自己赋值
{
_name = p._name;
_sex = p._sex;
_age = p._age;
cout << "operator=(const Person& p)" << endl;
}
return *this;
}
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
Student(string id = "2024593")
:_id(id)
{
cout << "Student::Student()" << endl;
}
Student(const Student& s)
:Person(s), // 父类调用父类的拷贝构造
_id(s._id)
{
cout << "Student::Student(const Student& s)" << endl;
}
Student& operator=(const Student& s)
{
if(&s != this) // 如果不是自己给自己赋值
{
Person::operator=(s); // 显示调用父类的赋值重载
_id = s._id;
cout << "operator=(const Student& s)" << endl;
}
return *this;
}
string _id;// 学号
};
int main()
{
Student s;
s._age = 100;
Student s_copy;
s_copy = s;
return 0;
}
4、析构函数
子类析构时会自动调用父类的析构函数,不需要显示自己写。
原因是为了保证析构顺序:先子后父。
构造:先父后子;子类构造可能会使用父类的成员
析构:先子后父;子类析构可能需要访问父类的成员
class Person
{
public:
Person(string name = "lw", string sex = "男", int age = 18)
:_name(name),
_sex(sex),
_age(age)
{
cout << "Person::Person()" << endl;
}
Person(const Person& p)
:_name(p._name),
_sex(p._sex),
_age(p._age)
{
cout << "Person::Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
if(&p != this) // 如果不是自己给自己赋值
{
_name = p._name;
_sex = p._sex;
_age = p._age;
cout << "operator=(const Person& p)" << endl;
}
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
Student(string id = "2024593")
:_id(id)
{
cout << "Student::Student()" << endl;
}
Student(const Student& s)
:Person(s), // 父类调用父类的拷贝构造
_id(s._id)
{
cout << "Student::Student(const Student& s)" << endl;
}
Student& operator=(const Student& s)
{
if(&s != this) // 如果不是自己给自己赋值
{
Person::operator=(s);
_id = s._id;
cout << "operator=(const Student& s)" << endl;
}
return *this;
}
~Student()
{
cout << "~Student()" << endl;
}
string _id;// 学号
};
int main()
{
Student s;
return 0;
}
子类的析构函数和父类的析构函数构成重载,因为由于多态原因,析构函数被特殊处理,函数名都会被处理成 destruction()。
总结:派生类这些默认成员函数的规则与以前类似,不同的是分为了子类部分和父类部分,父类部分就调用父类的默认成员函数。
六、友元与静态成员函数
1、父类的友元无法继承
2、父类的静态成员在整个继承体系中只有一个
(例:static int count; // 则父类和子类都有count,它们是同一个)
class Person
{
public:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
static int count;
};
int Person::count = 1;
class Student : public Person
{
public:
string _id;// 学号
};
int main()
{
Person p;
Student s;
cout << &p.count << endl;
cout << &s.count << endl;
return 0;
}
同一个地址,说明是同一个 count。
六、菱形继承
单继承:一个子类只有一个直接父类
多继承:一个子类有多个直接父类
菱形继承:多继承的两个父类继承了同一个类
菱形继承出现的问题:在 Assistant 类中,Person 类会有两份。
会出现数据冗余与二义性的问题。
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
public:
string _sid;// 学号
};
class Teacher : public Person
{
public:
string _tid;// 教工号
};
class Assistant : public Teacher, public Student
{
public:
string _major;// 专业
};
int main()
{
Assistant a;
a._name = "la";
return 0;
}
虽然我们可以用指定类域的方式解决二义性问题(如:Student::_name),但有很多情况我们不需要两份 Person,会造成空间的浪费。
我们可以增加虚继承来解决数据冗余和二义性的问题。
虚继承的方式:就是在有二义性的地方( Student 和 Teacher )继承方式前加 virtual
class Person
{
public:
string _name; // 姓名
};
class Student : virtual public Person
{
public:
string _sid;// 学号
};
class Teacher : virtual public Person
{
public:
string _tid;// 教工号
};
class Assistant : public Teacher, public Student
{
public:
string _major;// 专业
};
int main()
{
Assistant a;
a._name = "la";
return 0;
}
监视窗口:
虚拟继承原理:原本 Student 中和 Teacher 中各有一个类 Person,但使用虚拟继承后,Student 和 Teacher 有一份公共的类 Person。Student 中和 Teacher 中不保存类 Person ,而是保存 Person 类的偏移量的地址。
公共的类 Person 一般是在最后面(但没规定,也可以不在)
菱形继承的对象模型
在VS环境下
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;
}
没有使用虚拟继承时的 监视内存窗口:
虚拟继承后 (环境为VS,X86):
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.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
内存窗口:
等于在虚拟继承中, D中的 B 和 C 都存的是 A 偏移量的地址,形成了偏移量表,所有的 D 对象都有一份偏移量表,用来找公共的 A。
再定义一个 D dd;
它们有相同的偏移量表。
同理,在 B 和C 对象中也是存了偏移量表和公共的 A。
它们的模型是一样的。