继承与派生
一 继承与派生用法
注意:
除了构造函数和析构函数,基类的所有数据成员和所有成员函数,都会被派生类继承
在创建派生类对象时,会先调用基类的构造函数,用来初始化从基类继承的数据,再调用自己的构造函数,用来初始化自己定义的数据
派生类的成员函数中,不能直接访问基类的private成员
//------------------ main.cpp -------------------
#include <iostream>
#include "Father.h"
#include "Son.h"
int main()
{
Father wjl("王健林", 58);
Son wsc("王思聪", 32, "电竞");
cout << endl;
cout << wjl.description() << endl;
// 派生类对象调用方法的时候,先再自己定义的方法中寻找,如果有,就调用自己定义的方法
// 如果找不到,就到基类的方法中去找,如果有,就调用基类的这个同名方法
// 如果还是找不到,就发生错误
cout << wsc.description() << endl;
cout << endl;
return 0;
}
//-------------- Father 类 -----------------------
/* Father.h */
#pragma once
#include <string>
using namespace std;
class Father
{
public:
Father(const string& name, int age);
~Father();
string getName() const;
int getAge() const;
string description() const;
private:
string name;
int age;
};
/* Father.cpp */
#include "Father.h"
#include <iostream>
#include <sstream>
Father::Father(const string& name, int age)
{
cout << __FUNCTION__ << endl;
this->name = name;
this->age = age;
}
Father::~Father()
{
cout << __FUNCTION__ << endl;
}
string Father::getName() const
{
return name;
}
int Father::getAge() const
{
return age;
}
string Father::description() const
{
stringstream ret;
ret << "姓名:" << name << " 年龄:" << age;
return ret.str();
}
//-------------- Son 类 -----------------------
/* Son.h */
#pragma once
#include "Father.h"
class Son : public Father
{
public:
Son(const string& name, int age, const string& game);
~Son();
string getGame() const;
string description() const;
private:
string game;
};
/* Son.cpp */
#include "Son.h"
#include <iostream>
#include <sstream>
// 创建Son对象时,会先调用基类的构造函数,用来初始化从基类继承的数据,再调用自己的构造函数,用来初始化自己定义的数据
// 这里显示调用父类的构造函数,如果没有显示的调用父类的构造函数,那么会自动调用父类的默认构造函数
Son::Son(const string& name, int age, const string& game) :Father(name, age)
{
cout << __FUNCTION__ << endl;
this->game = game;
}
Son::~Son()
{
cout << __FUNCTION__ << endl;
}
string Son::getGame() const
{
return game;
}
string Son::description() const
{
stringstream ret;
// 派生类的成员函数中,不能访问从基类继承的private成员
ret << "姓名:" << this->getName() << " 年龄:" << this->getAge() << " 游戏:" << game;
return ret.str();
}
- 继承和派生在 UML 中的表示
注意是“空心三角箭头”,从子类【派生的类】指向父类【被继承的类】 父类,也称为“基类”
二 派生类(子类)对象的内存分布
设置 vs 编译器:
在命令行中添加选项:(打印指定类的内存分布)
/d1 reportSingleClassLayout类名(例如: /d1 reportSingleClassLayoutSon)
cout << sizeof(wlj) << endl; // 32
cout << sizeof(wsc) << endl; // 60
F7编译:
说明:成员函数,不占用对象的内存空间,但是也被子类继承了!!!
三 protected(保护)访问权限
为什么要使用 protected 访问权限?
子类的成员函数中,不能直接访问父类的 private 成员,已经这些成员已经被继承下来了, 但是却不能访问。
只有通过父类的 public 函数来间接访问,不是很方便。
比如,刚才Father 类中的 name 和 age 成员。
解决方案:
把 name 和 age 定义为 protected 访问访问权限。
效果:
Son 类的成员函数中,可以直接访问它的父类的 protected 成员。
但是在外部,别人又不能直接通过 Son 对象来访问这些成员。
一个类, 如果希望它的成员, 可以被自己的子类(派生类)直接访问, 但是, 又不想被外部访问那么就可以把这些成员, 定义为 protected访问权限
class Father
{
public:
Father(const string& name, int age);
~Father();
string getName() const;
int getAge() const;
string description() const;
protected: // 派生类的成员函数中可以直接访问
string name;
int age;
};
// 派生类重写的description()方法中可以直接访问基类的protected成员
string Son::description() const
{
stringstream ret;
// 这里可以直接访问,不需要调用getName()和getAge()方法
ret << "姓名:" << name << " 年龄:" << age << " 游戏:" << game;
return ret.str();
}
四 继承的三种方式
public(公有)继承 (这种方式使用最频繁)
父类中定义的成员(数据成员和函数成员)被继承后,访问权限不变!
public --> public
protected --> protected
private --> private
private(私有)继承
父类中定义的成员(数据成员和函数成员)被继承后,访问权限都变成 private
public --> private
protected --> private
private --> private
protected(保护)继承
public --> protected
protected --> protected
private --> private
小结:
public 继承全不变
private 继承全变私
protected 继承只把 public 降级为 protected
五 子类对父类成员 的访问权限
无论通过什么方式(public、protected、private)继承,
在子类内部均可访问父类中的 public、protected 成员,
private 成员不可访问(如果想要子类能够访问,就定义为 protected)
继承方式只影响外界通过子类对父类成员的访问权限。
public 继承,父类成员的访问权限全部保留至子类;
protected 继承,父类 public 成员的访问权限在子类中降至 protected;
private 继承,父类 public、protected 成员的访问权限在子类中均降至 private。
六 构造函数和析构函数调用顺序
当创建子类对象时, 构造函数的调用顺序:
静态数据成员的构造函数 -> 父类的构造函数 -> 非静态的数据成员的构造函数 -> 自己 的构造函数
注意:
无论创建几个对象, 该类的静态成员只构建一次, 所以静态成员的构造函数只调用 1 次!!!
子类的析构函数的调用顺序,和子类的构造函数的调用顺序相反
#include <iostream>
using namespace std;
class M
{
public:
M() { cout << __FUNCTION__ << endl; }
~M() { cout << __FUNCTION__ << endl; }
};
class N
{
public:
N() { cout << __FUNCTION__ << endl; }
~N() { cout << __FUNCTION__ << endl; }
};
class A
{
public:
A() { cout << __FUNCTION__ << endl; }
~A() { cout << __FUNCTION__ << endl; }
};
class B : public A
{
public:
B() { cout << __FUNCTION__ << endl; }
~B() { cout << __FUNCTION__ << endl; }
private:
M m1;
M m2;
static N n1;
};
N B::n1; // 静态成员,只会调用一次构造函数
int main()
{
{
B b1;
cout << "--------------" << endl;
B b2;
cout << endl << endl;
}
return 0;
}
如果在return前加上system("pause")暂停的话,静态成员的析构不会被调用
静态对象在程序终止时被销毁,所以: 静态成员的析构函数,在程序结束前,是不会被调用的
七 子类型关系
7.1 什么是子类型
公有继承时,派生类的对象可以作为基类的对象处理,派生类是基类的子类型。
子类型的关系具有单向传递性:c类是b类的子类型,b类是a类的子类型,c类也是a类的子类型
7.2 子类型的作用
在需要父类对象的任何地方, 可以使用”公有派生”的子类的对象来替代, 从而可以使用相同的函数统一处理基类对象和公有派生类对象 即:形参为基类对象时,实参可以是派生类对象
#include <iostream>
using namespace std;
class Father
{
public:
Father(){}
~Father(){}
void kill() { cout << "Father kill" << endl; }
};
class Son : public Father
{
public:
Son(){}
~Son(){}
void kill() { cout << "Son kill" << endl; }
};
void test(Father* f)
{
f->kill();
}
int main()
{
Son s;
test(&s);
return 0;
}
7.3 子类型的应用
- 1 基类(父类)的指针,可以指向这个类的公有派生类(子类型)对象。
Son wsc;
Father * f = &wsc;
- 2 公有派生类(子类型)的对象可以初始化基类的引用
Son wsc;
Father &f2 = wsc;
- 3 公有派生类的对象可以赋值给基类的对象
Son wsc;
Father f1 = wsc;
八 多重继承
多重继承基类构造函数的调用顺序和这里声明派生类时基类的出现顺序相同
多重继承的弊端:二义性
#include <iostream>
using namespace std;
class Father
{
public:
Father(const char* lastName = "无姓", const char* firstName = "无名")
{
this->lastName = lastName;
this->firstName = firstName;
}
~Father() {}
void dance() { cout << "Father dance" << endl; }
void playBasketball() { cout << "Father playBasketball" << endl; }
protected:
string lastName; // 姓
string firstName; // 名
};
class Mother
{
public:
Mother(const char* food, const char* lastName = "无姓", const char* firstName = "无名")
{
this->food = food;
this->lastName = lastName;
this->firstName = firstName;
}
~Mother() {}
void dance() { cout << "Mother dance" << endl; }
private:
string lastName; // 姓
string firstName; // 名
string food;
};
// 多重继承基类构造函数的调用顺序和这里声明派生类时基类的出现顺序相同(class Son : public Father, public Mother)
class Son : public Father, public Mother
{
public:
Son(const char* lastName, const char* firstName, const char* food, const char* game)
:Father(lastName, firstName),Mother(food)
{
this->game = game;
}
~Son() {}
void playGame() { cout << "Son playGame" << endl; }
void dance() //解决多重继承的二义性方法2:重写这个方法
{
Father::dance();
Mother::dance();
cout << "Son dance" << endl;
}
private:
string game;
};
int main()
{
Son wsc("王", "思聪", "川菜", "王者荣耀");
//wsc.dance(); // 如果Son类没有重写dance()方法,这里会报错,调用不明确
//解决多重继承的二义性方法1:
//使用"类名::"进行指定,指定调用从哪个基类继承的方法
wsc.Father::dance();
wsc.Mother::dance();
cout << endl;
//解决多重继承的二义性方法2:
//在子类中重写这个同名方法,在这个方法内部使用基类名进行限定,来调用对应的基类方法
wsc.dance();
return 0;
}
九 虚基类
虚基类用来解决菱形继承的问题
#include <iostream>
using namespace std;
// 电话类
class Tel
{
public:
Tel() { this->number = "未知"; }
~Tel(){}
protected:
string number;
};
// 座机类
class FixedLine : public Tel
{
};
// 手机类
class MobilePhone :public Tel
{
};
// 无线座机
class WirelessTel : public FixedLine, public MobilePhone
{
public:
void setNumber(const char* number)
{
//this->number = number; // 错误,指定不明确
this->MobilePhone::number = number;
}
};
int main()
{
WirelessTel w1;
return 0;
}
这里继承了两个number
解决方案:使用虚基类和虚继承 virtual
#include <iostream>
using namespace std;
// 电话类
class Tel // 虚基类
{
public:
Tel() { this->number = "未知"; }
~Tel(){}
protected:
string number;
};
// 座机类
class FixedLine : virtual public Tel // 虚继承
{
};
// 手机类
class MobilePhone :virtual public Tel // 虚继承
{
};
// 无线座机
class WirelessTel : public FixedLine, public MobilePhone
{
public:
void setNumber(const char* number)
{
this->number = number; // 这里可以直接访问number
}
};
int main()
{
WirelessTel w1;
return 0;
}