继承的语法
class Person
{
protected:
char name[20];
int age;
char address[20];
};
class Student : public Person
{
protected:
int _stuid;//学号
};
class teacher : public Person
{
protected:
int _jobid;//工号
};
其中类Person称为父类也被称为基类
继承后成员访问方式变化
其中如果基类为private那么在子类中,类内也不可以被访问,可以理解为private未被继承
其它的继承可遵循public>protected>private取其中小的方式进行访问,比如基类中成员为protected,子类以public方式进行继承,那么在子类中该成员的访问限定符就为两者中小的那个protected
如果不写继承方式默认以private方式进行继承
基类和派生类对象赋值转换
int main()
{
Person p;
Student s;
p = s;//可以转换
//s = p;//不可以转换
Person& rp = s;//证明了Student转换成Person对象的过程并没有产生临时变量(权限没有被缩小)
return 0;
}
子类可以赋值给父类,父类不可以赋值给子类,被称为切割或者切片,子类切掉父类没有的那一部分后剩下的的就是父类
注意子类赋值给父类的过程中不会产生临时变量
基类对象不能赋值给派生类对象。
但是基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。
继承中的作用域
父类和子类中可以取同名的成员,但是不建议.当父类和子类定义同名成员时,子类中的变量值优先取当前子类中对应的变量值(就近原则)
如果你想在子类中访问父类中对应的同名变量值,那么就要加上域限定符,指明作用域进行查找
子类和父类中有同名变量时,出现的上述现象被称为隐藏或被称为重定义
class Person
{
public:
void fun()
{
}
protected:
char name[20];
int age;
char address[20];
};
class Student : public Person
{
public:
void fun(int i)
{
}
protected:
int _stuid;//学号
};
父类和子类中的fun构成什么关系?
隐藏/重定义
注意这两个函数是不构成重载的,因为构成重载的条件是在同一作用域
派生类的默认成员函数
派生类必须调用父类的构造函数来初始化父类成员
#include <iostream>
using namespace std;
class Person
{
public:
Person(string name = "张三", int age = 20)
:_name(name)
,_age(age)
{
}
Person(const Person& pn)
:_name(pn._name)
,_age(pn._age)
{
}
Person& operator=(const Person& pn)
{
_name = pn._name;
_age = pn._age;
return *this;
}
protected:
string _name;
int _age;
};
class Student : public Person
{
public:
Student(const string name = "孙子", int age = 100, int stuid = 1211)
://,_name(30)//不能在初始化列表对从父类继承的成员变量进行初始化
Person(name, age)//把基类成员进行拷贝构造给子类
,_stuid(stuid)
{
}
Student(const Student& s)
:Person(s)//派生类中的基类成员必须通过基类的构造函数来初始化
,_stuid(s._stuid)
{
}
Student& operator=(const Student& s)
{
Person::operator=(s);//把父类的成员先给拷贝过来
_stuid = s._stuid;
return *this;
}
protected:
int _stuid;//学号
};
int main()
{
Person p1("李帅",20);
Person p2;
p2 = p1;
Student s1("张工", 20, 121111);
Student s2;
s2 = s1;
return 0;
}
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的复制。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序,所以不需要我们在派生类的析构函数中显示调用基类的析构函数。
继承与友元
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员,友元函数是你父亲的朋友,别人不一定知道他是你的朋友,要想让别人知道是你的朋友,那么你就要去声明他
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
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);
}
如果在派生类中向调用基类的友元函数,那么就要在基类中也声明为友元函数
菱形继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况
菱形继承的问题:菱形继承有数据冗余和二义性的问题
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。
D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。
虚拟继承语法:
例如B,C都继承A,D继承B也继承C,那么就要在B,C类继承A的时候在类A名前加virtual。
class A
{
public:
int _a;
};
// class B : public A
class B : virtual public A
{
public:
int _b;
};
// class C : public A
class C : virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
在腰部继承时加virtual,而不是头部A和尾部D加virtual
组合
// Car和BMW Car和Benz构成is-a的关系
class Car{
protected:
string _colour = "白色"; // 颜色
string _num = "陕ABIT00"; // 车牌号
};
class BMW : public Car{
public:
void Drive() {cout << "好开-操控" << endl;}
};
class Benz : public Car{
public:
void Drive() {cout << "好坐-舒适" << endl;}
};
// Tire和Car构成has-a的关系
class Tire{
protected:
string _brand = "Michelin"; // 品牌
size_t _size = 17; // 尺寸
};
class Car{
protected:
string _colour = "白色"; // 颜色
string _num = "陕ABIT00"; // 车牌号
Tire _t; // 轮胎
};
继承和组合
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
优先使用对象组合,而不是类继承 。
继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称
为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的
内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很
大的影响。派生类和基类间的依赖关系很强,耦合度高。
对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象
来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复
用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被
封装。
实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有
些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用
继承,可以用组合,就用组合。