Download presentation
Presentation is loading. Please wait.
1
C++语言编程 宁 博
2
程序设计方法的发展历程 面向过程的程序设计方法 程序的目的:用于数学计算 主要工作:设计求解问题的过程
缺点:对于庞大、复杂的程序难以开发和维护
3
程序设计方法的发展历程 面向过程的结构化程序设计方法 设计思路 程序结构 自顶向下、逐步求精。采用模块分解与功能抽象,自顶向下、分而治之
按功能划分为若干个基本模块 各模块间的关系尽可能简单,功能上相对独立;每一模块内部均是由顺序、选择和循环三种基本结构组成 其模块化实现的具体方法是使用子程序
4
程序设计方法的发展历程 面向过程的结构化程序设计方法 优点
有效地将一个较复杂的程序系统设计任务分解成许多易于控制和处理的子任务,便于开发和维护。
5
程序设计方法的发展历程 面向过程的结构化程序设计方法 缺点:可重用性差、数据安全性差、难以开发图形界面的应用 把数据和处理数据的过程相互独立
当数据结构改变时,所有相关的处理过程都要进行相应的修改 图形用户界面的应用,很难用过程来描述和实现,开发和维护都很困难
6
面向对象的设计思想 面向对象的程序设计方法 基本设计思想 将数据及对数据的操作方法封装在一起,作为一个相互依存、不可分离的整体——对象。
对同类型对象抽象出其共性,形成类。 类通过一个简单的外部接口,与外界发生关系 基本设计思想 封装 软件复用
7
面向对象的设计思想 面向对象的程序设计方法 优点 程序模块间的关系更为简单,程序模块的独立性、数据的安全性就有了良好的保障。
通过继承与多态性,可以大大提高程序的可重用性,使得软件的开发和维护都更为方便
8
面向对象的基本概念 ---- 对象 一般意义上的对象 是现实世界中一个实际存在的事物。
可以是有形的(比如一辆汽车),也可以是无形的(比如一项计划)。 是构成世界的一个独立单位,具有: 静态特征:可以用某种数据来描述 动态特征:对象所表现的行为或具有的功能
9
面向对象的基本概念 ---- 对象 面向对象方法中的对象
是系统用来描述客观事物的一个实体,它是用来构成系统的一个基本单位。对象由一组属性和一组行为构成。 属性:用来描述对象静态特征的数据项。 行为:用来描述对象动态特征的操作。
10
面向对象的基本概念 ---- 类 分类——人类通常的思维方法 分类所依据的原则——抽象
忽略事物的非本质特征,只注意那些与当前目标有关的本质特征,从而找出事物的共性,把具有共同性质的事物划分为一类,得出一个抽象的概念。 例如,石头、树木、汽车、房屋等都是人们在长期的生产和生活实践中抽象出的概念。
11
面向对象的基本概念 ---- 类 面向对象方法中的"类" 具有相同属性和行为的一组对象的集合
为属于该类的全部对象提供了抽象的描述,包括属性和行为两个主要部分。 类与对象的关系: 犹如模具与铸件之间的关系,一个属于某类的对象称为该类的一个实例。
12
面向对象的基本概念 ---- 封装 把对象的属性和行为结合成一个独立的系统单位
尽可能隐蔽对象的内部细节。对外形成一个边界(或者说一道屏障),只保留有限的对外接口使之与外部发生联系。
13
面向对象的基本概念 ---- 继承 继承对于软件复用有着重要意义,是面向对象技术能够提高软件开发效率的重要原因之一。
定义:特殊类的对象拥有其一般类的全部属性与行为,称作特殊类对一般类的继承。 例如:将Person作为一个一般类,Student便是一个特殊类。
14
面向对象的基本概念 ---- 多态性 多态性是指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或行为在一般类及其各个特殊类中具有不同的语义。 例如: 数的加法->实数的加法 >复数的加法
15
C++语言概述 C++语言的产生 C++的特点 一个简单的C++程序实例
16
C++语言的产生 C++是从C语言发展演变而来的,首先是一个更好的C。 引入了类的机制,最初的C++被称为"带类的C"。
1983年正式取名为C++。C++语言的标准化工作从1989年开始,于1994年制定了ANSI C++标准草案。以后又经过不断完善,成为目前的C++。
17
C++的特点 全面兼容C 支持面向对象的方法 它保持了C的简洁、高效和接近汇编语言等特点 对C的类型系统进行了扩充
类、对象、继承、抽象、封装、….
18
一个简单的C++程序实例 #include <iostream.h> void main(void) {
cout<<"Hello!\n"; cout<<"Welcome to c++!\n"; }
19
一个简单的C++程序实例 运行结果: Hello! Welcome to c++!
20
注释 C中的注释:块注释 /* */ C++中的注释 /* This is a comment */ 不允许嵌套 C++继承了C的块注释方式
增加了一种行注释方式 C++把任何一行中从“//”开始直到该行结束的所有内容皆视为注释
21
注释 ---- 一个简单的例子 #include <stdio.h> #include <conio.h>
注释 一个简单的例子 #include <stdio.h> #include <conio.h> void main() { // Checking if a keyword is ESC int i, key; while( 1 ) { key = getch(); // Get a key from console if (key == ‘\x1B’ { printf(“\nEscape! ”); return; } else printf(“\nKeycode is %2XH”, key);
22
作用域 模块:在C语言中模块的概念是指在花括号{}之间的一组语句 作用域的种类 局部模块作用语:变量定义在模块范围内
文件作用域:变量定义在全局范围内,只限于当前文件的存取; 全局作用域:变量定义在全局范围内,对全程序有效。实现方式:include和extern 类作用域:变量定义在类中,在类范围内有效
23
作用域 : : 运算符: 指明作用域。 例 int x; void f() { int x=1; ::x=2; return; }
24
作用域 ---- 一个简单的例子 #include <stdio.h> int global = 10;
void main() { int global = 5; printf(“The value of global is : %d\n”, global); return; }
25
作用域 ---- 作用域分辨操作符 全局变量访问 ::global #include <stdio.h>
int global = 10; void main() { int global = 5; printf(“The value of inner global is : %d\n”, global); printf(“The value of outer global is : %d\n”, ::global); return; }
26
指针 指针是C语言的一个非常重要的特征 实际上是内存地址,非常灵活且非常高效 但又潜伏着非常大的危险性 具有数据类型,可以进行指针运算
无值型指针,void *,是一种非常有用且十分灵活的指针类型
27
常量指针 定义格式 const type * ptr_name; 其含义是指向常量的指针 例子 不允许通过指针来修改其指向的对象的值
可以修改指针的值 例子 const int * ptr; const int i=10; ptr = & i; // 修改指针的值 *ptr = i+10; // 错误操作,试图修改指针指向的内容
28
常量指针 例子 const char * ptr; const char str[10]=“hehehe”;
ptr = str; // 指针赋值 ptr[3] = ‘ ’; // 错误操作,试图修改指针指向的内容
29
常量指针 const int * ptr; int i=10; ptr = &i; // 指针赋值, 等价于(const int *)&i
cout << “i = ” << *ptr << endl; 输出结果为:11
30
常量指针 不允许将const类型的变量地址赋值给一般的变量指针 int * ptr; const int i = 10;
ptr = &i; // 编译错误 ptr = (int *)&i; // 正确 *ptr = *ptr + 1; cout << “i = ” << *ptr << endl; 输出结果为:11
31
指针常量 定义格式 type * const ptr_name; 其含义是指针变量的值不可改变 例子 不允许修改指针变量的地址值
可以修改指针指向的变量值,如果指向的对象不是一个常量的话 例子 int * const ptr1; void * const ptr2;
32
指针常量 int num=10; int * const const_ptr = #
const int * ptr_to_const = # int const * ptr = # const_ptr =(int * const) & num; // 编译错误 //试图修改一个指针常量的地址值 *ptr = num; // 编译错误 //试图修改常量指针指向的对象值
33
void类型 1.说明函数没有返回值; void fun(void) { return ;} 2.表示函数不需要任何入口参数;
double fun(void) { } 3.可将指针说明为void型。这种指针可被赋以其它任何类型的指针。 double a=9.0; double *pd=&a; void *p; p=pd;
34
练习 用指针写一个swap函数,交换两个整数 a和b的值。打印交换前后a,b的值。
35
引用 先看一个简单的例子 int i, &j=i; i=1;
cout<<“i=” <<i<<endl; // output: i=1 j++; cout<<“i=” <<i<<endl; // output: i=2 i++; cout<<“j=” <<j<<endl; // output: j=3
36
引用 引用的定义格式 引用定义说明 type & ref_name = var_name; 引用必须在定义时进行初始化
被引用的变量必须在引用定义之前定义 引用一经定义,便无法重新引用其它变量
37
引用 对引用概念的理解 int i=10, &j=i;
在物理存储中,变量i有存储单元,但引用j没有存储单元,其具体表现是变量i和引用j的物理地址是相同的 内存单元 0XEFFF21 有两个名字,可以将引用j理解为变量i的别名 在同一个模块中的引用没有太大意义,但在函数调用时可以实现传名调用 变量i 0XEFFF21 10 引用j
38
练习 用引用写一个swap函数,交换两个整数 a和b的值。打印交换前后a,b的值。
39
C++的内存格局 全局数据区(data area) 代码区(code area) 栈区(stack area) 堆区(heap area)
40
C++的内存格局 全局变量、静态数据、常量存放在全局数据区; 所有类成员函数和非成员函数代码存放在代码区;
为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区; 其余的空间都被称为堆区。
41
堆内存的分配与释放 当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。 在C++中,申请和释放堆中分配的存贮空间,分别使用new和delete的两个运算符来完成,其使用的格式如下: 指针变量名=new 类型名(初始化式); delete 指针名; new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而动态创建的对象本身没有名字。
42
堆内存的分配与释放 C中的malloc和free函数在C++中可以用 alloc.h头文件中声明
void * malloc(size_t size); void free(void *);
43
堆内存的分配与释放 if ((array=(int*)malloc(arraysize*sizeof(int)))==NULL) {
cout<<“can’t allocate more memory.\n”; exit(1); } … free(array);
44
堆内存的分配与释放 new new 类型名T(初值列表)
结果值:成功:T类型的指针,指向新分配的内存。失败:0(NULL)
45
堆内存的分配与释放 delete delete 指针P 功能:释放指针P所指向的内存。P必须是new操作的返回值。
46
堆内存的分配与释放 0 1.用初始化式(initializer)来显式初始化 例如: int *pi=new int(0);
2.当pi生命周期结束时,必须释放pi所指向的目标: delete pi; 注意这时释放了pi所指的目标的内存空间,也就是撤销了该目标,称动态内存释放(dynamic memory deallocation),但指针pi本身并没有撤销,它自己仍然存在,该指针所占内存空间并未释放。 堆 0 Pi
47
堆内存的分配与释放 1.用初始化式(initializer)来显式初始化 例如: int *pi=new int(0);
2.当pi生命周期结束时,必须释放pi所指向的目标: delete pi; 注意这时释放了pi所指的目标的内存空间,也就是撤销了该目标,称动态内存释放(dynamic memory deallocation),但指针pi本身并没有撤销,它自己仍然存在,该指针所占内存空间并未释放。 堆 Pi
48
堆内存的分配与释放 对于数组进行动态分配的格式为: 指针变量名=new 类型名[下标表达式];
对于数组进行动态分配的格式为: 指针变量名=new 类型名[下标表达式]; delete [ ] 指向该数组的指针变量名;
49
堆内存的分配与释放 if ((array=new int [arraysize])==NULL) … delete [] array; {
cout<<“can’t allocate more memory.\n”; exit(1); } … delete [] array;
50
堆内存的分配与释放 【例】动态数组的建立与撤销 #include <iostream.h>
#include <string.h> void main(){ int n; char *pc; cout<<"请输入动态数组的元素个数"<<endl; cin>>n; pc=new char[n]; // strcpy(pc,"堆内存的动态分配"); cout<<pc<<endl; delete []pc; // 撤销并释放pc所指向的n个字符的内存空间 return ; }
51
堆内存的分配与释放 动态分配的三个特点:首先,变量n在编译时没有确定的值,而是在运行中输入,按运行时所需分配堆空间,这一点是动态分配的优点,可克服数组“大开小用”的弊端。delete []pc是将n个字符的空间释放,而用delete pc则只释放了一个字符的空间; 其次如果有一个char *pc1,令pc1=p,同样可用delete [] pc1来释放该空间。尽管C++不对数组作边界检查,但在堆空间分配时,对数组分配空间大小是纪录在案的。 第三,没有初始化式(initializer),不可对数组初始化。
52
堆内存的分配与释放 指针使用的几个问题: 1.动态分配失败。返回一个空指针(NULL),表示 发生了异常,堆资源不足,分配失败。
指针使用的几个问题: 1.动态分配失败。返回一个空指针(NULL),表示 发生了异常,堆资源不足,分配失败。 2.指针删除与堆空间释放。删除一个指针p(delete p;) 实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针
53
堆内存的分配与释放 3.内存泄漏(memory leak)和重复释放。new与 delete 是配对使用的, delete只能释放堆空间。 如果new返回的指针值丢失,则所分配的堆空间无 法回收,称内存泄漏,同一空间重复释放也是危险 的,所以必须妥善保存new返回的指针,以保证不 发生内存泄漏,也必须保证不会重复释放堆内存空 间。 4.动态分配的变量或对象的生命期。无名对象,它的 生命期并不依赖于建立它的作用域,比如在函数中 建立的动态对象在函数返回后仍可使用。我们也称 堆空间为自由空间(free store)就是这个原因。
54
练习 建立动态整型数组 用户输入数组长度, 并且用户输入初始化数组, 求和并打印到屏幕。
55
练习 编写程序。测试堆内存的容量:每次申请一个数组,内含100个整数,直到分配失败,并打印堆容量。
56
面向对象编成的基本特点 抽象 封装 继承 多态性
57
面向对象编成的基本特点 ---抽象 抽象是对具体对象(问题)进行概括,抽出这一类对象的公共性质并加以描述的过程。
只注意问题的本质及描述,而忽略实现过程或细节。 数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。 代码抽象:描述某类对象的共有的行为特征或具有的功能。 抽象的实现:通过类定义中的public来实现。 抽象出来的数据和行为对外是可见的
58
面向对象编成的基本特 ---- 抽象 class Watch { public:
void SetTime(int NewH, int NewM, int NewS); void ShowTime(); private: int Hour,Minute,Second; }; 抽象的行为
59
面向对象编成的基本特点 ---封装 将实现细节涉及到的数据成员、实现代码封装在类中,对外是不可见的。
目的是曾强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员 实现封装:通过类定义中的private来实现
60
面向对象编成的基本特点 ---封装 class Watch { public:
void SetTime(int NewH, int NewM, int NewS); void ShowTime(); private: int Hour,Minute,Second; }; 封装的数据成员
61
面向对象编成的基本特点 ---继承 继承是C++中支持层次分类的一种机制,允许程序员在保持原有类特性的基础上,进行更具体的类定义,形成一个类层次关系 继承的主要目的和作用 代码复用 提高程序设计语言的建模能力 接近现实世界的实际模型,也就是模型表达能加自然,这样使得建模现实世界更加容易 具体的内容将在后面的章节中介绍
62
面向对象编成的基本特点 -多态性 多态的概念:同一名称,不同的功能实现方式。 目的:达到行为标识统一,减少程序中标识符的个数。
63
类的概念和定义 类的概念 类是用来描述一组对象的相同属性和行为,它为属于该类的全部对象提供了统一的抽象描述 利用类可以实现细节进行封装
包括抽象属性和抽象行为 利用类可以实现细节进行封装 包括用于实现的成员数据和成员函数 利用类易于编写大型复杂程序,其模块化程度比C中采用函数更高
64
类的概念和定义 类的定义 类是一种用户自定义的类型 class 类名称 { public: 公有成员(外部接口) private:
私有成员 (实现细节) protected: 保护型成员 (用于类层次的访问控制) } 类是一种用户自定义的类型
65
类的概念和定义 class Watch { public: void SetTime(int NewH, int NewM, int NewS); void ShowTime(); private: int Hour, Minute, Second; }; 成员函数 成员数据
66
类的概念和定义 void Watch :: SetTime(int NewH, int NewM, int NewS) {
Hour=NewH; Minute=NewM; Second=NewS; } void Watch :: ShowTime() cout<<Hour<<":"<<Minute<<":"<<Second;
67
类的概念和定义 成员数据 成员函数 与一般的变量定义相同,但需要将它放在类的定义体中
在类中说明原形,在类外定义函数体实现,并在函数名前使用类名加以限定。也可以直接在类中定义函数体,形成内联成员函数。 在类的定义外部实现内联成员函数时一定要显式加上inline关键词 允许定义重载函数和带缺省形参值的函数
68
类的概念和定义 内联成员函数 实现内联成员函数的方式: 为了提高运行时的效率,对于较简单的函数可以声明为内联形式。 将函数体放在类的定义中。
在外部实现中显式使用inline关键字。
69
类的概念和定义 内联成员函数的内部实现 class Location { public: void Init(int initX,
int initY) X=initX; Y=initY; } int GetX() {return X;} int GetY() {return Y;} private: int X,Y; }; 内 联 成 员 函 数
70
类的概念和定义 内联成员函数的外部实现 class Location { public:
void Init(int initX, int initY); int GetX(); int GetY(); private: int X,Y; };
71
类的概念和定义 内联成员函数的外部实现 inline void Location:: Init(int initX,int initY)
{ X=initX; Y=initY; } inline int Location::GetX() { return X; } inline int Location::GetY() { return Y; }
72
类的概念和定义 私有成员 (私有数据成员和私有成员函数)
在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。 如果紧跟在类名称的后面声明私有成员,则关键字private可以省略。
73
类的概念和定义 私有成员 class Watch { private: int Hour,Minute,Second; public:
void SetTime(int NewH, int NewM, int NewS); void ShowTime(); };
74
类的概念和定义 私有成员 class Watch w; int i; i=w.Hour; // 编译错误,试图访问私有成员数据
w.SetTime(……); // 正确,因为SetTime是公有成员函数
75
类的概念和定义 公有成员(公有数据成员和公有成员函数)
在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
76
类的概念和定义 公有成员 class Watch { public: int Hour,Minute,Second;
void SetTime(int NewH, int NewM, int NewS); void ShowTime(); };
77
类的概念和定义 公有成员 class Watch w; int i; i=w.Hour; //正确,因为Hour是公有成员数据
w.SetTime(……); // 正确,因为SetTime是公有成员函数
78
类的概念和定义 保护成员(保护数据成员和保护成员函数) 与private类似,在类的外部函数中不能访问类中的保护成员
在类的子类中可能可以访问该类的保护成员,这取决于访问控制,在后面讲述
79
类的概念和定义 不能试图给类定义中的数据成员进行初始化 类仅仅是类型的说明,而对象的数据成员的初始化必须有构造函数来完成 错误!!!
class Watch { private: int Hour = 0, Minute = 0, Second = 0; public: void SetTime(int NewH, int NewM, int NewS); void ShowTime(); };
80
C++中的结构 C++中的结构比C语言中的结构具有更加强大的功能 在C++中结构和类具有类似的功能
所有的解释与类相同,除了 只是缺省的访问控制是public,而类的缺省访问控制是private 结构是C++扩展C的第一步,但不推荐这种结构编程方式
81
C++中的结构 Struct Person { int age; int getAge() { return age; }; };
Struct Student : Person { int sno; int getSno() { return sno; }; }
82
对象 类的对象是该类的某一特定实体,即类类型的变量。 定义形式: 类名 对象名; 例: watch myWatch;
定义形式: 类名 对象名; 例: watch myWatch; 当然也可以有对象的指针,对象的引用等
83
对象 类中成员的访问方式 类中的成员函数访问类中的成员数据 类外访问 直接使用成员名
使用“对象名.成员名”方式访问 public 属性的成员
84
对象 类中成员的访问方式 #include<iostream.h> class Watch { ......//类的定义略 }
//......类的实现略 void main(void) { Watch myWatch; myWatch.SetTime(8,30,30); myWatch.ShowTime();
85
构造函数 构造函数的作用是在对象被创建时使用特定的值构造对象,或者说将对象初始化为一个特定的状态。 在对象创建时由系统自动调用。
如果程序中未定义出,则系统自动产生出一个缺省形式的构造函数 ---- 缺省构造函数 允许为内联构造函数、重载构造函数、带缺省形参值的构造函数
86
构造函数 定义说明 构造函数名与所在的类名相同; 构造函数没有返回类型,因此在定义构造函数时不能带有任何带有返回值的返回语句;
不能将构造函数说明为void类型; 可以有返回语句 return; 构造函数只能用于构造类的实例对象,它的调用是隐含的,不能对任何已经构造的对象再次进行构造函数的显式调用;
87
构造函数 定义说明 构造函数一般总是被定义为公有成员,否则在外部定义类对象时无法对其进行初始化
构造函数的定义与普通成员函数一样,可以在类定义体内部,也可以放在类定义体的外部,且可以直接访问其类所在的所有其他成员,包括成员数据和成员函数
88
构造函数 定义说明 class Person { 必须将该构造函数变为公有成员 int age; Person() { age=0; };
class Student :public Person { int stno; Person p; // 编译错误 必须将该构造函数变为公有成员
89
构造函数 一个类可以有多个构造函数,和普通函数一样,同名的构造函数相互重载 Class d_String { Public:
d_String(const d_String *); d_String(const char *); …… };
90
构造函数 对象的初始化 d_String str1; // 调用缺省构造函数 d_String str1(); // 调用缺省构造函数
d_String str1(“Bing Wang”); // 调用构造函数 d_String str1=d_String(); // 调用缺省构造函数 d_String str1=d_String(“Bing Wang”); // 调用构造函数
91
构造函数 构造函数一般用来对对象的成员数据进行初始化 当类的成员数据比较多时这种初始化语句比较多 Class Account {
public: Account() { _name = 0; _balance = 0.0; _acct_nmbr = 0; };
92
构造函数 Account(const char * name, double balance, int acct_nmbr) {
_name = new char [ strlen(name)+1 ]; strcpy(_name, name); _balance = balance; _acct_nmbr = acct_nmbr; }; ……..
93
构造函数 成员初始化表 成员初始化表 初始化列表的效率比赋值语句的效率要高 Class Account { public:
Account() : _name ( 0), _balance (0.0), _acct_nmbr (0) {}; Account(const char * name, double balance, int acct_nmbr) : balance = balance, _acct_nmbr = acct_nmbr; { _name = new char [ strlen(name)+1 ]; strcpy(_name, name); }; …….. 初始化列表的效率比赋值语句的效率要高
94
构造函数 const成员函数 一个类中的成员函数可以是const class d_Set { public: d_Set();
unsigned long cardinality() const; d_Boolean is_empty() const; …… }
95
const成员函数 const成员函数含义是该函数只能读取该类中的成员数据,而不能修改,因此 根据const成员函数的定义
该成员函数不能修改所在类的成员数据 该成员函数不能调用所在类中的非const成员函数 根据const成员函数的定义 不能将构造函数定义为const类型,因为它要初始化成员数据的值 const关键字可以被用于参与对重载函数的区分 const是函数类型的一个组成部分,因此在实现部分也要带const关键字。
96
const对象 const对象与其他类型的const对象一样 const对象调用构造函数初始化对象后不能再被修改
const d_String str1 = “Bing Wang”; …. str1 = “Bing Wang”; //错误 通常,const对象只能调用const成员函数
97
举例 #include<iostream.h> class R { public:
R(int r1, int r2){R1=r1;R2=r2;} void print(); void print() const; private: int R1,R2; };
98
举例 void R::print() { cout<<R1<<":"<<R2<<endl;
} void R::print() const { cout<<R1<<";"<<R2<<endl; void main() { R a(5,4); a.print(); //调用void print() const R b(20,52); b.print(); //调用void print() const
99
隐含的this指针 来看一个例子 Class ArrayX { Private : int dim; double * x;
Public : ArrayX(int d); ~ArrayX(); void WriteX( int n, double val); double ReadX(int n); void PrintX( int n ); void PrintAll( void ); };
100
隐含的this指针 问题:成员函数,例如ReadX,是如何知道当前对象的地址的呢?因为只有通过地址才能操作该对象中的数据成员;这正好是this指针的作用 为了解决上述问题,实际上在成员函数参数表中存在一个额外的参数: WriteX(Array * this, double val); ReadX(ArrayX * this, int n); PrintX(ArrayX * this, int n); PrintAll(ArrayX * this);
101
隐含的this指针 这个ArrayX * this指针是隐含的,由他的调用这提供,指向被操作的对象
Void Write(int n, double val) { if (a < this->dim) this->x[n] = val; } 上述程序中this指针可缺省 Void Write(int n, double val) { if (a < dim) x[n] = val; }
102
隐含的this指针 This指针除了上述用法之外,可以使用在返回值中 class Screen { Public: Screen();
Screen & set( char ); Screen & move( int, int ); Screen & clear(); Screen & display(); Private: …….// 数据成员说明 };
103
隐含的this指针 This指针除了上述用法之外,可以使用在返回值中 Screen & Screen::clear( char bg) {
_cursor = 0; _screen.assign(_screen.size(), bg); return *this; } myScreen.clear().move(2,2).set(‘*’).display();
104
缺省构造函数 缺省构造函数是指没有参数的构造函数
当类的定义中没有定义构造函数时,系统会为该类生成一个缺省构造函数,在进行对象说明时系统会调用系统生成的缺省构造函数 系统生成的缺省构造函数不做任何事情 当类中用户定义了构造函数后,编译器就会忽略系统生成的缺省构造函数,即使你没有定义缺省构造函数 建议用户定义自己的缺省构造函数,因为系统生成的缺省构造函数不做任何事情
105
缺省构造函数 Class d_String { Public: d_String(); // 缺省构造函数
d_String(const d_String *); d_String(const char *); …… }; d_String str; // 调用用户定义的缺省构造函数
106
缺省构造函数 Class d_String { Public: //d_String(); // 缺省构造函数
//d_String(const d_String *); //d_String(const char *); …… // 没有其它构造函数定义 }; d_String str; // 调用系统生成的缺省构造函数
107
缺省构造函数 Class d_String { Public: //d_String(); // 缺省构造函数
d_String(const d_String *); d_String(const char *); …… }; d_String str; // 编译错误,因为编译器忽略系 // 统生成的缺省构造函数
108
拷贝构造函数 拷贝构造函数是一种特殊的构造函数,其形参为本类的对象引用 class 类名 { public : 类名(形参);//构造函数
类名(类名 &对象名);//拷贝构造函数 ... }; 类名:: 类名(类名 &对象名)//拷贝构造函数的实现 { 函数体 }
109
拷贝构造函数 class Location { public:
Location(int xx=0,int yy=0){X=xx; Y=yy;} Location(Location & p); int GetX() {return X;} int GetY() {return Y;} private: int X,Y; };
110
拷贝构造函数 如果没有定义类的拷贝构造函数,系统就会自动生成一个默认的拷贝构造函数,其功能是把初始值对象的每个数据成员的值都复制到新建立的对象中 何时调用拷贝构造函数 Location L1; Location L2(L1); // 调用拷贝构造函数 Location L3 = L1; // 调用拷贝构造函数 F( L1 ); // 调用拷贝构造函数 L1 = g(); //调用拷贝构造函数, 为被返回 // 的对象建立一个临时对象
111
拷贝构造函数 课堂提问 既然系统能够自动生成一个默认的拷贝构造函数,那么用户是否还有必要生成自己的拷贝构造函数呢?理由是什么?
112
浅拷贝与深拷贝 浅拷贝 ---- 通过一个例子来理解浅拷贝的概念 指针p1 C语言的例子 char * p1, *p2; Bin Wang
p1 = malloc( 20 ); strcpy(p1, “Bin Wang”); p2 = p1; Bin Wang 指针p2
113
浅拷贝与深拷贝 浅拷贝 ---- 通过一个例子来理解浅拷贝的概念 C++的例子 String::String() { str = 0; }
Class String { String(); String(const char *) ~String(); private: char * str; }; String::String() { str = 0; } ~String::String() { if (str!=0) delete str; } String::String(const char * s) { str = new char [strlen(s)+1]; strcpy(str, s); } String * s1 = new String(“Bin Wang”); // String * s2 = new String (s1); // 调用缺省拷贝构造函数
114
浅拷贝与深拷贝 浅拷贝 ---- 通过一个例子来理解浅拷贝的概念 浅拷贝的缺点 当一个指针指向同一个对象或内存单元时存在潜在的错误源泉,例如
当对象s1被析构以后,对象s2无法析构是由于缺省拷贝构造函数引起的,必须重写 s1 str Bin Wang s2 str
115
浅拷贝与深拷贝 深拷贝 String::String() { str = 0; }
~String::String() { if (str!=0) delete str; } String::String(const char * s) { str = new char [strlen(s)+1]; strcpy(str, s); } String::String(String & s) { str = new char [strlen(s.str)+1]; strcpy(str, s.str); String * s1 = new String( “Bin Wang”); String * s2 = new String( s1 ); 深拷贝 Class String { String(); String(const char *); String(String &); ~String(); private: char * str; };
116
浅拷贝与深拷贝 深拷贝 深拷贝可以很好地避免指针悬挂问题 s1 str Bin Wang s2 str Bin Wang
117
析构函数 完成对象被删除前的一些清理工作。 在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。
如果程序中未定义析构函数,编译器将自动产生一个缺省的析构函数。 与构造函数一样,析构函数一般总是被说明为类的一个公有函数成员,由~加上函数名称构成,没有返回值,且没有任何参数
118
析构函数 当一个对象中没有涉及动态内存分配时,可以使用系统生成的缺省析构函数。如果涉及到动态内存问题时,应该编写自己的显式析构函数;否则存在内存泄漏问题 当然也可能存在需要处理的情况:在对象的生命周期内动态获得的各种资源,在析构函数中应该需要处理;例如该对象获得的互斥锁,在析构函数中应该释放该锁;
119
析构函数 Class Account { Public: Account();
Account ( const char *, double = 0.0 ); Account ( const Account & ); ~Account(); Private: char *_name; unsigned int _acct_nmbr; double _balance; }; inline Account::~account() { delete [] _name; }
120
例1 (1) Account global( “James Joyce” ); (2) int main() {
(3) Account local ( Anna Livia Plurabelle”, ); (4) Account & loc_ref = global; (5) Account * pact = 0; (6) { Account local_two ( “stephen hero” ); (7) pact = new Account( “Stephen Dedalus” ); (8) } (9) delete pact; (10) }
121
例1 该例子中调用了4次构造函数; 对应地,程序也要调用4次析构函数;
122
类的应用举例 一圆型游泳池如图所示,现在需在其周围建一圆型过道,并在其四周围上栅栏。栅栏价格为35元/米,过道造价为20元/平方米。过道宽度为3米,游泳池半径由键盘输入。要求编程计算并输出过道和栅栏的造价。 游泳池 过道
123
类的应用举例 #include <iostream.h> const float PI = 3.14159;
const float FencePrice = 35.0; const float ConcretePrice = 20.0; //定义类Circle 及其数据和方法 class Circle { private: float radius; public: Circle(float r); //构造函数 float Circumference() const; //圆周长 float Area() const; //圆面积 };
124
类的应用举例 // 类的实现 // 构造函数初始化数据成员radius Circle::Circle(float r): radius(r)
{ } // 计算圆的周长 float Circle::Circumference() const { return 2 * PI * radius; } // 计算圆的面积 float Circle::Area() const return PI * radius * radius;
125
类的应用举例 void main () { float radius; float FenceCost, ConcreteCost;
// 提示用户输入半径 cout << "Enter the radius of the pool: "; cin >> radius; // 定义 Circle 对象 Circle Pool(radius); Circle PoolRim(radius + 3);
126
类的应用举例 // 计算栅栏造价并输出 FenceCost = PoolRim.Circumference() * FencePrice;
cout << "Fencing Cost is $" << FenceCost << endl; // 计算过道造价并输出 ConcreteCost = (PoolRim.Area() - Pool.Area())*ConcretePrice; cout << "Concrete Cost is $" << ConcreteCost << endl; }
127
类的应用举例 运行结果 Enter the radius of the pool: 40 Fencing Cost is $945.598
Concrete Cost is $
128
课堂练习 用类实现计数器抽象数据类型 class CCounter{ public: CCounter(); void Up();
void Down(); void Zero(); void Value(); private: int a; };
129
课堂练习 设计一个栈类型,考虑下面的数据结构 const int STACK_SIZE=20; class CStack{ public:
CStack(int sz); ~CStack(); void push(int e); int pop(); private: int *item; int sp; int size; };
130
课堂练习 private: int *item; int sp; int size; };
131
CPoint类 声明一个CPoint类来描述点对象。 //point.h class CPoint{ public:
CPoint (int x, int y); int XCoord(); int YCoord(); void Move(int xOffset, int yOffset); private: int X,Y; };
132
CPoint:: TPoint(int x,int y)
{ X=x; Y=y; } int CPoint:: XCoord() {return X;} int CPoint:: YCoord() {return Y; } void CPoint:: Move(int xOffset, int yOffset) X+= xOffset; Y+=yOffset; void main(void) CPoint p(3,4); p.Move(10,20); cout<<’(’<<p.XCoord()<<’,’<< p.XCoord()<<’)’<<endl;
133
类的静态成员 在C语言中有静态变量的概念 对应地,在C++中也有静态成员的概念
局部静态变量 ---- ,相当于一个局部范围内能够使用的全局变量 对应地,在C++中也有静态成员的概念 静态数据成员 静态成员函数
134
静态数据成员 用关键字static声明 该类的所有对象维护该成员的同一个拷贝 Class Score { char student[20];
int language; int maths; int history; Public: static int passmark; static int passnum; Score(); ~Score(); };
135
静态数据成员 必须在类外定义和初始化,用(::)来指明所属的类 int Score::passmark = 60;
Int Score::passnum = 0; 上述定义和初始化必须放在main()函数的外面来进行,象全局变量一样 Void main() { int Score::passmark = 60; int Score::passnum = 0; …… } int Score::passmark = 60; Int Score::passnum = 0; void main() { …. } Static int Score::passmark = 60; Static int Score::passnum = 0; void main() { …. }
136
静态数据成员 静态数据成员在类中仅仅是说明 其内存空间是在初始化语句时进行分配的 在类中说明静态数据成员是不能进行初始化
Class Score { char student[20]; int language; int maths; int history; Public: static int passmark = 60; static int passnum = 0; Score(); ~Score(); };
137
没有任何理由推荐这种调用方式,因为静态数据成员是独立于类对象的
静态数据成员可以是公有的,也可以是私有的或保护的 公有静态数据成员可以在类外访问 象普通成员函数一样通过对象来访问 void main() { cout << Score::passmark << endl; …. } void main() { Score s; cout <<s.passmark << endl; } 没有任何理由推荐这种调用方式,因为静态数据成员是独立于类对象的
138
静态数据成员 如果在类的成员函数中访问静态数据成员时可以将类名和分辨符省略 Class Score { Score() {
char student[20]; int language; int maths; int history; Public: static int passmark; static int passnum; Score() { Score::passmark=60; passnum=0; ~Score(); };
139
静态数据成员 例子:给予上面的类说明 #include <iostream.h>
int Score::passmark = 50; int Score::passnum = 0; void main() { cout << Score::passmark << endl; // 输出是多少? Score s; cout << s.passmark << endl; // 输出又是多少? cout << Score::passmark << endl; // 输出又是多少? }
140
静态数据成员 私有静态数据成员 保护型静态数据成员 象公有静态成员一样进行定义和初始化 在类外不能进行访问,只有类的成员函数才能访问它
象公有静态数据成员一样定义和初始化 在类中可以访问它 在类的子类中也可能可以访问它 ---- 在后面类的继承中讲述
141
静态成员函数 一个成员函数可以是静态的 class Student { char filename[20]; int age;
int grade; static int total; public: void PrintInfo(); static void PrintTotal(); }; 静态成员函数
142
没有任何理由推荐这种调用方式,因为静态成员函数是独立于类对象的
与静态数据成员类似,静态成员函数不依赖于类的对象而存在 类外代码可以使用类名和作用域操作符来调用静态成员函数。 class_name::func_name(arguments_list); Student::PrintTotal(); 静态成员函数也可以象普通成员函数那样调用 Student s; s.PrintTotal(); 没有任何理由推荐这种调用方式,因为静态成员函数是独立于类对象的
143
静态成员函数 在类的成员函数中调用静态成员函数时,其类名和分别符可以省略 Student::PrintTotal(); // 推荐方式
静态成员函数也有三种类型 公有静态成员函数 私有静态成员函数 保护型静态成员函数 公有静态成员函数可以在类外进行调用 私有静态成员函数可以在类内进行调用 保护型静态成员函数可以在类内或其子类中调用
144
静态成员函数 类的静态成员函数不能试图直接访问普通数据成员和调用普通成员函数 class A {
public: static void f1(); private: int x; }; void A::f1() { cout << x << endl; //错误 A a; cout << a.x << endl; // 正确 } 为什么?
145
静态对象 局部静态对象与C中的静态局部变量非常类似,但需要注意构造函数和析构函数的调用
其构造函数在代码执行过程中,第一次遇到它的变量定义时被调用,当函数再次进入时就不再再次调用构造函数 其析构函数在整个程序执行结束后,进程退出前被调用
146
静态对象 全局静态对象(变量)与全局对象(变量)等价 全局静态对象与C中的全局静态变量也非常类似,但也需要注意构造函数和析构函数的调用
其构造函数在main()函数前被调用,如果有多个全局对象,构造函数调用顺序取决于变量的定义顺序 其析构函数在整个程序执行结束后,进程退出前被调用
147
练习 记录一个类在程序运行时有多少个对象被创建。 class TPoint{ public: TPoint(int x,int y);
int XCoord(); int YCoord(); ~ TPoint(); static int ObjectExisted(); private: static int Number; int X,Y; };
148
友元 通常,对于类的私有成员,外部类或外部函数是无法访问和调用它们的 但有的时候某些类或函数需要访问另外一些类的私有成员
通过友元可以实现这种访问控制 友元函数 友元类 需要注意的是,友元打破了数据封装和数据隐藏的概念。但这种开放在某些时候是非常必要的,尤其是对于操作符的重载
149
友元函数 友元函数是在类定义中由关键字friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问 private 和 protected成员 访问对象中的成员必须通过对象名
150
友元函数 #include <iostream.h> #include <math.h> class Point
{ public: Point(double xi, double yi) {X=xi; Y=yi; } double GetX() {return X;} double GetY() {return Y;} friend double Distance( Point& a, Point& b); private: double X, Y; };
151
友元函数 double Distance( Point& a, Point& b) { double dx=a.X-b.X;
double dy=a.Y-b.Y; return sqrt(dx*dx+dy*dy); } int main() { Point p1(3.0, 5.0), p2(4.0, 6.0); double d=Distance(p1, p2); cout<<"The distance is "<<d<<endl; return 0;
152
友元函数 友元函数的说明可以放在类中的任何位置,public部分、private部分、或protected部分
它们具有相同的含义,因为友元函数不是类的成员函数 所以,一般将友元函数说明放在public部分
153
友元类 若类A为类B的友元类,则类A的所有成员函数都能访问类B的私有成员(私有成员数据和私有成员函数)。
定义语法:将友元类名在另一个类中使用friend修饰说明。
154
友元类 class A class B { friend class B; { public: public:
void Display() {cout<<x<<endl;} private: int x; } class B { public: void Set(int i); void Display(); private: A a; };
155
友元类 void B::Set(int i) { a.x=i; } void B::Display() a.Display();
156
友元类 与友元函数类似,友元类的的说明可以放在类中的任何位置,public部分、private部分、或protected部分
它们具有相同的含义 所以,一般将友元函数说明放在public部分
157
重载 重载指的是一个标识符或运算符可同 时用于为多个函数命名, 这多个函数的 作用域相同。在C++中, 重载可分为两
种情况: 函数重载和运算符重载。重载 函数的束定是在编译时完成的, 是通过 函数原型来实现的。
158
class A{ { public: void Print(int i) {cout<<i<<endl;} void Print(float f) {cout<<f<<endl;} void Print(const char s) {cout<<s<<endl;} void Print(const char s, int n) {cout<<&s[n]<<endl;} }; int main() { A a; a.Print(5); a.Print(4.3); a.Print(program); a.Print(program, 3); }
159
运算符重载 运算符可以看成是一种函数,即运算 符函数。对于基本类型,运算符都是 预定义的,但对于类对象,用户可以
重新定义运算符函数,以便设置运算 符在类对象中新的含义。这种定义运 算符在某类对象中的操作的做法,就 称为运算符重载。
160
问题举例 ---- 复数的运算 class Complex //复数类声明 { public:
Complex(double r=0.0,double i=0.0){real=r;imag=i;} //构造函数 void display(); //显示复数的值 private: double real; double imag; };
161
问题举例 ---- 复数的运算 如何实现复数的加减运算呢? 方法1:通过函数重载实现复数的加减运算
成员函数:Complex plus(Complex &) Complex minus(Complex &) c1.plus(c2) c1.minus(c2) 缺点:不符合通常的表达方式
162
问题举例 ---- 复数的运算 方法2:重载“+”、“-”运算符 c1 + c2, c1 – c2 符合通常的表达方式
如果没有重载Complex类的“+”、“-”运算符,上述表达式是错误的,因为C++中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)
163
规则和限制 可以重载C++中除下列运算符外的所有运算符: 只能重载C++语言中已有的运算符,不可臆造新的 不改变原运算符的优先级和结合性。
成员运算符:. 指针取内容运算法:* 作用域分辨符::: 三目运算符:? : # sizeof() 只能重载C++语言中已有的运算符,不可臆造新的 不改变原运算符的优先级和结合性。 不能改变操作数个数。 经重载的运算符,其操作数中至少应该有一个是自定义类型。
164
操作符重载的形式 重载为类成员函数。 重载为友元函数。
165
运算符函数重载 定义形式 函数类型 operator 运算符(形参) { ...... } class Complex { ……
Complex operator + (const Complex &); Complex operator – (const Complex &); };
166
运算符函数重载 实现 Complex complex::operator + (const Complex & r) {
Complex t; t.real = real + r.real; t.imag = imag + r.imag; return t; } 需要定义构造函数 Complex(double, double) 可以简化为 Complex complex::operator + (const Complex & r) { return Complex(real + r.real, imag + r.imag); }
167
运算符函数重载 调用的形式 c1 + c2; c1.operator+(c2) 不推荐这种方式,但是正确
重载为类成员函数时 参数个数=原操作数个数-1 (后置++、--除外) 重载为友元函数时 参数个数=原操作数个数,且至少应该有一个自定义类型的形参。 如果void display()是Complex的成员函数时 (c1 + c2).display();
168
运算符友元函数 如果需要重载一个运算符,使之能够用于操作某类对象的私有成员,可以此将运算符重载为该类的友元函数。
函数的形参代表依自左至右次序排列的各操作数。 后置单目运算符 ++和--的重载函数,形参列表中要增加一个int,但不必写形参名。
169
运算符友元函数 #include<iostream.h> class complex //复数类声明 {
public: //外部接口 complex(double r=0.0,double i=0.0) { real=r; imag=i; } //构造函数 friend complex operator + (complex c1,complex c2); friend complex operator - (complex c1,complex c2); void display(); //显示复数的值 private: //私有数据成员 double real; double imag; };
170
运算符友元函数 complex operator +(complex c1,complex c2) //运算符重载友元函数实现
{ return complex(c2.real+c1.real, c2.imag+c1.imag); } complex operator -(complex c1,complex c2) //运算符重载友元函数实现 { return complex(c1.real-c2.real, c1.imag-c2.imag);
171
类的继承与派生 被继承的已有类称为基类(或父类)。 派生出的新类称为派生类。
172
继承与派生问题举例
173
继承与派生问题举例
174
继承与派生问题举例
175
继承与派生的目的 继承的目的:实现代码重用。 派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造。
176
派生类的定义 class 派生类名:继承方式 基类名 { 成员定义; }
177
例子 基类 派生类 class Student : public Person { public: Student();
class Person { public: Person (); Person (Person &); ~Person(); int Age(); private: char name[20]; Date birthday; boolean sex; protected: char title[10]; }; 基类 派生类 class Student : public Person { public: Student(); Student(Student &); ~Student(); private: int sno; char dept[20]; };
178
类成员的访问控制 三种继承方式 公有继承 私有继承 保护继承 当继承方式缺省时为私有继承方式
179
公有继承(public) 基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可访问。
180
公有继承举例 ---- 1 class A { class B : public A { public: public: int a;
Private: int b; Protected: int c; }; class B : public A { public: int d; int f() { int i = a; // OKAY i = b; // incorrect i = c; // OKAY } Private: int e; Protected: int f; }; void main() { B bb; int j = bb.c; // incorrect j = bb.a; // okay }
181
私有继承(private) 基类的public和protected成员都以private身份出现在派生类中,但基类的private成员不可访问。 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员。 通过派生类的对象不能访问基类中的任何成员。
182
私有继承举例 class Rectangle:private Location { public:
void InitR(int x,int y,int w,int h); void Move(int xOff,int yOff); int GetX() {return Location::GetX();} int GetY() {return Location::GetY();} int GetH() {return H;} int GetW() {return W;} private: int W,H; };
183
私有继承举例 void Rectangle::InitR(int x,int y,int w,int h) { InitL(x,y);
W=w; H=h; } void Rectangle::Move(int xOff,intyOff) Location::Move(xOff,yOff);
184
私有继承举例 int main() { //通过派生类对象只能访问本类成员 Rectangle rect;
{ //通过派生类对象只能访问本类成员 Rectangle rect; rect.InitR(2,3,20,10); rect.Move(3,2); // rect.Location::Move(3,2); cout<<rect.GetX()<<',' <<rect.GetY()<<',' <<rect.GetH()<<',' <<rect.GetW()<<endl; return 0; }
185
保护继承(protected) 基类的public和protected成员都以protected身份出现在派生类中,但基类的private成员不可访问。 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员。 通过派生类的对象不能访问基类中的任何成员
186
private成员的特点与作用 一个类的private成员(数据和函数)能够被该类的成员所访问
187
protected 成员的特点与作用 对建立其所在类对象的模块来说(水平访问时),它与 private 成员的性质相同。
对于其派生类来说(垂直访问时),它与 public 成员的性质相同。 既实现了数据隐藏,又方便继承,实现代码重用
188
protected 成员举例 class A { class A { protected: protected: int x; int x;
} int main() { A a; a.X=5; //错误 class A { protected: int x; } class B: public A { public: void Function(); }; void B::Function() { X=5; //正确
189
单继承与多继承 单继承 AB 多重继承 (A, B)C 多重派生 AB AC 多层派生 :A B C
派生类只从一个基类派生。 多重继承 (A, B)C 派生类从多个基类派生。 多重派生 AB AC 由一个基类派生出多个不同的派生类。 多层派生 :A B C 派生类又作为基类,继续派生新的类。
190
多继承时派生类的定义 class 派生类名:继承方式1 基类名1, 继承方式2 基类名2,... { 成员定义; }
注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承。
191
多继承举例 class A{ private: public: int b; void setA(int); };
void showA(); private: int a; }; class B{ void setB(int); void showB(); private: int b; }; class C : public A, private B{ public: void setC(int, int, int); void showC(); int c;
192
多继承举例 int main() { C obj; obj.setA(5); obj.showA(); obj.setC(6,7,9);
void A::setA(int x) { a=x; } void B::setB(int x) { b=x; } void C::setC(int x, int y, int z) { //派生类成员直接访问基类的 //公有成员 SetA(x); SetB(y); c=z; } //其它函数实现略 int main() { C obj; obj.setA(5); obj.showA(); obj.setC(6,7,9); obj.showC(); obj.setB(6); // 错误 obj.showB(); // 错误 return 0; }
193
派生类的构造、析构函数 基类的构造函数不被继承,需要在派生类中自行定义。
定义构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化由基类完成。
194
单一继承时的构造函数 派生类名::派生类名(基类所需的形参,本类成员所需的形参) : 基类名(参数) { 本类成员初始化赋值语句; };
195
单一继承时的构造函数举例 #include<iostream.h> class B{ public: B();
B(int i); ~B(); void Print() const; private: int b; };
196
单一继承时的构造函数举例 B::B() { b=0;
cout<<"B's default constructor called."<<endl; } B::B(int i) b=i; cout<<"B's constructor called." <<endl;
197
单一继承时的构造函数举例 B::~B() {
cout<<"B's destructor called."<<endl; } void B::Print() const cout<<b<<endl;
198
单一继承时的构造函数举例 class C:public B { public: C(); C(int i,int j); ~C();
void Print() const; private: int c; };
199
单一继承时的构造函数举例 C::C() { c=0;
cout<<"C's default constructor called."<<endl; } C::C(int i,int j):B(i) c=j; cout<<"C's constructor called."<<endl;
200
单一继承时的构造函数举例 C::~C() {
cout<<"C's destructor called."<<endl; } void C::Print() const B::Print(); cout<<c<<endl;
201
单一继承时的构造函数举例 输出结果: int main() B's constructor called. { C obj(5,6);
C's constructor called. 5 6 C's destructor called. B's destructor called. int main() { C obj(5,6); obj.Print(); return 0; }
202
多继承时的构造函数 派生类名::派生类名(基类1形参,基类2形参,...基类n形参,本类形参):基类名1(参数), 基类名2(参数), ...基类名n(参数) { 本类成员初始化赋值语句; };
203
二义性问题 在多继承时,基类与派生类之间,或基类之间出现同名成员时,将出现访问时的二义性(不确定性)——采用虚函数(或同名覆盖原则来解决。
当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性——采用虚基类来解决。 这种继承关系构成了一个类格
204
二义性问题举例 ---- 1 class A class C: public A, piblic B { { public: public:
void f(); }; class B void f(); void g(); class C: public A, piblic B { public: void g(); void h(); }; 如果定义:C c1; 则 c1.f(); 具有二义性 而 c1.g(); 无二义性(同名覆盖)
205
二义性的解决方法 解决方法一:用类名来限定 c1.A::f() 或 c1.B::f()
解决方法二:同名覆盖 在C 中定义一个同名成员函数f(),f()再根据需要调用 A::f() 或 B::f()
206
同名覆盖原则 当派生类与基类中有相同成员时: 若未强行指明,则通过派生类对象使用的是派生类中的同名成员。
如要通过派生类对象访问基类中被覆盖的同名成员,应使用基类名类限定。
207
二义性问题举例 ---- 2 class B { public: int b; } class B1 : public B {
private: int b1; class B2 : public B int b2; }; class C : public B1,public B2 { public: int f(); private: int d; } C c; c.b // 二义性 c.B::b // 二义性 c.B1::b //无二义性 c.B2::b //无二义性
208
二义性问题举例 ---- 2 派生类C的对象的存储结构示意图: 除了二义性问题外,基类成员产生多个拷贝 b b1 C类对象 b b2 d
209
多态性的概念 多态性是面向对象程序设计的重要特征之一。 多态性是在不同类型的对象上调用相同的函数导致完全不同的行为。 多态的体现 函数重载
运算符重载 虚函数 多态性的实现 静态联编 动态联编
210
静态联编与动态联编 联编: 静态联编(静态束定) 动态联编 程序自身彼此关联的过程,确定程序中的操作调用与执行该操作的代码间的关系。
联编工作出现在编译阶段,用对象名或者类名来限定要调用的函数。---- 编译时就确定了函数的调用关系 动态联编 联编工作在程序运行时执行,在程序运行时才确定将要调用的函数。---- 在运行时才确定函数的调用关系,有时也称为迟(滞)后联编
211
静态联编与动态联编 ---- 例1 #include<iostream.h> class Point
{ public: Point(double i, double j) {x=i; y=j;} double Area() const{ return 0.0;} private: double x, y; }; class Rectangle: public Point { public: Rectangle(double i, double j, double k, double l); double Area() const {return w*h;} private: double w,h;
212
静态联编与动态联编 ---- 例1 Rectangle::Rectangle(double i, double j, double k,
double l) :Point(i,j) { w=k; h=l; } void fun(Point &s) { cout<<"Area="<<s.Area()<<endl; } void main() { Rectangle rec(3.0, 5.2, 15.0, 25.0); fun(rec); } 运行结果: Area=0 s.Area(): 属于静态联编
213
静态联编与动态联编 ---- 例2 #include<iostream.h> class Point
{ public: Point(double i, double j) {x=i; y=j;} virtual double Area() const{ return 0.0;} private: double x, y; }; class Rectangle:public Point { public: Rectangle(double i, double j, double k, double l); virtual double Area() const {return w*h;} private: double w,h; //其它函数同前例
214
静态联编与动态联编 ---- 例2 void fun(Point &s)
{ cout<<"Area="<<s.Area()<<endl; } void main() { Rectangle rec(3.0, 5.2, 15.0, 25.0); fun(rec); } 运行结果: Area=375 s.Area():虚函数 属于动态联编
215
虚函数 虚函数是动态联编的基础。 不能定义为静态的成员函数。 在类的定义中,在函数原型之前写virtual。
具有继承性,基类中定义了虚函数,派生类中无论是否说明,同原型函数(同名同参)都自动为虚函数。 本质:不是重载定义而是覆盖定义。 调用方式:通过基类指针或引用,执行时会 根据指向或引用的对象,决定调用哪个函数。
216
虚函数的例子 #include <iostream.h> class B0 //基类B0声明 { public: //外部接口
virtual void display() {cout<<"B0::display()"<<endl;} //虚成员函数 };
217
虚函数的例子 class B1: public B0 //公有派生 { public: void display()
{ cout<<"B1::display()"<<endl; } }; class D1: public B1 //公有派生 { cout<<"D1::display()"<<endl; }
218
虚函数的例子 void fun(B0 *ptr) //普通函数 { ptr->display(); }
void main() //主函数 { B0 b0, *p; //声明基类对象和指针 B1 b1; //声明派生类对象 D1 d1; //声明派生类对象 p=&b0; fun(p); //调用基类B0函数成员 p=&b1; fun(p); //调用派生类B1函数成员 p=&d1; fun(p); //调用派生类D1函数成员 }
219
虚函数的例子 程序的运行结果为: B0::display() B1::display() D1::display()
220
纯虚函数 class X{ virtual 返回类型 标识符(参数声明) =0; };
221
抽象类 带有纯虚函数的类成为抽象类 不能产生或生成抽象类的对象 抽象类只能作为基类来使用
继承接口,类似于分布式计算中的Interface的概念 从抽象类中派生出的类必须为纯虚拟函数提供实现,否则它们也是抽象类。
222
抽象类的作用 抽象类为抽象和设计的目的而建立,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。
对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。
223
抽象类的例子 #include <iostream.h> class B0 //抽象基类B0声明 {
public: //外部接口 virtual void display( )=0; //纯虚函数成员 };
224
抽象类的例子 class B1: public B0 //公有派生 { public:
void display(){ cout<<"B1::display()"<<endl; } //虚成员函数 }; class D1: public B1 //公有派生 void display(){ cout<<"D1::display()"<<endl; }
225
抽象类的例子 void fun(B0 *ptr) //普通函数 { ptr->display(); }
void main() //主函数 { B0 *p; //声明抽象基类指针 B1 b1; //声明派生类对象 D1 d1; //声明派生类对象 p=&b1; fun(p); //调用派生类B1函数成员 p=&d1; fun(p); //调用派生类D1函数成员 }
226
抽象类的例子 程序的运行结果为: B1::display() D1::display()
227
模板(Template)
228
模板函数 模板函数的引入 考虑 y = |x| 首先考虑到的是各种能够处理的类型进行重载 int absolute ( int x ) {
return x > 0 > x : -x; } double absolute ( double x ) { return x > 0 > x : -x; } float absolute ( float x ) { return x > 0 > x : -x; } 如此下去可能会需要 很多这样的函数,显得 很繁琐
229
模板函数 上述函数组的特点 这样的函数组当然可以用宏来解决上述问题 函数体完全一样 仅仅是函数的参数类型不同 成为参数化多态性
#define absolute(x) ((x)>0?(x):)(-x)) 如前所述,宏在C++中并不是一种推荐的编程风格,也存在各种各样的问题 前面介绍过的内联函数也无法解决这一问题
230
模板函数 引入模板函数的概念 模板函数的定义 返回类型可以是模板的类型,也可以是其他类型 解决参数化多态性的程序设计问题
Template <class T> 类型名 函数名(参数表) { 函数体的定义 } Template <typename T> 类型名 函数名(参数表) { 函数体的定义 }
231
模板函数 #include <iostream> Template <class T>
T absolute( T x ) { return x > 0 ? x : -x; } void main() { cout << “\n absolute(-99)=” << absolute(-99); cout << “\n absolute(-99.99)=” << absolute(-99.99); cout << “\n absolute(‘’)=” << absolute(‘’); }
232
模板函数 共有三次函数调用 多类型模板函数定义 absolute(-99):函数原型为int absolute(int x)
absolute(-99.99) :调用的函数原型为double absolute(double x) absolute(‘’) :函数原型为char absolute(char x) 多类型模板函数定义 Template <typename T1,…, Typename Tn> 类型名 函数名(参数表) { 函数体的定义 } Template <class T1,…,class Tn> 类型名 函数名(参数表) { 函数体的定义 }
233
模板函数 ---- 例1 #include <iostream> Template<class T>
模板函数 例1 #include <iostream> Template<class T> void outputArray( const T * P_array, const int count) { for( int i=0; i<count; i++) count << P_array[i] << “ ”; cout << endl; } void main() { const int aCount = 8, bCount = 8, cCount = 20; int aArray[aCount] = {1, 2, 3, 4, 5, 6, 7, 8}; double bArray[ bCount ] = {1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8}; char cArray[ cCount ] = “Welcome to see you!”;
234
模板函数 ---- 例1 cout << “a Array contains;” << endl;
模板函数 例1 cout << “a Array contains;” << endl; outputArray( aArray, aCount); cout << “b Array contains;” << endl; outputArray( bArray, bCount); cout << “c Array contains;” << endl; outputArray( cArray, cCount); } 运行结果: A Array contain: Welcom to see you!
235
模板函数 ---- 例2 #include <iostream.h>
模板函数 例2 #include <iostream.h> Template <class T1, class T2> T1 * add( T1 data[], T2 d, int n) { for(int i = 0; i < n; i++) data[i] += d; return data; }; void main() { float a[10] = {1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2}; add(a, 1, 5); for(int i = 0; i < 10; i++) cout << “ ” << a[i]; }
236
模板函数 ---- 例2 输出结果: 在模板函数中存在一个强制类型转换 模板函数的前向说明,与定义类似
模板函数 例2 输出结果: 在模板函数中存在一个强制类型转换 data[i] += d; Data[i] += T1(d); 模板函数的前向说明,与定义类似 Template <class T> 类型名 函数名(参数表) Template <class T1, class T2> T1 * add(T1 data[], T2 d, int n);
237
模板函数的特例与重载 对于上面的absolute模板函数 要支持complex类型变量
存在的问题是:复数绝对值的概念复数的模值,不同于简单类型的绝对值,这样模板函数的函数体实现是不同的, 这种情况称为特例 C++提供编写特例的机会,可以为complex类编写一个求复数绝对值(求模)的特例函数
238
模板函数的特例与重载 Template <class T> T absolute(T x) {
return x > 0 ? X : -x; } double absolute( complex cx) { return sqrt(real(cx) * real(cx) + imag(cx) * imag(cx)); 模板函数的特例
239
模板函数的特例与重载 对于一个模板函数调用 absolute(-99) absolute(-99.99)
absolute(complex(3,4)) 编译器首先检查是否有特例函数可以调用,然后再检查模板函数
240
模板函数的特例与重载 模板函数也可以重载 模板函数的重载和普通函数一样没有任何不同
Template <class T> T max(T x, T y) { return x <=y ? X : y; } Template <class T> T max(T x, T y, T z) { T w = (x>=y)? X:y; return(w>=z)?w”z; }
241
模板函数的特例与重载 Template <class T> T max(T x[], int n) {
T maxinum = x[0]; for(int i=0; i<n; i++) if (maxinum < x[i]) maxinum = x[i]; return maxinum; } 和普通函数一样,编译器会根据参数的不同来联编 不同的模板函数
242
起泡排序模板函数 对具有n个元素的序列按升序进行起泡排序的步骤:
首先将第一个元素与第二个元素进行比较,若为逆序,则将两元素交换。然后比较第二、第三个元素,依次类推,直到第n-1和第n个元素进行了比较和交换。此过程称为第一趟起泡排序。经过第一趟,最大的元素便被交换到第n个位置。 对前n-1个元素进行第二趟起泡排序,将其中最大元素交换到第n-1个位置。 如此继续,直到某一趟排序未发生任何交换时,排序完毕。对n个元素的序列,起泡排序最多需要进行n-1趟。
243
起泡排序模板函数 对整数序列 8 5 2 4 3 按升序排序 每趟沉下一个最大的 8 5 2 4 3 5 2 2 2 小的逐渐上升 2 4
初始状态 第一趟结果 第二趟结果 第三趟结果 第四趟结果
244
起泡排序模板函数 Template <class T> void BubbleSort(T A[], int n) { int i, j; int lastExchangeIndex; i = n – 1; while ( i > 0 ) { lastExchangeIndex = 0; for(j = 0; j<i; j++) if(A[j+1]<A[j]) { Swap(A[j], A[j+1]); lastExchangeIndex = j; } i = lastExchangeIndex; j A i 局部有序
245
模板类 模板类的需求与模板函数类似 Class IntArray { Class FloatArray { int * element;
int n; Public: Array(int * data, int i); ~Array(); void sort(); int find(int a); int sum(); }; Class FloatArray { float * element; int n; Public: Array(float * data, int i); ~Array(); void sort(); int find(float a); float sum(); };
246
模板类 模板类的定义 Template <class T> class 类名 {………}
Template <class T> class Array { T * element; int n; Public: Array(T * data, int i); ~Array(); void sort(); int find( T a ); T sum(); }
247
模板类 在类外实现模板类的成员函数 Template <class T> 类型名 类名<T>::函数名(参数表)
模板类的成员函数必须是模板函数 Template <class T> T Array<T>::sum() { T s = 0; for ( int i = 0; i < n; i ++) s += element[i]; return s; };
248
模板类 模板类的对象建立 类名<T> 对象1, …., 对象n;
Array <int> cI(Idata, 10); Array<float> cF(fData, 10);
249
完整的模板数据类的实现 #include <iostream.h>
Template <class T> class Array { T * element; int n; Public: Array(T * data, int i) : element(data), n(i) {} ~Array() {} void sort(); int find(T key); T sum(); };
250
完整的模板数据类的实现 Template <class T> void Array<T>::sort() {
int i, j; T d; for(i=1; i<n; i++) // 冒泡排序 for(j=n-1; j>=i; i--) if(element[j-1] > element[j]) { d=element[j-1]; element[j-1]=element[j]; element[j] = d; }
251
完整的模板数据类的实现 Template <class T> int Array<T>::find( T key) { for( int i=1; i<n; i++) if(element[i] == key) return i; return -1; } Template <class T> T Array<T>::sum() { T s = 0; for(int i=0; i<n;i++) s+=element[i]; return s;
252
模板类 多个模板类型参数的模板类定义 外部实现成员函数 对象的定义
Template <class T1, …., class Tn> Class 类名 { …….. }; 在定义多类型参数模板类时,每个类型参数必须至少被使用一次,否则错误 外部实现成员函数 类型名 类名<T1, …., Tn>::函数名(参数表) { …} 对象的定义 类名<t1,…,tn> 对象名(初始化参数列表);
253
模板类 Template <class T1, class T2> class Data2 { T1 a; T2 b;
Public: Data2(T1 x, T2 y) : a(x), b(y) {}; ~Data2() {} void OutIt(); }; Template <class T1, class T2> void Data2<T1, T2>::OutIt() { cout << “a=” << a << endl; cout << “b=” << b << endl; } Data2<char, float> Da(‘A’,5.5);
254
模板类的特例 与模板函数一样,模板类也允许模板类的特例存在,对于前面实现的模板类存在特例:char *
Template <class T> class Array { T * element; int n; Public: Array(T *data, int i) : element(data), n(i) {}; ~Array() {}; void sort(); int find(T key); T sum(); friend ostream &operator<<(ostream &out, Array<T> &array); };
255
模板类的特例 Template <class T> void Array<T>::sort() {
int i, j; T d; for(i=1; i<n; i++) // 冒泡排序 for(j=n-1; j>=i; i--) if(element[j-1] > element[j]) { d=element[j-1]; element[j-1]=element[j]; element[j] = d; }
256
模板类的特例 Template <class T> int Array<T>::find( T key) {
for( int i=1; i<n; i++) if(element[i] == key) return i; return -1; } Template <class T> T Array<T>::sum() { T s = 0; for(int i=0; i<n;i++) s+=element[i]; return s;
257
模板类的特例 Template <class T>
ostream &operator<<(ostream &out, Array<T> &array) { for(int i=0; i<array.n; i++) out << “ “ << array.element[i]; return out; }
258
模板类的特例 通过分析上述模板类的定义并不是适合于各种情况和各种数据类型 因此有时需要编写模板类的特例
下面是Array模板类的一个<char *>特例类的定义和实现
259
模板类的特例 ---- char * class Array <char *> { char * * element;
int n; Public: Array<char *> (char * *data, int i) : element(data), n(i) {}; ~Array() {}; void sort(); int find(char * key); // T sum(); friend ostream &operator<<(ostream &out, Array<char *> &array); };
260
模板类的特例 ---- char * void Array<T>::sort() { int i, j; char * d;
for(i=1; i<n; i++) // 冒泡排序 for(j=n-1; j>=i; i--) if(strcmp(element[j-1],element[j])>0) { d=element[j-1]; element[j-1]=element[j]; element[j] = d; }
261
模板类的特例 int Array<char *>::find( T key) {
for( int i=1; i<n; i++) if(strcmp(element[i], key)=0) return i; return -1; } Template <class T> ostream &operator<<(ostream &out, Array<T> &array) { for(int i=0; i<array.n; i++) out << “ “ << array.element[i]; return out;
262
模板栈 ---- 栈的数据结构及实现 Const int MaxStackSize = 50;
Template <class T> class Stack { Private: T stackList[MaxStackSize]; int top; Public: Stack(void); void Push( const T & item ); T Pop(void); woid ClearStack(void); T Peek(void) const; int StackEmpty(void) const; int StackFull(void) const; }; Template <class T> Stack<T>::Stack(void): top(-1) {} Void Stack<T>::Phsu(const T& item) { if(top==MaxStackSize -1) { std::cout<<“Stack overflow!\n”; exit( -1 ); } top++; stacklist[top] = item;
263
模板栈 ---- 栈的数据结构及实现 Template <class T>
T Stack<T>::Pop(void) { T temp; if(top == -1) { std::cout<<:Attemp to pop an empty stack!\N”; exit( -1 ); return stacklist[ top ]; } Template <class T> int Stack<T>::StackEmpty(void) const { return top ==-1; } Template <class T> int Stack<T>::StackFull(void) const { return top == MaxStackSize -1; } Void Stack<T>::ClearStack(void) { top = -1; }
264
Standard Template Library
STL,虽然是一套程序库(Library),确不是一般印象中的程序库,而是一个有着划时代意义,背后拥有着先进技术与深厚理论的产品。说他是产品也可以,说他是规格也可以,说是软件组件技术发展史上一个大突破点,它也当之无愧。 长久以来,软件界一直希望建立一种可重复运用的东西,以及一种可以制造出“可重复运用的东西”的方法,让工程师/程序员的心血不至于随时间的推移、人事异动而烟消云散。从副程式(subroutines)、程序(procedures)、函数(functions)、类别(classes),到函数库(function libraries )、类别库(class libraries )、各种组件(components),从结构化设计、模组化设计、物件导向设计,到样式(patterns)的归纳整理,无一不是软件工程的漫漫奋斗史,为的就是复用性(reusebility)的提升。 ————摘自《STL源码分析》
265
Standard Template Library
STL提供6大组件,彼此可以组合套用: 容器(containers):各种资料结构,如Vector、List、Map等,用来存储各种数据。 演算法(algorithms):各种常用的算法,如sort、search、copy等,它的作用是为提供各种常用的操作。 迭代器(iterators):一个非常重要的组件,用来将容器和演算法联系起来。也就是通常所说的泛型指针。 仿函数(functors):行为类似函数,可作为演算法的某种策略(policy)。 配接器(adapters):一种用来修饰容器或仿函数界面的东西。 配置器(allocators):负责空间配置与管理,用来保证容器空间的正确分配。
266
Vector Vector的行为方式完全是一个数组,它的内存空间完全是连续的,只不过这个数组的大小是可变的。
267
List 相对于Vector的线性存储空间,List就复杂的多,它每次添加或者删除一个元素,就会申请或者释放一个元素的空间,然后用指针将他们联系起来。这样的好处就是精确配置内存,绝对没有一点的浪费。而且对于元素的插入和删除,List都是常数时间。
268
template < class T >
struct _list_node { Typedef void *void_pointer; void_pointer prev; //指向前面元素的指针 void_pointer next; //指向后面元素的指针 T data; //节点实体,存储数据 }
Similar presentations