目录:
抽象
对同一类对象的共同属性和腥味进行概括,形成
- 先注意问题的本质,其次是实现过程或细节
- 数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)
- 代码抽象:描述某类对象的共同行为特征或具有的功能
- 抽象的实现:类
class Clock {
public: // 1
void setTime(int newH, int newM, int newS); // 2
void showTime(); // 2
private: // 1
int hour, minute, second;
};
1):特定的访问权限
2):外部接口
封装
将抽象出的数据、代码封装在一起,形成类
- 目的:增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口, 以特定的访问权限来使用类的成员
- 实现封装:类声明中的 {}
类定义的语法形式
class 类名称 {
public:
公有成员(外部接口)
private:
私有成员
protected:
保护成员
};
类成员的访问控制
公有类型成员
在关键字 public 后面声明,它们是类与外部的接口
任何外部函数都可以访问公有类型数据和函数
私有类型成员
在关键字 private 后面声明,只允许本类中的函数访问
类外部的任何函数都不能访问
默认不加关键字即 private
保护类型成员
与 private 类似,其差别表现在继承与派生时对派生类的影响不同
类的成员函数
- 在类中说明函数原型
- 可以在类外给出函数体实现,并在函数名前使用雷鸣加以限定
- 可以在类中给出函数体,形成内联成员函数
- 允许声明重载函数和带默认参数值的函数
内联成员函数
提高效率,简单的函数可以在类中实现函数体,用 inline 关键字声明为内联函数
不能有复杂结构(循环,switch)
构造函数
- 在对象被创建时使用特定的值构造对象
- 函数名与类名相同
- 不能定义返回值类型,也不能有 return 语句
- 参数可有可无
- 可以是内联函数
- 可以重载
- 可以带默认参数值
对象被创建时自动调用
默认构造函数
- 参数表为空的构造函数
- 全部参数都有默认值的构造函数
示例:
Clock();
Clock(int newH=0, int newM=0, int newS=0);
隐含生成的构造函数
如果程序中未定义构造函数,编译器将在需要时自动生成一个默认构造函数
- 参数列表为空,不为数据成员设置初始值
- 如果类内定义了成员的初始值,使用之;否则以默认方式初始化
- 基本数据类型的数据默认初始化的值是不确定的
如果类中已定义构造函数,默认情况下编译器不在隐含生成默认构造函数
当然,也可以显示要求编译器生成
Clock() = default; // 指示编译器提供默认构造函数
构造函数示例:
class Clock {
public:
Clock(int newH, int newM, int newS); // 构造函数
Clock(); // 默认构造函数
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) {}
// 自动调用构造函数
Clock c1(0,0,0); // 调用有参数的构造函数
Clock c2; // 调用无参数的构造函数
委托构造函数
类中往往有多个构造函数,只是参数表和初始化列表不同,其初始化算法都是相同的,这时为了避免代码重复,可以使用委托构造函数
参考上述示例,默认构造函数可以修改为
Clock::Clock():Clock(0,0,0) {} // 直接使用带参数的构造函数来执行初始化过程
复制构造函数
- 用已经存在的对象去初始化新的对象
- 隐含生成的复制构造函数可以实现对应数据成员一一复制
- 自定义的复制构造函数可以实现特殊的复制功能
class 类名 {
public:
类名(形参); // 构造函数
类名(const 类名 &对象名); // 复制构造函数
};
类名:类(const 类名 &对象名) // 复制构造函数的实现
{ 函数体}
三种被调用的情况:
- 定义一个对象时,以本类另一个对象作为初始值
- 函数的形参是类的对象,实参对象初始化形参对象时
- 函数返回值是类的对象,返回时初始化一个临时无名对象
示例:// 形参为类的对象 void fun1(Point p) {} // 返回值为类的对象 Point fun2() { Point a(1,2); return a; } int main() { Point a(4,5); Point b(a); // 用本类对象初始化新的对象 }
移动构造函数
基于右值引用,通过移动数据方式构造新对象,与复制构造函数类似
参数为类对象的右值引用
不分配新内存,为配合异常捕获机制,需声明 noexcept 表示不会抛出异常
被移动的对象不应再使用,需要销毁或重新赋值
示例:
#include <utility> class astring { public: std::string s; astring (astring&& o) noexcept: s(std::move(o.s)) // 显示移动所有成员 { 函数体 } };
左右值引用:
- 对持久存在变量的引用称为左值引用,& 表示
- 对短暂存在可被移动的右值的引用称为右值引用,&& 表示
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); // 将 n 转化为右值, // move 函数承诺除对 n 重新赋值或销毁外,不以 rr_n 以外方式使用
析构函数
- 完成对象被删除前的一些清理工作
- 在对象的生存期结束的时候系统自动调用,然后再释放此对象所属的空间
- 如果未声明,编译器自动产生一个默认的析构函数,函数体为空
class Point {
public:
~Point(); // 析构函数
};
Point::~Point() {}
组合
- 类中的成员是另一个类的对象
- 可以在已有抽象的基础上实现更复杂的抽象
构造组合类对象时的初始化次序- 首先对构造函数初始化列表中列出的成员(基础类型成员,对象成员)初始化,初始化次序是成员在类体中定义的次序
- 成员对象构造函数调用顺序:按对象成员的声明顺序,先声明者先构造
- 初始化列表中未出现的成员对象,调用默认构造函数初始化
- 处理完初始化列表后,再执行构造函数的函数体
// Point 类
class Point {
public:
Point(int xx=0, int yy=0) {
x = xx;
y = yy;
}
Point(Point &p);
private:
int x, y;
};
Point::Point(Point &p) {
x = p.x;
y = p.y;
}
// Line 类
class Line {
public:
Line(Point xp1, Point xp2);
Line(Line &l);
private:
Point p1, p2; // Point 类的对象
double len;
};
// 组合类的构造函数
Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) {
函数体
}
// 组合类的复制构造函数
Line::Line(Line &l) : p1(l.p1), p2(l.p2) {
函数体
}
int main() {
Point myp1(1,1), myp2(4,5); // 建立 Point 类的对象
Line line(myp1, myp2); // 建立 Line 类的对象
Line line2(line); // 利用复制构造函数建立一个新对象
}
前项引用声明
两个类互为引用编译会报错,解决编译问题
class B; // 前项引用声明
class A {
public:
void f(B b);
};
class B {
public:
void g(A a);
};
- 在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象
- 当使用前项引用声明时,只能使用被声明的符号,而不能涉及类的任何细节
class Fred; // 前项引用声明
class Barney {
Fred x; // 错误
};
class Fred {
Barney y;
};