• 1. 1第九章 类与对象C++语言程序设计
  • 2. 2本章主要内容面向对象的思想 OOP的基本特点 类概念和声明 对象 构造函数析构函数 内联成员函数 拷贝构造函数 运算符重载及友元函数
  • 3. 3回顾:面向过程的设计方法重点: 如何实现细节过程,将数据与函数分开。 形式: 主模块+若干个子模块(main()+子函数)。 特点: 自顶向下,逐步求精——功能分解。 缺点: 效率低,程序的可重用性差。面向对象的思想
  • 4. 4面向对象的方法目的: 实现软件设计的产业化。 观点: 自然界是由实体(对象)所组成。 程序设计方法: 使用面向对象的观点来描述模仿并处理现实问题。 要求: 高度概括、分类、和抽象。面向对象的思想
  • 5. 5抽象抽象是对具体对象(问题)进行概括,抽出这一类对象的公共性质并加以描述的过程。 先注意问题的本质及描述,其次是实现过程或细节。 数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。 代码抽象:描述某类对象的共有的行为特征或具有的功能。 抽象的实现:通过类的声明。OOP的基本特点
  • 6. 6抽象实例——钟表数据抽象: int Hour, int Minute, int Second 代码抽象: SetTime(), ShowTime() OOP的基本特点
  • 7. 7抽象实例——钟表类class Clock { public: void SetTime(int NewH, int NewM, int NewS); void ShowTime(); private: int Hour,Minute,Second; };OOP的基本特点
  • 8. 8抽象实例——人数据抽象: char *name,char *gender,int age,int id 代码抽象: 生物属性角度: GetCloth(), Eat(), Step(),… 社会属性角度: Work(), Promote() ,…OOP的基本特点
  • 9. 9封装将抽象出的数据成员、代码成员相结合,将它们视为一个整体。 目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。 实现封装:类声明中的{}OOP的基本特点
  • 10. 10封装实例: class Clock { public: void SetTime(int NewH,int NewM, int NewS); void ShowTime(); private: int Hour,Minute,Second; };边界特定的访问权限OOP的基本特点外部接口
  • 11. 11继承与派生是C++中支持层次分类的一种机制,允许程序员在保持原有类特性的基础上,进行更具体的说明。 实现:声明派生类OOP的基本特点
  • 12. 12多态性多态:同一名称,不同的功能实现方式。 目的:达到行为标识统一,减少程序中标识符的个数。 实现:重载函数和虚函数OOP的基本特点
  • 13. 13c++中的类类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分。 利用类可以实现数据的封装、隐藏、继承与派生。 利用类易于编写大型复杂程序,其模块化程度比C中采用函数更高。类 和 对 象
  • 14. 14类的声明形式 类是一种用户自定义类型,声明形式: class 类名称 { public: 公有成员(外部接口) private: 私有成员 protected: 保护型成员 }类 和 对 象
  • 15. 15公有类型成员在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。类 和 对 象
  • 16. 16私有类型成员在关键字private后面声明,只允许本类中的函数、友元函数访问,而类外部的任何函数都不能访问。 注意:在类中若没有显示规定访问属性,则默认为private。 类 和 对 象
  • 17. 17保护类型与private类似,允许该类的成员函数、友元函数、以及继承此类的所有派生类的成员函数和友元函数的所访问。类 和 对 象
  • 18. 18类的成员class Clock { public: void SetTime(int NewH, int NewM, int NewS); void ShowTime(); private: int Hour, Minute, Second; };类 和 对 象成员数据成员函数
  • 19. void Clock :: SetTime(int NewH, int NewM, int NewS) { Hour=NewH; Minute=NewM; Second=NewS; } void Clock :: ShowTime() { cout<
  • 20. 20成员数据与一般的变量声明相同,但需要将它放在类的声明体中。 通常访问属性为private。类 和 对 象
  • 21. 21成员函数在类中说明原形,可以在类外给出函数体实现,并在函数名前使用类名加以限定。也可以直接在类中给出函数体,形成内联成员函数。 允许声明重载函数和带默认形参值的函数。 注意:对于带默认形参的函数,若成员函数的声明与定义分开,则参数的默认值只能在函数的声明中给出。类 和 对 象
  • 22. 22内联成员函数为了提高运行时的效率,对于较简单的函数可以声明为内联形式。 内联函数体中不要有复杂结构(如循环语句和switch语句)。 声明内联成员函数的方式: 将函数体放在类的声明中,隐含为内联函数。 使用inline关键字,关键字加到函数声明或函数定义均可。类 和 对 象
  • 23. 23内联成员函数举例(一)class Point { public: void Init(int initX,int initY) { X=initX; Y=initY; } int GetX() {return X;} int GetY() {return Y;} private: int X,Y; };类 和 对 象
  • 24. 24内联成员函数举例(二)class Point { public: void Init(int initX,int initY); int GetX(); int GetY(); private: int X,Y; };类 和 对 象
  • 25. inline void Point:: Init(int initX,int initY) { X=initX; Y=initY; } inline int Point::GetX() { return X; } inline int Point::GetY() { return Y; }25
  • 26. 26对象类的对象是该类的某一特定实体,即类类型的变量。 声明形式: 类名 对象名; 例: Clock myClock;类 和 对 象
  • 27. 27类中成员的访问方式类中成员互访 直接使用成员名 类外访问 使用“对象名.成员名”方式访问 public 属性的成员 当对象为指针时,可用“对象名->成员名”的形式访问public 属性的成员。类 和 对 象
  • 28. 28例.类的应用举例#include using namespace std; class Clock { int Hour, Minute, Second; public: void SetTime(int NewH, int NewM, int NewS); void ShowTime(); };类 和 对 象
  • 29. void Clock :: SetTime(int NewH, int NewM, int NewS) { Hour=NewH; Minute=NewM; Second=NewS; } void Clock :: ShowTime() { cout<
  • 30. void main(void) { Clock myClock; myClock.SetTime(8,30,30); myClock.ShowTime(); } 30
  • 31. 隐含的this指针一个类的每个成员函数都隐含有一个所属类的指针参数,其名字系统规定为this。 对象调用成员函数时: 》把实参传送给成员函数中的形参; 》将对象的地址或指针对象的值传送 给成员函数中隐含的this指针。 执行成员函数时: 对成员数据的访问及成员函数的调用,隐含着对this指针所指对象中数据成员的访问,及成员函数的调用。31类 和 对 象
  • 32. 隐含的this指针所有的成员函数都有一个隐含参数 this ,它是一个指向所属对象自身的指针。 成员函数在访问同一对象的成员时,无须通过 . 或 -> 来指定该成员所属对象,就是因为成员名前的 this-> 修饰是系统默认的。类 和 对 象32
  • 33. 对象成员的访问class X { int a; public: void set(int b){this->a = b;} void set(int a,int b){this->a=a+b;} …… }; 可以把这两个成员函数的声明理解成: void set(X *this,int b); void set(X *this,int a,int b);多余,这是系统默认的必须,以便与参数 a 相区分33
  • 34. 相关说明类的大小 = 所有数据成员所占存储字节数的总和。 类对象中只保留数据成员的存储空间,不保留成员函数的存储空间。 当一个对象调用成员函数时: -- 从所属类中得到编译后的该成员函数执行代码的入口地址; -- 将实参的值或引用传送给形参; -- 把对象的地址传送给this指针,再接着执行函数体,执行后返回到调用前的位置。 34类 和 对 象
  • 35. 35构造函数类中的成员函数:函数名与类名相同; 访问属性:public; 构造函数的作用:是在对象被创建时使用特定的值构造对象,或者说对类对象中的数据成员初始化。构造函数和析构函数
  • 36. 构造函数通常在对象创建时由系统自动调用。 如果程序中未声明,则系统自动产生出一个默认的构造函数。 允许为内联函数、重载函数、带默认形参值的函数。 构造函数和析构函数36
  • 37. 37无参、带参数构造函数举例class Clock { public: Clock (int NewH, int NewM, int NewS);//构造函数 Clock ( ) {Hour =0; Minute =0; Second =0;} void SetTime(int NewH, int NewM, int NewS); void ShowTime(); private: int Hour,Minute,Second; };构造函数和析构函数函数名与类名相同
  • 38. 构造函数的实现: Clock::Clock(int NewH, int NewM, int NewS) { Hour= NewH; Minute= NewM; Second= NewS; } 建立对象时构造函数的作用: void main() { Clock c (1,2,3); //隐含调用带参数构造函数。 Clock b;//隐含调用无参数的构造函数。 c.ShowTime(); b.ShowTime(); }38
  • 39. 默认的构造函数 若定义类时没有任何构造函数,则系统隐含定义一个无参的构造函数,该函数的函数体也为空。 如上例如无构造函数: Clock ( ) { } 调用系统所给的构造函数不会执行任何操作。 因此,对于无参的构造函数可以是系统定义的,也可以是用户定义。 构造函数和析构函数39
  • 40. 执行类对象定义语句时: 首先,为对象分配存储空间; 然后,调用构造函数为数据成员赋初值。构造函数和析构函数40
  • 41. 拷贝构造函数若在主函数中: void main () { Clock a(2,3,4); Clock b(a); //需要调用拷贝构造函数 ….. } 问题:若类中没有定义拷贝构造函数怎么办? 则系统为该类隐含定义一个拷贝构造函数。 构造函数和析构函数41
  • 42. 拷贝构造函数系统为类隐含定义的拷贝构造函数: Clock (Clock &x) { *this = x; // 将能用形参的值赋给被初始化的对象 } 调用系统隐含定义的构造函数有什么隐患?构造函数和析构函数42
  • 43. 拷贝构造函数利用默认的拷贝构造函数,有可能造成“浅层复制”问题。例如假定  class String{  char *p;  public:  String(const char *c);  ...  }; 中没有定义拷贝构造函数,则执行  String s1("This is a string");  String s2(s1); 的效果是:构造函数和析构函数43
  • 44. Thisisastringps2ps1“浅层复制"示意问题:两个对象相互牵连,当对一个对象进行操作时,有可能影响另一个对象。44
  • 45. 构造函数..解决办法:在 String 中增加如下的拷贝构造函数,实现“深层复制” : String::String(String &s){ p=new char[strlen(s.p)+1]; strcpy(p,s.p); }; 这样,执行  String s1("This is a string");  String s2(s1); 的效果是:45
  • 46. “深层复制”示意ThisisastringThisisastringps2ps146
  • 47. 拷贝构造函数若类对象需要用动态空间保存数据,该类通常需要定义拷贝构造函数; 若需要定义拷贝构造函数,通常也就需要重载赋值操作符; 构造函数和析构函数47
  • 48. 48拷贝构造函数拷贝构造函数是一种特殊的构造函数,其形参为本类的对象引用。 class 类名 { public : 类名(形参);//构造函数 类名(类名 &对象名);//拷贝构造函数 ... }; 类名:: 类名(类名 &对象名)//拷贝构造函数的实现 { 函数体 }构造函数和析构函数
  • 49. 49例.拷贝构造函数举例class Point { public: Point (int xx=0,int yy=0) {X=xx; Y=yy;} Point (Point& p); int GetX() {return X;} int GetY() {return Y;} private: int X,Y; };构造函数和析构函数
  • 50. Point::Point (Point& p) { X=p.X; Y=p.Y; cout<<"拷贝构造函数被调用"<
  • 51. 51例.拷贝构造函数举例当用类的一个对象去初始化该类的另一个对象时,系统自动调用拷贝构造函数实现拷贝赋值。 void main(void) { Point A(1,2); Point B(A); //拷贝构造函数被调用 cout<
  • 52. 52例.拷贝构造函数举例若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。例如: void fun1(Point p) { cout<
  • 53. 53拷贝构造函数(上例续)当函数的返回值是类对象时,系统自动调用拷贝构造函数。例如: Point fun2() { Point A(1,2); return A; //调用拷贝构造函数 } void main() { Point B; B=fun2(); }构造函数和析构函数
  • 54. 54拷贝构造函数如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个拷贝构造函数。 拷贝构造函数执行的功能是:用作为初始值的对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。构造函数和析构函数
  • 55. 构造函数... 构造函数定义举例: class MyClass{ int a,b; public: MyClass(){ a=b=0;} MyClass(int x):a(x),b(0){} MyClass(int x,int y); ...... }; MyClass::MyClass(int x,int y) :a(x){ b=y;}只有构造函数才能利用初始化表进行初始化55
  • 56. 构造函数举例构造函数在对象诞生时被调用,例如: MyClass x1(7), x2=8; MyClass y(3,5); MyClass z, w[5];调用 MyClass(int x);调用 MyClass(int x,int y);调用 MyClass();56
  • 57. 构造函数举例 构造函数可以有可选参数 class MyClass{ int a,b; public: MyClass(int x=0,int y=0); ...... }; MyClass::MyClass(int x,int y) :a(x),b(Y){}57
  • 58. 析构函数特殊的成员函数,其函数名就是在所在类的类名前加上~; 析构函数没有参数,无返回值类型修饰(连 void 也不能有); 析构函数在对象消亡时被自动调用;构造函数和析构函数58
  • 59. 59析构函数在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。 用 delete 释放一个动态对象时,将自动调用该对象的析构函数。对于动态对象数组,将针对每一个数组元素调用一次析构函数 如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数。构造函数和析构函数
  • 60. 60构造函数和析构函数举例#include using namespace std; class Point { public: Point(int xx,int yy); ~Point(); //...其它函数原形 private: int X,int Y; };构造函数和析构函数
  • 61. Point::Point(int xx,int yy) { X=xx; Y=yy; } Point::~Point() { } //...其它函数的实现略 注意:当对象中不带有动态存储空间时,通常不需要用户定义该类的析构函数。61
  • 62. 示例:为 Array 增加一个析构函数 class Array{ int *a; //指向数组空间的指针 int n; //数组大小 public: ...... ~Array(){ delete []a; } ...... };构造函数和析构函数举例构造函数和析构函数62
  • 63. 63类的应用举例一圆型游泳池如图所示,现在需在其周围建一圆型过道,并在其四周围上栅栏。栅栏价格为35元/米,过道造价为20元/平方米。过道宽度为3米,游泳池半径由键盘输入。要求编程计算并输出过道和栅栏的造价。游泳池过道
  • 64. #include using namespace std; const float PI = 3.14159; const float FencePrice = 35; const float ConcretePrice = 20; //声明类Circle 及其数据和方法 class Circle { private: float radius; public: Circle(float r); //构造函数 float Circumference(); //圆周长 float Area(); //圆面积 };64
  • 65. // 类的实现 // 构造函数初始化数据成员radius Circle::Circle(float r) {radius=r} // 计算圆的周长 float Circle::Circumference() { return 2 * PI * radius; } // 计算圆的面积 float Circle::Area() { return PI * radius * radius; }65
  • 66. void main () { float radius; float FenceCost, ConcreteCost; // 提示用户输入半径 cout<<"Enter the radius of the pool: "; cin>>radius; // 声明 Circle 对象 Circle Pool(radius); Circle PoolRim(radius + 3); 66
  • 67. // 计算栅栏造价并输出 FenceCost = PoolRim.Circumference() * FencePrice; cout << "Fencing Cost is ¥" << FenceCost << endl; // 计算过道造价并输出 ConcreteCost = (PoolRim.Area() - Pool.Area())*ConcretePrice; cout << "Concrete Cost is ¥" << ConcreteCost << endl; } 运行结果 Enter the radius of the pool: 10 Fencing Cost is ¥2858.85 Concrete Cost is ¥4335.3967
  • 68. 友元函数private的成员可以被成员函数、友元函数及友元类所访问; C++允许在一个类中把外部的有关函数及类声明为它的友元函数或友元类; 被声明为友元函数或友元类后,可直接访问该类的私有成员。友元函数和友元类68
  • 69. 友元函数友元函数的声明 例如: class Fraction{ int nume; int deno; public: friend istream& operator>>(istream&,Fraction&);//注意不是成员函数 …… //其它成员函数 };友元函数和友元类69
  • 70. 友元函数Istream& operator>>(istream& istr, Fraction&x) { //从键盘上按规定格式输入一个分数到X中 char ch; cout<<“输入一个分数,分子和分母用斜线分开:”; istr>>x.nume>>ch>>x.deno; if (x.deno==0) {cerr<<“除数为0,退出运行\n” exit(1);} return istr; } 友元函数和友元类70
  • 71. class Point { int x,y; friend class Circle; public: Point() {x=0;y=0;} Point(int xx,int yy) {x=xx; y=yy; }; Class Circle { Point center; //圆心坐标,数据成员为类对象,初始化只能通过初始化表实现。 int radius; public: Circle() { radius=0;} Circle(int a,int b, int r): center(a,b) //通过初始化表初始化 { radius=r; } …… };友元函数和友元类71
  • 72. 友元类class XX{ int a; friend class YY;//声明YY类为XX类的友 元类 public: XX():a(0){} XX(int data):a(data){} ... }; class YY{ int yy; XX xx; public: YY(int m):yy(m){} YY(int m,int n):yy(m),xx(n){} ... };友元函数和友元类隐含调用 XX() 相当于此处有 ,xx()调用 XX(int)72
  • 73. 友元函数及友元类声明友元函数及友元类用关键词friend开始,且声明语句可在类中任何位置,与它前面使用的任一访问权限关键字无关。 当数据成员为类类型时,对该数据成员的初始化只能通过初始化表实现,不能通过函数体实现。 若没有在初始化表中对类数据成员初始化,则系统隐含调用该类的无参构造函数使之初始化。友元函数和友元类73
  • 74. 74组合的概念类中的成员数据是另一个类的对象。 可以在已有的抽象的基础上实现更复杂的抽象。类 的 组 合
  • 75. 75举例(一)class Point { private: float x,y; //点的坐标 public: Point(float h,float v); //构造函数 float GetX(void); //取X坐标 float GetY(void); //取Y坐标 void Draw(void); //在(x,y)处画点 }; //...函数的实现略类 的 组 合
  • 76. class Line { private: Point p1,p2; //线段的两个端点 public: Line(Point a,Point b); //构造函数 Void Draw(void); //画出线段 }; //...函数的实现略76
  • 77. 77类组合的构造函数设计原则:不仅要负责对本类中的基本类型成员数据赋初值,也要对对象成员初始化。 声明形式: 类名::类名(对象成员所需的形参,本类成员形参) :对象1(参数),对象2(参数),...... { 本类初始化 }类 的 组 合
  • 78. 78类组合的构造函数调用构造函数调用顺序:先调用内嵌对象的构造函数(按内嵌时的声明顺序,先声明者先构造)。然后调用本类的构造函数。(析构函数的调用顺序相反) 若调用默认构造函数(即无形参的),则内嵌对象的初始化也将调用相应的默认构造函数。类 的 组 合
  • 79. 79类的组合举例(二)class Part //部件类 { public: Part(); Part(int i); ~Part(); void Print(); private: int val; };类 的 组 合
  • 80. class Whole { public: Whole(); Whole(int i,int j,int k); ~Whole(); void Print(); private: Part one; Part two; int date; };80
  • 81. Whole::Whole() { date=0; } Whole::Whole(int i,int j,int k): two(i),one(j),date(k) {} //...其它函数的实现略81
  • 82. 82前向引用声明类应该先声明,后使用 如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。 前向引用声明只为程序引入一个标识符,但具体声明在其它地方。类 的 组 合
  • 83. 83前向引用声明举例class B; //前向引用声明 class A { public: void f(B b); }; class B { public: void g(A a); };类 的 组 合
  • 84. 84前向引用声明注意事项使用前向引用声明虽然可以解决一些问题,但它并不是万能的。需要注意的是,尽管使用了前向引用声明,但是在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。请看下面的程序段: class Fred; //前向引用声明 class Barney { Fred x; //错误:类Fred的声明尚不完善 }; class Fred { Barney y; };类 的 组 合
  • 85. 85前向引用声明注意事项class Fred; //前向引用声明 class Barney { public: void method() { x->yabbaDabbaDo(); //错误:Fred类的对象在定义之前被使用 } private: Fred* x; //正确,经过前向引用声明,可以声明Fred类的对象指针 }; class Fred { public: void yabbaDabbaDo(); private: Barney* y; }; 类 的 组 合
  • 86. 86前向引用声明注意事项应该记住:当你使用前向引用声明时,你只能使用被声明的符号,而不能涉及类的任何细节。类 的 组 合
  • 87. 87对象数组声明: 类名 数组名[元素个数]; 访问方法: 通过下标访问 数组名[下标].成员名 数 组
  • 88. 88对象数组初始化数组中每一个元素对象被创建时,系统都会调用类构造函数初始化该对象。 通过初始化列表赋值。 例: Point A[2]={Point(1,2),Point(3,4)}; 如果没有为数组元素指定显式初始值,数组元素便使用默认值初始化(调用默认构造函数)。 数 组
  • 89. 89数组元素所属类的构造函数不声明构造函数,则采用默认构造函数。 各元素对象的初值要求为相同的值时,可以声明具有默认形参值的构造函数。 各元素对象的初值要求为不同的值时,需要声明带形参的构造函数。 当数组中每一个对象被删除时,系统都要调用一次析构函数。 数 组
  • 90. 90对象数组应用举例class Point { public: Point(); Point(int xx,int yy); ~Point(); void Move(int x,int y); int GetX() {return X;} int GetY() {return Y;} private: int X,Y; }; 数 组
  • 91. Point::Point() { X=Y=0; cout<<"Default Constructor"<
  • 92. #include #include "Point.h" using namespace std; int main() { cout<<"Entering main..."<
  • 93. 运行结果: Entering main... Default Constructor Default Constructor Exiting main... Destructor Destructor93
  • 94. The End第九章94