25
第 3 第 C++ 第第 3.1 第第第第第第 3.2 第第第第第 3.3 第第第第第第 3.4 第第

第 3 章 C++ 进阶

Embed Size (px)

DESCRIPTION

第 3 章 C++ 进阶. 3.1 多态和虚函数 3.2 运算符重载 3.3 输入输出流库 3.4 模板. 3.1 多态和虚函数. class CCircle:public CShape {public: CCircle(float r) {R=r;} float area() {return (float)(3.14159265 * R * R);} private: float R; }; void main() {CShape *s[2]; s[0] = new CTriangle(3,4); - PowerPoint PPT Presentation Copyright Complaint Adult Content Flag as Inappropriate Report This Download Presentation 第 3 章 C++ 进阶 An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. - - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - - Presentation Transcript 第3章C++进阶 3.1 多态和虚函数 3.2 运算符重载 3.3 输入输出流库 3.4 模板 3.1多态和虚函数 class CCircle:public CShape {public: CCircle(float r) {R=r;} float area() {return (float)(3.14159265 * R * R);} private: float R; }; void main() {CShape *s[2]; s[0] = new CTriangle(3,4); cout

Citation preview

Page 1: 第 3 章 C++ 进阶

第 3 章 C++ 进阶 3.1 多态和虚函数 3.2 运算符重载 3.3 输入输出流库 3.4 模板

Page 2: 第 3 章 C++ 进阶

3.1 多态和虚函数 3.1.1 虚函数

[例 Ex_VirtualFunc] 虚函数的使用。#include <iostream.h>class CShape{ public:

virtual float area() { return 0.0;}};class CTriangle:public CShape{ public: CTriangle(float h, float w) { H=h; W=w; } float area() { return (float)(H * W * 0.5 ); } private:

float H, W;};

class CCircle:public CShape{public:

CCircle(float r){ R=r; }float area(){return (float)(3.14159265 * R * R);}private:float R;

};void main(){ CShape *s[2];

s[0] = new CTriangle(3,4);cout<<s[0]->area()<<endl;s[1] = new CCircle(5);cout<<s[1]->area()<<endl;

} 运行结果为:678.5398

Page 3: 第 3 章 C++ 进阶

3.1 多态和虚函数说明:(1)  虚函数在重新定义时参数的个数和类型必须和基类中的虚函数完全匹配,这一点和函数重载完全不同。(2)  虚函数所具备的上述功能,只有通过基类指针才可实现。虚函数在用对象名和成员运算符以正常方式调用时,不能达到其效果。(3)  如果不使用 new 来创建相应的派生类对象,也可用下列方法来实现:void main(){ CShape *p1, *p2;

CTriangle tri(3, 4);CCircle cir(5);p1 = &tri;p2 = &cir;cout<<p1->area()<<endl;cout<<p2->area()<<endl;} (4)  虚函数必须是类的一个成员函数,不能是友元函数,不能是静态的成员函数。(5) 可把析构函数定义为虚函数,但不能将构造函数定义为虚函数。通常在释放基类及其派生类中的动态申请的存储空间时,也要把析构函数定义为虚函数,以便实现撤消对象时的多态性。

Page 4: 第 3 章 C++ 进阶

3.1 多态和虚函数3.1.2 纯虚函数和抽象类 定义一个基类时,会遇到这样的情况:无法定义基类中虚函数的具体实现,其实现完全依赖于其不同的派生类。例如,一个“形状类”由于没有确定的具体形状,因此其计算面积的函数也就无法实现。这时可将基类中的虚函数声明为纯虚函数。 声明纯虚函数的一般格式为:

virtual < 函数类型 >< 函数名 >(< 形数表 >) = 0 ; 它与一般虚函数不同的是:在纯虚函数的形参表后面多了个“ =0” 。把函数名赋于 0 ,本质上是将指向函数的指针的初值赋为 0 。需要说明的是,纯虚函数不能有具体的实现代码。 抽象类是指至少包含一个纯虚函数的特殊的类。它本身不能被实例化,也就是说不能声明一个抽象类的对象。必须通过继承得到派生类后,在派生类中定义了纯虚函数的具有实现代码,才能获得一个派生类的对象。 与虚函数使用方法相同,也可以声明指向抽象类的指针,虽然该指针不能指向任何抽象类的对象,但可以通过该指针获得对派生类成员函数的调用。事实上,纯虚函数是一个特殊的虚函数。

Page 5: 第 3 章 C++ 进阶

3.2 运算符重载 3.2.1 运算符重载的语法

< 函数类型 >< 类名 >::operator < 重载的运算符 >(< 形参表 >){ … } // 函数体运算符重载函数的函数是以特殊的关键字 operator 开始的,因而编译器很容易与其他的函数名区分开来。重载的运算符必须是合法的运算符。

说明:(1)   当用成员函数实现双目运算符的重载时,运算符的左操作数一定是对象,右操作数作为调用运算符重载函数的参数,参数可以是对象、对象的引用或是其他类型的参数。(2)  不是所有的运算符都可以重载。不允许重载的运算符除三目运算符“ ?:”外,还有成员操作符“ .”、成员指针取值操作符“ *”、作用域操作符“ ::” 以及 sizeof运算符。(3)  只能对已定义了的运算符进行重载,而且当重载一个运算符时,该运算符的操作数个数、优先级和结合性是不能改变的。

Page 6: 第 3 章 C++ 进阶

3.2 运算符重载[例 Ex_Complex] 运算符的简单重载。#include <iostream.h>class CComplex{ public:CComplex(double r = 0, double i = 0) { realPart = r;imagePart = i;}void print(){ cout<<"该复数实部 = "<<realPart<<", 虚部 = "<<imagePart<<endl;}CComplex operator + (CComplex &c); // 重载运算符 +CComplex operator + (double r); // 重载运算符 +private:double realPart; // 复数的实部double imagePart; // 复数的虚部};CComplex CComplex::operator + (CComplex &c) // 参数是 CComplex 引用对象{ CComplex temp;temp.realPart = realPart + c.realPart;temp.imagePart = imagePart + c.imagePart;return temp;}

Page 7: 第 3 章 C++ 进阶

3.2 运算符重载CComplex CComplex::operator + (double r) // 参数是 double 类型数据{ CComplex temp;temp.realPart = realPart + r;temp.imagePart = imagePart;return temp;}void main(){ CComplex c1(12,20), c2(50,70), c;c = c1 + c2;c.print();c = c1+ 20;c.print();} 运行结果为:该复数实部 = 62, 虚部 = 90该复数实部 = 32, 虚部 = 20 对运算符“ +” 作了两次重载,一个用于实现两个复数的加法,另一个用于实现一个复数与一个实数的加法。当重载一个运算符时,必须定义该运算符要完成的具体操作,而且当运算符重载函数是类的成员函数时,该函数的形参个数要比运算符操作数个数少一个,双目运算符重载的成员函数只有一个参数,单目运算符重载的成员函数没有参数。

Page 8: 第 3 章 C++ 进阶

3.2 运算符重载3.2.2 赋值运算符的重载 对象的成员中有数组或动态的数据类型时,就不能直接相互赋值,否则在程序的编译或执行过程中出现编译或运行错误。例如:class Cdemo{ public:CDemo(char *s){ ps = new char[strlen(s) + 1];strcpy(ps, s); }~CDemo(){ if (ps) delete[] ps; }

void print(){ cout<<ps<<endl; }private:char *ps;};void main(){ CDemo d1("Key"), d2("Mouse");d1 = d2; }程序运行到“ d1 = d2” 时发生运行错误。因此,必须重载“ =” 运算符,它与其他运算符的重载相同。

Page 9: 第 3 章 C++ 进阶

3.2 运算符重载[例 Ex_Evaluate] 赋值运算符的重载。#include <iostream.h>#include <string.h>class CDemo{ public:// 同上面的斜体部分代码CDemo& operator = (CDemo &a) // 赋值运算符重载{ if (ps) delete[] ps;if (a.ps){ ps = new char[strlen(a.ps) + 1];strcpy(ps, a.ps);}else ps = 0;return *this; }private:char *ps;};void main(){ CDemo d1("Key"), d2("Mouse");d1 = d2;d1.print(); }运行结果为: Mouse 赋值运算符不能重载为友元函数,只能是一个非静态成员函数。

Page 10: 第 3 章 C++ 进阶

3.2 运算符重载3.2.3 提取和插入运算符重载

C++ 允许用户重载“ >>” 和“ <<” 运算符,以便用户利用标准的输入输出流来输入输出自己定义的数据类型 ( 包括类 ) ,实现对象的输入输出。重载这两个运算符时最好将重载声明为类的友元函数,以便访问类中的私有成员。友元重载的一般格式如下:friend < 函数类型 >operator < 重载的运算符 >(< 形参 >)// 单目运算符重载{ … } // 函数体friend < 函数类型 >operator < 重载的运算符 >(< 形参 1, 形数 2>)// 双目运算符重载{ … } // 函数体对于单目运算符的友元重载函数,只有一个形参,形参类型可以是类的对象,可以是引用,这取决于不同的运算符。对于双目运算符的友元重载函数来说,它有两个形参,这两个形参中必须有一个是类的对象。需要说明的是, =、 ()、 [] 和 -> 运算符不能用友元来重载。

Page 11: 第 3 章 C++ 进阶

3.2 运算符重载[例 Ex_ExtractAndInsert] 提取和插入运算符的重载。#include <iostream.h>class CStudent{ public:friend ostream& operator<< ( ostream& os, CStudent& stu );friend istream& operator>> ( istream& is, CStudent& stu );private:char strName[10]; // 姓名char strID[10]; // 学号float fScore[3]; // 三门成绩};ostream& operator<< ( ostream& os, CStudent& stu ){ os<<endl<<" 学生信息如下: "<<endl;os<<" 姓名: "<<stu.strName<<endl;os<<" 学号: "<<stu.strID<<endl;os<<"成绩: "<<stu.fScore[0]<<",\t"<<stu.fScore[1]<<",\t"<<stu.fScore[2]<<endl;return os;}

Page 12: 第 3 章 C++ 进阶

3.2 运算符重载istream& operator>> ( istream& is, CStudent& stu ){ cout<<"请输入学生信息 "<<endl;cout<<" 姓名: ";is>>stu.strName;cout<<" 学号: ";is>>stu.strID;cout<<" 三门成绩: ";is>>stu.fScore[0]>>stu.fScore[1]>>stu.fScore[2];return is; }void main(){ CStudent one;cin>>one;cout<<one; }运行结果为: 请输入学生信息 :姓名: LiMing学号: 21010212三门成绩: 80 90 75 学生信息如下:姓名: LiMing学号: 21010212成绩: 80, 90, 75

Page 13: 第 3 章 C++ 进阶

3.3 输入输出流库 3.3.1 概述 输入输出操作是由“流”来处理的。数据从一个位置到另一个位置的流动抽象为“流”。当流被建立后就可以使用一些特定的操作从流中获取数据或向流中添加数据。从流中获取数据的操作称为“提取”操作,向流中添加数据的操作称为“插入”操作。 ios 类提供对流状态进行设置的功能,是虚基类,其它类都是从这个派生而来的, streambuf不是 ios 类的派生类,在类 ios 中只是有一个指针成员,指向 streambuf类的一个对象。 streambuf类是用来为 ios 类及其派生类提供对数据的缓冲支持。 itream 和 ostream 类是 ios 的公有派生类,前者向流中插入数据的有关操作,后者从流中提取数据的有关操作。 iostream 类是 itream 和 ostream 类公有派生的。 四个预定义的标准流对象: cin、 cout、 cerr 和 clog 。当用户在程序中包含了头文件“ iostream.h” ,编译器调用相应的构造函数,产生这四个标准流对象,用户在程序中就可以直接使用它们了。其中, cin 是 istream 类的对象,用处理标准输入 cout 是 ostream 类的对象,用处理标准输出。 cerr 和 clog都是 ostream 类的对象,用来处理标准出错信息,并将信息显示在屏幕上。在这四个标准流对象中,除了 cerr 不支持缓冲外,其余三个都带有缓冲区。 标准流通常使用提取运算符“ >>” 和插入运算符“ <<” 来进行输入输出操作的,而且系统还会自动地完成数据类型的转换。

Page 14: 第 3 章 C++ 进阶

3.3 输入输出流库3.3.2cout 和 cin 输出流 (cout)

cout 可以输出一个整数、实数、字符及字符串, cout 中的插入符“ <<” 可以连续写多个,每个后面可以跟一个要输出的常量、变量、转义序列符、对象以及表达式等。 (1)    width 函数格式: int width();int width(int);第一种格式用来获取当前输出数据时的宽度,另一种格式是用来设置当前输出数据时的宽度。(2)    precision 函数格式: int precision();int precision(int);这两种格式分别用来获取和设置当前浮点数的有效数字的个数,第二种格式函数还将返回设置前的有效数字的个数。需要说明的是, C++ 默认的有效数字的个数为 6 。(3)    fill 函数格式: char fill();char fill(char);这两种格式分别用来获取和设置当前宽度内的填充字符,第二种格式函数还将返回设置前的填充字符。

Page 15: 第 3 章 C++ 进阶

3.3 输入输出流库 输入流 (cin)

格式: cin>> < 表达式 1 > [>> < 表达式 2> ...]提取符“ >>” 可以连续写多个,每个后面跟一个表达式,该表达式通常是获得输入值的变量或对象。要求用户从键盘上输入三个整数。输入时,必须在三个数值之间加上一些空格来分隔,空格的个数不限,最后用回车键结束输入;或者在每个数值之后按回车键。

格式算子 oct 、 dec 和 hex 格式算子 oct、 dec 和 hex能分别将输入或输出的数值转换成八进制、十进制及十六进制。

Page 16: 第 3 章 C++ 进阶

3.3 输入输出流库输入输出过程中发现操作错误,流就会将发生的错误记录下来。用户可以使用错误检测功能,检测和查明错误发生的原因和性质,然后调用 clear 函数清除错误状态,使流能够恢复处理。在 ios 类中,定义了一个公有枚举成员 io_state 来记录各种错误的性质:enum io_state { goodbit = 0x00, // 正常eofbit = 0x01, // 已达到文件尾failbit = 0x02, // 操作失败badbit = 0x04 // 非法操作 };在 ios 类中又定义了检测上述流状态的下列成员函数:int ios::rdstate(); // 返回当前的流状态int ios::bad(); // 如果 badbit位被置位,返回非 0void ios::clear(int); // 清除错误状态int ios::eof(); // 返回非 0 表示提取操作已到文件尾int ios::fail(); // 如果 failbit位被置位,返回非 0int ios::good (); // 操作正常时,返回非 0 当输入一个浮点数,会自动进行类型转换。只有键入字符或字符串时,才会产生输入错误,但由于 cin 有缓冲区,输入的字符或字符串会暂时保存到它的缓冲区中,因此为了能继续提取用户的输入,必须先将缓冲区清空。。

Page 17: 第 3 章 C++ 进阶

3.3 输入输出流库3.3.4 使用输入输出成员函数 输入操作的成员函数 数据的输入 /输出可以分为三大类:字符类、字符串和数据。(1) 使用 get 和 getline 函数int get();istream& get( char& rch );istream& get( char* pch, int nCount, char delim = '\n' );getline 函数原型如下:istream& getline( char* pch, int nCount, char delim = '\n' ); (2) 使用 read 函数istream& read( char* pch, int nCount );istream& read( unsigned char* puch, int nCount );istream& read( signed char* psch, int nCount ); read 函数的这几种形式都是从输入流中读取由 nCount 指定数目的字节并将它们放在由 pch或 puch或 psch 指定的数组中。 输出操作的成员函数ostream& put( char ch );ostream& write( const char* pch, int nCount );ostream& write( const unsigned char* puch, int nCount );ostream& write( const signed char* psch, int nCount );

Page 18: 第 3 章 C++ 进阶

3.3 输入输出流库3.3.5 文件流概述 文件看作是由连续的字符的数据顺序组成。根据文件中数据的组织方式,可分为文本文件和二进制文件。文本文件中每一个字节用以存放一个字符的 ASCII码值,二进制文件是将数据用二进制形式存放在文件中。 一个文件指针总是和一个文件所关联的,在文件每一次打开时,文件指针指向文件的开始,随着对文件的处理,文件指针不断地在文件中移动,并一直指向最新处理的字符 ( 字节 )位置。 文件处理方式,一种称为文件的顺序处理。另一种称为文件的随机处理,并指向所要处理的字符 ( 字节 )位置。按照这两种处理方式,可将文件相应地称为顺序文件和随机文件。

Page 19: 第 3 章 C++ 进阶

3.3 输入输出流库3.3.6 顺序文件操作 文件的打开和关闭 声明一个 ifstream、 ofstream或 fstream 类对象。 使用文件流类的成员函数打开或创建一个指定的文件,使得该文件与声明的文件流对象联系起来,这样对流对象的操作也就是对文件的操作。 ifstream、 ofstream或 fstream 类构造函数中总有一种原型和它的成员函数 open 功能相同。它们的函数原型如下:ifstream( const char* szName, int nMode = ios::in, int nProt = filebuf::openprot );void ifstream::open( const char* szName, int nMode = ios::in, int nProt = filebuf::openprot );ofstream( const char* szName, int nMode = ios::out, int nProt = filebuf::openprot );void ofstream::open( const char* szName, int nMode = ios::out, int nProt = filebuf::openprot );fstream( const char* szName, int nMode, int nProt = filebuf::openprot );void fstream::open( const char* szName, int nMode, int nProt = filebuf::openprot );

Page 20: 第 3 章 C++ 进阶

3.3 输入输出流库 文件的读写 [例 Ex_File] 将文件内容保存在另一文件中,并将内容显示在屏幕上。#include <iostream.h>#include <fstream.h>void main(){ fstream file1; // 定义一个 fstream 类的对象用于读file1.open("Ex_DataFile.txt", ios::in);if (!file1) { cout<<"Ex_DataFile.txt 不能打开! \n";return;}fstream file2; // 定义一个 fstream 类的对象用于写file2.open("Ex_DataFileBak.txt", ios::out | ios::trunc);if (!file2) { cout<<"Ex_DataFileBak.txt 不能创建! \n";file1.close();return; }char ch;while (!file1.eof()){ file1.read(&ch, 1);cout<<ch;file2.write(&ch, 1); }file2.close(); // 不要忘记文件使用结束后要及时关闭file1.close();}

Page 21: 第 3 章 C++ 进阶

3.3 输入输出流库3.3.7 随机文件操作随机文件可以在文件中来回移动文件指针,从而可以实现非顺序读写文件数据的功能,达到快速检索、修改和删除文件数据的效果。seekg 和 seekp 函数将文件指针移动到指定的位置。它们的原型如下:istream& seekg( long pos );istream& seekg( long off, ios::seek_dir dir );ostream& seekp( long pos );ostream& seekp( long off, ios::seek_dir dir );其中, pos 用来指定文件指针的绝对位置。若用 off指定文件指针的相对偏移量时,文件指针的最终位置还需根据 dir 值才能确定。 dir 值可以是:ios::beg 从文件流的头部开始ios::cur 从当前的文件指针位置开始ios::end 从文件流的尾部开始

Page 22: 第 3 章 C++ 进阶

3.4 模板 3.4.1 函数重载机制的不足

函数重载方便用户对函数名的记忆,完善了同一个函数的代码功能。允许多个同名的函数存在,同名的各个函数的形参必须有区别:形参的个数不同,或者形参的个数相同,但形参类型有所不同。如果需要处理所有标准数据类型的参数,则还需要更多的代码进行函数重载。可以看出函数重载有时也显得比较烦琐,解决这个问题就是使用函数模板。

3.4.2 函数模板 格式:template < 数据类型声明 >函数体template 是一个声明模板的关键字,数据类型声明是使用 class 关键字来定义类型名称,而函数体与传统的函数定义相似。

Page 23: 第 3 章 C++ 进阶

3.4 模板[例 Ex_FunTemplate] 使用函数模板。#include <iostream.h>template <class T> // 定义第一个函数模板T sum(T x, T y, T z){ return x+y+z; }template <class T, class T1> // 定义第二个函数模板T sum(T x, T1 y){ return x+y; }void main(){ cout<<sum(2,'a')<<endl; // 结果为 99cout<<sum('a',2)<<endl; // 结果为 'c'cout<<sum(2,5,7)<<endl; // 结果为 14cout<<sum(1.2,5.0,7.5)<<endl; // 结果为 13.7}运行结果如下:99c1413.7

Page 24: 第 3 章 C++ 进阶

3.4 模板3.4.3 类模板 template < 数据类型声明 > class 类名{ ... // 成员的定义 } ;注意 :大括号后的分号不能省略。3.4.4 标准模板库简介 标准模板库 STL(Standard Template Library) 是通过 STL中的相应运算法则在应用程序的迭代子 (iterator)、容器以及其他定义的序列中建立一种统一的标准。从根本上来说, STL是一个基于模板的群体类库,它包含群体类 (链表或列表、向量、栈、队列、集合、映象 )、算法 (排序、查找 ) 以及迭代子。STL中的群体类是基于模板的,包含线性群体类,也包含非线性群体类。其主要群体类有:deque ( 双端队列 )list (链表、列表 )map (映象 )multimap( 多重映象 )multiset ( 多重集合 )set (集合 )vector( 向量 )

Page 25: 第 3 章 C++ 进阶

3.4 模板 STL的迭代子有顺序和直接访问,顺序迭代子使用 ++、 --等进行指针移动,只能顺序访问群体类中的对象,而直接访问迭代子是通过 iterator_traits 来直接访问群体类中的某个特定对象。 iterator_traits 用来指定要访问的属性类型,它可以有:

iterator_category ( 目录类型 )value_type ( 数值类型 )difference_type (距离类型,表示两个指针的地址之间的距离 ) pointer ( 指针类型 )reference (引用类型 )

STL的算法是用函数模板实现的,可以实现对不同类型对象的通用操作。算法与 STL 群体类之间是通过迭代子 iterator 来进行沟通的。 STL的主要算法有:排序 (sort、merge)查找 (find、 search)比较 (equal)集合 (includes、 set_union、 set_difference)计算 (accumulate、 partial_sum)统计 (max 、min)管理 (swap、fill、 replace、 copy 、 unique、 rotate、 reverse)堆操作 (make_heap、 push_heap、 pop_heap、 sort_heap)