类和对象
面向对象程序设计的基本特点
抽象
- 对同一类对象的共同属性和行为进行概括,形成类。
- 先注意问题的本质及描述,其次是实现过程或细节。
- 数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。
- 代码抽象:描述某类对象的共有的行为特征或具有的功能。
- 抽象的实现:类。
- 抽象实例——钟表
- 数据抽象:
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 类名称 { public: 公有成员(外部接口) private: 私有成员 protected: 保护型成员 }
|
类内初始值
- 可以为数据成员提供一个类内初始值
- 在创建对象时,类内初始值用于初始化数据成员
- 没有初始值的成员将被默认初始化
类成员访问控制
- 公有类型成员
- 在关键值public后面声明,它们是类与外部的借口,任何外部函数都可以访问公有类型数据和函数
- 私有类型成员
- 在关键字private后面声明,只允许本类中的函数访问,而外部任何函数都不能访问。
- 如果紧跟在累名称后面声明私有成员,则关键字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,就可以通过构造函数来设置。
构造函数的形式
- 函数名和类名相同
- 不能定义返回值类型,也不能有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
| #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; }
|
::符号是作用域解析运算符,也就是函数定义是需要用,类名::来限制成员函数。
一件很重要的事儿是类的定义结束之后有个分号!!!(浪费大把时间)
默认构造函数
- 调用时可以不需要实参的构造函数
- 参数表全为空的构造函数
- 全部参数都有默认值的构造函数
- 下面两个都是默认构造函数,如在类中同时出现,将产生编译错误:
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); };
|