# 类与对象
抽象
:是对同一类对象的共同属性和行为进行概括,形成类。数据抽象
:描述某类对象的属性或状态(对象相互区别的物理量)- 代码抽象:描述某类对象的共有的行为特征或具有的功能
- 抽象的实现:类
封装
:将抽象出的数据,代码封装在一起,形成类。目的
:增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员- 实现封装:类声明中的 {}
# 类与对象的关系
对象
是现实中的对象在程序中的 模拟
- 类是同一类对象的抽象,对象是类的
某一特定实体
- 定义类的对象,才可以通过对象使用
类
中定义的功能。
| class 类名称 |
| { |
| public: |
| 公有成员(外部接口) |
| private: |
| 私有成员 |
| protected: |
| 保护型成员 |
| } |
# 对象定义的语法:
# 类内初始化
- 可以为
数据成员
提供一个类内初始值 - 在创建对象时,类内初始值用于初始化数据成员
- 没有初始值的成员将被默认初始化
# 类的成员函数
- 在类中说明
函数原型
- 可以在
类外
给出函数体实现,并在函数名前使用类名加以限定 - 也可以直接在类中给出函数体,形成内联成员函数
- 允许声明重载函数和带默认参数值的函数
# 内联成员函数
- 为了提高运行时的
效率
,对于较简单的函数可以声明为 内联形式
- 内联函数体中
不要有复杂结构
(如循环语句和 switch 语句) - 在类中声明内联成员函数的方式
| |
| * project: 钟表类 |
| * environment :VS2022 |
| * time:2023 年 1 月 31 日 |
| */ |
| #include <iostream> |
| using namespace std; |
| class Clock |
| { |
| public: |
| |
| void setTime(int newH = 0, int newM = 0, int newS = 0); |
| 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 << "Time:" << hour << ":" << minute << ":" << second << endl; |
| }; |
| |
| int main(void) |
| { |
| Clock C; |
| cout << "First time set and output:" << endl; |
| C.setTime(); |
| C.showTime(); |
| cout << "Second time set and output:" << endl; |
| C.setTime(12, 48, 30); |
| C.showTime(); |
| |
| return 0; |
| } |
# 构造函数
构造函数:在对象被创建时使用特定的值构造对象,将对象 初始化
为一个特定的初始状态。
# 构造函数形式:
- 函数名与类名相同
- 不能定义返回值类型,也不能有 return 语句
- 可以有形式参数,也可以没有形式参数
- 可以是内联函数
- 可以重载
- 可以带默认参数值
# 构造函数的调用时机
在对象创建时被自动调用。
# 隐含生成的构造函数
- 如果程序中未定义构造函数,编译器将在需要时自动生成一个默认构造函数 (并不是全部)
- 参数列表为空,不为数据成员设置初始值
- 如果类内定义了成员的初始值,则使用类内定义的初始值
- 如果没有定义类内的初始值,则以默认方式初始化
- 基本类型的数据默认初始化的值是不确定的
| 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(int newH, int newM, int newS):hour(newH),minute(newM),second(newS) |
| { |
| } |
| |
| Clock::Clock():hour(0), minute(0), second(0) |
| {} |
# =default
如果类中已定义构造函数,默认情况下编译器就不再隐含生成默认构造函数。如果此时依然希望编译器隐含生成默认构造函数,可以使用 =default
| class Clock |
| { |
| public: |
| Clock() = default; |
| Clock(int newH, int newM , int newS ); |
| |
| |
| void setTime(int newH, int newM, int newS); |
| void showTime(); |
| private: |
| int hour, minute, second; |
| }; |
# 委托构造函数
类中往往有多个构造函数,只是参数表和初始化列表不同,其初始化算法都是相同的,这时,为了避免代码重复,可以使用委托构造函数.
委托构造函数使用类的其他构造函数执行初始化过程
| Clock::Clock(int newH, int newM, int newS):hour(newH),minute(newM),second(newS) |
| { |
| } |
| Clock::Clock():Clock(0,0,0){} |
# 复制构造函数
- 我们经常会需要用一个
已经存在
的对象,去初始化新的对象 -- 复制构造函数
- 隐含生成的复制构造函数可以实现对应数据成员一一复制
- 自定义的复制构造函数可以实现特殊的复制功能
复制构造函数是一种特殊的构造函数,其形参为本类的对象引用。作用是用一个已存在的对象去初始化同类型的新对象.
| class 类名 |
| { |
| public: |
| 类名(形参); |
| 类名(const 类名 & 对象名); |
| |
| 类名(const 类名 & 对象名) = delete; |
| |
| } |
| 类名::类 (const 类名 & 对象名) |
| { |
| 函数体 |
| } |
# 复制构造函数被调用的三种情况
- 定义一个对象时,以本类另一个对象作为
初始值
,发生复制构造, - 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造
- 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用 return 语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生复制构造。(一般此项编译器可以选择取消)。
| |
| * project: 复制构造函数 |
| * environment :VS2022 |
| * time:2023 年 1 月 31 日 |
| */ |
| #include <iostream> |
| using namespace std; |
| class Point |
| { |
| public: |
| Point(int xx = 0, int yy = 0) |
| { |
| x = xx; |
| y = yy; |
| }; |
| Point(const Point& p); |
| void setX(int xx) |
| { |
| x = xx; |
| } |
| void setY(int yy) |
| { |
| y = yy; |
| } |
| int getX() const { return x; } |
| int getY() const { return y; } |
| private: |
| int x,y; |
| }; |
| |
| Point::Point(const Point& p) |
| { |
| x = p.x; |
| y = p.y; |
| cout << "Calling the copy constructor" << endl; |
| } |
| void fun1(Point p) |
| { |
| cout << p.getX() << endl; |
| } |
| Point fun2() |
| { |
| Point a(1, 2); |
| return a; |
| } |
| int main(void) |
| { |
| Point a(4,5); |
| Point b(a); |
| cout << b.getX() << endl; |
| fun1(b); |
| b = fun2(); |
| cout << b.getX() << endl; |
| return 0; |
| } |
# 左值右值
左值位于赋值 运算左侧
的对象或变量,右值是位于赋值 运算右侧
的值。
- 对
持久
存在变量的引用称为 左值引用
,用 & 表示 - 对
短暂存在
可被移动的 右值
的引用称之为右值引用,用 &&
表示
| float n = 6; |
| float &lr_n = n; #左值引用 |
| float &&rr_n = n; #错误,右值引用不能绑定到左值 |
| float &&rr_n = n*n; #右值表达式绑定到右值引用 |
通过标准库 <utility>
中的 move函数
可将 左值对象
强行移动为 右值
| float n = 10; |
| float &&rr_n = std::move(n); |
| |
# 移动构造函数
基于 右值引用
,移动构造函数通过 移动数据方式
构造新对象,与复制构造函数类似,移动构造函数参数为该类对象的右值引用
| #include <utility> |
| class astring |
| { |
| public: |
| std::string s; |
| astring (astring&& o) noexcept:s(std::move(o.s)) |
| {函数体} |
| } |
# 析构函数
- 完成对象
被删除前
的一些 清理
工作 - 在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间
- 如果程序中未声明析构函数,
编译器
将自动产生一个默认的析构函数,其函数体为空。
# 类的组合
- 类中的
成员
是另一个类的对象 - 可以在已有抽象的基础上实现更复杂的抽象
原则:不仅要负责对本类中的基本类型成员数据初始化,也要对对象成员初始化
| 类名::类名(对象成员所需的形参,本类成员形参) |
| :对象1(参数),对象2(参数),..... |
| { |
| 函数题其他语句 |
| } |
# 构造组合类对象时的初始化次序
| |
| * project: 类的组合,线段(Line)类 |
| * environment :VS2022 |
| * time:2023 年 1 月 31 日 |
| */ |
| #include <iostream> |
| #include <cmath> |
| using namespace std; |
| class Point |
| { |
| public: |
| Point(int xx = 0, int yy = 0) |
| { |
| x = xx; |
| y = yy; |
| }; |
| Point(const Point& p); |
| int getX() const { return x; } |
| int getY() const { return y; } |
| private: |
| int x, y; |
| }; |
| |
| class Line |
| { |
| public: |
| Line(Point xp1, Point xp2); |
| Line(Line& l); |
| double getLen() |
| { |
| return len; |
| } |
| private: |
| Point p1, p2; |
| double len; |
| }; |
| Point::Point(const Point& p) |
| { |
| x = p.x; |
| y = p.y; |
| cout << "Calling the copy constructor" << endl; |
| } |
| |
| Line::Line(Point xp1, Point xp2) :p1(xp1), p2(xp2) |
| { |
| cout << "Calling constructor of Line" << endl; |
| double x = static_cast<double>(p1.getX() - p2.getX()); |
| double y = static_cast<double>(p1.getY() - p2.getY()); |
| len = sqrt(x * x + y * y); |
| } |
| |
| Line::Line(Line& l) :p1(l.p1), p2(l.p2) |
| { |
| cout << "Calling the copy constructor of Line" << endl; |
| len = l.len; |
| } |
| |
| int main(void) |
| { |
| Point myp1(1, 1), myp2(4, 5); |
| Line line(myp1, myp2); |
| Line line2(line); |
| cout << "The length of line is"; |
| cout << line.getLen() << endl; |
| cout << "The length of the line2 is:" << line2.getLen() << endl; |
| |
| return 0; |
| } |
# 前向引用声明
- 类应该先
声明,后使用
- 如果需要在某个类的生命之前,引用该类,则应进行前向引用声明
- 前向引用声明只为程序引入一个
标识符
,但具体声明在其他地方 - 在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象
- 当使用前向引用声明时,只能使用被
声明的符号
,而不能涉及类的任何细节。
| class B; |
| class A |
| { |
| public: |
| void f(B b); |
| }; |
| class B |
| { |
| public: |
| void g(A a); |
| }; |
# 参考资料: