第四章 类与对象
面向对象程序的基本特点
抽象
- 对同一类对象的共同属性和行为进行概括,形成类。
首先注意问题的本质及描述,其次是实现过程或细节;
数据抽象:描述某类对象的属性或状态(对象相互区别的物理量);
代码抽象:描述某类对象的共有行为特征或具有的功能;
抽象的实现:类;
- 例如:钟表抽象:
数据抽象:int hour,int minute, int second;
代码抽象:setTimer(),showTime()
class Clock{
public:
void setTime(int newH, int mewM, int newS);
void showTime();
private:
int hour, minute, second;
}
封装
将抽象出的数据,代码封装在一起,形成类;
- 将抽象出的数据成员、代码成员相结合,将它们视为一个整体。
目的:增强安全性和简化编程,使用者不必了解具体的实现细节,而只需通过外部接口,以特定的访问权限,来使用类的成员。
实现封装:类声明中的{}
class Clock{
public:
void setTime(int newH, int mewM, int newS);
void showTime();
private:
int hour, minute, second;
};
继承
在已有类的基础上进行扩展形成新的类。
详见第七章;
即增加新的属性和行为
多态
详见第八章;
类与对象
类和对象的定义
设计类就是设计类型
此类型的“合法值”是什么?
此类型应该有什么样的函数和操作符?
新类型的对象改如何被创建和销毁?
如何进行对象的初始化和赋值?
对象作为函数的参数如何以值传递?
谁将使用此类型的对象成员?
类定义的语法
…
类内初始值
private:
int x = 0;
…
- 共有成员,私有,保护成员;
private可省略;
- 对象定义语法:
如:Clock myClock;
- 类的成员函数
在类中声明函数原型;
可以在类外给出函数体实现,并在函数名前使用类名加以限定;
也可以直接在类中给出函数体,形成内联成员函数;
允许声明重载函数和带默认参数值的函数;
- 内联成员函数
为了提高运行时的效率,对于较简单的函数可以声明为内联形式。
内联函数体中不要有复杂结构(如循环语句和switch语句);
在类中声明内联成员函数的方式:
将函数体放在类的声明中
使用inline关键字
类和对象的程序举例
- 还是钟表类
class Clock{
public:
void setTime(int newH, int mewM, int newS);
void showTime();
private:
int hour, minute, second;
};
- 成员函数的实现
void Clock::setTime(int newH, int mewM, int newS){
hour = newH;
minute = newM;
second = mewS;
}
void Clock::showTime(){
cout << hour << ":" << minute << ":" << second;
}
区别在于类名;
- 对象的使用
int main(){
Clock myclock;
myClock.setTime(8, 30, 30);
myClock.showTime();
return 0;
}
构造函数
构造函数的基本概念
-
类中的特殊函数
-
用于描述初始算法
构造函数的作用
在对象被创建时使用特定的值构造对象,将对象初始化为一个特定的初始状态。
例如:希望在构造一个Clock类的对象时,将初始时间设为0:0:0,就可以通过构造函数来设置。
构造函数的形式
函数名与类名相同
不能定义返回值类型,也不能有return语句
可以有形式参数,也可以没有
可以使内联函数
可以重载
可以带默认参数值
构造函数的调用时机
在对象创建时被自动调用
例如 :Clock myClock(0,0,0)
默认构造函数
调用时可以不需要实参的构造函数
参数表为空的构造函数
全部参数都有默认值的构造函数
Clock(); Clock(int newH=0, ...)
以上两个默认构造函数不能在类中同时出现;将产生编译错误;
如果完全没有定义构造函数:编译器将自动生成一个默认构造函数
叫做隐含生成的构造函数
参数列表为空,不为数据成员设置初始值;
如果类内定义了成员的初始值,则使用类内定义的初始值;
如果没有,则以默认方式初始化;
基本类型的数据默认初始化的值是不确定的。
还可以:Clock()=default; //指示编译器提供默认构造函数
例子
class Clock{
public:
Clock(int newH, int mewM , int newS); //构造函数
void setTime(int newH, int mewM, int newS);
void showTime();
private:
int hour, minute, second;
};
// 构造函数的实现:
Clock::Clock(int mewH, int newM , int newS):
hour(newH), minute(newM), second(newS){ //初始化列表,比在函数体中赋值效率更高
}
一般:
Clock::Clock():hour(0), minute(0), second(0) //默认构造函数
一个类包含构造函数和默认构造函数;
为了: Clock c1(3, 4,5) 和 Clock c2;
委托构造函数
委托函数们只是形参不同,初始化列表不同;
为了减少重复;
回顾Clock类的两个构造函数;
例如:Clock():Clock(0, 0, 0){}
复制构造函数
用已经存在的对象初始化新对象
用已有对象的引用作为构造函数的参数;
默认复制构造函数实现两个对象的数据成员之间的一一对应的复制;很多时候都令人满意了
复制构造函数定义
复制构造函数是一种特殊的构造函数,其形参为本类的对象引用,作用是用一个已存在的对象去初始化同类型的新对象;
…待补充;
print是值引用,每次执行print的时候,都会调用复制构造函数把实参赋值给形参。
例子
…
析构函数
析构函数
消亡时调用;
析构函数完成对象被删除前的一些清理工作;
目前仍未有太多的关于占用的体会;
如果没有定义析构函数,编译器也会自动产生默认的,其函数体为空;
原型: ~类名();
析构函数没有参数和返回类型;
类的组合
部件组装思想;
组合的概念
类中的成员是另一个类的对象;
可以在已有的抽象的基础上实现更复杂的抽象;
类组合的构造函数设计
原则:不仅要负责对本类中的基本类型成员数据初始化,也要对对象成员初始化。
声明形式:
类名::类名(对象成员所需的形参, 本身成员形参):
对象1(参数), 对象2(参数), …
{
//函数体其他语句
}
构造组合类对象的初始化次序
-
首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序。
-
成员对象构造函数调用顺序:按对象成员的定义顺序,先声明先构造
-
初始化列表中未出现的成员对象,调用默认构造函数(即无形参的)初始化;
-
-
处理完初始化列表之后,再执行构造函数的函数体;
例子
在下面的主函数:
int main(){
Point myp1(1,1), myp2(3, 4); //建立Point类对象
Line line(myp1, myp2); //建立Line类对象
Line line2(line); //利用拷贝构造函数建立一个新对象
}
关于Point类复制构造函数调用:
myp2 --> myp1
调用两次
顺序由后到前
然后进入Line构造函数:
是两次调用Line复制构造函数;
顺序取决于定义顺序
接着进入Line构造函数函数体:
计算线段长度并返回
接着是用line初始化line2:
调用line复制构造函数;
然后又进到Point的复制构造函数;初始化p1和p2 , 又是两次调用;
然后才进入line复制构造函数的函数体;
其实自己debug看看就好
前向引用声明
由于类应该先声明,后使用;
如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。
前向引用声明只为程序引入一个标识符,但具体声明在其他地方。
就:Class B;这样;
注意事项
在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。
当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节。
如使用class A{ B x; } // 错误
允许的如:class A{public: void f(B, b);};
UML简介
UML三个基本部分
事物(Things)
关系(Relationships)
图(Diagrams)
点线面关系;
举例:
类图
- Clock类的完整表示:
| Clock |
|---|
| - hour: int - minute:int - second:int + showTime():void +setTime(newH:int=0, newM:int=0, newS:int=0):void |
- Clock类的简洁表示
| Clock |
|---|
对象图
和类图差不多
依赖关系
类A使用类B或类A依赖类B
作用关系——关联
包含关系——聚集
继承关系——泛化
注释
以上图标:

@startuml
Call <|-- AudioCall : Generalization(泛化)
Call <|… CallImpl : Realization(实现)
Call <-- Beat : Dependency(依赖)
@enduml
@startuml
Customer – Product : Association(关联)(这是双向关联)
Customer “1”–>“1” Address : has(单向关联)
Node “1”–>“1” Node : contains(自关联)
@enduml
@startuml
Company o-- Employee : Aggregation(聚合)
Company *-- Department : Composition(组合)
@enduml
结构体
结构体是一种特殊形态的类。
与类的唯一区别:
类的缺省访问权限是private,结构体的是public
什么时候用结构体而不是用类
-
定义主要用来保存数据而没有什么操作的类型。
-
人们习惯将结构体的数据成员设为公有,因此这时用结构体更方便。
结构体在C++中用的并不多,一般用来与C兼容;
结构体定义 :
Struct 结构体名称{
公有成员
protected:
保护成员
private:
私有成员
};
结构体中可以有数据成员和函数成员;C中只有数据成员;
结构体的初始化
如果:
一个结构体的全部数据成员都是公共成员;
没有用户定义的构造函数;
没有基类和虚函数(第七章介绍)
这个结构体的变量可以这样初始化:
类型名 变量名 = {成员数据1初始值, 成员数据2初始值,......};
然后使用也很简单;
联合体
不是很常用,但很有用;
目的是:存储空间的共用;
定义形式
定义 :
union 联合体名称{
公有成员
protected:
保护成员
private:
私有成员
};
特点:
成员共用同一组内存单元
任何两个成员不会同时有效;
按照占用空间最多的成员分配空间;
无名联合 :
union{
int i;
float f;
}
//在程序中可以这样使用:
i = 10;
f =1.3;
如此对i赋的值被f赋值冲掉了;
枚举类
也称为强类型的枚举
定义语法
enum class 枚举类名:底层类型{枚举值列表};
默认的底层类型是int
优势
-
强作用域;例:使用Type的枚举值General:
Type::General; 这样就不会重名了 -
转换限制;指枚举对象不可以与整形隐式转换。
-
可以指定底层类型; 例:
enum class Type:char{General, Light, Medium, Heavy};
还无法直接比较不同的枚举类;
部件组装
本文深入讲解面向对象编程的核心概念,包括抽象、封装、继承和多态。通过实例演示类与对象的设计,构造函数和析构函数的使用,以及类组合的构造函数设计原则。此外,文章还介绍了UML的基本组成部分,以及结构体、联合体和枚举类的定义和应用。
647

被折叠的 条评论
为什么被折叠?



