第五章 类与对象
面向对象程序的基本特点
- 抽象:对同一类对象的共同属性和行为概括,形成类
- 封装{}:将抽象出的数据、代码封装在一起,形成类
- 继承:在已有类的基础上,扩展形成新的类
- 多态:统一名称,不同功能的实现方式
类与对象
类的语法形式
class 类名称
{
public:
公有成员(外部接口)
private:
私有成员(只允许本类中的函数访问,
类外部的任何函数都不能访问)
protected:
保护型成员
};
- 对象定义的语法
类名 对象名;clock myClock
- 类的成员函数
在类中声明函数原型;
可以在类外给出函数体实现,并在函数名前使用类名加以限定;
也可以直接在类中给出函数体,形成内联函数成员;
允许声明重载函数和带默认参数值的函数。
程序举例
钟表类定义
#include<iostream>
using namespace std;
class Clock{
public:
void setTime(int newH = 0,int newM = 0,int newS = 0);
//当调用setTime时,如果有实参则用实参值,否则用默认。
void showTime();
private:
int hour, minute, second;
}
成员函数的实现
void Clock::setTime(int newH,int newM,int newS){
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime(){
cout<< hour<< ":"<< minute<< ":"<< second;
}
对象的使用
int main(){
Clock myClock;
myClock.setTime(8,30,30);//与myClock通信,设定时间
myClock.showTime();//显示时间
return 0;
}
构造函数
类中的构造函数,用于描述初始化算法。
- 构造函数的作用
在构造函数中描述,如何对类的对象进行初始化,把初始化的规则算法写在构造函数中。 - 构造函数的形式
①函数名与类名相同
②不能定义返回值类型,也不能有return语句
③可以有形式参数,也可以没有
④可以是内联函数
⑤可以重载
⑥可以带默认值 - 构造函数调用机制:在对象被创建时自动调用
- 默认构造函数:调用时可以不需要实参
- 隐含生成的构造函数:若程序中未定义构造函数,编译器将自动生成一个默认构造函数
①参数列表为空,不为数据成员设置初始值
②若类内定义了成员的初始值,则使用默认值,否则,以默认方式初始化
③基本类型的数据默认初始化的值是不确定的
若程序中已定义构造函数,默认情况下编译器就不再隐含生成默认构造函数。若希望生成默认构造函数可以使用 “=default”
class Clock{
public:
Clock() =default; //提供默认构造函数
Clock(int newH, int newM, int newS); //构造函数
private:
int hour, minute, second;
}
例题
//类定义
class Clock{
public:
Clock(int newH,int newM, int newS);
//构造函数的原型声明,与类名一致,但是不能定义其返回类型!!
void setTime(int newH,int newM, int newS);
void showTime();
private:
int hour, minute, second;
}
//构造函数的实现:
//写类名,不是全局函数,是类的成员函数。
//不能规定返回类型,没有return语句!
Clock::Clock(int newH, int newM, int newS):
hour(newH), minute(newM),second(newS){}
//此行为初始化列表,表示用newH初始化hour这个变量...
//比在函数体中写表达式赋值效率高
//主函数定义
int main(){
Clock c(0,0,0);//自动调用构造函数
c.shouTime();
}
另例:
class Clock{
public:
Clock(int newH,int newM, int newS); //构造函数
Clock(); //默认构造函数
void setTime(int newH,int newM, int newS);
void showTime();
private:
int hour, minute, second;
};
Clock::Clock():hour(0), minute(0), second(0){}
//默认构造函数
int main(){
Clock c1(8,10,0); //调用有参数的构造函数
Clock c2; //调用无参数的构造函数
... ...
}
委托构造函数
使用类的其他构造函数执行初始化过程
复制构造函数
形参必须是本类的对象引用。
作用是用一个已经存在的对象去初始化同类型的新对象。
结构:
class 类名 {
public:
类名(形参);//构造函数
类名(const 类名&对象名);//复制构造函数
//不允许定义函数名 也不能有return语句
//...
};
类名::类(const 类名&对象名) //复制构造函数的实现
{ 函数体 }
在引用时,是双向传递的,但在初始化时不希望新对象对原引用对象修改。
因此加 const,说明引用是常引用,只能使用引用去读取数据,但是不能用引用对其指向的对象进行修改。这样能同时保证引用,以及实参的安全性。
复制构造函数被调用的情况
- 定义一个对象时,以本类另一个对象做为初始值,发生复制构造
- 如果函数的型参是类的对象,调用函数时,将使用实参对象初始化形参对象,形实结合,发生复制构造
- 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化另一个临时无名对象,传递给主调函数,发生复制构造
隐含的复制构造函数
- 如果没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的复制构造函数
- 此构造函数的执行功能: 用初始值对象的每个数据成员,初始化将要建立对象的对应数据成员
若不希望对象被复制构造,用 “=delete” 指示编译器不生成默认复制构造函数
析构函数
- 析构函数完成对象被删除前的清理工作
- 在对象的生存期结束的时刻,系统自动调用析构函数
- 如果程序中未声明析构函数,编译器将自动生成一个默认的析构函数,其函数体为空
析构函数的原型:~类名();
析构函数没有参数,没有返回类型
定义一个内联成员函数提高程序的执行效率
用已有的类初始化一个新对象,需要一个复制构造函数。
类的组合
- 类中的成员是另一个类的对象
- 可以在已有的抽象基础上实现更复杂的抽象
类组合的构造函数设计
- 原则:不仅要对负责本类的基本类型成员数据初始化,也要对对象成员初始化
- 声明形式
类名::类名(对象成员所需的形参,本类成员形参):
对象1(参数),对象2(参数),...
{
//函数体其他语句
}
构造组合类对象时初始化次序
-
首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序
成员对象构造函数调用顺序:按对象成员的定义顺序,先声明者构造;
初始化列表中未定义的成员,调用默认构造函数(即无形参的)初始化 -
处理完初始化列表后,再执行构造函数的函数体
前向引用声明
因为类应该先声明,后使用
如果需要在某个类的声明之前引用该类,则应进行前向引用声明
前向引用声明只为程序引入一个标识符,但具体声明在其他地方
例如:
class B; //前向引用声明
class A {
public:
void f(B b);
};
class B {
public:
void g(A a);
};
UML 图形建模语言
- 事物things
- 关系relationships
- 图diagrams
结构体 联合体
- 结构体是一种特殊形态的类,与类的唯一区别:类的缺省访问权限是private,结构体的缺省访问权限是public。
- 什么时候用结构体而不用类:定义主要用来保存数据,而没有什么操作的类型;人们习惯将结构体的数据成员设为公有2,此时用结构体更方便。
结构体定义
struct 结构体名称 {
公有成员
protected:
保护型成员
private:
私有成员
};
联合体:所有成员共用相同存储单元
union 结构体名称 {
公有成员
protected:
保护型成员
private:
私有成员
};
枚举类
定义语法形式 enum class 枚举类型名:底层类型{枚举值列表};
例如:
enum class Type {General, Light, Medium, Heavy};
enum class Type: char {General, Light, Medium, Heavy};
enum class Category {General=1, Pistol, MachineGun, Cannon};
枚举类优势:
- 强作用域:将作用于限制在枚举类中
枚举时必须带上类名Type::General
- 转换限制:
- 可指定底层类型
实验四
#eg1:声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,有两个公有成员run、stop。
其中rank为枚举类型CPU_rank,声明为 enum CPU_rank {P1=1,P2,P3,P4,P5,P6,P7},frequency单位为MHz的整型数,voltage为浮点型的电压值。
注意不同的访问属性的成员访问方式,并观察构造函数和析构函数的调用顺序。
#include <iostream>
using namespace std;
enum CPU_Rank { P1 = 1, P2, P3, P4, P5, P6, P7 };
//声明CPU类,包含私有数据成员 rank、frequency、voltage
class CPU {
private:
CPU_Rank rank;
int frequency;
float voltage;
public :
CPU(CPU_Rank r, int f, float v) //构造函数
{
rank = r;
frequency = f;
voltage = v;
cout << "构造了一个CPU!" << endl;
}
~CPU() { cout << "析构了一个CPU!" << endl; } //析构函数
//外部接口函数
//以上是访问数据成员接口
CPU_Rank GetRank() const { return rank; }
int GetFrequency() const { return frequency; }
float GetVoltage() const { return voltage; }
//设置私有成员接口
void setRank(CPU_Rank r) { rank = r; }
void setFrequency(int f) { frequency = f; }
void setVoltage(float v) { voltage = v; }
void Run() { cout<<"CPU开始运行!"<<endl; }
void Stop() { cout << "CPU停止运行!" << endl; }
};
int main()
{
CPU a(P6, 300, 2.8);
a.Run();
a.Stop();
return 0;
}