类与对象
面向对象编程的基本特征
抽象
- 概括同一类对象的共同属性和行为,形成一个类。
- 首先关注问题的本质和描述,然后是实现过程或细节。
- 数据抽象:描述某一类对象的属性或状态(区分对象的物理量)。
- 代码抽象:描述某一类对象所具有的共同行为特征或功能。
- 抽象的实现:类。
- 抽象示例—时钟
- 数据抽象: int hour,int minute,int second
- 代码抽象: setTime(),showTime()
1 2 3 4 5 6 7
| class Clock { public: void setTime(int newH, int newM, int newS); void showTime(); private: int hour, minute, second; };
|
封装
- 将抽象的数据和代码封装在一起,形成一个类。
- 目的:增强安全性,简化编程。用户不需要了解具体的实现细节,只需通过具有特定访问权限的外部接口使用类成员。
- 封装的实现:类声明中的 {}
- 示例:
1 2 3 4 5
| class Clock { public: void setTime(int newH, int newM, int newS); void showTime(); private: int hour, minute, second; };
|
继承
多态
- 多态:同名,不同功能实现。
- 目的:实现统一的行为识别,减少程序中的标识符数量。
类与对象的定义
类定义的语法形式
1 2 3 4 5 6 7 8 9
| class class_name { public: public members (external interface) private: private members protected: protected members }
|
类内初始值
- 可以为数据成员提供类内初始值
- 创建对象时,使用类内初始值初始化数据成员
- 没有初始值的成员将被默认初始化
类成员访问控制
- 公有类型成员
- 在 public
关键字后声明,它们是类与外部之间的接口,任何外部函数都可以访问公有类型的数据和函数
- 私有类型成员
- 在 private
关键字后声明,只有该类中的函数可以访问,外部函数无法访问。
- 如果私有成员在类名后立即声明,可以省略关键字
private
- 保护类型成员
- 类似于私有,区别在于在继承和派生过程中对派生类的影响不同
类成员函数
- 在类中声明函数原型
- 可以在类外提供函数体实现,并在函数名前加上类名
- 也可以直接在类中提供函数体,形成内联成员函数
- 允许声明重载函数和具有默认参数值的函数
类与对象的程序示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #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 = 0,int newM = 0,int newS = 0); { hour = newH; minute = newM; second = newS; } void Clock::showTime() { cout << hour << ":" << minute << ":" << second << endl; }
int main() { Clock myClock; myClock.setTime(8,30,30); myClock.showTime(); return 0; }
|
构造函数与析构函数
构造函数
构造函数的目的
在创建对象时使用特定值构造对象,初始化对象为特定初始状态。
当希望构造一个 Clock 类对象并将初始时间设置为 0:0:0
时,可以通过构造函数进行设置。
构造函数的形式
- 函数名与类名相同
- 不能定义返回类型,且不能有返回语句
- 可以有形式参数或没有形式参数
- 可以是内联函数
- 可以重载
- 可以有默认参数值
构造函数调用的时机
在创建对象时自动调用
构造函数示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <iostream> using namespace std;
class Clock{ public: Clock(int newH,int newM,int newS); void setTime(int newH = 0,int newM = 0,int newS = 0); void showTime(); private: int hour,minute,second; };
Clock::Clock(int newH,int newM,int newS): hour(newH),minute(newM),second(newS){}
void Clock::setTime(int newH = 0,int newM = 0,int newS = 0); { hour = newH; minute = newM; second = newS; } void Clock::showTime() { cout << hour << ":" << minute << ":" << second << endl; }
int main() { Clock c(0,0,0); c.showTime(); return 0; }
|
:: 符号是作用域解析运算符,表示函数定义需要使用 class_name::
来限定成员函数。
非常重要的一点是,类定义结束后有一个分号!!!(浪费了很多时间)
默认构造函数
- 可以不带实际参数调用的构造函数
- 参数列表为空的构造函数
- 所有参数都有默认值的构造函数
- 以下两个都是
默认构造函数。如果它们同时出现在一个类中,将会发生编译错误:
1 2 3
| Clock(); Clock(int newH=0,int newM=0,int newS=0);
|
隐含生成的构造函数
如果程序中未定义构造函数,编译器将在需要时自动生成一个默认构造函数
- 参数列表为空,不为数据成员设计初始值
- 如果类内定义了成员的初始值,则使用类内定义的初始值
- 如果没有定义类内的初始值,则以默认方式初始化
- 基本类型的数据默认初始化的值是不确定的
“=default”
如果类内已定义构造函数,默认情况下编译器不再隐含生成默认构造函数。
如果你坚持希望隐含生成默认构造函数,用“=default”
委托构造函数
委托构造函数(delegating
constructor)使用类其他构造函数执行初始化过程
例如
1 2 3
| Clock(int newH,int newM,int newS):hour(newH),minute(newM),second(newS){} Clock():Clock(0,0,0){}
|
复制构造函数
复制构造函数是一种特殊的构造函数,其形参为本类的对象引用。作用是用一个已存在的对象去初始化同类型的新对象。
用法:
1 2 3 4 5 6 7 8 9
| class 类名 { public: 类名(形参); 类名(const 类名&对象名); }; 类名::类(const 类名&对象名) {函数体}
|
隐含的复制构造函数
- 如果没有为类声明拷贝初始化构造函数,则编译器自己生成一个复制构造函数。
- 这个构造函数执行的功能是:用作为初始值对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。
“=delete”
如若不希望被复制构造
1
| Point(const Point&p) = delete;
|
复制构造函数被调用的三种情况
- 定义一个对象时,以另一个对象作为初始值,发生复制构造
- 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造
- 如果函数的返回值是类的对象,函数执行完成返回主函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主函数,此时发生复制构造
析构函数
作用:完成对象被删除前的一些清理工作
- 在对象生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间
- 如果程序中未声明析构函数,编译器会自动生成一个默认的析构函数,函数体为空
类的组合
简单来说,就是类的数据成员是其他类的对象,下面用计算两点之间线段的距离的程序来说明。
线段类和点类实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include <iostream> #include <cmath> using namespace std;
class Point{ public: Point(float xx,float yy); Point(); Point(Point &p); float getX(){return x;} float getY(){return y;} private: float x,y; }; Point::Point(float xx,float yy):x(xx),y(yy){} Point::Point():x(0),y(0){} Point::Point(Point &p) { x=p.x; y=p.y; cout << "Calling the copy constructor of Point" << endl; }
class Line{ public: Line(Point o1,Point o2); Line(Line &l); float getLen(); private: Point p1,p2; float len=0; }; Line::Line(Point o1,Point o2):p1(o1),p2(o2) { float x=p1.getX()-p2.getX(); float y=p1.getY()-p2.getY(); len= sqrt(x*x+y*y); cout << "Calling the constructor of Line" << endl; } Line::Line(Line &l): p1(l.p1),p2(l.p2) { len=l.len; cout << "Calling the copy constructor of Line" << endl; } float Line::getLen() {return len;} int main() { Point myp1(1,1),myp2(4,5); Line line(myp1,myp2); Line line2(line); cout << "The length of line is " << line.getLen() << endl; cout << "The length of line2 is " << line2.getLen() << endl; return 0; }
|
前向引用声明
遇到两个类相互引用的情况,也称为循环依赖,简单理解就是你不能使用一个在前面完全没有出现过的标识符。
1 2 3 4 5 6 7 8 9
| class B; class A{ public: void f(B b); }; class B{ public: void g(A a); };
|