您好,欢迎来到爱玩科技网。
搜索
您的当前位置:首页C++讲义

C++讲义

来源:爱玩科技网
目 录

第1章

C++对C与类无关的扩展(1) .............................................................................................. 6 1.1

C++的基本知识 ......................................................................................................................... 6 1.1.1 命名空间 ............................................................................................................................ 6 1.1.2 C++输入输出简介 ............................................................................................................. 6

1.2

C++对C语言与类无关的性能扩充 ........................................................................................ 7 1.2.1 新增const常量定义符 ...................................................................................................... 7 1.2.2 功能 .................................................................................................................................... 7 1.2.3 定义 .................................................................................................................................... 7 1.2.4 C++中为什么要提供const ............................................................................................... 7 1.2.5 正确区分const在修饰指针时的两种不同用法 .............................................................. 7 1.2.6 正确区分符号名之前有无const修饰时的不同 .............................................................. 8 1.2.7 正确区分C语言中的#define与C++中的const的不同: ............................................ 8

1.3 bool类型 .................................................................................................................................... 8 1.4

string 类型 ................................................................................................................................. 9 1.4.1 定义string类型的变量 ..................................................................................................... 9 1.4.2 转换为C风格的字符串 ................................................................................................... 9 1.4.3 string 的使用 ..................................................................................................................... 9

1.5

枚举类型 .................................................................................................................................. 10 1.5.1 枚举类型的声明和定义 .................................................................................................. 10 1.5.2 匿名枚举类型 .................................................................................................................. 10

1.6

变量的块内定义 ...................................................................................................................... 10 1.6.1 正确理解C中变量的定义要求 ..................................................................................... 10 1.6.2 C++中的变量定义的规则 ............................................................................................... 10 1.6.3 正确理解C++中的变量的各种作用域。 ...................................................................... 11

1.7

变量的引用操作符 & ............................................................................................................. 11 1.7.1 引用 .................................................................................................................................. 11 1.7.2 定义的语法 ...................................................................................................................... 11 1.7.3 引用调用的优点 .............................................................................................................. 12 1.7.4 正确区分引用在如下两种不同的应用场合时的差别: .............................................. 13 1.7.5 函数形参定义为引用参数时的调用要求 ...................................................................... 13 1.7.6 函数形参定义为引用参数时的的编程效果 .................................................................. 13

第2章

C++对C与类无关的扩展(2) ............................................................................................ 15 2.1

函数 .......................................................................................................................................... 15 2.1.1 函数原型 .......................................................................................................................... 15

2.2 函数返回一个引用 .................................................................................................................. 15 2.2.1 引用的返回 ...................................................................................................................... 15

2.3

内联函数 .................................................................................................................................. 16 2.3.1 含义 .................................................................................................................................. 16 2.3.2 为什么内联函数优越于宏替换 ...................................................................................... 16 2.3.3 内联函数的定义 .............................................................................................................. 16 2.3.4 不适宜定义为内联函数的场合 ...................................................................................... 17

2.4

函数默认参数 .......................................................................................................................... 17 2.4.1 函数重载 .......................................................................................................................... 17

1

2.5 函数模板 .................................................................................................................................. 18 2.5.1 函数模板 .......................................................................................................................... 18

2.6 定义语法 .................................................................................................................................. 18 2.6.1 应用实例 .......................................................................................................................... 18

2.7 new 和 delete 操作符 ............................................................................................................ 19 第3章

C++对C语言与类有关的性能扩充 ...................................................................................... 20 3.1 基本知识 .................................................................................................................................. 20 3.2

面向对象(Object Oriented) ................................................................................................ 20 3.2.1 面向对象(OO): .......................................................................................................... 20 3.2.2 面向对象程序设计方法(OOP): ................................................................................. 20 3.2.3 应用实例 .......................................................................................................................... 21 3.2.4 OOP的优点: ................................................................................................................. 22

3.3 抽象 .......................................................................................................................................... 22 3.3.1 如何实现抽象 .................................................................................................................. 22

3.4 封装 .......................................................................................................................................... 22 3.5 继承与派生 .............................................................................................................................. 23 3.6 多态性 ...................................................................................................................................... 23 3.7

类和对象 .................................................................................................................................. 23 3.7.1 C++中的类: ................................................................................................................... 23 3.7.2 如何正确定义出类的成员 .............................................................................................. 24 3.7.3 类中成员的访问控制项 .................................................................................................. 25

3.8 类中成员之间的访问方式: .................................................................................................. 25 第4章

C++类和对象的编程 ............................................................................................................... 26 4.1 对象的含义 .............................................................................................................................. 26 4.2 对象定义语法 .......................................................................................................................... 26 4.3 客观对象 .................................................................................................................................. 26 4.4 C++中的对象 ........................................................................................................................... 26 4.5 使用对象 .................................................................................................................................. 26 4.6

类中的各种特殊成员函数 ...................................................................................................... 27 4.6.1 内联成员函数 .................................................................................................................. 27 4.6.2 允许带有缺省形参值的函数 .......................................................................................... 27 4.6.3 类中的函数重载 .............................................................................................................. 27 4.6.4 构造函数 .......................................................................................................................... 27 4.6.5 析构函数 .......................................................................................................................... 29 4.6.6 拷贝构造函数 .................................................................................................................. 30

4.7

异常处理 .................................................................................................................................. 32 4.7.1 异常处理的方法 .............................................................................................................. 32 4.7.2 异常规格说明 .................................................................................................................. 32

第5章

C++中的对象的各种编程 ....................................................................................................... 33 5.1

对象赋值 .................................................................................................................................. 33 5.1.1 方法 .................................................................................................................................. 33 5.1.2 应用目的 .......................................................................................................................... 33 5.1.3 实现规则 .......................................................................................................................... 33

5.2

对象数组 .................................................................................................................................. 33 5.2.1 定义的语法 ...................................................................................................................... 34 5.2.2 访问方法 .......................................................................................................................... 34

2

5.2.3 对象数组的初始化 .......................................................................................................... 34 5.2.4 对象数组编程时的构造函数设计 .................................................................................. 34 5.2.5 对象数组编程时的析构函数设计 .................................................................................. 34

5.3

对象指针 .................................................................................................................................. 35 5.3.1 C/C++语言中的指针 ....................................................................................................... 35 5.3.2 C++中的对象指针 ........................................................................................................... 35

5.4 对象可以作为函数的形参和返回值的定义 .......................................................................... 36 5.5 类中包含有对象成员(类的聚集,对象内嵌,包容法使用类的资源) .......................... 37 5.6

类中的静态成员 ...................................................................................................................... 37 5.6.1 静态成员的编程 .............................................................................................................. 37 5.6.2 静态成员的访问方法: .................................................................................................. 38 5.6.3 静态成员函数与非静态成员函数之间的访问: .......................................................... 38

5.7 C++中的this指针 ................................................................................................................... 39 第6章

友员函数与运算符重载 .......................................................................................................... 41 6.1

友员函数 .................................................................................................................................. 41 6.1.1 友员函数 .......................................................................................................................... 41 6.1.2 为什么需要友员函数: .................................................................................................. 42 6.1.3 友员函数与成员函数在编程方面的不同点 .................................................................. 42 6.1.4 友员函数与一般函数(非成员函数)在编程方面的不同点 ...................................... 42

6.2

友员类: .................................................................................................................................. 42 6.2.1 含义 .................................................................................................................................. 42 6.2.2 友员类的定义 .................................................................................................................. 42 6.2.3 应用目的 .......................................................................................................................... 43

6.3 类的引用性前向说明 .............................................................................................................. 43 6.4

运算符重载 .............................................................................................................................. 43 6.4.1 运算符重载的实现 .......................................................................................................... 43 6.4.2 使用类的成员函数实现重载 .......................................................................................... 44 6.4.3 使用友员函数实现重载 .................................................................................................. 46 6.4.4 自动类型转换 .................................................................................................................. 46 6.4.5 运算符重载编程的进一步说明 ...................................................................................... 47

第7章

C++类的继承与派生 ............................................................................................................... 48 7.1

入门知识 .................................................................................................................................. 48 7.1.1 继承 .................................................................................................................................. 48 7.1.2 派生 .................................................................................................................................. 48 7.1.3 为什么需要继承与派生 .................................................................................................. 48 7.1.4 应用的目的 ...................................................................................................................... 48 7.1.5 继承的可行性 .................................................................................................................. 48

7.2

如何实现继承与派生 .............................................................................................................. 49 7.2.1 基类和派生类 .................................................................................................................. 49 7.2.2 派生类的定义语法 .......................................................................................................... 49 7.2.3 如何设计派生类成员 ...................................................................................................... 49

7.3

继承的方式 .............................................................................................................................. 50 7.3.1 三种继承的方式差别 ...................................................................................................... 50 7.3.2 public 和 private继承方式的区别 ................................................................................ 50

7.4

继承的多重性 .......................................................................................................................... 52 7.4.1 含义 .................................................................................................................................. 52

3

7.4.2 对象的多重相关性 .......................................................................................................... 52 7.4.3 多重继承时的派生类的定义语法 .................................................................................. 52 7.4.4 应用场合 .......................................................................................................................... 52 7.5 继承时的构造函数 .................................................................................................................. 52

7.5.1 设计原则 .......................................................................................................................... 52 7.5.2定义的格式 ............................................................................................................................. 53 7.6 继承时的析构函数 .................................................................................................................. 54

7.6.1 设计原则 .......................................................................................................................... 54 7.6.2 定义格式 .......................................................................................................................... 54 7.6.3 原因 .................................................................................................................................. 54 7.6.4 调用次序 .......................................................................................................................... 54

第8章 多重多级继承及虚基类 .......................................................................................................... 56

8.1 多重多级继承时访问二义性 .................................................................................................. 56

8.1.1 访问时的二义性 .............................................................................................................. 56 8.1.2 在多重继承时的二义性 .................................................................................................. 56 8.1.3 同名支配(覆盖)原则 .................................................................................................. 57 8.1.4 在多级继承时的二义性 .................................................................................................. 57 8.2 虚基类 ...................................................................................................................................... 57

8.2.1 定义语法 .......................................................................................................................... 57 8.2.2 作用 .................................................................................................................................. 58 8.2.3 如何判断是否为虚基类的问题 ...................................................................................... 58 8.2.4 处理的方法 ...................................................................................................................... 58 8.2.5 带有虚基类的最远的派生类的构造函数 ...................................................................... 58

第9章 多态性和虚函数 ...................................................................................................................... 60

9.1 多态性 ...................................................................................................................................... 60

9.1.1 多态性 .............................................................................................................................. 60 9.1.2 作用 .................................................................................................................................. 60 9.1.3 赋值兼容原则 .................................................................................................................. 60 9.1.4 指向派生类及基类对象的指针 ...................................................................................... 61 9.2 虚函数 ...................................................................................................................................... 61

9.2.1 定义语法 .......................................................................................................................... 61 9.2.2 虚函数的特点 .................................................................................................................. 62 9.2.3 为什么需要虚函数 .......................................................................................................... 62 9.2.4 静态联编 .......................................................................................................................... 63 9.2.5 动态联编的应用 .............................................................................................................. 63 9.2.6 如何实现动态联编 .......................................................................................................... 63 9.3 虚析构函数 ..............................................................................................................................

9.3.1 原因 .................................................................................................................................. 9.3.2 应用场合 .......................................................................................................................... 9.4 纯虚函数和抽象基类 .............................................................................................................. 65

9.4.1 纯虚函数 .......................................................................................................................... 65 9.4.2 定义语法 .......................................................................................................................... 65 9.4.3 抽象基类的编程规则 ...................................................................................................... 65 9.4.4 抽象基类的用途 .............................................................................................................. 65

第10章 综合应用 .................................................................................................................................. 68

10.1 类模板 ...................................................................................................................................... 68

4

10.1.1 10.1.2 模板的声明 ...................................................................................................................... 68 模板的使用 ...................................................................................................................... 68

5

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

第1章 C++对C与类无关的扩展(1)

【本章中的知识要点】

□ 正确理解C语言与C++语言的差别 □ C++对C语言扩充体现的几个方面 【重点难点】

□ const用法 □ string用法 □ 引用

1.1 C++的基本知识

1.1.1 命名空间

C++提供名字空间来防止命名的冲突。例如,如果两个库都定义了cout,当一个应用程序使用这两个库时,将导致命名冲突。大型应用系统由许多人来完成,命名冲突是一种潜在的危险,程序员必须细心的定义标志符以保证名字的唯一性。名字空间std涵盖了标准C++的定义和声明,例如:std::cout。

 命名空间的声明: namespace ttl {

int x; }

 命名空间的使用(两种方式):

1) 先引用再访问例如:

using ttl::x; 或者 using namespace ttl; x=6; x=6; 2) 直接访问如: ttl::x=34;

1.1.2 C++输入输出简介

C++的基本输出包括cin和cout。

中科院计算所培训-沈阳

6

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

1.2 C++对C语言与类无关的性能扩充

1.2.1 新增const常量定义符 1.2.2 功能

它用来冻结一个变量的值,使其值在程序中不能被进一步改变(置成只读属性,即变为常量)。

1.2.3 定义

const 类型定义符 变量名==初值;

如: const int sumValue=10; //常量定义 const int *ptr; //限定指针

void Function(const int & X) //限定函数的形参 { }

1.2.4 C++中为什么要提供const

其目的是替代C语言中的编译预处理命令#define(常量定义),但比它更加语义精确 因为#define无法准确地反映常量的数据类型。如:

#define sumValue 10 //此时的sumValue是char型还是int型? 对变量一经const 修饰后,必须给它赋初值(但用于函数的形参时不需赋初值),一经const修饰后便不能修改此符号名之值。  const int x=12;

x=54; //error

 void Display(const int *ptr, const int n)

{

cout<< ptr[0];//==*(ptr+0),显示数组中的第一个元素之值

ptr[0]=1; //错误,不能修改其值(不能通过指针来改变目标单元之值)

*ptr=1; //错误,不能修改其值(不能通过指针来改变目标单元之值) }

1.2.5 正确区分const在修饰指针时的两种不同用法

 const int *ptr=&X; 此时不能通过指针来访问改变目标单元之值,即*ptr=1 是错误的,

但是ptr本身可以被改变 ptr=&Y;此种用法常见于函数的形参。 void fun(const int * ptr) { *ptr=1; //错误的 }

 int * const ptr=&X;此时ptr本身不可以被改变( ptr=&Y是错误的);但ptr所指向的

目标的单元之值是可以被改变的(如 *ptr=2; 即 X=2);此时ptr类似于数组名(常量指针)。 int ptr[10]; ptr[0]=2;

中科院计算所培训-沈阳

7

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

1.2.6 正确区分符号名之前有无const修饰时的不同

 int sum=1; //无const修饰时

此时 sum为变量,意味着其值可以被改变(如:sum=2;);同时其内存字节数为两个字节(在PC下为16位)。

 const int sum=1; //有const修饰时

此时 sum为常量,意味着其值不可以被改变(如:sum=2;);同时不需要占用内存空间。另外C++语言视经const修饰的符号名为常量特性,因而它可以用于任何可能出现数字常量的场合(如数组的大小的说明等)。 const int ArraySize=10; char array[ArraySize];

1.2.7 正确区分C语言中的#define与C++中的const的不同:

 由#define所定义的符号名为全局性常量,因而在整个程序中应保持符号名的唯一性;  const可以说明一个局部或全局性同名常量(据此可以改变const符号名之值)。

 另外在语法方面,#define为编译预处理命令,而const为定义符,语句以“;”结束。

#define Min 1

const int Max=100; void main(void)

{ #define Min 2 //错误,不能再定义同名常量 const int Max=200; //可以再定义同名局部常量 }

要点const也可以限定引用与函数(见后文介绍)。

1.3 bool类型

在C语言中真值用非零表示,假值用零表示。C++新增了数据类型bool(增加可读性),可以取值true或false,用来表示真值或假值。bool依然支持非零和零的形式。例如:

int val; cin>>val; if(val) …

也是合法的。如果val的值非零,条件判断为真(true);如果val的值为零,条件判断为假(false)。 在默认情况下,bool表达式输出时,真值为1,假值为0。例如:

bool flag; flag=(3<5);

cout<可以用以下方式使其输出结果为true: cout<< boolalpha<中科院计算所培训-沈阳

8

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

1.4 string 类型

C++提供string类型来代替C语言的以null为结尾的char类型数组。使用string类型必须包含头文件string。有了string类型,程序员不用需要关心存储的分配,也无需处理繁杂的null结束字符,这些操作将由系统自动处理。

1.4.1 定义string类型的变量

方式如下: string s1;

string s2=\"Hello\"; string s3=s2;

string s4(\"Hello World\");

1.4.2 转换为C风格的字符串

我们经常会在程序设计中遇到需要C风格字符串的情况(如以null结尾的char数组)。例如,当我们打开一个文件,而文件又要求必须为一个C风格字符串时,则可用函数c_str如: string filename=”tt.txt”; ifstream if;

if.open(filename.c_str());

1.4.3 string 的使用

【实例练习】

#include #include using namespace std; void main() {

string str=\"Hello\";

cout<<\"str=\"<string s(10,'*'); //s表示含有十个'*'的字符串 cout<string str1;

cout<<\"请输入一个字符串: \";

cin>>str1; //注意:输入的字符串以空格或回车代表输入结束 cout<<\"str1=\"<string s1=\"Hello\ string s3=s1+s2;

cout<<\"s3=\"<中科院计算所培训-沈阳

9

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

else cout<<\"s1!=s2\"<1.5 枚举类型

1.5.1 枚举类型的声明和定义

在C语言中,我们如下方式声明一个枚举类型:

enum COLOR{RED,GREEN,BLUE};

然后可以定义一个类型为enum COLOR 枚举类型变量: enum COLOR CL;

而在C++中可以不用关键字enum,如下:

COLOR CL;

1.5.2 匿名枚举类型

声明形式:enum {MAX=1000,MIN=1};

在这里我们可以将MAX和MIN作为常量使用,例如,

int arr[MAX];实际上enum的主要用途就是定义常量。

1.6 变量的块内定义

1.6.1 正确理解C中变量的定义要求

一般应该集中定义出。例如在C中: void main() { int x=1; //正确 for(int i=0; i<10; i++) //错误 { int y=2;

printf(“%d”,y);

int c=10; //错误 。。。 } }

1.6.2 C++中的变量定义的规则

可以采用“随用随定义”的方式定义出变量,即变量的块内定义---C++中允许变量在使用之前的任一语句块中定义出来,包括for循环中。

void main(void) void main(void)

{ int x=1; //局部变量 { for(int loop=1;loop<10;loop++) { int y; //块内变量 { int z=1; //块内变量 x=3,y=4; } } } }

中科院计算所培训-沈阳

10

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

1.6.3 正确理解C++中的变量的各种作用域。

 文件域----全局变量(其作用域为从定义点到当前文件结束有效)。  函数域-----局部变量(其作用域为本函数内有效)。

 函数原型域-----函数的形参,为局部变量,只在该函数体内有效。 

块内域-----块内定义的变量(比局部变量的作用域更小),块内定义的变量的作用域为从定义点到当前块内有效,语句块执行完毕,自动释放。

【实例练习】

int X=1; //全局变量

void Function(int a) //函数的形参 { int b=a; //局部变量 X=b; //正确 Y=X; //错误 }

void main()

{ int Y=2; //局部变量 X=2; //正确 Function(X);

for(int i=0;i<10;i++) //块内定义的变量 { X=2; //正确

int C=0; //块内定义的变量 }

X=2,Y=3; //正确 C=X; //错误 }

1.7 变量的引用操作符 &

1.7.1 引用

C++利用“&”来产生一个变量的引用(该变量的别名,同一内存单元二种不同的变量名)。 int X=1; int & NewX=X; NewX=3; //注意:改变的其实为变量X printf(\"%d\ //X的值被改变为3,因为X与NewX其实为同一个变量。

1.7.2 定义的语法

 类型定义符 &变量名=原变量名;

如: int sumValue=1;

int & sumData=sumValue;

即sumData与sumValue为同一个变量,但在不同的应用场合下以不同的变量名出现。  为什么要提供引用操作符:C++中的引用主要是用来解决函数调用中形参为指针型的参数时

编程其函数体时的不安全性(易出错),操作更加简单、安全。

中科院计算所培训-沈阳

11

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

如:int add(int *X, int * Y) 改进为 int add(int &X, int &Y) { *X=*X+*Y; { X=X+Y; return *X; return X; } }

void main() void main() { int a=1, b=2; { int a=1, b=2; add(&a, &b); add(a, b); } }

【蓝梦提示】

□ 在这两种情况下,调用时都是采用传地址方式调用,但采用引用调用时编程其函

数体时较简单、安全。

1.7.3 引用调用的优点

通过引用调用可以提高调用速度,因为只需要传送地址而不需要直接将大量数据拷贝,因而特别适用于形参为大量数据的应用场合。如:

struct Data

{ int a[1000]; } AData;

void fun(struct Data X) { }

这是采用传值调用时的函数定义,但是在调用时的效率较低,因为需要进行实参与形参之间的数据拷贝

void fun(struct Data &X) { }

这是采用引用调用时的函数定义,在调用时的效率较高,因为形参与实参之间本身是同一个变量。从而在调用fun(AData) 时,如果使用二种不同函数定义时,其调用效果不同。

选学内容有关函数调用的两种型式----传值调用与传地址调用  传值调用:当函数的形参是以“值”的形式定义时,系统将以传值拷贝方式被调用,在调用时实参拷贝到形参,但在内存中将同时存在实参和形参,调用时占用两倍的内存空间,同时调用时的效率较低。

void add(int x, int y) { return x+y ; }

void main()

{ int a=1,b=2;

add(a,b); //在调用时刻在内存中同时存在 a,b与x,y变量。 }

 传地址调用:当函数的形参是以“地址”的形式定义时,它将以传地址方式被调用,在调用时系

统将实参的地址传送到形参中,但在内存中将仅有一份参数存在(此时实参和形参变为同一变量),调用时的效率较高。

void add(int *x, int *y) void add(int &x, int &y)

中科院计算所培训-沈阳

12

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

{ //C语言的编程方法 {//C++语言的编程方法 return *x+ *y; return x+y; } }

void main() void main()

{ int a=1, b=2; { int a=1, b=2; add(&a, &b); add(a, b); } }

1.7.4 正确区分引用在如下两种不同的应用场合时的差别:

 对一般的变量进行引用:如引用的变量不是用作函数的形参或返回值,则说明时必需加以初

始化以指明它的同地址变量。

void main(void) { int X=3;

int &refX=X; fun(refX);

cout << X; //X的值被改变为4 }

void fun( int & a)

{ a++; //a 为refX的引用 }

 函数的形参为引用:如引用的变量用作函数的形参或返回值,则说明时不必要加以初始化以

指明它的同地址变量,此时形参为实参的引用。

int add(int &X, int &Y) //正确 int add(int &X=1, int &Y=2) //错误 { {

X=X+Y; X=X+Y; return X; return X; } }

1.7.5 函数形参定义为引用参数时的调用要求

调用时实参不能为常量而必须为赋已知值的变量。因为编译器无法为常量(无内存地址)建立内存地址,从而无法产生引用。同时在函数调用时系统将实参地址拷贝到形参,从而使形参即为实参,但在函数体内采用形参名来访问它。

int add(int &a, int & b) void main()

{ { int X=1, Y=2;

return a+ b; add(X,Y); //调用正确 } add(1,2);} //调用错误

1.7.6 函数形参定义为引用参数时的的编程效果

 其一能在一个函数体内访问或修改另一个函数体内定义的变量的值 。

【实例练习】

#include using namespace std;

void IncValue( int &X); //函数的原型说明或定义(在C++中必须强制说明) void main()

中科院计算所培训-沈阳

13

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

{ int A=1;

IncValue(A);

cout <<\"The Value of A Is:\"<void IncValue(int &X)

{ X=X+1; //表面上为改变 X的值,其实是改变了main()内的A的值 }

 其二是能使某一函数间接实现“多个返回值”(因为在正常情况下,函数只能实现返回一个

值)。

【实例练习】

#include

using namespace std;

void CalculateCircle(int R, float &CircleLen, float &CircleArea); void main()

{ int circleR =10;

float Length, Area; //定义时未赋值 CalculateCircle (circleR, Length, Area);

cout<<”Circle Length=”<void CalculateCircle (int R, float &CircleLen, float &CircleArea) { CircleArea=3.1415*R*R; // CircleArea 引出到Area CircleLen=2*3.1415*R; // CircleLen 引出到Length }

【蓝梦提示】

□ C++语言中的函数在调用时不仅可以引入参数(利用形参),而且也能引出参数(单个参数时采用return语句,而多个参数时采用形参为引用定义的方式)。 【本章总结】 本章主要讲解了C++的基本输入输出、命名空间的应用以及const、string、bool类型的应用引用的基本用法。 中科院计算所培训-沈阳

14

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

第2章 C++对C与类无关的扩展(2)

【本章中的知识要点】

□ 函数原型及带默认形参的函数 □ 返回引用的函数

□ 内联函数、函数重载、函数模板 □ 操作符new和delete用法 【重点难点】

□ 函数重载及函数模板的应用

2.1 函数

2.1.1 函数原型

在C语言或C++语言中,我们使用术语“定义“来表示一条变量分配存储的语句,或表示函数头和函数体。我们使用术语“声明”来表示对某个数据类型的描述。函数原型体现了函数声明的风格,它指的是函数头以及函数头中包含的参数列表。例如:

int f(int x,int y);

便是对函数f的声明。 在C++中没有参数的函数参数表可以为空,但在C中要有一个void参数如: int disp(){函数体;} //C++写法 int disp(void ){函数体;} //C语言写法 2.2 函数返回一个引用

2.2.1 引用的返回

C++中,默认情况下,当函数返回一个值时: return expression;

expression被求值,并将该值拷贝到临时存储空间,以便函数调用者访问。我们将这总返回方式称为传值返回。 当调用如下函数:

int f(){ return i;}

时,i的值将拷贝到临时存储空间,调用者是i的一个副本,也就是说如果调用函数f:

j=f();

中科院计算所培训-沈阳

15

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

则i的值就拷贝到临时存储空间,然后在拷贝到j,(见图1-5)

临时存储空间 i 拷贝到 拷贝到 j == 8 =======> 8 =====> 8 图 1-5 传值返回 函数返回值还有另外一种形式,即引用返回。在这种情况下返回值不再拷贝到临时存储空间,甚至连return语句所用的那个存储单元对调用者都是可访问的。引用返回语法是在返回类型前加一个&标记。

int & f( ){return i;}此时再次调用f时即j=f( );i的值直接拷贝到j中(图1-6)。

i 拷贝到 j 8 ======> 8 图1-6

注意:如果有这样的函数定义: int & f( ) {

int i;

return i; //error: }

是错误的,当f返回时i已经不存在了。

2.3 内联函数

2.3.1 含义

内联函数类似于宏扩展。当预处理器扩展一个宏时,它将用宏定义替换每个宏。当宏替换或函数扩展完成后,再执行程序,这样避免了函数调用的开销,程序可以更有效的执行。使用宏或内联函数的缺点是,如果函数很大或很多地方都调用了这个函数,程序的可执行码变得很大。

2.3.2 为什么内联函数优越于宏替换

与通过预处理器进行宏阔展不一样,内联函数的扩展是通过编译器完成的。当预处理器扩展一个宏时,它只是进行简单的文本替换,而不考虑代码的语义。而编译器扩展内联函数时,就不得不考虑语义了,正因为这个原因,内联函数通常比宏更受欢迎。

2.3.3 内联函数的定义

【实例练习】

中科院计算所培训-沈阳

16

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

#include using namespace std;

inline void swap(int &,int &); //函数声明

int main() {

int I=7,j=-3; swap(I,j);

cout<<”I=”<void swap(int &a,int &b) {

int t; t=a; a=b; b=t; }

注意关键字inlilne出现在函数声明而不是函数定义部分。

2.3.4 不适宜定义为内联函数的场合

如果函数体内有循环语句、条件语句、switch等控制流语句时,不能定义为内联函数,因为它们有改变程序流程的语句,系统无法正确地进行宏替换

2.4 函数默认参数

C++允许程序员在函数声明中以常数形式为函数指定默认值。如果在调用函数时不提供这个参数,则将使用默认值代替。

void f(int x,int y=5,int k=6);//函数声明 对函数的有效调用方式是: f(12,32,123); f(12,32); f(23);

这里要特别注意函数的声明顺序。例如:

f(int x,int y=5,int z);//错误 f(int x,int y=12,int z=34);//ok f(int x=23,int y=3,int z);//错误

函数参数初始化的顺序是从右向左一次进行的。

2.4.1 函数重载

C++允许在同一范围内使用相同名字的函数,但要保证函数参数个数或参数类型不同。如果有多个明为f的函数被定义,就称f被重载。编译器通过实参类型与同名函数的参数表进行匹配以决定调用哪个函数。例如:

void f(int x,int y);

中科院计算所培训-沈阳

17

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

void f(int x);

void f(float x,floaty); …

f(12,12); //调用f(int x,int y) f(13); //调用 f(int x)

f(12.12f,12.432f); //调用 f(float x,float y)

2.5 函数模板

2.5.1 函数模板

它是C++中实现静态多态性的一种手段,可以用来创建一个通用功能的函数以支持多种不同形参,进一步简化重载函数的函数体设计(因为有些重载函数的函数体是相同的)。

int add(int x, int y) { x=x+y; //函数体是相同的只是不同类型的形参 return x; }

float add(float x, float y)

{ x=x+y ; //函数体是相同的只是不同类型的形参,为什么不可以只写一个呢? return x; }

2.6 定义语法

使用关键字template来定义说明一个函数模板

template //或者template Type add(Type X, Type Y) { return X+Y; } 要点采用Type来代替某一数据类型定义符(占位符号),告诉编译器此处可以用合适的数据类型替代。 2.6.1 应用实例

求各种类型的两数之和

【实例练习】

#include using namespace std;

template

Type add(Type X, Type Y) //定义两数之和函数模板 { return X+Y; }

template //定义三数之和函数模板,但第二个数必须为整数 Type add(Type X, int Y, Type Z) { return X+Y+Z;

中科院计算所培训-沈阳

18

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

}

template //定义三数之和的函数模板,但第二个数必须为浮点数 float add(Type X, float Y,Type Z) { float f=X+Y+Z; return f; }

void main(void) {

cout<【蓝梦提示】

□ 在函数模板调用时必须保证实参与形参相互匹配。

2.7 new 和 delete 操作符

含义:new 和 delete 用于动态分配和释放存储空间。

用法:new 和 delete 必须配套使用,由new 开辟的空间一定要由delete 来释放。 例如:

int *p=new int;//new 进行动态分配的时候会返回它所开辟的内存的地址 …

delete p; // 当不再使用p时,一定要对它进行释放; 也可以象这样来创建: int *p=new int(5);

//动态分配一个整型空间并将其初始化为5

也可以用new 动态分配一个连续的存储空间,例如: int *p=new int[5];//动态分配5个整型空间 …

delete [] p;

【本章总结】 熟练掌握函数重载及函数模板的应用。

中科院计算所培训-沈阳

19

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

第3章 C++对C语言与类有关的性能扩充

【本章中的知识要点】

□ 面向对象程序设计方法及编程思想

□ OOP的四个基本机制(抽象、封装、继承、多态) □ C++的源程序格式。 【重点难点】

□ 面向对象的特点及如何实现对事务的抽象。

3.1 基本知识

1、OOP(Object Oriented Programming)是面向对象程序设计方法,是目前程序设计的主流方法。 2、C++是混合编程语言:C++是在C的基础上产生又保持与C相兼容,因而C++既可以实现面向对象编程又可以实现面向过程编程(兼容C)。

3.2 面向对象(Object Oriented)

3.2.1 面向对象(OO):

它是一种解决问题的方法或者观点,它认为自然界是由一组彼此相关并能相互通信的实体(Object,中文为“对象”)所组成的。如“人类”的相互关系如下: 父母 兄妹 我 朋友 儿女

3.2.2 面向对象程序设计方法(OOP):

 含义:它要求程序员使用面向对象的观点来分析问题(即将所要解决的问题转化为程序中的

对象---任何问题在程序中都被映射为对象),以找出问题的属性(通过数据来描述)与方法(通过函数来体现),然后用计算机语言来描述,最后在计算机中加以处理的一种程序设计方法。  编程要求:程序员在描述或处理问题时应具有高度的概括、分类并对它加以抽象的能力,才

中科院计算所培训-沈阳

20

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

能准确地描述出某一实体(待解决的问题)。

 应用目的:实现软件设计的产业化,变手工作坊式编程为软件构件式工业化,达到快速高效

编程。实现可重用的软件组件。  编程特点:

①根据所要解决的问题的各个相关性转化为程序中的各个类相关性,然后将每个类具体化产生出对应的问题对象,以消息传递的机制来组织对象之间的相互作用。

②程序组成形式---对象+消息,对象与对象之间通过消息作为连接相互驱动。对象(问题)之间的关系是编程关心的重点,而对象(问题)的功能实现过程则处于次要的地位(只在某一函数内考虑问题的实现)。

3.2.3 应用实例

计算两数之和  设计流程:

 定义出一个“加”类---描述出各种类型的数相加操作;

 将此类(实例化)具体化以定义产生出一个对象(代表本次计算的问题);  向此对象发送一条消息----将二数相加并发送消息参数;  再向此对象发送一条消息----显示出总和值。

【实例练习】

#include

using namespace std; class AddClass //定义一个\"加\"类 {

public:

void Add(int x, int y) { sum=x+y; } void Add(float x, float y) { sumf=x+y; } void printSum(int flag) { cout<<\"Total Sum=\"<void printSum(float flag) { cout<<\"Total Sum=\"<int sum;

float sumf; };

void main(void){

AddClass addObj; //定义出一个对象(它代表所要解决的问题\"1+2 = ?\")

addObj.Add(1,2);//向此问题的对象发送一条\"累加\"的消息同时附带二个消息参数 addObj.Add(3.4F, 5.6F);

addObj.printSum(1); //再向此问题的对象发送一条\"显示总和\"的消息

中科院计算所培训-沈阳

21

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

addObj.printSum(1.0F); }

3.2.4 OOP的优点:

 实现数据与方法的封装,通过方法来操作改变数据,提高了数据访问的安全性;  程序更加模块化,便于代码重用;

 由于程序是类的集合从而可以根据问题的相关性来组装程序,而面向过程则是函数的集合,

零零散散不便于代码重用。

3.3 抽象

对具体问题(对象)进行分类概括,提取出这一类对象的公同性质并且加以描述的过程。 编程的要求:

 先注意问题的本质及描述,其次是实现过程或细节。它直接决定程序的优劣----类的定义及组

成元素;

 所涉及到的主要内容:数据抽象(属性)---描述某类对象的属性或状态(对象相互区别的物

理量),代码抽象(方法)---描述某类对象的共有的行为特征或具有的功能。

3.3.1 如何实现抽象

通过类的定义这一机制来达到。 实例 1钟表这一类对象

 数据抽象------ int Hour, int Minute, int Second  代码抽象------ SetTime()、ShowTime()

实例 2人这一类对象(人类----地球上的所有人)  数据抽象------ char * name, char *sex, int age  代码抽象------ Eat()、Work()、Walk()、Study()

【蓝梦提示】

□ 对同一类问题可以有不同的抽象结果,这取决于程序员看问题时的视角

3.4 封装

提供将抽象出的数据成员、代码成员相组合的一种机制,它能将这二类成员再组合在一起,形成问题的类,然后再根据类来实例化以产生对象(或实体)。

1 目的:增强使用的安全性,使用者不必了解具体的实现细节,而只需要通过设计者所提供的外部接口来操作它。

2 优点:实现高度模块化,从而产生出软件构件(控件)。

3 特征:①有一个特定的边界---所有的内部变化都在此边界内;②有外部接口(类中的public成员)---此对象利用它与其它对象发生关联;③有特定的数据保护或访问权限(类中的private成员)----在对象外部不能访问或修改受保护的内部实现细节。

class Watch { public:

void SetTime(int NewH, int NewM, int NewS); void ShowTime(); private: int Hour, minute, Second;

中科院计算所培训-沈阳

22

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

};

4、如何实现封装:采用C++中的类定义的语法规则来实现。 选学内容(以下内容将在以后的课程内容中将会详细讲解,学员可以先预习!): 3.5 继承与派生

它是C++中支持层次分类的一种机制,允许程序员在保持原有类的特性基础上进行更具体的类定义。

① 可行性----因为现实世界中的各个对象并不是孤立的,而是相互关联的(在横向上体现关联,而在纵向上体现继承),因而为继承提供了可行性。

② 目的---通过继承可以达到对现有系统(程序)的重用并且还能扩充和改进现有系统。 ③ 常见形式:多重继承和单一(简单)继承。

④ 常用方式:覆盖或重载继承(重新定义出)和添加继承(新增成员)。 ⑤ 实现途径:通过定义出派生类来达到。

3.6 多态性

同一名称,不同的功能实现方式。

① 常见形式----静态多态性和动态多态性。

② 目的----达到行为标识统一,减少程序中标识符的个数。 ③ 实现途径----通过重载函数和虚函数来实现。

3.7 类和对象

3.7.1 C++中的类:

1 含义:它是对具有相同特性的一类实体(对象)的总体描述,每一个实体都包括有属性和方法(以“人”与“我”为例讲解“类”与“对象”的关系)。 2 定义语法:

class 类名

{ public:成员函数(方法)的定义; private:成员数据(属性)的定义; }; 如: class Window

{ public:

Window(int x, int y, int h, int w); ~Window();

void OpenWindow(); private: int X, Y, H, W; };

【实例练习】

编程设计一个时间(时钟)程序

□ 分析问题以找出问题的共性(属性与方法)。 □ 对问题进行抽象:

中科院计算所培训-沈阳

23

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

成员数据(属性)------ int Hour, int Minute, int Second; 成员函数(方法)----SetTime()、Showtime();

□ 对问题的属性与方法进行封装:定义出对应的类

class TimeClass

{ public: //先public成员

void SetTime(int NewH, int NewM, int NewS); void Showtime();

protected: //再protected成员 private: //后private成员 int Hour, int Minute, int Second; };

□ 实现类中的各个成员函数体(将功能具体实现):

void TimeClass::SetTime(int NewH, int NewM, int NewS) { Hour=NewH, Minute =NewM,Second =NewS; }

void TimeClass::ShowTime()

{ cout<}

□ 使用类来解决具体的问题

#include “iostream.h” void main(void)

{ TimeClass myWatch; myWatch.SetTime(8,30,0); cin.get();

myWatch.ShowTime(); }

【蓝梦提示】

□ 类是C语言中的结构体的进一步扩充与深化-----即允许带有成员函数的定义;类

中成员的前后摆放位置一般为先public成员、再protected成员、后private成员不带任何访问控制定义的类成员(缺省定义时)是private成员。

class TimeClass

{ void SetTime(int NewH, int NewM, int NewS); void Showtime();

int Hour, int Minute, int Second; }; //此时全部为private成员

3.7.2 如何正确定义出类的成员

1 成员数据:类同于一般的变量定义,但需要将它放在类的定义体中。

class Window { private: int X,Y,Z; //类中的成员数据定义 };

中科院计算所培训-沈阳

24

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

int X,Y,Z ; //错误!编译系统误认为是程序中的全局变量定义

2 成员函数:在类中加以原型定义说明,在类外加以函数体定义并且在函数名前使用“类名::”加以限定;也可以直接在类中加以说明并定义出函数体,形成内联成员函数。

class Window { public: Window() { //内联成员函数 } void Function(); //在类中加以原型定义 };

void Window::Function() {//在类外加以函数体定义但要求使用“类名::”加以限定 }

3.7.3 类中成员的访问控制项

(参见前面TimeClass类的编程规则)

1、public:不仅能被类中其它成员访问,也能在类外被类的对象访问。 2、private:只能被类中其它成员访问,不能在类外被类的对象访问。

3、protected:类同于private,其差别表现在继承与派生时对派生类的影响不同(详见以后的继承与派生部分)。

【蓝梦提示】

□ 不同的访问控制选项的差别主要体现在类中访问与在类外访问的不同一般将类中

的成员函数设计为public成员(将功能设置为可操作性),而成员数据设计为private成员(将属性量加以隐藏保护)

3.8 类中成员之间的访问方式:

1、类中成员互访:直接使用成员名来访问它。 2、类外访问:使用“对象名.成员名;”方式访问public的类中成员(数据或函数)。

【实例练习】

class Window{ public:

void OpenWindow();

long Area() { return H*W ;//直接使用成员名来访问H与W }

private: int X,Y,W,H ;

};void Window::OpenWindow() { rectangle(X,Y,X+W,Y+H) ; //X,Y,W,H等成员数据只能被类中其它成员访问

cout<//public成员也能在类外被类的对象访问并且使用“对象名.成员名;”方式。

myWin.X=10; //错误,private成员不能在类外被类的对象访问(数据封装) }

【本章总结】 本章主要讲解了OOP的四个基本机制以及面向对象的特点。类的成员函数及类的访问属性。 中科院计算所培训-沈阳

25

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

第4章 C++类和对象的编程

【本章中的知识要点】

□ C++中的类与对象的编程 □ 类中成员的访问控制项 □ 类中的各种特殊成员函数 □ 构造函数与析构函数的编程 【重点难点】

□ 内联函数的用法

□ 构造函数及拷贝构造函数的应用

4.1 对象的含义

它是具有该类的类型的某一特定实体,即类的类型的变量。

4.2 对象定义语法

类名 变量名(或对象名); 如:Window myWin;

4.3 客观对象

 现实中的一个具体实体,它具有如下特点:

① 状态----由对象的物理属性决定(以一定的物理量来表示)。 ② 行为----该对象与其它对象之间的相互作用后所产生的行为结果。 ③ 标识----描述和标识该对象的唯一标识ID(由实体的名称来表示)。 ④ 事件----对象能接受消息,从而产生相互作用。

4.4 C++中的对象

 它是对客观对象的软件抽象或描述,将所要解决的问题抽象为一个软件对象,然后采用一定

的符号加以描述,形成一个软件模块。

4.5 使用对象

对象名.成员名;

如:myWin.OpenWindow();

【蓝梦提示】

□ 只能访问类中的public成员。

中科院计算所培训-沈阳

26

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

4.6 类中的各种特殊成员函数

4.6.1 内联成员函数

将成员函数的定义体直接放在类中或者在类外部使用inline来修说明其函数体。一般适用于函数体短小的应用(2-3条语句时)

4.6.2 允许带有缺省形参值的函数

应用的目的:为给用户提供一种功能,但允许以多种方式来操作它。

class Window

{ public: // //缺省形参值的函数,形参值一般在原型中给出 void OpenWindow(int x=10, int y=10);

long Area() //成员函数的定义体直接放在类中,也可成为内联成员函数 { return H*W ; }

private: int X,Y,W,H;

}; //在类外部使用inline来修说明其函数体 inline void Window::OpenWindow(int x, int y) { }

4.6.3 类中的函数重载

其目的是使类的对象与外界交互通信更加多样化(应用的目的:为给用户提供一种功能,但以多种实现方式来提供它)。

class TV { public:

void setChannel(Remoter Obj) //使用遥控器操作TV频道 { }

void setChannel(Hand Obj) //使用手动操作TV频道 { } };

4.6.4 构造函数

1、概念:函数名与类名同名的并且无任何的返回值的成员函数。 2、定义语法:

class A {

int x,int y; public:

A( ){x=0;y=0;} // 构造函数

A( int a,int b){x=a;y=b;}//构造函数

中科院计算所培训-沈阳

27

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

};

3、作用:完成对象的初始化赋值,主要体现在如下场合:

① 创建对象时,系统自动调用它来初始化对象中的数据成员;

void main()

{ Window Win(10,10,300,300); //系统自动调用构造函数 Win.WindowArea(); }

程序的输出(根据输出的信息来讲解构造函数与析构函数执行的规则)如下: Constructor Is Called 90000

Destructor Is Called

② 对象进行类型转换时,系统自动调用它完成将一种类型对象转化为目标类的类型的对象;

【实例练习】

#include using namespace std; class MyClass {

public:

MyClass(int i=0) //进行对象类型转换 { m=i; }

void Print()

{ cout <void fun(MyClass c) { c.Print(); }

void main() { fun(5);

MyClass mc=12; //自动转型 mc.Print(); }

【蓝梦提示】

□ 如果不希望出现自动转型操作,可以在构造函数前家explicit例如:

explicit Myclass(int i=0);

③ 初始化由new所创建的对象初始状态

void main() //创建出无名的数据堆堆对象时将自动调用构造函数 { MyClass *Obj=new MyClass(5); Obj->Print() ; delete Obj ;

中科院计算所培训-沈阳

28

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

MyClass stackObj(5) ; //创建出栈对象时将自动调用构造函数 stackObj.Print(); }

4、调用时机:当对象(堆或栈对象)创建时,系统自动调用它来实现对象初始化。

5、可能的编程形式:可以将构造函数设计为内联函数、重载函数、带有缺省形参值的函数;如果用户未定义出,系统自动产生一个缺省形式的构造函数。

class Window

{ public: Window() //构造函数可以为内联函数并与另一个形成重载 { X=0,Y=0,H=600,W=800 ; }

Window(int x, int y, int h=400,int w=400) ; //可以为带有缺省形参值的函数 private: int X,Y,H,W ; };

4.6.5 析构函数

1、概念:函数名与类名重名且以~开头的无任何的返回值和形参定义的public属性的成员函数。 2、定义的语法:类名:: ~类名()

{ 函数体; }

3、调用时机:它在对象的生存期结束这一时刻(堆或栈对象),系统自动调用它,然后再释放此对象所属的空间。

void main()

{ MyClass *Obj=new MyClass(5) ; Obj->Print() ;

delete Obj ; //堆对象被删除时将触发调用析构函数 MyClass stackObj(5) ; stackObj.Print() ;

}//栈对象被删除时将触发调用析构函数 4、可能的编程形式:

可以设计为内联函数;但析构函数由于无形参定义,因而无法形成重载,也无法形成带有缺省形参值的函数,即每一个类只能有一个析构函数。用户未定义出自己的析构函数时,编译器将自动产生一个缺省形式的析构函数。

5、应用场合:如果用户希望在对象被删除之前还想完成某一事情(如清理内存、释放资源、GDI等)可以定义出它。下面给出构造函数与析构函数相互配合应用的例。

class MyClass

{ public: MyClass() //构造函数可以为内联函数 { ptr=new char [1024] ;//申请资源 }

~MyClass() //析构函数可以为内联函数 { delete [] ptr ; //释放资源 }

private: char * ptr ; };

中科院计算所培训-沈阳

29

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

4.6.6 拷贝构造函数

1、拷贝构造函数:它是一种特殊形式的构造函数(形参为本类的对象引用)。 2、定义语法: 类名::类名(类名 &对象名)

{ 函数体; } 如: class Window { public: Window(Window & Win) { X=Win.X; Y=Win.Y; H=Win.H; W=Win.W; } long Area() { return H*W; }

private: int X,Y,H,W; };

3、作用:实现用一个类的对象拷贝初始化该类的另一个对象。 4、应用场合:

① 当用一个类的对象初始化该类的另一个对象时,系统自动调用它实现拷贝赋值;

void main()

{ Window myWin(10,10,300,300); Window copyWin(myWin); }

② 函数的形参为类的对象并且采用传值调用时(如果为引用调用,将不涉及到它),调用时实参赋值给形参时,系统也自动调用它;

void ShowArea(Window Win) { cout <void main()

{ Window myWin(10,10,300,300);

ShowArea(myWin) ; //myWin --> Win

}

【蓝梦提示】

□ 函数的形参为类的对象并且采用引用调用时,不涉及拷贝构造函数的编程。void ShowArea(Window &Win) // Win 为 myWin对象的引用 { cout <void main()

{ Window myWin(10,10,300,300); ShowArea(myWin); }

中科院计算所培训-沈阳

30

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

③ 函数的返回值为类对象时,在函数返回时系统也将自动调用它。

Window getWindow()

{ Window Win(10,10,300,300) ; return Win ; }

void main()

{ Window myWin ; myWin=getWindow(); }

5、实现的机制:拷贝构造函数的实现是借助于this指针(指向被赋值对象)来达到的。

将对象拷贝的语句转化为对拷贝构造函数的调用;然后在拷贝构造函数体内实现成员赋值。6、类中必须定义出它的场合:由于系统所生成的缺省形式的拷贝构造函数的赋值规则是对应的成员相互赋值。当对象带有指针型的成员数据时,必须在类中定义出自己赋值要求的拷贝构造函数(此时将实现指针的目标单元拷贝而非指针本身拷贝),否则将导致二个对象内的成员指针指向相同的地址,从而使析构函数删除指针指向的内存二次,出现删除空指针(未指向确定的目标或所指向的目标根本不存在)错误。

【实例练习】

class Window

{ public:Window()

{ Title=new char[20];

strcpy(Title,\"Window Title\") ; }

~Window()

{ delete []Title ; }

private: char *Title ; } ;

void main()

{ Window WinOne ;

Window WinCopy(WinOne) ; }

Window::Window(Window &win) //定义出自己的拷贝构造函数 { Title=new char[20] ;

strcpy(Title,win.Title) ; //将内容进行拷贝,而非指针赋值 } /*

Window::Window(Window &win) //系统定义出的缺省形式拷贝构造函数 { Title=win.Title ; //将指针相互赋值 } */

中科院计算所培训-沈阳

31

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

4.7 异常处理

4.7.1 异常处理的方法

把会抛出异常的函数或代码放在try语句内。在函数或代码内通过throw语句抛出异常,catch语句捕捉异常。

【实例练习】

#include int Div(int x,int y) {

if(y==0)

throw y; //如果除数为零抛出一个整型异常 return x/y; }

void main() {

try //由于除法运算可能出现除零异常,因此放在try语句中 {

cout<<”5/2=”<catch(int) //捕获整型异常 {

cout<<”exception of dividing zero.\\n”; }

catch(…){cout<<”所有异常”<}

一个try语句后可接多个catch语句,catch语句按先后顺序执行,如果前一个catch语句没有捕捉到错误,会继续向后执行,只要找到一个匹配的类型,后面的异常处理都将被忽略。

4.7.2 异常规格说明

 void f() throw(int,long,float,char*);//括号中说明此函数会抛出的异常类型。  void f() throw();//函数不会抛出任何异常。

【本章总结】 本章主要讲解了类中各种成员函数的用法及异常处理的应用。

中科院计算所培训-沈阳

32

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

第5章 C++中的对象的各种编程

【本章中的知识要点】

□ 对象赋值、对象数组、对象指针、对象内嵌 □ 静态成员 □ this指针 【重点难点】

□ 对象数组、对象内嵌的应用 □ 静态成员的应用及this指针的特点

5.1 对象赋值

对象从本质上来说是类的变量,变量之间是可以相互赋值的。因而对象之间也可以相互赋值,通过对象之间的相互赋值,从而达到使二个对象的数据成员值(状态相同)一致。

5.1.1 方法

类名 对象名1(初值),对象名2; 对象名2=对象名1; 如:Window winOne,winTwo; winTwo=winOne;

5.1.2 应用目的

实现两个对象在运行过程中保持状态(属性相同)。

5.1.3 实现规则

应该重载“=”运算符。当类中不包含指针型数据成员时,可以采用缺省赋值语义来实现对应数据成员相互赋值(编译系统实现的);当类中包含有指针型数据成员时,必须定义出满足实际赋值要求的操作函数(重载“=”运算符)。否则将导致二个对象内的成员指针指向相同的地址,从而使析构函数删除指针指向的内存二次,出现删除空指针(未指向确定的目标或所指向的目标根本不存在)错误。

选学内容 运算符重载---C++中允许某些运算符具有在不同的应用场合下具有不同的运算功能的特性。详细的内容请见后面的章节,目前请正确地区分“*”与“&”的不同用法 5.2 对象数组

同一类的一些对象之间可以形成对象数组。

中科院计算所培训-沈阳

33

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

5.2.1 定义的语法

类名 数组名[元素个数];

如: Window ArrayName[10];

5.2.2 访问方法

采用下标(从0 到 n-1),但每一元素为一个对象。 数组名[下标].成员名

如: ArrayName[0].OpenWindow();

5.2.3 对象数组的初始化

首先在类中定义出对应的初始化的构造函数(因为对象数组的初始化其实就是数组中的每个元素对象的初始化),然后通过初始化列表的方式一一为各个元素对象赋初始值。

【实例练习】

class Window

{ public: Window(int x, int y, int h, int w) { X=x, Y=y , H=h, W=w; }

private: int X,Y,H,W; };

void main()

{ Window WinArray[2]= {Window(10,10,200,200), Window(20,20,300,300)}; for(int i=0; i<2; i++)

{ WinArray[i].MoveWindow(10*i, 20*i); } }

5.2.4 对象数组编程时的构造函数设计

数组中的每一个元素对象被创建时,系统都要调用构造函数来初始化各元素对象,用户可根据应用需要,将构造函数设计为:

 不定义出构造函数---采用缺省构造函数,各元素对象的初值要求为0时;  定义出具有缺省形参的构造函数---各元素对象的初值要求为相同的值时;  定义出带形参的构造函数---各元素对象的初值要求有不相同的初值时。

5.2.5 对象数组编程时的析构函数设计

当数组中的每一个元素对象被删除时,系统都需要调用一次析构函数,将导致析构函数被多次调用。因此释放资源时应先识别指针是否为NULL,避免多次删除。

【实例练习】

class Window

{ public:void OpenWindow(int x, int y, int h, int w) { Title=new char[20];

strcpy(Title,\"Window Title\"); rectangle(x,y,x+w, y+h);

中科院计算所培训-沈阳

34

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

}

~Window()

{ if(Title !=NULL) //先识别指针是否为NULL,未删除时才删除它 delete []Title; }

void MoveWindow(int newX, int newY) { }

private: char *Title; };

void main()

{ Window WinArray[2];

WinArray[0].OpenWindow(10,20,200,200); WinArray[1].MoveWindow(100,200); }

5.3 对象指针

5.3.1 C/C++语言中的指针

它是一种特殊的变量,其值代表另一个变量的存储地址(可以给出内存存储图来说明)。 1、定义语法:数据类型 * 指针变量名;

如: int * ptr;

2、初始化:通过对指针进行初始化来指示其所指向的目标变量单元,否则为空指针。

int X=1;

int *ptr=&X; //将 ptr指示目标变量X单元

【蓝梦提示】

□ 指针在使用之前必须进行初始化,否则为空指针(未指向目标变量单元或目标变

量单元已不再存在)

实例 1 int X=1;

int *ptr; *ptr=3; //未指向目标变量单元,此时的ptr为空指针 cout << X; 实例 2 int *ptr=new int(3);

cout<<*pt; delete ptr; //目标变量单元已被释放,此时的ptr已变为空指针 cout << *ptr ; //继续使用ptr,此时ptr的目标变量单元已为空(不再存在)

5.3.2 C++中的对象指针

C++中允许定义出指向某一对象内存地址的指针。 1、定义语法: 类名 *对象指针名;

如: Window *Ptr ;

2、对象指针的初始化:通过对对象指针进行初始化来指示其所指向的目标对象,否则该对象指针将

中科院计算所培训-沈阳

35

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

为空指针。

① 采用栈对象的存储地址进行初始化

对象指针名=&对象名 ; void main()

{ Window winOne(10,10,200,200),*ptr;

ptr=&winOne ; //winOne为栈对象,采用其存储地址进行初始化 }

② 采用堆对象的存储地址进行初始化

无构造函数定义时: 对象指针名=new 类名 ;

有构造函数定义时: 对象指针名=new 类名(初值); void main()

{ Window *ptr ; //定义出对象指针

ptr=new Window(10,10,200,200) ;

//new创建出无名的堆对象并采用其存储地址对指针进行初始化 ptr -> OpenWindow() ; //利用指针访问对象成员 delete ptr ; //删除堆对象 }

【蓝梦提示】

□ 使用new(触发调用构造函数)创建出无名的堆对象后,一定要用delete(触发

调用析构函数)删除该堆对象

3、利用对象指针访问目标对象成员: 对象指针名 ->成员名;

如: ptr -> OpenWindow() ; 4、如何正确地创建一批对象及删除该一批对象

 创建出一批对象: Window *ptr=new Window[5] ;  使用该一批对象:

ptr[0].OpenWindow() ; //通过对象数组中的元素对象来访问 ptr ->OpenWindow() ; //通过指针直接来访问元素对象

(*ptr).OpenWindow() ; //通过指针所指向的目标对象来访问  删除该一批对象: delete [] ptr ; void main()

{ Window *ptr=new Window[5] ;//通过定义出对象数组来创建出一批对象 ptr[0].OpenWindow() ; ptr[1].OpenWindow() ; delete [] ptr ; }

5.4 对象可以作为函数的形参和返回值的定义

【蓝梦提示】

□ 在此种应用中需要注意拷贝构造函数的设计

void ShowArea(Window win) //对象可以作为函数的形参定义 { cout<<\"Area Is\" <Window getWindow() //对象可以作为函数的返回值定义

中科院计算所培训-沈阳

36

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

{ Window win(10,10,200,200) ; return win ; }

5.5 类中包含有对象成员(类的聚集,对象内嵌,包容法使用类的

资源)

1、对象之间的关系:主要有内嵌(包含)、继承(父子)、引用关系。

2、对象内嵌:允许一个类中包含有另一个类的对象并作为此类的成员数据(类似于结构体中包含有另一个结构体)。

class Dialog { public:

Dialog(int OkX, int OkY, int OkH,int OkW, int cancelX, int cancelY, int cancelH, int cancelW, int x, int y, int h,int w):Ok(OkX, OkY, OkH, OkW), Cancel(cancelX, cancelY, cancelH,cancelW)

{ X=x, Y=y, H=h,W=w; }

private: Button Ok,Cancel; //Dialog类中内嵌有Button类的两个对象Ok、Cancel int X,Y,H,W; };

3、构造函数的设计:由于有对象内嵌式的类的数据成员,因而构造函数也必须为这些内嵌的对象成员赋初值。定义形式如下:

类名:: 类名(对象成员的初值定义,类的成员初值定义):成员1(初值),成员2(初值) { 本类成员赋初值;//参考上面的实例 }

4、对象内嵌时的初始化:采用初始化列表为各个对象成员赋初值;

Dialog(int OkX, int OkY, int OkH,int OkW,int cancelX, int cancelY, int cancelH, int cancelW,int x, int y, int h,int w): Ok(OkX, OkY, OkH, OkW),Cancel(cancelX, cancelY, cancelH,cancelW)

5、对象内嵌时的初始化顺序:产生对象时先调用对象成员所在类的构造函数,再调用本类的构造函数; Ok-->Cancel -->Dialog

【蓝梦提示】

□ 各个对象成员的构造函数调用次序仅由各个对象成员在类中的说明次序决定,析

构函数的调用次序则相反。

5.6 类中的静态成员

在类体中以static修饰的成员。

5.6.1 静态成员的编程

1、正确理解静态全局变量与静态局部变量的应用不同。

2、定义的语法:在类中以static定义出此成员,对于数据还应在类外对它初始化并采用类名加以限定。

中科院计算所培训-沈阳

37

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

class Chinese

{ public: static int getSkinColor() //静态成员函数,能够访问static数据 { return SkinColor ; }

private: static int SkinColor; //在类中以static定义出 char *Name ; };

int Chinese::SkinColor=YELLOW ;//在类外对它初始化(注意必须采用类名来限定它)

3、为什么需要静态成员:由于每个对象都拥有自己的数据成员,对象之间不能彼此互访;如想让各个对象都能共享某一个相同的数据成员值,可将此数据成员定义为静态成员。

如:中国人的肤色这个属性量SkinColor,因为全体中国人都是“黄皮肤” !但姓名Name不能为static型,否则全体中国人都是同一个姓名,这违背逻辑!

4、实现机制:编译器只为该数据成员分配一份内存空间,该类的各个对象共用它。

Chinese Li, zhang, Wang;

【蓝梦提示】

□ 静态成员数据可以为public或者private(此时必须定义出静态成员函数)

5、静态成员数据的初始化:不能在类定义体内初始化(类是一种描述并非具体实现),只能在类定义体外使用类名限定的方法来初始化,未强行初始化时系统自动赋初值为0。

class Chinese

{ private: static int SkinColor=YELLOW ; //错误,不能在类中对它初始化 };

int Chinese::SkinColor=YELLOW ;//必须在类外对它初始化而且采用类名来限定它 int SkinColor=YELLOW ; //错误,因为未采用类名来限定它,这样变为全局变量

6、静态成员函数:根据OOP的数据封装规则,应该将类中的静态成员数据为private成员。但此时将不能通过对象来访问它,这可通过提供public的静态成员函数来实现。

5.6.2 静态成员的访问方法:

1、静态成员数据:为private/protected属性时,只能通过公有的静态成员函数访问它;为public时,可以采用“类名::成员名”访问它(或者“对象名.成员名”)。 2、静态成员函数(系统自动将它看作为内联函数):采用“类名::成员函数名(实参)”访问它。

void main()

{ Chinese Li ,Zhang;

Li.getSkinColor(); //采用“对象名.成员名”格式,一般不采用此方式 Chinese::getSkinColor(); //采用“类名::成员名”格式 }

5.6.3 静态成员函数与非静态成员函数之间的访问:

1、静态成员函数访问静态成员数据,可以直接使用其变量名来访问; 2、静态成员函数访问非静态成员数据时,则需要使用“对象名.成员名”(因为静态成员函数中无this指针定义,不能隐含指定成员数据所属的对象)。

3、非静态成员函数可以直接访问类中的静态成员数据和非静态成员数据。

【实例练习】

#include #include

中科院计算所培训-沈阳

38

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

using namespace std; class Chinese {

public:

Chinese(string na){Name=na;} public:

static int getSkinColor(Chinese Person) //静态成员函数 { cout <void ShowMessage() //非静态成员函数 { cout << Name; //可以直接访问类中的非静态成员数据 if(SkinColor==YELLOW) cout <<\" 皮肤颜色: Yellow\"<private:

static int SkinColor; //在类中以static定义出 string Name;

enum{YELLOW};//隐式枚举类型 };

int Chinese::SkinColor=YELLOW;

//在类外对它初始化(注意必须采用类名来限定它) void main() {

Chinese Li(\"Li\");

Chinese::getSkinColor(Li);//并打印输出Li的姓名 Li.ShowMessage();

}

4、应用场合:取代和减少程序中的全局变量的个数(因为这是面向过程的设计方式),同时又可以对它加以隐藏、封装保护,模块化并利于代码重用。

5.7 C++中的this指针

1、this指针:它是隐含在类的成员函数中的一对象指针,每当成员函数被调用时,this指针自动指向该成员函数所属的类的对象(即this指针代表当前对象)。 2、系统定义的格式: 类名 *const this;

要点:该指针是由系统自动产生的,不需要人为定义出;并且它是常量指针,不能被赋值,只能使用它。 3、this指针的用途:

 利用它可将成员函数的形参名与类中成员数据同名 class Window

{ public: Window(int X, int Y, int H, int W) //成员函数的形参名 { this->X=X ;

中科院计算所培训-沈阳

39

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

this->Y=Y ; this->H=H ; this->W=W ; }

private: int X,Y,H,W ; //类中成员数据 } ;

 利用this指针能保证对象的每一个成员函数能够访问其对应的成员数据,从而使C++中的各个对象具有自己的数据成员空间,同时共用同一个成员函数代码的机制得以实现(因为每当成员函数被调用时,未加以强制说明的成员数据是由this指向的,而this指针又自动指向该成员函数所属的类的对象,当然此成员数据即为同一对象的成员数据)。 Window winA, winB ;

winA.GetX()---->winA.GetX() ---->winA.GetX()

{ return X; {return this->X ;--->return winA.X ; } }

winB.GetX()---->winB.GetX() ---->winB.GetX()

{ return X; { return this->X ;---> return winB.X ; } }

 在成员函数中检测出接受成员函数调用的对象是谁,由此可以根据不同的对象实现不同的行为。

【实例练习】

class Window

{public: void CopyWindow(Window & WinObj) { if( this != &WinObj //在程序内检测源目对象是否为同一个对象 { X=WinObj.X ; Y=WinObj.Y ; H=WinObj.H ; W=WinObj.W ; } }

private: int X,Y,H,W ; };

void main()

{ Window winA(10,10,300,300), winB(20,20,200,200) ;

winA.CopyWindow(winA) ; //因为自己拷贝自己,毫无意义 ! winA.CopyWindow(winB) ; //拷贝的源对象应该为另一个对象 }

【本章总结】 本章主要讲解了对象数组、对象嵌套及静态函数和this指针的用法其中需要熟练掌握的有对象嵌套、静态函数和this指针的内容。 中科院计算所培训-沈阳

40

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

第6章 友员函数与运算符重载

【本章中的知识要点】

□ 友员函数及友员类的编程规则 □ 类的前向说明

□ 运算符(一元及二元)重载的实现 【重点难点】

□ 运算符(一元及二元)重载的实现

6.1 友员函数

6.1.1 友员函数

在类定义体中由关键字friend加以修饰说明的非成员函数,在它的函数体中能够通过该类的对象来访问类中的private/protected成员。

【实例练习】

class Window

{public: Window(int x, int y, int h, int w) { X=x , Y=y , H=h , W=w ; }

friend long Area(Window & WinObj) ; //在类中定义出友员函数的原型 int getH() { return H ; }

int getW() { return W ; }

private: int X,Y,H,W ; };

long Area(Window & WinObj) //在类外定义出友员函数的函数体 { return (long)WinObj.H*WinObj.W ;

//实现通过对象访问类中的private成员,因为对窗口的面积的计算最简单的方式应该是H*W。

void main()

{ Window winA(10,10,300,300) ;

cout <<\"Window Area is \"<【蓝梦提示】

□ 可以将友员函数的定义体直接放在类体中,形成内联函数;

□ 友员函数在类中的摆放位置可以在public或者private之后,其编程效果相同

class Window

中科院计算所培训-沈阳

41

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

{ private: int X,Y,H,W ;

friend long Area(Window & WinObj) { } } ;

6.1.2 为什么需要友员函数:

 在封装和快速性两方面合理选择----OOP中类的主要优点是可以实现数据隐藏与保护,即不

允许非成员函数对它访问,这样可以提高数据使用的安全性。但在访问数据的效率方面则下降(因为正常时应该通过public型的方法来访问)。但在某些应用场合下,非成员函数体中需要通过对象名直接访问类中的private成员,以达到高速高效率地访问数据,这可以通过友员函数来实现。

friend long Area(Window & WinObj) { //非成员函数体中需要通过对象名访问private成员 return (long)WinObj.H*WinObj.W ;

//否则应该(long)WinObj.getH()*WinObj.getW() ;

}

 有些函数需要放在类的外面或者类设计完以后再加以补充的,此时可能不能设计为类中的

成员函数,但是又需要访问类中的私有成员。

6.1.3 友员函数与成员函数在编程方面的不同点

 友员函数在类体内应该采用friend加以限定,但成员函数不需要。  友员函数体在类体外不需要采用“类名::”加以限定。

 调用它时不需要采用“对象名.成员名”方式,而是直接调用它。

由于友员函数不是成员函数,因而无this指针,在函数体中访问对象中的成员时必须通过对象名(可将形参定义为引用),而在成员函数体内可以直接访问成员数据。

6.1.4 友员函数与一般函数(非成员函数)在编程方面的不同点

 友员函数在类体内应该采用friend加以限定,但非成员函数不需要在类中声明。  在友员函数体内可以利用对象访问类中的private成员,但非成员函数则不能。

6.2 友员类:

6.2.1 含义

一个类为另一个类的友员类---此类的所有成员函数都能访问对方类的私有成员。

6.2.2 友员类的定义

将此类名在另一个类中使用friend加以修饰说明。

class MyClass ; //类的引用性前向说明 class Window { public:

friend class MyClass ; //类名的使用

中科院计算所培训-沈阳

42

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

private: int X,Y,H,W ; };

class MyClass //类的定义体 { public: void ShowArea()

{ cout <<\"Area is\" <Window win ; };

6.2.3 应用目的

在类的封装和共享两方面合理选择---类的主要优点是实现数据隐藏与保护,从而将数据与方法相互组合产生的代码块,但这样将使各个类相互孤立,无法实现类之间的共享。但在应用需求中可能要实现多个类之间的共享,这可以采用友元类的方式来实现。

6.3 类的引用性前向说明

当类名使用在定义之前时,需要使用前向说明以便编译器知道此标识符是类名,从而正确地编译。

6.4 运算符重载

6.4.1 运算符重载的实现

运算符重载

1、含义:运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据导致不同类型的行为。

2、为什么需要运算符重载:C++中预定义了许多运算符,能够满足用户的一般应用要求,但是其运算的对象应是基本的数据类型,而不适用于用户自定义数据类型(如类的对象运算),这可以使用运算符重载来实现。

如: X + Y; 但 \"ABC\" + \"DEF\";则错误。

3、应用目的:可以实现同一运算符具有多种不同运算功能,如将“+(加)”变化为“*(乘)”,但一般不实现此规则;另外还可以扩充运算符的运算对象数据类型(如将+也适用对象类型),这才是运算符重载的真正目的。

4、运算符种类:共有三种,从而在C++中只需要掌握三种类型的运算符重载的规则。 ① 一元运算符(一元前置):如 +X, ++X ② 一元运算符(一元后置):如 X++,X-- ③ 二元运算符:如 X+Y, X-Y, X*Y

5、实现手段:借助于类运算符函数(将运算符函数设计为类中的成员函数)和友员运算符函数(将运算符函数设计为类的友员函数)来实现。 6、运算符重载的规则:

1) C++中除了少数几个之外,全部可以重载,而且只能重载C++中有的运算符。 2) 重载之后运算符的优先级和结合性都不会变。 3)运算符是针对新类型数据的实际需要,对原有数据类型进行适当的改造。一般来讲重 载的功能应该与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个

中科院计算所培训-沈阳

43

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

操作对象是自定义类型的。

7、重载形式:类的重载形式有两种,一种是重载为类的成员函数,一种是重载为友元函数。

1) 运算符重载为类的成员函数的一般语法为: 函数类型 operator 运算符(行参表) {

函数体; }

2) 运算符重载为友元函数的一般语法为: friend 函数类型 operator 运算符(行参表) {

函数体; }

8、重载的实现机制:编译系统将指定的对象运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参;通过函数调用从而在函数体中达到特定的运算功能的重新定义。 ① 一元前置运算符:

++Obj1----> operator++(Obj1) //借助于友员运算符函数 { 函数体 }

++Obj1----> Obj1.operator++() //借助于类运算符函数 { 函数体 }

【蓝梦提示】

□ 重载一元前置运算符时,运算符函数不需要带一个int标识的形参定义。

② 一元后置运算符:

Obj1++----> operator++(Obj1, int X) //借助于友员运算符函数,但带int参数 { 函数体 }

Obj1++----> Obj1.operator++(int X) //借助于类运算符函数,但带 int参数 { 函数体 }

【蓝梦提示】

□ 重载一元后置运算符时,运算符函数需要带一个int标识的形参定义。

③ 二元运算符

Obj1+ Obj2---->operator+(Obj1, Obj2) //借助于友员运算符函数 { 函数体 }

Obj1+ Obj2---->Obj1.operator+(Obj2) //借助于类运算符函数 { 函数体 }

6.4.2 使用类的成员函数实现重载

此方法的本质是将运算符函数设计为类的成员函数。因为运算符函数是成员函数,从而允许它访问类中私有成员数据,从而方便重载的实现。

中科院计算所培训-沈阳

44

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

1、定义形式:类同于一般的成员函数并且也允许为内联函数,只是函数名有要求。 2、如何设计它:

 函数返回值类型--->与运算结果的数据类型一致。  函数名--->以“operator 运算符”加以命名。

 函数形参--->各个形参类型与运算对象的数据类型一致(一般采用引用),形参的个数为运

算对象的个数减一。

 函数体--->按照运算符在运算过程中的自然运算语义来编程。

【实例练习】

□ 实现复数的各种计算(相加及对它取负等运算)。

class Complex

{ public:Complex(int r=0, int i=0);

Complex operator+(const Complex &Obj); Complex operator-();

Complex & operator=(Complex &Obj); //“=”必须实现为类运算符函数 private: int real, image ; };

Complex ::Complex(int r, int i) { real=r ; image=i ; }

Complex Complex::operator+(const Complex &Obj) { int r=real + Obj.real ;

//当“+”用于基本类型的数据运算时,仍然使用C++语言中标准的运算规则 int i=image + Obj.image ; return Complex(r, i) ; }

Complex Complex::operator-()

{ return Complex(-real,-image) ; }

Complex & Complex::operator=(Complex &Obj) //定义出重载“=”运算符函数 { real=Obj.real ; image=Obj.image ; return *this ; }

void main()

{ Complex C1(10,20), C2(20,30), C ;//将具体运算的复数转化为Complex类的对象

C=C1+C2 ; //通过将“+”用于指定类的对象运算,从而间接地实现复数的运算。另外

//只在将“+”用于指定类的类型对象运算时,编译系统才启动重载规则

C=-C1 ; }

【蓝梦提示】

□ 被重载的运算符如用于基本类型的数据运算,系统仍旧使用原始的运算功能;□ 只有被应用于指定的类的类型对象运算时才转化为对运算符函数的调用。

中科院计算所培训-沈阳

45

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

6.4.3 使用友员函数实现重载

将运算符函数设计为非成员函数,同时为能访问类中的private成员数据,可将它设计为友员函数。

1、定义形式:类同于一般的友员函数的设计并且也允许编程为内联函数形式,只是其函数名有要求(以“operator 运算符”加以命名;)。 2、如何设计它:

 函数返回值类型--->与运算结果的数据类型一致;  函数名--->以“operator 运算符”命名;

 函数形参--->各个形参类型与运算对象的数据类型一致(一般采用引用),形参个数与运

算对象的个数相同;

 函数体--->按照运算符在运算过程中的自然运算语义来编程。

【实例练习】

class Complex{ public:Complex(int r=0, int i=0) ;

friend Complex operator+(const Complex &Obj1,const Complex &Obj2) ; friend Complex operator-(const Complex &Obj) ;

Complex & operator=(Complex &Obj) ; //“=”必须实现为类运算符函数 Private: int real, image ; } ;

Complex::Complex(int r, int i){ real=r ; image=i ; }

Complex operator+(const Complex &Obj1,const Complex &Obj2) { int r=Obj1.real + Obj2.real ;

//当“+”用于基本类型的数据运算时,仍然使用C++语言中标准的运算规则 int i=Obj1.image + Obj2.image ; return Complex(r,i) ; }

Complex operator-(const Complex &Obj){ return Complex(-Obj.real,-Obj.image) ; }

Complex & Complex::operator=(Complex &Obj) //定义出重载“=”运算符函数 { real=Obj.real ; image=Obj.image ; return *this ; }

void main(){ Complex C1(10,20), C2(20,30), C ;

C=C1+C2 ; //只在将“+”用于指定类的类型对象运算时,编译系统才启动重载规则 C=-C1 ;}

【蓝梦提示】

□ 注意形参的定义与类运算符函数的不同;由于在友员运算符函数中无this指针, □ 因而在函数体内必须通过对象名来访问类中的private成员数据。

6.4.4 自动类型转换

在C和C++中,如果编译器看到一个表达式或函数调用了一个不合适的类型,它经常会执行一个

中科院计算所培训-沈阳

46

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

自动类型转换。在C++中,可以通过定义自动类型转换函数来为用户定义类型达到相同效果。这些函数有两种类型:特殊类型的构造函数和重载的运算符。构造函数的方式已经在前面章节中介绍过,这里不再赘述,主要来讲一讲重载运算符的方式。

可以创建一个成员函数,这个函数通过在关键字operator后跟随想要转换到的类型的方法,将当前类型转换为希望的类型,并且不需要设置它的返回类型。 实例:

【实例练习】

#include using namespace std; class A{ int i; public:

A(int x=0):i(x){}

void dis(){cout<class B{ int x; public:

B(int i):x(i){}

operator A() const{return A(x);} };

void g(A a){ a.dis(); }

void main(){ B b(12); g(b); //

g(1); //直接调用A类的构造函数实现转换 }

6.4.5 运算符重载编程的进一步说明

1、通过运算符重载可以扩充C++中各基本类型的运算符的功能(使其也能适用于新的数据类型如复合类型的数据运算)。

如: Z=X+Y; ---> Obj3=Obj1 + Obj2 ;

2、重载后的运算符,其优先级和结合性仍保持语言中的原始规则;

3、重载后的运算符运算的功能尽量保持原有含义不变,以免误解造成运算的混乱(如不要将“+”重载为两对象相减);

4、对如下运算符不能重载:.成员分隔符,::作用域限定符,?:条件运算符,sizeof运算符,

成员指针选择符”.*”

5、重载的要求:一般将一元运算符重载为类成员函数(“=”也必须重载为类运算符函数,请见前面的应用例),而将二元运算符重载为友员运算符函数(当然也可重载为类运算符函数)。

【本章总结】 本章主要讲解了友元函数及运算符重载的应用,其中应熟练掌握的是运算符重载的用法。 中科院计算所培训-沈阳

47

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

第7章 C++类的继承与派生

【本章中的知识要点】

□ 类的继承与派生目的,派生类的正确定义,子类中的成员正确设计 □ 继承方式的合理选用,多重继承的编程 【重点难点】

□ 继承中类之间的访问属性、继承方式的合理选用

7.1 入门知识

7.1.1继承

保持已有类的特性而构造新类的过程称为继承;

7.1.2 派生

在已有类的基础上新增自己的特性而产生新类的过程称为派生。

7.1.3为什么需要继承与派生

 类的定义和设计为实现数据隐藏、封装提供了途径,但如何有效地创建类?  如果能在已有的资源基础上设计新类,则能加快编程速度  为什么要继承:新的问题与已解决过的问题有相似性。

人 亚洲人 中国人 北京人

美国人 中国人

美籍华人

 为什么要派生改造:新的问题与已解决过的问题又有差异。

7.1.4应用的目的

实现代码重用,在原有程序的基础上快速编程新的应用。

7.1.5继承的可行性

由于自然界的各个实体(对象)具有自然的相关性,这在纵向上主要体现为继承与派生的特性(参见上述例)。而C++中的类是对自然界中的实体描述,当然在编程时可以借用实体自然的相关性来组织程序的各个模块,以减少重复编程。

中科院计算所培训-沈阳

48

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

7.2 如何实现继承与派生

7.2.1 基类和派生类

被继承特性的类称为基类(或父类);而新增特性从而派生出的类称为派生类(或子类) Point -->Rectangle -->Window -->Dialog -->CommonDialog

【蓝梦提示】

□ 基类和派生类只是相对的问题层次关系,而不是绝对的问题层次关系 □ 具体谁是基类和派生类这要取决于所解决的问题层次

7.2.2 派生类的定义语法

class 派生类名 :继承方式 基类名1,继承方式 基类名2 { 成员定义; };

如: class Dialog: public Window //单一(简单)继承 { } ;

class PaintBrush: public Rectangle, public Circle //多重继承 { };

7.2.3 如何设计派生类成员

比较基类和派生类之间的相同点和差异部分,对相同点则加以继承(不必再定义出);而对差异部分则加以扩充(新增、重载或者覆盖),对此应该定义出。

class Window

{public: void OpenWindow() ;

void MoveWindow(int newX, int newY) ; void CloseWindow() ; void setX(int newX) { X=newX; }

void setY(int newY) { Y=newY; } private: int X,Y,H,W ;

//它们不仅可以表示窗口的位置与大小,同时也可以表示对话框的位置与大小

} ;

class Dialog: public Window

{ //重载基类中的成员,因为画对话框与画窗口是不一样的

public: void OpenWindow(int x, int y)

{ setX(x) ; setY(y) ;

中科院计算所培训-沈阳

49

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

Window::OpenWindow() ; }

void MoveWindow(int newX, int newY) ; //覆盖基类中的成员 void SetText(char *newText) ; //新增新的成员函数 private: char * Text ; //新增新的成员数据 Button OKButton,CancelButton ; } ;

void main()

{ Dialog myDialog ;

myDialog.OpenWindow(10,10) ;

myDialog.MoveWindow(100,100) ; //访问Dialog类中MoveWindow()

myDialog.Window::MoveWindow(100,100) ; //访问Window类中MoveWindow() }

7.3 继承的方式

主要有public、private、protected三种,不同的继承方式的直接影响主要体现在派生类的成员如何访问基类中的成员(垂直方向上)及派生类对象如何访问基类中的成员(水平方向上),它们在垂直方向上是相同的,主要体现在水平方向上的不同。

7.3.1 三种继承的方式差别

1、public(公有):继承时保持基类中各成员属性不变,并且基类中private成员被隐藏。派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象只能访问基类中的public成员。 2、private(私有):继承时基类中各成员属性均变为private,并且基类中private成员被隐藏。派生类的成员也只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象不能访问基类中的任何的成员。 3、protected(保护性):继承时基类中各成员属性均变为protected,并且基类中private成员被隐藏。派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象不能访问基类中的任何的成员。

7.3.2 public 和 private继承方式的区别

1、C++中的继承与派生为程序员实现代码的重用提供了可行性;public 公有继承方式它主要揭示了父类与子类在概念上的相关性---即子类应该是父类的特化(或为父类的超集),当描述具有泛化和特化概念的两个层次实体时,应采用public继承方式。

class Point { public: Point(int x,int y) { PointX=x , PointY=y ; }

int GetX()

{ return PointX ; }

int GetY()

中科院计算所培训-沈阳

50

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

{ return PointY ; }

private: int PointX ,PointY ; } ;

class Circle :public Point //public继承方式,因为圆(具有一定的半径值)是点的特化

{ public: Circle(int X, int Y, int R):Point(X,Y) { Radius=R ; }

//...其它成员定义 private: int Radius ; };

2、private私有继承方式则主要着重于实现代码重用,应用它的主要目的是为了继承父类的接口或父类的数据结构、重用基类中的某些成员函数等。在概念上不必非要具有泛化与特化的子类型关系。如表单类Table从点Point类继承,程序员的主要目的是要重用基类中的GetX()、GetY()等函数,同时也可以将点的X、Y坐标看成为表格的行列单元格位置,从而可以重用Point类的数据结构。此时应采用private继承方式,而不宜使用public继承方式。

class Table:private Point //private继承方式,因为表格不是点的特化 { public: Table(int Row, int Column):Point(Row,Column) { } int GetRow()

{ return GetX(); }

int GetColumn()

{ return GetY() ; }

//...其它成员定义 };

3、protected保护型继承方式则是前二种的折中,即派生类与基类之间有一定的概念相关性但是又不希望派生类的对象访问基类中成员但允许派生类中成员使用基类中成员。例如点--->圆,具有概念的相关性但是又不希望直接通过圆对象来访问点中成员而只允许圆类中成员访问基类点中成员, 不能完全设计为public 继承方式,但如定义为private继承方式则表明派生类与基类无任何关系,但从逻辑上来说圆是点的特化。此时可以采用protected保护型继承方式。

class Circle : protected Point //protected继承方式

{ public: Circle(int X, int Y, int R):Point(X,Y) //必需包含对直接基类的构造函数的调用 { Radius=R ;

cout <//...其它成员定义 private: int Radius ; } ;

void main()

{ Circle myCircle(10,10,100) ;

cout <中科院计算所培训-沈阳

51

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

}

7.4 继承的多重性

C++允许派生类以两种形式继承基类,即单一(简单)继承和多重继承

7.4.1 含义

 单一继承:派生类只从一个基类派生。  多重继承:派生类从多个基类派生。

7.4.2 对象的多重相关性

自然界中的某些实体可能会与两类以上的实体发生相关性,这样就出现了继承的多重性。

7.4.3 多重继承时的派生类的定义语法

class 派生类名:继承的方式 基类名1,....继承的方式 基类名n { 成员定义 ; } ;

如:class Master: public Student ,public Person //研究生不仅是学生同时也是人 { } ;

class PaintBrush: public Rectangle, public Circle //画笔不仅可以画出四边形也可以画出圆 { } ;

【蓝梦提示】

□ 派生类继承了从基类名1到基类名n的所有的成员;每一个“继承的方式”只用

于其后基类,它们可以是相同的继承方式,也可以为不同的继承方式;在概念及操作方式上,可以将多重继承视为单一继承的扩展

7.4.4 应用场合

它常用于设计一个综合多个类特性的派生类,提高代码的可重用效率。

7.5 继承时的构造函数

派生类没有继承基类的构造函数,为了实现对派生类对象的初始化,必需在派生类中自行定义出对应的构造函数,以实现对派生类对象初始化。

7.5.1 设计原则

派生类的构造函数必需包含对直接基类的构造函数的调用,以实现对从基类中继承来的成员初始化。

中科院计算所培训-沈阳

52

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

7.5.2定义的格式

①单一继承时

派生类构造函数名(基类的形参定义,派生类的形参定义):基类的构造函数名(形参名) { 派生类的成员赋值 }

class Circle :public Point

{ public: Circle(int X, int Y, int R) :Point(X,Y) //必须要对基类的构造函数调用 { Radius=R ; }

//...其它成员定义 private : int Radius ; };

②多重继承时

派生类构造函数名(基类1的形参定义,...,基类n的形参定义,派生类的形参定义) :基类1的构造函数名(形参名),...,基类n的构造函数名(形参名) {

派生类的成员赋值 }

如: class Master: public Student, public Person

{ public: Master(char*Name, int ID,char Department, int age): Student(Department),

Person(Name, ID)

{ MasterAge=age; }

private: int MasterAge; };

③多重继承并且内嵌有对象数据成员时

派生类构造函数名(基类1的形参定义,...,基类n的形参定义,对象数据成员所需参数定义,派生类自己的形参定义):基类1的构造函数名(形参名),...,基类n的构造函数名(形参名),对象数据成员的初始化

{

派生类的成员赋值 }

class Dialog:public WindowClass { public:

Dialog(int X, int Y, int H,int W, char *Title):WindowClass(X,Y,H,W),\\

OKButton(X+ W/3,Y+2*H/3,20,10),CancelButton(X+2*W/3,Y+2*H/3,20,10) { TitleText=new char [100]; //申请内存空间 strcpy(TitleText,Title,); }

~Dialog() //不需显示调用直接基类WindowClass类的析构函数 {

delete [] TitleText; //必须释放所申请的内存空间 }

void OpenWindow()

中科院计算所培训-沈阳

53

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

{ WindowClass::OpenWindow(); //调用基类中的OpenWindow()以画出窗口 OKButton.OpenButton(); //画出按钮 CancelButton.OpenButton();

outtextxy(X+W/3,Y+H/3,TitleText); //显示出对话框的标题文字 }

void MoveWindow(int NewX,int NewY) { CloseWindow(); SetX(NewX);

SetY(NewY); //也可以在基类中将X,Y设置为protected成员 OpenWindow(); }

private:

char *TitleText;

Button OKButton,CancelButton; };

【蓝梦提示】

□ 一旦基类中定义出构造函数和析构函数,派生类也必需定义出它们,否则全部

采用缺省形式。

□ 派生类只需为它的直接基类定义构造函数的形参并调用它,而不需为它的上基

类(爷爷类)定义形参(以区别于虚基类构造函数的设计格式)。

7.6 继承时的析构函数

派生类没有继承基类的析构函数,为了实现在派生类的对象删除时能完成指定的任务,必需

在派生类中自行定义出自己的析构函数。

7.6.1 设计原则

只需要为本层次的类定义出析构函数,不需显示调用直接基类的析构函数(这将由系统隐式地调用)。

7.6.2 定义格式

类同于一般的析构函数定义。

类名::~类名() { }

7.6.3 原因

因为一个类中只有一个析构函数(无重载形式),能由系统准确识别并且自动隐式调用。

7.6.4 调用次序

 构造函数的调用次序---首先基类,后内嵌的对象数据成员,再派生类;  而析构函数则相反---首先派生类,后内嵌的对象数据成员,再基类。

中科院计算所培训-沈阳

54

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

class Base

{ public: Base(int b) { B=b ;

cout <<\"Base Constructor Called\"<~Base()

{ cout <<\"Base Destructor Called\"<{ cout << B<class Derived: public Base

{ public: Derived(int b, int d):Base(b) { D=d ;

cout <<\"Derived Constructor Called\"<~Derived()

{ cout <<\"Derived Destructor Called\"<void print()

{ Base::print() ; cout << D<private: int D ; } ;

void main()

{ Derived Obj(1,2) ; Obj.print() ; }

程序的输出结果为: Base Constructor Called Derived Constructor Called 1 2

Derived Destructor Called Base Destructor Called

【本章总结】 本章主要讲解了继承和派生的特点以及子类和父类之间的访问属性。需要熟练掌握的是子类继承父类后其构造函数的设计以及他们之间的访问特点。 中科院计算所培训-沈阳

55

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

第8章 多重多级继承及虚基类

【本章中的知识要点】

□ 继承时的二义性产生的时机,同名支配原则 □ 虚基类及派生类时的构造函数设计规则 【重点难点】

□ 虚基类的应用,派生类构造函数的设计

8.1 多重多级继承时访问二义性

8.1.1 访问时的二义性

对某一函数调用时将有多种可能的形式出现。 如:class Window { public: Window();

Window(int x=0, int y=0, int h=600, int w=800); };

void main()

{ Window winA; //系统不知将调用那一种形式的构造函数来实现初始化,这是典型的访问时的//二义性问题。但在多重多级继承时还会产生另一种形式的访问时的二义性问题。

}

8.1.2 在多重继承时的二义性

在多重继承时,基类与派生类之间或基类之间出现同名成员时,将出现访问时的二义性。这可采用类名指定或支配原则来解决访问时的二义性问题。

class Person

{ public: Outobj Eat(Food &obj); //每个人都有“吃”的行为(方法) };

class Teacher

{ public: Outobj Eat(Food &obj);

//每个老师也应该都有“吃”的行为(方法),但与一般的人的吃法有差别

void TeachMethod(Tool &teachTool) ;//每个老师都应该有自己的教学方法 };

class TrainTeacher: public Person, public Teacher { public: void TeachMethod(Tool &teachTool) ; };

//每个职业技能培训的老师也都应该有自己的教学方法,但与一般学校的老师的教学方法有//差别,故重写此方法。 void main()

{ TrainTeacher zhang ;

中科院计算所培训-沈阳

56

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

zhang .TeachMethod(Computer) ;

//调用时将采用同名覆盖原则来决定调用TrainTeacher类中的 TeachMethod()函数

zhang.Eat(apple) ; //调用时将出现访问时的二义性(到底是执行Person::Eat()还是Teacher::Eat() ?) }

8.1.3 同名支配(覆盖)原则

派生类中的成员名覆盖基类中的同名成员;调用时未强行指明时则为派生类中的同名成员;如访问被覆盖的同名基类成员,应使用基类名加以限定。

void main()

{ TrainTeacher zhang;

zhang.TeachMethod(Computer) ;

//调用时将采用同名覆盖原则来决定调用TrainTeacher类中的TeachMethod()函数 zhang.Teacher::TeachMethod(Blackboard) ;

//调用时由于采用了基类名加以限定,从而调用Teacher类中的TeachMethod()函数

zhang.Person::Eat(apple) ; //必须采用基类名加以限定否则将出现访问时的二义性 zhang.Teacher::Eat(apple) ; //必须采用基类名加以限定否则将出现访问时的二义性 }

8.1.4 在多级继承时的二义性

派生类从多个基类继承派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性(类似生物学中的“近亲繁殖”)---这可以通过采用虚基类的机制来解决。 例如:

class A{ public:

void dis(){cout<<\"Hello A\"<class B: public A{ };

class C: public A{ };

class D:public B,public C{ };

void main(){ D d;

d.dis();//编译器无法判断该使用从B类继承来的dis()还是从C类继承来的dis()。 }

8.2 虚基类

在派生类的定义时以virtual加以修饰的基类

8.2.1 定义语法

各个虚基类的说明位置无先后次序要求;每个virtual只对其后的基类名起作用,参见上述实

中科院计算所培训-沈阳

57

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

例和如下的Circle类的定义。

8.2.2 作用

它主要用来解决多重与多级继承时可能发生的对同一基类继承多次而产生访问的二义性问题,为最远的派生类提供一份基类的成员而不重复对它产生多次拷贝,因为此时编译系统将采用优化的编译方式来处理各级派生类的成员。

8.2.3 如何判断是否为虚基类的问题

画出多重与多级继承时的关系链,观察是否有从某一个共同的起点出发,经过不同的途径,最后又汇合在一处的结点;此共同的起点(基类)应为虚基类。

8.2.4 处理的方法

应该将此共同的起点基类设计为虚基类。

8.2.5 带有虚基类的最远的派生类的构造函数

1、特点:虚基类和一般基类的最大的差别在于派生类的构造函数定义(一般的基类方式继承时只需要初始化其直接基类)。

2、规则:最远的派生类的构造函数不仅需要分别对它的直接基类初始化,也需要对共同基类(虚基类)初始化并且调用其构造函数。

【实例练习】

#include #include using namespace std; class A {

public:

A(int i=0){x=i;cout<<\"Contructor A\"<class B:virtual public A {

public:

B():A(10){cout<<\"Construtor B\"<class C:virtual public A {

public:

C():A(32){cout<<\"Constructor C\"<class D:public B,public C {

中科院计算所培训-沈阳

58

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

public:

D():A(100){} };

void main() {

D d; d.dis(); }

【蓝梦提示】

□ 一旦基类中定义出构造函数,派生类也必须定义出自己的构造函数,否则将全部采用缺省形式

【本章总结】 本章主要讲解了多重继承的特点及虚基类的应用。需要熟练掌握的是在多重继承中派生类构造函数的设计

中科院计算所培训-沈阳

59

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

第9章 多态性和虚函数

【本章中的知识要点】

□ 多态性(静态及动态) □ 赋值兼容原则

□ 虚函数及纯虚函数和抽象基类等的编程 【重点难点】

□ 虚函数及纯虚函数和抽象基类应用

9.1 多态性

9.1.1 多态性

同一名称,不同的功能实现方式(允许在派生类中对原有基类的成员加以重写并重定义出新的功能实现方式);子类的对象共享父类的行为。

class Window

{ public: void OpenWindow()

{ 功能实现为画出窗口; } };

class Dialog: public Window

{ public: void OpenWindow() //重写原有基类的成员 { 功能实现为画出对话框; } } ;

9.1.2 作用

利用它可以设计出可扩充性好的程序(覆盖或替换、重载基类的成员);从而可以实现从抽象到具体,从一般到特殊(在采用泛泛的问题解决方法的基础上来解决某一特殊的问题),同时也体现出所解决问题的层次性,在不同的层次根据问题的特殊性相应地采取对应的解决方法,但要求保持问题解决的统一性。

9.1.3 赋值兼容原则

一个派生类的对象在使用上可以被当作基类的对象;反之则禁止。具体表现为: ①派生类的对象可以被赋值给基类的对象(俗话“长子当父”);

Window win ; Dialog dlg ; win=dlg ;

中科院计算所培训-沈阳

60

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

②派生类的对象可以初始化基类的对象引用;

Dialog dlg ;

Window & win=dlg ;

③派生类的对象地址可以被赋值给基类的指针。 Dialog dlg ;

Window *ptr=&dlg ;

主要的原因:派生类是基类的超集,并包含有基类的成员(继承来的);当将派生类的对象赋值给基类的对象时,系统将继承来的成员赋值给基类的对象;而当将基类的对象赋值给派生类的对象时将导致派生类对象有未被赋值成员(成员不够)。

9.1.4 指向派生类及基类对象的指针

在public方式继承时,指向基类对象的指针也可以指向派生类对象,反之则禁止。具体表现为:

Window *Pb, ObjB ; Dialog *Pd,ObjD ;

Pb=&ObjB ; //基类对象的指针可以指向基类对象

Pb=&ObjD ; //基类对象的指针也可以指向派生类对象 Pd=&ObjD ;

Pd=&ObjB ; //错误,因为派生类对象的指针只能指向派生类对象

Pd=(Derived *)Pb ; //将基类对象的指针赋值给派生类对象的指针时,必需强制类型转换

【蓝梦提示】

□ 基类对象的指针是实现动态多态性的一个手段(利用它可以访问基类对象的成员;

可以访问派生类对象中继承来的基类成员;经强制类型转换为派生类指针,可以访问派生类对象中新增的成员,(Derived *)Pb->新增的成员 ;)。

9.2 虚函数

在基类中以virtual加以修饰的非静态成员函数并且在一级或多级派生类中被重新定义的成员函数。

9.2.1 定义语法

virtual只在类体内修饰成员函数的说明而非成员函数体的定义。 class Window

{ public: virtual void OpenWindow() { 功能实现为画出窗口 ; } };

class Dialog: public Window { public :

virtual void OpenWindow() //应该重写原有基类的成员,否则应用无意义

中科院计算所培训-沈阳

61

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

{ 功能实现为画出对话框; } };

9.2.2 虚函数的特点

① 具有身份的继承性---在基类中一旦将某一函数定义为虚函数,在以后的各级派生类中的同名成员函数自动也为虚函数而不管是否有virtual修饰。

class Window

{ public: virtual void OpenWindow() { } };

class Dialog: public Window { public:

void OpenWindow() // 系统自动地也将它认为是虚函数而不管是否有virtual修饰 { } };

② 虚函数的重写是替换(或称覆盖)而非重载(系统将忽略virtual)定义。

class Window { public: virtual void OpenWindow(); };

class Dialog: public Window

{ public: virtual vod OpenWindow(int x, int y) ; //重载继承基类中的 };

void main()

{ Window objB;

objB.OpenWindow(); //访问基类中的OpenWindow() Dialog objD;

objD.OpenWindow(100,200); //访问子类中的OpenWindow(int x, int y)

objD.OpenWindow(); //错误,因为同名覆盖(基类中的OpenWindow()不可见) objD.Window::OpenWindow() ; //必需采用基类名限定 }

【蓝梦提示】

□ 派生类中如没有重新定义出虚函数,则将继承基类中的虚函数,此时系统仍旧

采用静态联编;析构函数可以为虚函数但构造函数不能为虚函数,并且虚函数不适用于有大量for、do--while、while语句的场合以免频繁调用,降低速度。9.2.3 为什么需要虚函数

当派生类覆盖基类中同名成员函数时,为避免调用时的二义性可以采用静态或动态联编。

中科院计算所培训-沈阳

62

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

9.2.4 静态联编

当未将同名函数设计为虚函数时,系统依据支配原则或定义时的对象类型编译所要调用的代码,这可采用对象名或者类名限定的方法来访问。

void ShowWindow(Window &win) { win.OpenWindow(); }

void main()

{ Window win; Dialog dlg;

ShowWindow(win); //调用Window::OpenWindow(),根据定义时的类型 ShowWindow(dlg); //调用Dialog::OpenWindow() dlg.Window::OpenWindow(); //调用Window::OpenWindow() }

9.2.5 动态联编的应用

因为有些问题在设计时不能预知或为提高程序灵活性,需要根据指针所指向的目标对象的类型编译所要调用的代码,此时必须使用动态联编。

9.2.6 如何实现动态联编

在基类中将同名成员函数定义为虚函数;调用这些同名函数时应采用基类对象的指针或对象引用,此时根据指针实际所指向的目标对象的类型动态地调用此对象所属的类中的虚函数。

【实例练习】

#include #include using namespace std; class Window

{ public : virtual void OpenWindow(){cout<<\"The base Window\"<class Dialog : public Window

{ public: virtual void OpenWindow(){cout<<\"The Dialog\"<class CommonDialog : public Window

{ private: virtual void OpenWindow(){cout<<\"The CommonDialog\"<void ShowWin(Window *prtB) //采用基类对象的指针 { prtB->OpenWindow() ; }

void ShowWin(Window &objB) //采用基类对象的引用 { objB.OpenWindow() ; }

void main(void) { Window win ; Dialog dlg ;

中科院计算所培训-沈阳

63

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

CommonDialog commonDlg ; ShowWin(&win) ;

ShowWin(win) ; //调用Window类中的虚函数

ShowWin(&dlg) ;

ShowWin(dlg) ; //调用Dialog类中的虚函数

ShowWin(&commonDlg) ;

ShowWin(commonDlg) ; //调用CommonDialog类中的虚函数 }

【蓝梦提示】

□ 在调用虚函数时是根据基类的指针当前实际所指向的目标对象而非该指针在定

义时的类类型;虚函数的访问控制并不受控于指针当前所指向类成员的访问控制,而只受控于指针定义时所在类成员的访问控制。

9.3 虚析构函数

C++中允许将析构函数设计为虚函数但不允许将构造函数定义为虚函数。

9.3.1 原因

构造函数必需在创建对象这一时刻被调用而不能迟后调用,故不能为虚函数;析构函数是在对象被创建以后调用,因而可以迟后调用(动态),即可为虚函数。

9.3.2 应用场合

当利用基类的指针来定义产生派生类的对象时,为删除派生类的对象应设计出虚析构函数(将基类的析构函数设计为虚函数)。

class Window { public: Window() ;

virtual ~Window() ; } ;

class Dialog: public Window { public: Dialog() ;

virtual ~Dialog() ; };

void main()

{ Window *PtrWin=new Dialog ; delete PtrWin ;

/*因为如果采用静态联编(未将~Window()设计为虚析构函数时)将会调用~Window(),但由于PtrWin实际指向为Dialog类对象,因而应该调用~Dialog(),这可以通过将~Window()设计为虚析构函数来达到。动态联编*/

} 选学内容动态联编的实现机制:采用虚函数表vptr的技术来实现虚函数的调用,编译器为每个带有虚函数的类生成一个vptr,每个vptr表中存储有相应的虚函数的地址。 中科院计算所培训-沈阳

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

9.4 纯虚函数和抽象基类

9.4.1 纯虚函数

在基类中没有函数的定义体或定义体等于零的成员函数。 抽象基类:包含一个或多个纯虚函数的基类。

9.4.2 定义语法

class Person //全世界的人,因而是抽象的“人”。因为并没有说明是那个国家的人 { public: virtual OutObj Eat(Food obj)=0 ; } ; //亚洲人,仍然为抽象的“人”。

class Asian: public Person //因为并没有说明是亚洲的那个国家的人

{ public: virtual OutObj Eat(Food obj)=0 ; //在子类中允许继续设计为纯虚函数 } ;

class Chinese:public Asian //中国人,此时为一个具体国家的人,但她首先是亚洲人

{ public: virtual OutObj Eat(Food obj) //在子类中必须重写原有抽象基类中的纯虚函数; { //具体的实现应该是将Food(食品)作成中餐

} };

class American:public Person

{ public: virtual OutObj Eat(Food obj)

{ //具体的实现应该是将Food(食品)作成西餐 } };

9.4.3 抽象基类的编程规则

1、它必须要被继承与派生,而且在子类中必须重写原有抽象基类中的纯虚函数;

2、不能建立抽象基类的对象,也不能把抽象基类作为函数的形参和返回值类型,但可以有指向抽象基类的指针(必需采用其不再为抽象基类的派生类对象实例化它)。 Person men ; //错误 Asian women ; //错误 void Myfun(Person men) ; //错误 Person Myfun() ; //错误

Person *Ptr=&Zhang ; //可以,其中 Chinese Zhang Person & men=Zhang ; //可以,其中 Chinese Zhang

9.4.4 抽象基类的用途

它是OOP中实现从“泛化”到“特化”的一种有效手段。由抽象基类派生出的新类应提供纯虚函数的实现或仍将它说明为纯虚函数(此时该派生类仍为抽象类,参见上述的Asian类定义)。从而借助该特性,用户可以根据应用需要,在本层次类中对已有纯虚函数加以具体实现,从而实现自己的特殊应用功能。

class Shape

{ public: virtual double Area() const=0 ;

中科院计算所培训-沈阳

65

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

};

【蓝梦提示】

□ 在初始的基类中定义出功能要求,但由于问题的不确定性只能给出功能实现的基

本要求,由后续的子类(在问题明确后)中再针对具体的问题给出实际的解决方法,但遵守原始的抽象基类的功能要求。

class Polygon :public Shape

{ public: virtual double Area() const=0; };

【蓝梦提示】

□ 由于在多边形的面积问题解决方法中,问题仍然为不确定性,因而可以继续不给

出实际的解决方法,留给下一层次的子类来解决。但遵守抽象基类的编程要求,需要在本类中继续声明出它。

class Triangle: public Polygon

{ public: virtual double Area() { return H*W*0.5; } };

class Rectangle: public Polygon { public: virtual double Area() { return H*W ; } };

class Circle :public Shape

{ public: virtual double Area()

{ return radius*radius*3.1415926 ; } };

【蓝梦提示】

□ 由于在三角形、四边形、圆等面积问题解决方法中,问题都变为确定的(可解决

性),因而可以给出具体的解决方法(具体的实现过程),但在编程实现的格式方面要遵守抽象基类的编程要求(double Area()的原型要求),这样使整个问题的解决能保持一致性。因而抽象基类相当于一个“接口”定义。抽象类对于程序设计的好处在于通过抽象类保证了进入继承层次的每个子类都提供纯虚函数所要求的行为,这保证了围绕这个继承层次所建立起来的软件系统能提供一个统一的功

能。

【实例练习】

#include #include using namespace std;

中科院计算所培训-沈阳

66

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

class Number //Number 是一个抽象的问题,但应该提供一个Show()功能 {

public:

Number(int i) { val=i; }

virtual void Show()=0; //由于问题的不确定性,因而无法给出具体的功能实现 protected: int val; //注意它的访问控制为protected,因而可以在子类中直接使用它 };

class Hextype :public Number {

public:

Hextype(int i):Number(i) { }

virtual void Show() //由于问题的确定性,因而可以给出具体的功能实现 { cout <class Dectype :public Number { public:

Dectype( int i):Number(i) { }

virtual void Show() //由于问题的确定性,因而可以给出具体的功能实现 { cout <void fun(Number &n) {

n.Show(); //实际调用时为派生类的对象 }

int main() {

Dectype d(50); fun(d);

Hextype h(16); fun(h); return 0; }

【本章总结】

中科院计算所培训-沈阳

67

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

本章主要讲解了虚函数及纯虚函数和抽象类的编程,其中需要熟练掌握的虚函数的应用及实现了。 第10章 综合应用

10.1 类模板

C++最重要的特性就是代码重用,代码重用的目的是按不同方式重复使用代码来实现类、结构、函数等。为了代码重用,代码必须是通用的。通用代码必须不受使用的数据类型和操作的影响,即无论使用什么数据类型,通用代码是不变的,这就使通用代码可以重用。在多数情况下,越通用的代码越能够重用。这种程序设计类型称为参数化程序设计。模板使C++支持参数化的工具。

使用类模板使用户可以为类声明一种模式,使得类中的某些数据成员、某些成员函数的参数、某些成员函数的返回值能取任意类型。

10.1.1 模板的声明

其声明形式如下:

template <模板参数表> 类声明

“模板参数表”中可以包含下列内容:

1) class 标志符(指明可以接受一个类型参数)

2) 类型说明符 标志符(指明可以接受一个由“类型说明符”所规定类型的常量做为参数。)

当“模板参数表”同时包含上述内容时,各项内容以逗号隔开。 注意:模板类的成员函数必须是函数模板。

10.1.2 模板的使用

使用一个模板类建立对象时,应按如下形式声明:

模板<模板参数表> 对象名1,…,对象名n; 其中,“模板参数表”由用逗号分隔的若干类型标志符和常量表达式构成。这里,“模板参数表”中的参数与类模板声明时“模板参数表”中的参数一一对应。 【实例练习】

#include #include using namespace std;

template //类模板声明 class A {

D i; T t;

int a[Max]; public:

中科院计算所培训-沈阳

68

中科院计算所培训中心 计算机专业技能培训系列教材-教材名称

A(D x,T tt); void dis(); };

template //函数模板 A::A(D x,T tt) {

i=x; t=tt;

for(int i=0;itemplate //函数模板 void A::dis() {

cout<<\"(\"<void main() {

A a(12,12.3f); //使用模板类建立对象 a.dis();

A aa(12.12f,23.34); //使用模板类建立对象 aa.dis(); }

【本章总结】 本章主要讲解了类模板的定义和实现。 中科院计算所培训-沈阳

69

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- aiwanbo.com 版权所有 赣ICP备2024042808号-3

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务