SHU面向对象复习大纲
面向对象复习
某学校专业课OOP复习
因为只是为了让自己看懂,所以写的比较简略,就这样
C->C++
特别强调
定义 分配内存单元
声明 不涉及内存分配
访问 读取修改内存所存放的数据
左值与右值
左值(Lvalue)
- L->Location可寻址
- 可出现在运算符左侧
其实是一个意思了
右值(Rvalue)
R->Readable可读取
变量的生命周期
静态变量
后面一起讲罢
引用
引用的概述
数据类型 & 引用名 = 已存在的变量名
- 变量的引用只是声明,不是定义,不会另外分配内存空间(常称:引用不占空间);
- 声明引用时,必须用一个变量实体对其进行初始化;
- 这样的“绑定”从引用声明开始,直到引用的生命期结束不能更改;
- 对引用的操作就是对其“绑定”的变量的操作(读/写);
- 程序中应保证变量实体的生命期包含其引用的生命期。
常量的引用
可以对字面常量、符号常量、甚至变量取别名
const 数据类型 & 引用名 = 常量;
const 数据类型 & 引用名 = 已存在的变量名;
可以将变量视为常量,但不能将常量作为变量。
函数
函数的形式参数
无形式参数
传值型
引用型
函数的返回类型
void
值返回
- 函数结果存放在一个临时变量中,该临时变量用函数调用表达式本身表示;
- 该临时变量的在参与一次运算后被销毁。
- 不宜作为左值
引用返回
- 返回一个已经存在的变量
面向对象程序设计-封装
类与类的对象
类的成员默认是private
sizeof()
例如:
class Time
{
private: // 私有的(访问属性)
int h, m, s;
};
Time t;
cout << sizeof(t) << endl;
// 输出12,这里sizeof(int)为4
//这里的12个字节,存放着对象t的h, m, s的值。
Time t1(10, 20, 30);
//则 t1 占12个字节。h, m, s的值分别为10, 20, 30
对象的非静态数据成员所占用的空间总和为对象的基本空间
至于静态怎么样我们下期再说
一个对象的基本空间占sizeof(类型名或对象名)字节;
注意到:
- 运算符sizeof能够测量对象的基本空间及对象的资源空间所占用的内存单元字节数的总和。 (×)
资源空间可不能测量(因为基本空间里面只有指针?)
this指针常量
Time::Time(Time *const this, int hour,int minute, int second)
非静态成员函数中会有一个隐含的this指针常量。
因此参数表的第一个永远都是this
同时,也会在非常量成员函数中传入this
int main()
{
Time t1, t2(10, 20, 30), *p = &t1;
t1.Show( &t1 ); // 相当于传递对象t1的地址
t1.Settime( &t1, 8, 30, 0);
p->Show( p ); // 相当于传递指针变量
t2.Show( &t2 );
return 0;
}
常量成员函数
不能修改成员的属性
格式:在所声明及定义的成员函数首部后添加保留字const
实际上是将this变为常量指针常量,故无法对本对象进行修改。
int Time::GetHour() const // 定义常量成员函数
{
return h;
}
int Time::GetHour(const Time *const this)
const // 常量成员函数中的隐含参数this为常量指针常量
{
return h;
}
值得说明的是,const int *p代表指向内容不可变,int *const p则代表指针本身不可变
常量成员函数的主要作用是处理常量对象
程序中直接定义常量对象并不多见,最常见的是出现在函数形式参数位置上,对实参目标的保护:
- 常量引用(形参)所绑定的实参对象
- 常量指针(形参)所指向的目标对象
例如
void Print(const Time &t, const Time *p)
{
t.Show();
p->Show();
}
若Time::Show不是常量成员函数,则编译不通过。
编译器会认为:
- 用常对象t的地址&t,初始化了Show的指针变量this
- 用常对象*p的地址p,初始化了Show的指针变量this
联系前文,会引用传入隐含参数this,违反了不能将常量当作变量
补充:常量成员
class Test
{
public:
Test():a(0){ }
private:
const int a; // 常量数据成员
};
类中常量数据成员必须在构造函数的初始化列表中进行初始化!!!!
因为一旦进入构造函数,此常量数据成员不能再改变。
封装和隐藏
分离文件
类模板和模板类
用泛型写的模板称为类模板
类模板实例化后则是模板类
值得注意的是:类模板得写在头文件中
template <typename T> class Vec2
{
public:
// 其他成员函数(略)
private:
T a, b; // 数据成员的数据类型待定
};
构造函数与赋值运算
构造函数用于创建对象,析构函数则用于销毁对象
赋值并没有创建对象,请注意和初始化区分
非常有趣的一点是,在初始化对象,如
Time t=10中,这里的等于号“=”不是赋值,而是初始化记号,当然这是为了继承C语言的风格而做出的妥协,C++风格的写法应该是Time t(10)
构造函数
任何类都至少有两个构造函数(构造与拷贝构造)
默认构造函数
不需要实参的构造函数
未定义构造函数时,系统会提供一个默认构造函数
该函数将不对对象的数据成员进行初始化。所创建的对象数据成员的初值取决于对象的存储类型。
若定义了构造函数,系统将不再提供默认的构造函数。
仍需要,可以重载,通用的方法是
设计所有参数均带默认值的构造函数。当不提供实参时,该构造函数便成为默认的构造函数。
调用场合
创建对象时不显式地初始化
创建对象、数组
Time t, ta[10];
Complex z, za[10];
创建堆对象、数组
Time *pt0, *pt1; // 仅定义指针变量
Complex *pz0, *pz1; // 没有创建对象
pt0 = new Time; // 创建1个对象
pt1 = new Time[10]; // 创建10个对象
pz0 = new Complex; // 创建1个对象
pz1 = new Complex[10]; // 创建10个对象
需要注意的当然是对象得new出来才能算是创建
同时注意,语句:
Time t();
Complex z();
均不是创建对象,而是函数原型,用于声明无参数的函数。
转换构造函数
带一个实参的构造函数被称为转换构造函数。
和强制转换运算符区分
之所以称这种构造函数为转换构造函数,是因为可以用它实现数据类型的自动转换或强制转换。
析构函数
特征
- 没有返回类型
- 不能重载(还记得重载的实现原理吗?)
- 没有显式参数(因为还有隐含的this指针)
- 显式调用
对象构造和析构的顺序
- 全局对象、静态全局对象在主函数执行之前被构造,直至主函数结束后析构。
- 静态局部对象在其所在的函数第一次被调用时创建,直至主函数结束后析构。
- 局部自动对象(包括值传递的形参对象)在其所在函数调用时创建,函数返回后析构。
- 函数值返回的临时对象在函数返回时创建,在参与其所在的表达式运算后析构。
- 动态对象(堆对象)在执行 new 操作时创建,在执行 delete 时析构。
- 在上述基本原则下,先创建的对象后析构,后创建的对象先析构。
calloc的缺陷
拷贝构造函数
引用本类对象(为什么是引用?)
场合(如创建副本时)
浅拷贝和深拷贝
独立空间
赋值运算符
组合成员
构造时机
冒号语法,函数体内为赋值运算
构造顺序
声明顺序,与冒号语法无关
必须使用显式冒号语法
- 类的常量数据成员
- 类的引用型成员
- 无法默认构造的组合成员(其所在的类无默认构造函数)
静态成员和友元
静态本质上是普通成员/函数加上了访问域控制
静态成员
静态成员关键字一样,和其他“静态”无联系
类外初始化,如: int Student::num = 0;
生命周期
修改?
静态成员函数
只认类型不认对象
不含this指针
直接 类名::静态成员函数名(实参) 因为可能不创建对象
例子:设置导师
友元
不默认联系对象(this),显示传递
朋友的朋友不是我的朋友
运算符重载
重载双目运算符
友元和成员函数
操作数不同
重载算术运算符
值返回
重载迭代赋值运算符
引用返回
重载关系运算符
重载插入抽取运算符
友元
第一个操作数是i/ostream对象
引用返回对象->使连续操作成为可能
重载单目运算符
成员函数不必传参
重载前增(减)量运算符
例如++a或--a
- 先增(减)值,再以新值参加所在表达式的其他运算;
- 结果可以作左值。
实现
Date & Date::operator++() // 前增量运算符函数
{
days[1] = isLeapyear() ? 29 : 28;
day++;
if(day > days[month-1]){ day = 1; month++; }
if(month>12){ month = 1; year++;}
return *this; // 引用返回“本对象”,可作左值
}
重载后增(减)量运算符
例如,对象++和 对象--
- 先以原值参加所在表达式的其他运算一次,再增(减)值;
- 只能作右值。
- 加入一个形参int用以区分
// 后增(减)量运算。难点:
// 运算符函数需要实现“既返回对象的原值,又修改对象的值”
Date Date::operator++(int) // 后增量运算符函数
{
Date temp(*this); // 拷贝构造对象,保存本对象的原值
++(*this); // 利用前增量运算符函数
return temp; // 返回局部自动对象,不能引用返回
}
这里我们可以看到,利用先前的前自增是一种高效的方式
拾遗
[]运算符
资源释放?
面向对象程序设计-继承与多态
继承
概述
由基类派生其他类的语法格式:
class 派生类名 : public 基类类名
{ // 新增加的属性和行为
// 或者基类成员函数的覆盖、重载
}
- 无论那种继承方式,派生类都全部继承了基类的一切成员(基类的构造函数、拷贝构造函数和析构函数除外);
- 从基类继承而来的成员已经成为派生类的成员,在不引起混淆的情况下,我们仍称它们为基类的成员。基类成员在派生类中的访问属性变化见表13-2;(基本常用公共继承)
- 从基类继承而来的成员函数可以被覆盖(此时显式参数不变)也可以被重载(此时显式参数不同);
- 派生类中可以增加新的数据成员和新的成员函数。
构造函数
构造顺序
- 先使用基类的构造函数来构造继承自基类的成员
- 使用冒号语法
- 然后调用组合成员的构造函数
- 之后是其他成员
- 最后进入函数体内
析构则相反
基类及其子类共用一个静态数据成员
空间
赋值兼容性
向上兼容,派生类可以初始化基类对象,但基类对象不能初始化派生类对象
多态性
先期联编
- 预先设计好
- 本质上还是产生了多个函数
- 静态
迟后联编
- 运行时产生
- 根据实际对象动态确定
虚函数
虚析构函数
基类指针操作派生类成员,若不使用虚析构函数,则可能会导致其无法调用派生类析构函数
或者这么想——虚函数更类似于一种标记,要求程序匹配具体的对象进行操作
不过,值得注意的是:
void Office1(Student x, double credit)
{// 形参为值传递,创建学生对象,不办实事
x.SetCredit(credit);
x.Tuition();
cout << x << endl;
}
像这种情况,值传递创建的是基类对象,不享受多态性
纯虚函数与抽象类
纯虚函数,函数声明后有=0
- 纯虚函数没有函数体。抽象类就是用来被继承的。不能创建抽象类的对象,但可以定义抽象类的指针变量、声明抽象类的引用;
- 构造函数、拷贝构造函数不能是虚函数,因为虚函数是认具体对象的,而执行构造函数时所处理的对象尚未构造完成,对象尚未成型;
- 静态成员函数不能是虚函数,因为静态成员函数不默认联系某对象;析构函数可以是虚函数。而且最好将析构函数声明为虚函数,以便当对象生命期结束时能够确实调用自己的析构函数完成善后工作;
- 为了使一些作为友元函数的运算符享受到动态多态性带来的便利(如插入运算符“<<”等),应该在基类中声明一个虚函数或纯虚函数以便在派生类中覆盖,并被重载运算符函数调用。

浙公网安备 33010602011771号