300 likes | 437 Views
第九章 C++ 的 I/O 流库. 屏幕输出 键盘输入 插入符和提取符的重载 格式化的输入和输出 磁盘文件的输入和输出 字符串流. C++ 语言提供了优越的输入 / 输出处理概念,也就是输入 / 输出流 ( I/O Stream), 这种 I/O 流是由 OOP 概念派生出来的。 在 I/O 流结构中, ios 是基类,它包含如下两个派生类: (1) istream 类
E N D
第九章 C++的I/O流库 屏幕输出 键盘输入 插入符和提取符的重载 格式化的输入和输出 磁盘文件的输入和输出 字符串流
C++语言提供了优越的输入/输出处理概念,也就是输入/输出流(I/O Stream),这种I/O流是由OOP概念派生出来的。 • 在I/O流结构中,ios是基类,它包含如下两个派生类: • (1)istream类 • istream类主要供处理数据输入之用,其底下包含一些成员函数,可以处理格式化或非格式化的输入动作,例如get(),getline(),peek(),putback(),read()及">>"等。 • (2)ostream类 • ostream类主要供处理数据输出之用,其底下包含一些成员函数,可以处理格式化或非格式化的输出动作,例如put(),write()及"<<"等。
iostream类派生于istream和ostream类,因此,它将继承istream和ostream类的特性,ifstream和ofstream这两个类则分别派生于istream和ostream类,因此它们也将分别继承其基类的特性。iostream类派生于istream和ostream类,因此,它将继承istream和ostream类的特性,ifstream和ofstream这两个类则分别派生于istream和ostream类,因此它们也将分别继承其基类的特性。 ifstream:对文件进行提取操作 ostream:对文件进行插入操作
四个流类对象 • cin: istream类的对象,用来处理标准键盘输入 • cout: ostream类的对象,用来处理标准屏幕输出 • cerr: ostream类的对象,用来处理标准出错信息,它提供不带缓冲区的输出 • clog: ostream类的对象,也用来处理标准出错信息,但它提供带缓冲区的输出 在使用上述类时,要包含iostream.h文件。
屏幕输出 最一般的屏幕输出是将插入符作用在流类对象cout上。 #include "iostream.h" #include "string.h" void main() { cout<<"The length of \"this is a string\" is:\t" <<strlen("this is a string")<<endl; cout<<"The length of \"this is a string \" is:\t" <<sizeof("this is a string")<<endl; }
#include "iostream.h“\\Ex9_2 void main() { int a=100; int *pa=&a; cout<<"a="<<a<<endl<<"&a="<<long(&a)<<endl; cout<<"*pa="<<*pa<<endl<<"&pa="<<&pa<<endl; }
#include "iostream.h“//Ex9_3 void main() { char *str="string"; cout<<"The string is :"<<str<<endl; cout<<"The address is :"<<(void*)str<<endl; }
使用成员函数put()输出一个字符 成员函数put()提供了一种将字符送进输出流的方法,其使用格式为: cout.put(const char c) #include "iostream.h“\\Ex9_4 void main() { cout<<'a'<<"."<<'b'<<'\n'; cout.put('a').put(',').put('b').put('\n'); char c1='A',c2='B'; cout.put(c1).put(c2).put('\n'); }
使用成员函数write输出一个字符串 成员函数write()也可以提供一种将字符串送到输出流的方法,其使用格式为: cout.write(const char *str,int n) 其中,str是一个字符指针或字符数组,n是一个int型数,规定输出的字符数。如果显示整个字符串,则用strlen(str)。
#include "iostream.h" #include "string.h" void PrintString(char *s) { cout.write(s,strlen(s)).put('\n'); cout.write(s,6)<<"\n"; } void main() { char str[]="I love C++"; cout<<"The string is : "<<str<<endl; PrintString(str); PrintString("This is a string"); }
键盘输入 最一般的键盘输入是将提取符作用在流类的对象cin上,其格式如下: cin>><表达式>>><表达式>… #include "iostream.h" void main() { int a,b; cout<<"Please enter two integes:"; cin>>a>>b; cout<<a<<","<<b<<endl; }
#include "iostream.h“\\Ex9_8 #include "string.h" void main() { const int SIZE=20; char buf[SIZE]; char *largest; int curLen,maxLen=-1,cnt=0; cout<<"Input words:\n"; while(cin>>buf); { curLen=strlen(buf); cnt++; if(curLen>maxLen) { maxLen=curLen; largest=buf; } } cout<<endl; cout<<cnt<<endl; cout<<maxLen<<endl; cout<<largest<<endl; }
使用成员函数get()获取一个字符 • get()函数可以从输入流获取一个字符,并把它放置到指定变量中。该函数的使用格式: • cin.get(char &ch) • &ch是一个字符变量的地址值。
#include "iostream.h" void main() { char ch; cout<<"Input :"; while((ch=cin.get())!=EOF); cout.put(ch); cout<<"ok!"; } get函数的另一种形式 cin.getline(char *buf,int Limit, Deline=‘\n’); Limit限制字符个数,最多为Limit-1个, Deline为结束符,默认值为‘\n’
#include "iostream.h“\\Ex9_10 • const int SIZE=80; • void main() • { int lcnt=0,lmax=-1; • char buf[SIZE]; • cout<<"Input :\n"; • while(cin.getline(buf,SIZE)) • { • int count=cin.gcount();\\istream的成员函数,返回 • lcnt++; 实际读入字符个数 • if(count>lmax) lmax=count; • cout<<"Line # "<<lcnt<<"\t"<<"chars read: "<<count<<endl; • cout.write(buf,count).put('\n').put('\n'); • } • cout<<endl; • cout<<"Total line:"<<lcnt<<endl; • cout<<"Longest line: "<<lmax<<endl; • }
虚函数 虚函数是动态联编的基础。虚函数是成员函数,而且是非static的成员函数。说明虚函数的方法如下:virtual <类型说明符><函数名>(<参数表>) 其中,被关键字virtual说明的函数称为虚函数。 如果某类中的一个成员函数被说明为虚函数,这就意味着该成员函数在派生类中可能有不同的实现。当使用这个成员函数操作指针或引用所标识对象时,对该成员函数调用采取动态联编方式,即在运行时进行关联或束定。 动态联编只能通过指针或引用标识对象来操作虚函数。如果采用一般类型的标识对象来操作虚函数,则将采用静态联编方式调用虚函数。
#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; }; Rectangle::Rectangle(double i,double j,double k,double l): Point(i,j) { w=k; h=l;} void fun(Point& s) { cout<<s.Area()<<endl;} void main() { Rectangle rec(3.0,5.2,15.0,25.0); fun(rec); }
通过例子可以看到,派生类中对基类的虚函数进行替换时,要求派生类中说明的虚函数与基类中的被替换的虚函数之间满足如下条件: (1) 与基类的虚函数有相同的参数个数; (2) 其参数的类型与基类的虚函数的对应参数类型相同; (3) 其返回值或者与基类虚函数的相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中被替换的虚函数所返回的指针或引用的基类型的子类型。满足上述条件的派生类的成员函数,自然是虚函数,可以不必加virtual说明。
#include <iostream.h> class A { public: virtual void act1(); void act2(){act1();} }; void A::act1() { cout<<"A::act1() called."<<endl;} class B : public A { public: void act1(); //it is a virtual since B is the derived class of A }; void B::act1() { cout<<"B::act1() called."<<endl;} void main() { B b; b.act2(); //call B::act1() } 2. 将A::act2()的实现改为:this->act1(); 3.将A::act2()的实现改为: A::act1();
总结动态联编的实现需要如下三个条件: (1) 要有说明的虚函数; (2) 调用虚函数操作的是指向对象的指针或者对象引用;或者是由成员函数调用虚函数; (3) 子类型关系的建立。 构造函数中调用虚函数时,采用静态联编即构造函数调用的虚函数是自己类中实现的虚函数。如果自己类中没有实现这个虚函数,则调用基类中的虚函数,而不是任何派生类中实现的虚函数。
#include <iostream.h>//Ex8.11 class A { public: A(){} virtual void f(){cout<<"A::f() called.\n";} }; class B : public A {public: B(){f();} void g(){f();} }; class C : public B {public: C(){} virtual void f(){cout<<"C::f() called.\n";} }; void main() { C c; c.g(); }
#include <iostream.h>//Ex8.12 class A { public: virtual void print(int x,int y) { cout<<x<<", "<<y<<endl;} }; class B : public A { public: virtual void print(int x,float y) { cout<<x<<", "<<y<<endl;} }; void show(A& a) { a.print(3,8); a.print(6,5.9); } void main() { B b; show(b); }
纯虚函数与抽象类 • 纯虚函数是一种特殊的虚函数,它的一般格式如下:class <类名>{virtual <类型><函数名>(<参数表>)=0; …};在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
#include <iostream.h> class point {public: point(int i=0, int j=0) { x0=i; y0=j; } virtual void set() = 0; virtual void draw() = 0; protected: int x0, y0; }; class line : public point {public: line(int i=0, int j=0, int m=0, int n=0):point(i, j) { x1=m; y1=n; } void set() { cout<<"line::set() called.\n"; } void draw() { cout<<"line::draw() called.\n"; } protected: int x1, y1; };
class ellipse : public point {public: ellipse(int i=0, int j=0, int p=0, int q=0):point(i, j) { x2=p; y2=q; } void set() { cout<<"ellipse::set() called.\n"; } void draw() { cout<<"ellipse::draw() called.\n"; } protected: int x2, y2; }; void drawobj(point *p) { p->draw();} void setobj(point *p) { p->set();} void main() { line *lineobj = new line; ellipse *elliobj = new ellipse; drawobj(lineobj); drawobj(elliobj); cout<<endl; setobj(lineobj); setobj(elliobj); cout<<"\nRedraw the object...\n"; drawobj(lineobj); drawobj(elliobj); }
抽象类 带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。
#include <iostream.h>//Ex8_15 #include <math.h> class F {public: virtual double operator()(double x) const=0; }; class Integral { public: virtual double operator()(double a,double b,double eps) const=0; }; class Simpson : public Integral { public: Simpson(const F& ff) : f(ff){} //initializing f by ff virtual double operator()(double a, double b, double eps) const; private: const F& f; }; double Simpson::operator ()(double a,double b,double eps) const { int done(0); int n; double h,Tn,T2n,In,I2n; n=1; h=b-a; Tn=h*(f(a)+f(b))/2.0; //overloaded operator () In=Tn;
while(!done) { double temp(0.0); for(int k=0;k<=n-1;k++) { double x=a+(k+0.5)*h; temp += f(x); } T2n=(Tn+h*temp)/2.0; I2n=(4.0*T2n-Tn)/3.0; if(fabs(I2n-In)<eps) done=1; else { Tn=T2n; n*=2; h/=2; In=I2n;} } return I2n; } class Function : public F {public: virtual double operator()(double x) const { return log(1.0+x)/(1.0+x*x); } }; void main() { Function f; Simpson simp(f); cout<<simp(0,2,1E-10)<<endl; }
虚析构函数 在析构函数前面加上关键字virtual进行说明,称该析构函数为虚析构函数。例如:class B{ virtual ~B(); …};该类中的析构函数就是一个虚析构函数。如果一个基类的析构函数被说明为虚析构函数,则它的派生类中的析构函数也是虚析构函数,不管它是否使用了关键字virtual进行说明。说明虚析构函数的目的在于在使用delete运算符删除一个对象时,能保析构函数被正确地执行。因为设置虚析构函数后,可以采用动态联编方式选择析构函数。
#include <iostream.h> class A { public: virtual ~A(){cout<<"A::~A() called.\n";} }; class B : public A { public: B(int i){buf=new char[i];} virtual ~B() { delete[] buf; cout<<"B::~B() called.\n";} private: char* buf; }; void fun(A* a) { delete a;} void main() { A* a=new B(15); fun(a); }