目录
一.继承的基本概念
继承,其实就是一种代码复用的手段,子类继承父类就能使用父类中的变量
例如:在师生关系中,学生和老师的共同信息有:姓名,性别,年龄,身高等,同时学生又有一些特有的信息:学号,学院等,老师也有特有信息:工号,所教科目等
这样可以将师生的共同信息提取出来:
class Person
{
public:
string name;
string sex;
int age;
int height;
}
接下来在实现student和teacher类的时候就只需要继承上面的person类外加特有属性即可:
class Student : public Person
{
protected:
int _stuid; // 学号
};
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
派生类也被称为子类,基类也被称为父类
//子类对象可以直接调用父类变量
Student s;
s._stuid = 100000;
s.name ="张三";
s.age = 20;
二.继承关系和访问限定符
继承的方式不同,那么子类中继承到父类的变量的访问权限也不相同,以下这张表表现得很清楚
简单总结了一下:
- 在无继承体系中,pretected和private没有区别
- 在继承体系中,父类的private成员在子类中不可见(继承了但不能使用)
- 实际使用中一般都用public继承
- 使用关键字class时默认继承方式是private,而struct默认继承方式是public
三.继承中的作用域
继承体系中基类和子类拥有独立的作用域
子类和父类中有同名成员时,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义(在子类成员函数中,可以使用 基类::基类成员 显式访问)
需要注意的是如果是成员函数的隐藏,只需要函数名相同就能构成隐藏(返回值和参数数量、类型可以不同)
注意实际中在继承体系里最好不要定义同名的成员
class Person
{
protected :
int _num = 111; // 身份证号
};
class Student : public Person
{
protected:
int _num = 999; // 学号
};
Student st;
cout<<st._num;//打印999
cout<<st.Person::_num;//打印111
四.父子类的对象赋值转换
- 子类对象可以赋值给父类的对象/父类的指针/父类的引用。有个形象的说法叫做切片或者切割(走了一个特殊处理,中间不会产生一个临时变量),意味着把子类中父类那部分切割来赋值过去
- 父类不能赋值给子类
- 父类的指针或者引用可以通过强制类型转换赋值给子类的指针或引用,但是必须是父类的指针是指向子类对象才是安全的
class Student : public Person//学生类继承于Person类
{
public:
int _stuid; // 学号
};
void test1()
{
Student stu;
//子类对象可以赋值给父类对象/指针/引用
Person per = stu;//走了一个特殊处理:中间不会产生一个临时变量
Person* pper = &stu;//这个指针也是直接指向对象父类那一部分
Person& rper = stu;
//父类对象不能赋值给子类对象
stu = per;
//父类的指针可以通过强制类型转换赋值给子类的指针
pper = &stu;
Student * ps1 = (Student*)pper; // 这种情况转换时可以的。
ps1->__stuid = 10;
pper = &per;
Student* ps2 = (Student*)pper; // 这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->__stuid = 10;
}
int main()
{
test1();
return 0;
}
五.子类中的默认成员函数
默认成员函数有:构造函数、拷贝构造函数、赋值重载函数、析构函数(取地址重载这里不讲)
子类中的默认成员函数有一些特殊的行为:
- 子类的构造函数必须显式调用父类的构造去初始化父类的那部分成员(拷贝构造也是)
- 子类的operator=中必须调用父类的operator=完成父类成员赋值
- 子类的析构函数不用显式调用父类的析构,编译器会自动去调用
- 子类初始化对象时,先初始化父类成员再初始化子类成员
- 子类析构清理时,先调用子类的析构再调用父类的析构(与构造相反)
可以使用以下代码来进行验证:
class person
{
public:
person(const char* name = "Jack")
:_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=()" << endl;
if (this != &p)
{
_name = p._name;
}
return *this;
}
~person()
{
cout << "~person" << endl;
}
protected:
string _name;
};
class student :public person
{
public:
student(const char* name,int num)
:person(name)
,_num(num)
{
cout << "student()" << endl;
}
student(const student& s)
:person(s)//子类可以赋值给父类
,_num(s._num)
{
cout << "student(const student& s)" << endl;
}
student& operator=(const student& s)
{
cout << "student operator=()" << endl;
if (this != &s)
{
person::operator=(s);
_num = s._num;
}
return *this;
}
~student()
{
cout << "~student()" << endl;
}
protected:
int _num;//学号
};
int main()
{
student s1("Peter", 18);
student s2(s1);
s1 = s2;
return 0;
}
实际上将父类当做子类的成员变量即可,只是该成员变量是一个类,这样看就好理解一些
六.继承与友元
友元关系不能继承,父类友元不能访问子类的私有和保护成员
class A
{
public:
int _a1;
friend void print(B& bb);
protected:
int _a2;
};
class B:public A
{
public:
int _b1;
protected:
int _b2;
};
void print(B& bb)
{
cout << bb._a1;
cout << bb._a2;
}
七.继承与静态成员
父类定义了stacit静态成员,则整个继承体系中只有一个这样的成员。无论派生出多少个子类,都只有这一个static成员实例
八.菱形继承和虚拟继承
在使用继承的时候,有时候会出现这种情况:类B、C继承了类A,而类D继承了B和C
此时就出现了一个问题,类D的实例化对象中,既有B类也有C类,然而B和C都有A类,那么此时D中就出现了两个A类,造成了数据冗余,于是可以使用虚拟继承来解决
虚拟继承:在继承前加上virtual关键字
class A
{
int _a = 1;
};
class B :virtual public A
{
int _b = 2;
};
class C :virtual public A
{
int _c = 3;
};
class D :public B, A
{
int _d = 4;
};
注意:虚拟继承只需要在导致二义性的地方加上virtual即可,不需要每个继承都加上
九.继承和组合
- public继承是一种is-a的关系,也就是说每个子类对象都是一个父类对象
- 组合是一种has-a的关系,例如B组合了A,每个B对象都有一个A对象
- 优先使用对象组合,而不是类继承(继承耦合度高,不利于维护)