1.继承的介绍
首先容我先向大家举一个列子:
我这里定义了一个Person的类
class Person
{
protected:
string name;
int age;
string address;
};
在这个基础上,我要定义一个关于Student , Worker 的类
由于Student Worker都具有Person类中的成员变量 , 为了实现类的复用,由此引出继承
继承的概念:
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用
上一段代码帮助大家理解:
class Person
{
protected:
string name;
int age;
string address;
};
// 学生这个类继承Person这个类中的成员变量与成员函数
class Student : public Person
{
protected:
string stuid;
};
继承的格式:
class 派生类的名字 : 继承方式 基类的名字
继承后的子类成员访问权限:
注意事项1:父类的private成员在派生类中无论以什么方式继承都是不可见的
这里的不可见指的是派生类对象不管在类里面还是类外面都是不可访问的
用一段代码帮助大家理解
#include<iostream>
#include<string>
using namespace std;
class Person
{
protected:
string name;
int age;
private:
string address;
};
class Student : public Person
{
void print()
{
cout << name << age << address << stuid << endl;// 不能使用address
}
protected:
string stuid;
};
int main()
{
Student s;
s.address = "faf";不能通过s去访问address
}
2.基类的私有成员在子类都是不可见的,基类的其它成员在子类的访问方式 = min(成员在基类的访问限定符,继承方式), public > protected > private
#include<iostream>
#include<string>
using namespace std;
class A
{
public:
string name;
protected:
int age;
private:
string address;
};
class B : public A
{
// A中的name在B中的访问是public-> 类里面可以访问 , 类外面也可以访问
// A中的age在B中的访问是protected -> 类里面可以访问 ,类外面不可以访问
public:
void print()
{
cout << age << " " << name << " " << endl;
}
};
int main()
{
B s;
s.name = "fdadfa";
// s.age = 18; // 这个方式是错的
return 0;
}
3. struct 默然的继承方式和访问限定符都是公有的
class 默然的继承方式和访问限定符都是私有的
2.切割(切片)
前提:用public继承
原则:子类可以赋值给父类,父类不可以给子类
#include<iostream>
#include<string>
using namespace std;
class A
{
public:
string name;
protected:
int age;
private:
string address;
};
class B : public A
{
public:
void print()
{
cout << age << " " << name << " " << endl;
}
};
int main()
{
B s;
A t = s;// 子类可以给父类
// B c = t; 父类不可以给子类
return 0;
}
注意:切割要满足赋值兼容 (不会产生临时变量)
#include<iostream>
using namespace std;
class Person
{
public:
string name = "fafafa";
int age = 19;
string address = "dfdadfadaf";
};
class Student : public Person
{
public:
void print()
{
cout << name << " " << age << " " << address << " " << stuid << endl;
}
protected:
string stuid = "121231";
};
int main()
{
Student t;
Person& tmp = t;
tmp.age = 29;
tmp.name = "aaaaaaaaaa";
t.print();
}
tmp是子类中对父类内容的引用,因此对tmp的改变可以影响子类中的内容
3.继承中的作用域
1.在继承体系中基类和派生类都有独立的作用域
2.子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏(重定义)
3.只需要函数名相同就构成隐蔽
#include<iostream>
using namespace std;
class A
{
public:
void print()
{
cout << num << endl;
}
int num = 1;
};
class B : public A
{
public:
int num = 0;
void print()
{
cout << num << endl;
}
};
int main()
{
B s;
s.print();
return 0;
}
4.子类的默认成员构造函数
1.构造函数
子类的成员变量可以分为 内置类型 , 自定义类型 , 父类成员变量
原则处理:内置类型不做处理 , 自定义类型调用它的构造函数 , 父类成员变量调用父类的构造函数
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
Person(string _name, int _age, string _address)
:name(_name), age(_age), address(_address)
{}
protected:
string name;
int age;
string address;
};
class Student : public Person
{
public:
Student(string _name, int _age, string _address, string _stuid)
: Person(_name, _age, _address), stuid(_stuid)
{}
void print()
{
cout << name << age << address << stuid << endl;
}
protected:
string stuid;
};
int main()
{
Student s("fdadfad", 11, "fadfafadfafd", "111111111111");
return 0;
}
2.拷贝构造函数
原则处理:内置类型浅拷贝 , 自定义类型调用它的拷贝构造函数 , 父类成员变量调用父类的拷贝构造函数
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
Person(string _name, int _age, string _address)
:name(_name), age(_age), address(_address)
{}
Person(const Person& s)
: name(s.name), age(s.age), address(s.address)
{}
Person& operator=(const Person& s)
{
name = s.name;
age = s.age;
address = s.address;
return *this;
}
protected:
string name;
int age;
string address;
};
class Student : public Person
{
public:
Student(string _name, int _age, string _address, string _stuid)
: Person(_name, _age, _address), stuid(_stuid)
{}
Student(const Student& s)
: Person(s), stuid(s.stuid) {}
Student& operator=(const Student& s)
{
Person::operator=(s);
stuid = s.stuid;
}
void print()
{
cout << name << age << address << stuid << endl;
}
protected:
string stuid;
};
int main()
{
Student s("fdadfad", 11, "fadfafadfafd", "111111111111");
return 0;
}
3.复制重载
原则处理:内置类型浅拷贝 , 自定义类型调用它的复制重载函数 , 父类成员变量调用父类的复制重载函数
4.析构函数
注意:析构函数名在编译时会被统一命名成destructor
先析构子 , 再析构父
父类的析构函数会自动调用的
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
Person(string _name, int _age, string _address)
:name(_name), age(_age), address(_address)
{}
Person(const Person& s)
: name(s.name), age(s.age), address(s.address)
{}
Person& operator=(const Person& s)
{
name = s.name;
age = s.age;
address = s.address;
return *this;
}
~Person()
{
cout << "~Person" << endl;
}
protected:
string name;
int age;
string address;
};
class Student : public Person
{
public:
Student(string _name, int _age, string _address, string _stuid)
: Person(_name, _age, _address), stuid(_stuid)
{}
Student(const Student& s)
: Person(s), stuid(s.stuid) {}
Student& operator=(const Student& s)
{
Person::operator=(s);
stuid = s.stuid;
}
~Student()
{
cout << "~Student" << endl;
Person::~Person();
}
void print()
{
cout << name << age << address << stuid << endl;
}
protected:
string stuid;
};
int main()
{
Student s("fdadfad", 11, "fadfafadfafd", "111111111111");
return 0;
}
这段代码的结果是:
原因是:即使我显示的调用Person的析构函数,在Student析构时,仍会自动调用Person的析构函数
5.多继承
多继承可以看作是单继承的扩展。所谓多继承是指派生类具有多个基类,派生类与每个基类之间的关系仍可看作是一个单继承。
多继承下派生类的定义格式如下:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
其中,<继承方式1>,<继承方式2>,…是三种继承方式:public、private、protected之一。
#include<iostream>
using namespace std;
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;
};
6.菱形继承
有上图可知B中继承了两份A的成员变量
导致数据冗余和二义性
解决方式:虚拟继承
#include<iostream>
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 s;
s._a = 1;
s._b = 2;
s._c = 3;
s._d = 4;
return 0;
}
底层:
这是s的地址
这是s继承B的内容:
继承的第一个是一个指针 575e9c58(小端)这里的地址后会存一个偏移量
在s的原地址上加上一个偏移量就是_a的值了