笔记按照中国大学MOOC上北京大学郭炜老师主讲的程序设计与算法(三)C++面向对象程序设计所作,B站上也有资源。原课程链接如下:
其他各章节链接如下:
程序设计与算法(三)C++面向对象程序设计笔记 第一周 从C到C++
程序设计与算法(三)C++面向对象程序设计笔记 第二周 类和对象基础
程序设计与算法(三)C++面向对象程序设计笔记 第三周 类和对象提高
程序设计与算法(三)C++面向对象程序设计笔记 第四周 运算符重载
程序设计与算法(三)C++面向对象程序设计笔记 第五周 继承
程序设计与算法(三)C++面向对象程序设计笔记 第六周 多态
程序设计与算法(三)C++面向对象程序设计笔记 第七周 输入输出和模板
程序设计与算法(三)C++面向对象程序设计笔记 第八周 标准模板库STL(一)
程序设计与算法(三)C++面向对象程序设计笔记 第九周 标准模板库STL(二)
程序设计与算法(三)C++面向对象程序设计笔记 第十周 C++11新特性和C++高级主题
继承:在定义一个新的类 B 时,如果该类与某个已有的类 A 相似(指的是 B 拥有 A 的全部特点),那么就可以把 A 作为一个基类,而把 B 作为基类的一个派生类(也称子类)
派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数
派生类一经定义后,可以独立使用,不依赖于基类
派生类拥有基类的全部成员函数和成员变量,不论是 private , protected , public
所有的学生都有的共同属性:姓名、学号、性别、成绩
所有的学生都有的共同方法(成员函数):是否该留级、是否该奖励
而不同的学生,又有各自不同的属性和方法:研究生(导师、系)、大学生(系)、中学生(竞赛特长加分)
如果为每类学生都从头编写一个类,显然会有不少重复的代码,浪费
比较好的做法是编写一个“学生”类,概括了各种学生的共同特点,然后从“学生”类派生出“大学生”类,“中学生”类,“研究生类”
class 派生类名: public 基类名
{
};
class CStudent {
private:
string sName;
int nAge;
public:
bool IsThreeGood() { };
void SetName( const string & name ) { sName = name; }
//......
};
class CUndergraduateStudent: public CStudent {
private:
int nDepartment;
public:
bool IsThreeGood() { ...... }; //覆盖
bool CanBaoYan() { .... };
}; // 派生类的写法是:类名:public 基类名
class CGraduatedStudent:public CStudent {
private:
int nDepartment;
char szMentorName[20];
public:
int CountSalary() { ... };
};
派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前
#include
#include
using namespace std;
class CStudent {
private:
string name;
string id; //学号
char gender; //性别,'F'代表女,'M'代表男
int age;
public:
void PrintInfo();
void SetInfo( const string & name_,const string & id_,
int age_, char gender_ );
string GetName() { return name; }
};
class CUndergraduateStudent:public CStudent
{//本科生类,继承了CStudent类
private:
string department; //学生所属的系的名称
public:
void QualifiedForBaoyan() { //给予保研资格
cout << “qualified for baoyan” << endl;
}
void PrintInfo() {
CStudent::PrintInfo(); //调用基类的PrintInfo
cout << “Department: ” << department <<endl;
}
void SetInfo( const string & name_,const string & id_,
int age_,char gender_ ,const string & department_) {
CStudent::SetInfo(name_,id_,age_,gender_); //调用基类的SetInfo
department = department_;
}
};
void CStudent::PrintInfo()
{
cout << "Name: " << name << endl;
cout << "ID: " << id << endl;
cout << "Age: " << age << endl;
cout << "Gender: " << gender << endl;
}
void CStudent::SetInfo( const string & name_,const string & id_,
int age_,char gender_ )
{
name = name_;
id = id_;
age = age_;
gender = gender_;
}
int main()
{
CUndergraduateStudent s2;
s2.SetInfo(“Harry Potter ”, “118829212”,19,‘M’,“Computer Science”);
cout << s2.GetName() << “ ” ;
s2.QualifiedForBaoyan ();
s2.PrintInfo ();
return 0;
}
输出结果:
Harry Potter qualified for baoyan
Name: Harry Potter
ID: 118829212
Age: 19
Gender: M
Department: Computer Science
继承:“是”关系
复合:“有”关系
写了一个 CMan 类代表男人
后来又发现需要一个 CWoman 类来代表女人
CWoman 类和 CMan 类有共同之处
就让 CWoman 类从 CMan 类派生而来,是不合适的。因为“一个女人也是一个男人”从逻辑上不成立
好的做法是概括男人和女人共同特点,写一个 CHuman 类,代表“人”,然后 CMan 和 CWoman 都从
CHuman 派生
几何形体程序中,需要写“点”类,也需要写“圆”类,两者的关系就是复合关系 —— 每一个“圆”对象里都包含(有)一个“点”对象,这个“点”对象就是圆心
如果要写一个小区养狗管理程序,需要写一个**“业主”类,还需要写一个“狗”**类。
而狗是有 “主人” 的,主人当然是业主(假定狗只有一个主人,但一个业主可以有最多 10 条狗)
循环定义,无法计算一个 CMaster 类和 CDog 类的对象占多少字节,编译时无法通过
另一种写法:为**“狗”类设一个“业主”类的成员对象;为“业主”**类设一个“狗”类的对象指针数组
这样就避免了循环定义
两条狗的主人有可能是一个人,比如修改了一条狗的主人信息,另外一条狗的主人和这条狗是相同的,另外一条狗里面的主人信息也要进行相应的修改,要维护相同主人的多条狗里面包含的多个主人对象的信息一致性是一件很啰嗦的事情
凑合的写法:为**“狗”类设一个“业主”类的对象指针;为“业主”**类设一个“狗”类的对象数组
从逻辑上讲一般要求复合关系成员对象是所属类的组成部分或固有属性,狗不是主人的一部分,也不是主人的固有属性
此外,所有”狗“的对象都被包含在一个或多个”业主“对象里,要对”狗“对象进行操作就要通过它的主人来进行
正确的写法:为“狗”类设一个“业主”类的对象指针;为“业主”类设一个“狗”类的对象指针数组
派生类可以定义一个和基类成员同名的成员,这叫覆盖。在派生类中访问这类成员时,缺省的情况是
访问派生类中定义的成员。要在派生类中访问由基类定义的同名成员时,要使用作用域符号 ::
class base {
int j;
public:
int i;
void func();
};
class derived :public base{
public:
int i;
void access();
void func();
};
void derived::access() {
j = 5; //error
i = 5; //引用的是派生类的 i
base::i = 5; //引用的是基类的 i
func(); //派生类的
base::func(); //基类的
}
derived obj;
obj.i = 1;
obj.base::i = 1;
派生类不能访问从基类继承的私有成员,公有成员可以
一般来说,基类和派生类不定义同名成员变量
实际上用的不是很多
class Father {
private:
int nPrivate; //私有成员
public:
int nPublic; //公有成员
protected:
int nProtected; //保护成员
};
class Son :public Father{
void AccessFather () {
nPublic = 1; // ok
nPrivate = 1; // wrong
nProtected = 1; // OK,访问从基类继承的protected成员
Son f;
f.nProtected = 1; // wrong ,f不是当前对象
}
};
int main()
{
Father f;
Son s;
f.nPublic = 1; // Ok
s.nPublic = 1; // Ok
f.nProtected = 1; // error
f.nPrivate = 1; // error
s.nProtected = 1; // error
s.nPrivate = 1; // error
return 0;
}
派生类对象的构造函数怎么初始化从基类继承的私有成员?
class Bug {
private :
int nLegs; int nColor;
public:
int nType;
Bug (int legs, int color);
void PrintBug (){ };
};
class FlyBug: public Bug // FlyBug是Bug的派生类
{
int nWings;
public:
FlyBug(int legs,int color, int wings);
};
Bug::Bug(int legs, int color)
{
nLegs = legs;
nColor = color;
}
//错误的FlyBug构造函数
FlyBug::FlyBug ( int legs,int color, int wings)
{
nLegs = legs; // 不能访问
nColor = color; // 不能访问
nType = 1; // ok
nWings = wings;
}
//正确的FlyBug构造函数:
FlyBug::FlyBug (int legs, int color, int wings):Bug(legs, color)
{
nWings = wings;
}
int main() {
FlyBug fb(2,3,4);
fb.PrintBug();
fb.nType = 1;
fb.nLegs = 2; // error. nLegs is private
return 0;
}
一个派生类对象里面包含一个基类对象,派生类对象初始化时它里面的基类对象也必须要调用基类的构造函数初始化
在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数,因为在派生类的构造函数里面可能要访问从基类继承的成员变量
调用基类构造函数的两种方式:
显式方式 —— 在派生类的构造函数中,为基类的构造函数提供参数,如:
derived::derived(arg_derived-list):base(arg_base-list)
隐式方式 —— 在派生类的构造函数中,省略基类构造函数时,派生类的构造函数则自动调用基类的无参构造函数,如果基类没有无参构造函数编译出错
派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数
class Base {
public:
int n;
Base(int i):n(i)
{ cout << "Base " << n << " constructed" << endl;}
~Base()
{ cout << "Base " << n << " destructed" << endl; }
};
class Derived:public Base {
public:
Derived(int i):Base(i)
{ cout << "Derived constructed" << endl; }
~Derived()
{ cout << "Derived destructed" << endl;}
};
int main() { Derived Obj(3); return 0; }
输出结果:
Base 3 constructed
Derived constructed
Derived destructed
Base 3 destructed
class Bug {
private :
int nLegs;
int nColor;
public:
int nType;
Bug ( int legs, int color);
void PrintBug (){ };
};
class Skill {
public:
Skill(int n) { }
};
class FlyBug: public Bug {
int nWings;
Skill sk1, sk2;
public:
FlyBug( int legs, int color, int wings);
};
FlyBug::FlyBug( int legs, int color, int wings):
Bug(legs,color),sk1(5),sk2(color) ,nWings(wings) {
}
在创建派生类的对象时:
先执行基类的构造函数,用以初始化派生类对象中从基类继承的成员
再执行成员对象类的构造函数,用以初始化派生类对象中成员对象
最后执行派生类自己的构造函数
在派生类对象消亡时:
先执行派生类自己的析构函数
再依次执行各成员对象类的析构函数
最后执行基类的析构函数
析构函数的调用顺序与构造函数的调用顺序相反
派生类写 public 是公有派生,也可以写 private、protected,只不过很少用
class base { };
class derived : public base { };
base b;
derived d;
1)派生类的对象可以赋值给基类对象
b = d;
把 d 里面所包含的基类对象的内容拷贝到 b 里面
2)派生类对象可以初始化基类引用
base & br = d;
可以认为这个基类的引用实际上引用了派生类对象里面包含的基类对象
3.派生类对象的地址可以赋值给基类指针
base * pb = & d;
可以认为这个指针指向了派生类对象里面包含的基类对象,这个基类对象放在派生类对象存储空间的最前面,基类对象的起始地址就是整个派生类对象的起始地址
如果派生方式是 private 或 protected ,则上述三条不可行
类A派生类B,类B派生类C,类C派生类D,则:
在声明派生类时,只需要列出它的直接基类
构造函数的初始化列表里面只需要指明直接基类如何初始化,间接基类如何初始化是由直接基类的构造函数决定的
#include
using namespace std;
class Base {
public:
int n;
Base(int i):n(i) {
cout << "Base " << n << " constructed" << endl;
}
~Base() {
cout << "Base " << n << " destructed" << endl;
}
};
class Derived:public Base
{
public:
Derived(int i):Base(i) {
cout << "Derived constructed" << endl;
}
~Derived() {
cout << "Derived destructed" << endl;
}
};
class MoreDerived:public Derived {
public:
MoreDerived():Derived(4) {
cout << "More Derived constructed" << endl;
}
~MoreDerived() {
cout << "More Derived destructed" << endl;
}
};
int main()
{
MoreDerived Obj;
return 0;
}
输出结果:
Base 4 constructed
Derived constructed
More Derived constructed
More Derived destructed
Derived destructed
Base 4 destructed