C++面向对象程序设计-北京大学-郭炜【课程笔记(七)】
- 1、类型转换运算符
- 2、自增、自减运算符的重载
- 3、继承和派生的基本概念
- 3.1、基本概念
- 3.2、派生类对象的内存空间
- 4、继承关系和复合关系
- 4.1、继承关系的使用
- 4.2、复合关系的使用
- 5、派生类覆盖基类成员
- 6、存储权限说明符:protected
- 7、派生类的构造函数
- 8、public继承的赋值兼容规则
- 9、直接基类与间接基类(套娃)
毕业中:学习速度较慢
开始课程:P21 6_6. 类型转换运算符的重载
课程链接:程序设计与算法(三)C++面向对象程序设计 北京大学 郭炜
课程PPT:github提供的对应课程PPT
1、类型转换运算符
重载强制类型转换符 double 形式:
operator double () {return real};
类型转换符重载是不需要写返回值类型的,默认其double
自身。
案例:解释包含在代码中
#include <iostream>
using namespace std;
class Complex
{
double real, imag;
public:
Complex(double r=0, double i=0):real(r), imag(i) {};
operator double () {return real;}
// 重载强制类型转换运算符 double,返回类型就是double,不用单独写出
};
int main()
{
Complex c(1.2, 3.4);
cout << (double)c << endl; // 输出1.2 //等价于c.operator double()
double n = 2 + c; // 等价于double n = 2+c.operator double()
// 本来2和c是不能相加的,因为2为一个整形数字,c确实一个对象,
// 所以这里调用了重载类型转换运算符
cout << n; // 输出 3.2
}
2、自增、自减运算符的重载
-
自增运算符++、自检运算符–有前置/后置之分,为了区分所有重载的是前置运算符还是后置运算符,C++规定:
-
前置运算符作为一元运算符重载
-
重载为成员函数:
T & operator++();
T & operator–();
重载为全局函数:
T & operator++(T &);
T & operator—(T &);
使用样例:++obj, obj.operator++(), operator++(obj) 都调用上述函数 -
后置运算符作为二元运算符重载,多写一个没用的参数即可,这参数不具备任何意义,也不会被使用;
-
重载为成员函数:
T operator++(int);
T operator–(int);
重载为全局函数:
T operator++(T &, int);
T operator–(T &, int);
使用样例:obj++, obj.operator++(0), operator++(obj,0) 都调用上函数
注意事项:在vs
中,obj++也调用前置重载,而dev
则令obj++编译出错。
案例:课程中的所有解释均在代码中呈现,请配合课程看代码;
#include<iostream>
using namespace std;
class CDemo
{
private:
int n;
public:
CDemo(int i=0):n(i) {}
CDemo & operator++(); // 用于前置形式,返回值为引用
CDemo operator++(int); // 用于后置形式,返回值为对象
// 后置类型的++为什么没使用&引用呢!
// 答:在C++中 ++a;a的返回值就是a的引用;所以在重载前置++运算符时,我们需要尽量维持它原本的属性。
operator int() {return n;} // 类型强制转换运算符,直接可以cout输出对象
friend CDemo & operator--(CDemo &); // 全局前置
friend CDemo operator--(CDemo &, int); // 全局后置
};
CDemo & CDemo::operator++() // 返回值为CDemo对象的引用
{
// 前置 ++
++n;
return * this; // 返回值是一个对象,即对* this的引用;
} // ++s即为:s.operator++(),即返回值就是s的引用(& s)
CDemo CDemo::operator++(int k) // 这里的k是无用的参数; 返回值为CDemo对象
{
// 后置 ++
CDemo tmp(*this); // 记录修改前的对象
n++;
return tmp; // 返回修改前的对象
} // s++即为:s.operator(0);
CDemo & operator--(CDemo & d) // 这里的引用可不是只为了节省空间,而是要通过该引用更改成员变量
{
// 前置--
d.n--;
return d; // 返回对操作数的引用,即d的引用
} // --s即为:operator--(s)
CDemo operator--(CDemo & d, int)
{
// 后置--
CDemo tmp(d); // 生成一个临时的对象,因为后置本身输出的值大小不变,但其运行完后,该对象中的--或++才生效,所以返回的对象是其自身。
d.n--;
return tmp; // 返回操作数的对象,即d
} // s--即为:operator--(s, 0)
int main()
{
CDemo d(5);
cout << (d++) << ","; //等价于 d.operator++(0); //重载为成员函数的形式
cout << d << ","; // 可以重载左移运算符或者重载一个强制类型转换运算符即可输出d;
cout << (++d) << ","; //等价于 d.operator++(); //重载为成员函数的形式
cout << d << endl;
cout << (d--) << ","; //等价于 operator--(d,0); //重载为全局函数的形式
cout << d << ",";
cout << (--d) << ","; //等价于 operator--(d); //重载为全局函数的形式
cout << d << endl;
return 0;
}
// OUT
MacBook-Air beida_lesson % g++ 21.cpp -o 21
MacBook-Air beida_lesson % ./21
5,6,7,7
7,6,5,5
注意事项:一共有以下一些观点:
由上述代码可知,后置运算符的重载比前置运算符的重载多了一个临时函数的构建,所以前置运算符在时间上的开销要小于后置运算符重载的开销,特别是遇到递归时,变得就比较明显了。所以提倡写前置运算符。
3、继承和派生的基本概念
3.1、基本概念
- 继承:在定义一个新的类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),那么就可以把A作为一个
基类
,而把B作为基类的一个派生类(也称子类)
。- 派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。
- 派生类一经定义后,可以独立使用,不依赖于基类。
- 派生类拥有基类的全部成员函数和成员变量,不论是private、protected、public。
- 在派生类的各个成员函数中,不能访问基类中的private成员。
案例
所有学生都有的共同属性:
- 姓名
- 学号
- 性别
- 成绩
所有学生都有的共同方法:(成员函数)
- 是否该留级
- 是否该奖励
不同学生又有个字==各自不同的属性和方法
- 研究生
- 导师
- 系
- 大学生
- 系
- 中学生
- 竞赛特长加分
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 CanBao Yan() {.....};
}; // 派生类的写法是:类名:public基类名
// 研究生
class CGraduatedStudent:public CStudent
{
private:
int nDepartment;
char szMentorName[20];
public:
int CountSalary() {.....};
}
3.2、派生类对象的内存空间
派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。
在派生类对象中,包含着基类对象
,而且基类对象的春初位置位于派生类对象新增的成员变量之前
。
举例一个完整的例子:两个类的简单学生管理程序
// File name:22.cpp
#include<iostream>
#include<string>
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;}
};
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_;
}
class CUndergraduateStudent:public CStudent
{
// 本科生类,继承了CStudent类
private:
string department; // 学生所属的系的名称
public:
void QualifiedForBaoyan()
{
// 输出给予保研资格
cout << "qualified for baoyan" << endl;
}
void Printfo() // 覆盖:CUndergraduateStudent自己的Printfo
{
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_;
}
};
int main()
{
CUndergraduateStudent s2;
s2.SetInfo("Harry Potter", "118829212", 19, 'M', "Computer Science");
cout << s2.GetName() << "" ;
s2.QualifiedForBaoyan();
s2.PrintInfo();
return 0;
}
// OUT
MacBook-Air beida_lesson % g++ 22.cpp -o 22
MacBook-Air beida_lesson % ./22
Harry Potterqualified for baoyan
Name = Harry Potter
ID = 118829212
Age = 19
Gender = M
4、继承关系和复合关系
继承关系:“是” 关系
- 基类A,B是基类A的派生类。
- 逻辑上要求:“一个B对象也是一个A对象”。(例:从学生类派生出中学生类,因为中学生也是学生。)
复合关系:“有”关系。
- 类C中“有”成员变量k,k是类D的对象,则C和D是复合关系
- 一般逻辑上要求:“D对象是C对象的固有属性或组成部分”。
4.1、继承关系的使用
4.2、复合关系的使用
5、派生类覆盖基类成员
派生类可以定义一个和基类成员同名的成员,这叫覆盖
。在派生类中访问这类成员时,缺省的情况是访问派生类中定义的成员。要在派生类中访问有基类定义的同名
成员时,要使用作用域符号::
。
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,这是base的私有成员变量,派生类derived不能访问
i = 5; // 引用的是派生类的i
base::i = 5; // 引用的基类的i
func(); // 派生类的
base::func(); // 基类的
}
int main()
{
derived obj;
obj.i = 1; // 派生类的i
obj.base::i = 1; // 引用的基类的i
}
// 在派生类和基类中不要写相同的成员变量,如上代码中的i
注意事项:在派生类和基类中不要写相同的成员变量,如上代码中的
i
6、存储权限说明符:protected
特点:基类的protected成员:可以被以下函数访问:
- 基类的成员函数
- 基类的有缘函数
派生类的成员函数可以访问当前对象的基类的保护成员
class People
{
private: int nPrivate; // 私有成员
public: int nPublic; // 公有成员
protected: int nProtected; // 保护成员
};
class Son:public People
{
void Accesspeople()
{
nPublic = 1; // Ok
nPrivate = 1; // wrong
nProtected = 1; // Ok, 访问从基类继承的protected成员
Son f; // 非当前对象
f.nProtected = 1; // wrong, f 不是当前对象
}
};
int main()
{
People f;
Son s;
f.nPublic = 1;
s.nPublic = 1;
f.nProtected = 1;
f.nPrivate = 1;
s.nProtected =1;
s.nPrivate = 1;
}
7、派生类的构造函数
class Bug
{
private:
int nLegs;
int nColor;
public:
int nType;
Bug(int nLegs_, int nColor_);
void PrintBug(){};
};
class FlyBug:public Bug // FlyBug是Bug的派生类
{
int nWings;
public:
FlyBug(int nLegs_, int nColor_, int nWings_);
};
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构造函数
// 直接初始化派生类所包含的基类的Bug对象(即Bug构造函数)初始化列表
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;
}
例:
#include <iostream>
class Base
{
public:
int n;
Base(int i):n(i)
{std::cout << "Base" << n << " constructed " << std::endl;}
~Base()
{std::cout << "Base " << n << "destructed" << std::endl;}
};
class Derived:public Base
{
public:
Derived(int i):Base(i) // 1、先进入Base构造函数
{ std::cout << "Derived constructed " << std::endl;} //2、进入Drivate构造函数
~Derived()
{std::cout << "Derived destructed" << std::endl;}
};
int main()
{
Derived Obj(3);
return 0;
// 3、执行 Derived析构函数
// 4、执行 Base析构函数
}
// OUT
MacBook-Air beida_lesson % ./23
Base3 constructed
Derived constructed
Derived destructed
Base 3destructed
例题2:
#include <iostream>
class Bug
{
private:
int nLefs; int nColor;
public:
int nType;
Bug(int legs, int color);
void PrintBug() {};
};
class Skill
{
public:
Skill(int n) {};
};
class nWings
{
public:
nWings(int nWings_) {};
};
class FlyBug:public Bug
{
nWings w1;
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(2), w1(wings) {}
老师版本:
#include <iostream>
class Bug
{
private:
int nLefs; 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);
// nWings(int nWings_) { nWings = nWings_};
};
FlyBug::FlyBug(int legs, int color, int wings):
Bug(legs, color), sk1(5), sk2(2), nWings(wings) {}
8、public继承的赋值兼容规则
以下等号“=”并没有采用运算符重载。
9、直接基类与间接基类(套娃)
例:
#include <iostream>
class Base
{
public:
int n;
Base(int i):n(i)
{std::cout << "Base" << n << " constructed " << std::endl;}
~Base()
{std::cout << "Base " << n << "destructed" << std::endl;}
};
class Derived:public Base
{
public:
Derived(int i):Base(i)
{ std::cout << "Derived constructed " << std::endl;}
~Derived()
{std::cout << "Derived destructed" << std::endl;}
};
class MoreDerived:public Derived
{
public:
MoreDerived():Derived(4)
{
std::cout << "More Derived constructed" << std::endl;
}
~MoreDerived()
{
std::cout << "More Derived destructed " << std::endl;
}
};
int main()
{
MoreDerived Obj;
return 0;
}
// OUT
~MoreDerived()
^
2 errors generated.
MacBook-Air beida_lesson % g++ 23.cpp -o 23
MacBook-Air beida_lesson % ./23
Base4 constructed
Derived constructed
More Derived construct