文章目录
- 派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。派生类一经定义后,可以独立使用,不依赖于基类。
- 派生类拥有基类的全部成员函数和成员变量,不论是private、protected、public 。在派生类的各个成员函数中,但是不能访问基类中的private成员。
- 派生类的写法
class 派生类名:public 基类名 { };
- 派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。
-
继承和复合
• 继承:“是”关系。
– 基类 A,B是基类A的派生类。
– 逻辑上要求:“一个B对象也是一个A对象”。
• 复合:“有”关系。
– 类C中“有”成员变量k,k是类D的对象,则C和D是复合关系
– 一般逻辑上要求:“D对象是C对象的固有属性或组成部分”。
• 父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,父类引用是无法调用的。 -
派生类覆盖基类成员
派生类可以定义一个和基类成员同名的成员,这叫覆盖。在派生类中访问这类成员时,缺省的情况是访问派生类中定义的成员。要在派生类中访问由基类定义的同名成员时,要使用作用域符号::。
-
类的保护成员
• 基类的private成员:可以被下列函数访问
– 基类的成员函数
– 基类的友员函数• 基类的public成员:可以被下列函数访问
– 基类的成员函数
– 基类的友员函数
– 派生类的成员函数
– 派生类的友员函数
– 其他的函数• 基类的protected成员:可以被下列函数访问
– 基类的成员函数
– 基类的友员函数
– 派生类的成员函数可以访问当前对象的基类的保护成员 -
派生类的构造函数
• 在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数。
• 调用基类构造函数的两种方式
– 显式方式: 在派生类的构造函数中,为基类的构造函数提供
参数.derived::derived(arg_derived-list) : base(arg_base-list) 初始化列表的方式
– 隐式方式: 在派生类的构造函数中,省略基类构造函数时,派生类的构造函数则自动调用基类的默认构造函数.
• 派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数。
• 包含成员对象的派生类的构造函数写法:class Bug { private : int nLegs; int nColor; public: int nType; Bug ( int legs, int color); void PrintBug (){ }; }; class Skill { public: Skill(int n) { } }; class FlyBug: public Bug { int nWings; Skill sk1, sk2; public: FlyBug( int legs, int color, int wings); }; //初始化列表的方式 FlyBug::FlyBug( int legs, int color, int wings): Bug(legs,color),sk1(5),sk2(color) ,nWings(wings) { }
• 在创建派生类的对象时:
1) 先执行基类的构造函数,用以初始化派生类对象中从基类继承的成员;
2) 再执行成员对象类的构造函数,用以初始化派生类对象中成员对象。
3) 最后执行派生类自己的构造函数
析构函数的调用顺序与构造函数的调用顺序相反 -
public继承的赋值兼容规则
1) 派生类的对象可以赋值给基类对象
cpp b = d;
2) 派生类对象可以初始化基类引用base & br = d;
3) 派生类对象的地址可以赋值给基类指针base * pb = & d;
• 如果派生方式是 private或protected,则上述三条不可行
• protected继承时,基类的public成员和protected成员成为派生类的protected成员。
• private继承时,基类的public成员成为派生类的private成员,基类的protected成员成为派生类的不可访问成员。
• protected和private继承不是“是”的关系。 -
基类与派生类的指针强制转换
• 公有派生的情况下,派生类对象的指针可以直接赋值给基类指针
Base * ptrBase = &objDerived;
ptrBase
指向的是一个Derived类的对象;
*ptrBase
可以看作一个Base类的对象,访问它的public成员直接通过ptrBase即可,但不能通过ptrBase访问objDerived对象中属于Derived类而不属于Base类的成员
• 即便基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类没有,而派生类中有的成员。
• 通过强制指针类型转换,可以把ptrBase转换成Derived类的指针Base * ptrBase = &objDerived;
Derived *ptrDerived = (Derived * ) ptrBase;
程序员要保证ptrBase指向的是一个Derived类的对象,否则很容易会出错。 -
直接基类与间接基类
• 在声明派生类时,只需要列出它的直接基类
– 派生类的成员包括
• 派生类自己定义的成员
• 直接基类中的所有成员
• 所有间接基类的全部成员 -
虚函数
• 在类的定义中,前面有 virtual 关键字的成员函数就是虚函数。
• virtual 关键字只用在类定义里的函数声明中,写函数体时不用class base { virtual int get() ; }; int base::get() { }
-
第五周作业
// 全面的Mystring 类 #include <cstdlib> #include <iostream> using namespace std; int strlen(const char * s) { int i = 0; for(; s[i]; ++i); return i; } void strcpy(char * d,const char * s) { int i = 0; for( i = 0; s[i]; ++i) d[i] = s[i]; //最后结束符号'\0' d[i] = 0; } int strcmp(const char * s1,const char * s2) { for(int i = 0; s1[i] && s2[i] ; ++i) { if( s1[i] < s2[i] ) return -1; else if( s1[i] > s2[i]) return 1; } return 0; } void strcat(char * d,const char * s) { int len = strlen(d); strcpy(d+len,s); } class MyString { //your code starts here private: char * str; int size; public: //构造函数 MyString(){ str = new char[2]; // 确保分配的是数组 str[0] = 0; // 既然是个字符串,里面起码也是个空串,不能让 str == NULL size = 0; } //构造函数 MyString(const char * s) { //如果 s == NULL,就让它出错吧 size = strlen(s); str = new char[size+1]; strcpy(str,s); } // 重载 = MyString & operator=(const char * s ) { //如果 s == NULL,就让它出错吧 int len = strlen(s); if( size < len ) { // 空间不够则分配空间 delete [] str; str = new char[len+1]; } strcpy( str,s); size = len; return * this; } void duplicate(const MyString & s) { if( size < s.size ) { //否则就不用重新分配空间了 delete [] str; str = new char[s.size+1]; } strcpy(str,s.str); size = s.size; } MyString(const MyString & s):size(0),str(new char[1]) { duplicate(s); } MyString & operator=(const MyString & s) { if( str == s.str ) return * this; duplicate(s); return * this; } bool operator==(const MyString & s) const { return strcmp(str,s.str ) == 0; } bool operator<(const MyString & s) const { return strcmp(str,s.str ) < 0; } bool operator>(const MyString & s) const { return strcmp(str,s.str ) > 0; } MyString operator + ( const MyString & s ) { char * tmp = new char[size + s.size + 2];//确保能分配一个数组 strcpy(tmp,str); strcat(tmp,s.str); MyString os(tmp); delete [] tmp; return os; } MyString & operator += ( const MyString & s) { char * tmp = new char [size + s.size + 2]; strcpy( tmp,str); strcat( tmp,s.str); size += s.size; delete [] str; str = tmp; return * this; } char & operator[](int i) const { return str[i]; } MyString operator()(int start,int len) const { char * tmp = new char[len + 1]; for( int i = 0;i < len ; ++i) tmp[len] = 0; tmp[i] = str[start+i]; MyString s(tmp); delete [] tmp; return s; } ~MyString() { delete [] str; } friend ostream & operator << ( ostream & o,const MyString & s) { o << s.str ; return o; } friend MyString operator +( const char * s1,const MyString & s2) { MyString tmp(s1); tmp+= s2; return tmp; } //your code ends here }; int CompareString( const void * e1, const void * e2) { MyString * s1 = (MyString * ) e1; // 强制两类型转换 MyString * s2 = (MyString * ) e2; if( * s1 < *s2 ) return -1; else if( *s1 == *s2) return 0; else if( *s1 > *s2 ) return 1; } int main() { MyString s1("abcd-"),s2,s3("efgh-"),s4(s1); MyString SArray[4] = {"big","me","about","take"}; cout << "1. " << s1 << s2 << s3<< s4<< endl; s4 = s3; s3 = s1 + s3; cout << "2. " << s1 << endl; cout << "3. " << s2 << endl; cout << "4. " << s3 << endl; cout << "5. " << s4 << endl; cout << "6. " << s1[2] << endl; s2 = s1; s1 = "ijkl-"; s1[2] = 'A' ; cout << "7. " << s2 << endl; cout << "8. " << s1 << endl; s1 += "mnop"; cout << "9. " << s1 << endl; s4 = "qrst-" + s2; cout << "10. " << s4 << endl; s1 = s2 + s4 + " uvw " + "xyz"; cout << "11. " << s1 << endl; qsort(SArray,4,sizeof(MyString),CompareString); for( int i = 0;i < 4;i ++ ) out << SArray[i] << endl; //s1 的从下标 0 开始长度为 4 的子串 cout << s1(0,4) << endl; //s1 的从下标 5 开始长度为 10 的子串 cout << s1(5,10) << endl; return 0; }
// 继承自string的 Mystring #include <cstdlib> #include <iostream> #include <string> #include <algorithm> using namespace std; class MyString:public string { //your code starts here public: // 构造函数 MyString():string() {}; MyString( const char * s):string(s){}; MyString( const string & s ): string(s){}; // 重载 () MyString operator() ( int s, int l) { return substr(s,l); }; //your code ends here }; int main() { MyString s1("abcd-"),s2,s3("efgh-"),s4(s1); MyString SArray[4] = {"big","me","about","take"}; cout << "1. " << s1 << s2 << s3<< s4<< endl; s4 = s3; s3 = s1 + s3; cout << "2. " << s1 << endl; cout << "3. " << s2 << endl; cout << "4. " << s3 << endl; cout << "5. " << s4 << endl; cout << "6. " << s1[2] << endl; s2 = s1; s1 = "ijkl-"; s1[2] = 'A' ; cout << "7. " << s2 << endl; cout << "8. " << s1 << endl; s1 += "mnop"; cout << "9. " << s1 << endl; s4 = "qrst-" + s2; cout << "10. " << s4 << endl; s1 = s2 + s4 + " uvw " + "xyz"; cout << "11. " << s1 << endl; sort(SArray,SArray+4); for( int i = 0;i < 4;i ++ ) cout << SArray[i] << endl; //s1 的从下标 0 开始长度为 4 的子串 cout << s1(0,4) << endl; //s1 的从下标 5 开始长度为 10 的子串 cout << s1(5,10) << endl; return 0; }
// 魔兽世界 二 #include <iostream> #include <cstdio> #include <cstring> #include <string> using namespace std; #define WARRIOR_NUM 5 #define WEAPON_NUM 3 #define MAX_WARRIORS 1000 enum { DRAGON,NINJA,ICEMAN,LION,WOLF }; /* char * CWarrior::Names[WARRIOR_NUM] = { "dragon","ninja","iceman","lion","wolf" }; 红方司令部按照 iceman、lion、wolf、ninja、dragon 的顺序制造武士。 蓝方司令部按照 lion、dragon、ninja、iceman、wolf 的顺序制造武士。 */ class CHeadquarter; class CWeapon { public: int nKindNo; int nForce; static int InitialForce[WEAPON_NUM]; static const char * Names[WEAPON_NUM]; }; class CWarrior { protected: CHeadquarter * pHeadquarter; int nNo; public: static const char * Names[WARRIOR_NUM]; static int InitialLifeValue [WARRIOR_NUM]; CWarrior( CHeadquarter * p,int nNo_); virtual void PrintResult(int nTime,int nKindNo); virtual void PrintResult(int nTime) = 0; virtual ~CWarrior() { } }; class CDragon; class CNinja; class CIceman; class CLion; class CWolf; class CHeadquarter { private: int nTotalLifeValue; bool bStopped; int nColor; int nCurMakingSeqIdx; int anWarriorNum[WARRIOR_NUM]; int nTotalWarriorNum; CWarrior * pWarriors[MAX_WARRIORS]; public: friend class CWarrior; static int MakingSeq[2][WARRIOR_NUM]; void Init(int nColor_, int lv); ~CHeadquarter () ; int Produce(int nTime); void GetColor( char * szColor); int GetTotalLifeValue() { return nTotalLifeValue; } }; class CDragon:public CWarrior { private: CWeapon wp; double fmorale; public: void Countmorale() { fmorale = pHeadquarter -> GetTotalLifeValue() /(double)CWarrior::InitialLifeValue [0]; } CDragon( CHeadquarter * p,int nNo_): CWarrior(p,nNo_) { wp.nKindNo = nNo % WEAPON_NUM; wp.nForce = CWeapon::InitialForce[wp.nKindNo ]; Countmorale(); } void PrintResult(int nTime) { CWarrior::PrintResult(nTime,DRAGON); printf("It has a %s,and it's morale is %.2f\n", CWeapon::Names[wp.nKindNo], fmorale); } }; class CNinja:public CWarrior { private: CWeapon wps[2]; public: CNinja( CHeadquarter * p,int nNo_): CWarrior(p,nNo_) { wps[0].nKindNo = nNo % WEAPON_NUM; wps[0].nForce = CWeapon::InitialForce[wps[0].nKindNo]; wps[1].nKindNo = ( nNo + 1) % WEAPON_NUM; wps[1].nForce = CWeapon::InitialForce[wps[1].nKindNo]; } void PrintResult(int nTime) { CWarrior::PrintResult(nTime,NINJA); printf("It has a %s and a %s\n", CWeapon::Names[wps[0].nKindNo], CWeapon::Names[wps[1].nKindNo]); } }; class CIceman:public CWarrior { private: CWeapon wp; public: CIceman( CHeadquarter * p,int nNo_): CWarrior(p,nNo_) { wp.nKindNo = nNo % WEAPON_NUM; wp.nForce = CWeapon::InitialForce[ wp.nKindNo ]; } void PrintResult(int nTime) { CWarrior::PrintResult(nTime,ICEMAN); printf("It has a %s\n", CWeapon::Names[wp.nKindNo]); } }; class CLion:public CWarrior { private: int nLoyalty; public: void CountLoyalty() { nLoyalty = pHeadquarter ->GetTotalLifeValue(); } CLion( CHeadquarter * p,int nNo_): CWarrior(p,nNo_) { CountLoyalty(); } void PrintResult(int nTime) { CWarrior::PrintResult(nTime,LION); CountLoyalty(); printf("It's loyalty is %d\n",nLoyalty); } }; class CWolf:public CWarrior { public: CWolf( CHeadquarter * p,int nNo_): CWarrior(p,nNo_) { } void PrintResult(int nTime) { CWarrior::PrintResult(nTime,WOLF); } }; CWarrior::CWarrior( CHeadquarter * p,int nNo_) { nNo = nNo_; pHeadquarter = p; } void CWarrior::PrintResult(int nTime,int nKindNo) { char szColor[20]; pHeadquarter->GetColor(szColor); printf("%03d %s %s %d born with strength %d,%d %s in %s headquarter\n" , nTime, szColor, Names[nKindNo], nNo, InitialLifeValue[nKindNo], pHeadquarter->anWarriorNum[nKindNo],Names[nKindNo],szColor); } void CHeadquarter::Init(int nColor_, int lv) { nColor = nColor_; nTotalLifeValue = lv; bStopped = false; nCurMakingSeqIdx = 0; nTotalWarriorNum = 0; for( int i = 0;i < WARRIOR_NUM;i ++ ) anWarriorNum[i] = 0; } CHeadquarter::~CHeadquarter () { int i; for( i = 0;i < nTotalWarriorNum; i ++ ) delete pWarriors[i]; } int CHeadquarter::Produce(int nTime) { int nSearchingTimes = 0; if( bStopped ) return 0; while( CWarrior::InitialLifeValue[MakingSeq[nColor][nCurMakingSeqI dx]] > nTotalLifeValue && nSearchingTimes < WARRIOR_NUM ) { nCurMakingSeqIdx = ( nCurMakingSeqIdx + 1 ) % WARRIOR_NUM ; nSearchingTimes ++; } int nKindNo = MakingSeq[nColor][nCurMakingSeqIdx]; if( CWarrior::InitialLifeValue[nKindNo] > nTotalLifeValue ) { bStopped = true; if( nColor == 0) printf("%03d red headquarter stops makingwarriors\n",nTime); else printf("%03d blue headquarter stops makingwarriors\n",nTime); return 0; } nTotalLifeValue -= CWarrior::InitialLifeValue[nKindNo]; nCurMakingSeqIdx = ( nCurMakingSeqIdx + 1 ) % WARRIOR_NUM ; int nTmp = anWarriorNum[nKindNo]; anWarriorNum[nKindNo] ++; switch( nKindNo ) { case DRAGON: pWarriors[nTotalWarriorNum] = new CDragon( this,nTotalWarriorNum+1); break; case NINJA: pWarriors[nTotalWarriorNum] = new CNinja( this,nTotalWarriorNum+1); break; case ICEMAN: pWarriors[nTotalWarriorNum] = new CIceman( this,nTotalWarriorNum+1); break; case LION: pWarriors[nTotalWarriorNum] = new CLion( this,nTotalWarriorNum+1); break; case WOLF: pWarriors[nTotalWarriorNum] = new CWolf( this,nTotalWarriorNum+1); break; } pWarriors[nTotalWarriorNum]->PrintResult(nTime); nTotalWarriorNum ++; return 1; } void CHeadquarter::GetColor( char * szColor) { if( nColor == 0) strcpy(szColor,"red"); else strcpy(szColor,"blue"); } const char * CWeapon::Names[WEAPON_NUM] = {"sword","bomb","arrow" }; int CWeapon::InitialForce[WEAPON_NUM]; const char * CWarrior::Names[WARRIOR_NUM] = { "dragon","ninja","iceman","lion","wolf" }; int CWarrior::InitialLifeValue [WARRIOR_NUM]; int CHeadquarter::MakingSeq[2][WARRIOR_NUM] ={ { 2,3,4,1,0 },{3,0,1,2,4} }; int main() { int t; int m; //freopen("war2.in","r",stdin); CHeadquarter RedHead,BlueHead; scanf("%d",&t); int nCaseNo = 1; while ( t -- ) { printf("Case:%d\n",nCaseNo++); scanf("%d",&m); int i; for(i = 0;i < WARRIOR_NUM;i ++ ) scanf("%d", & CWarrior::InitialLifeValue[i]); // for(i = 0;i < WEAPON_NUM;i ++ ) // scanf("%d", & CWeapon::InitialForce[i]); RedHead.Init(0,m); BlueHead.Init(1,m); int nTime = 0; while( true) { int tmp1 = RedHead.Produce(nTime); int tmp2 = BlueHead.Produce(nTime); if( tmp1 == 0 && tmp2 == 0) break; nTime ++; } } return 0; }
-
多态
• 调用哪个虚函数取决于指针p指向哪种类型的对象
• 派生类的指针可以赋给基类指针。通过基类指针调用基类和派生类中的同名虚函数时:
(1)若该指针指向一个基类的对象,那么被调用是基类的虚函数;
(2)若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数。class Base { public: void fun1() { this->fun2(); } //this是基类指针,fun2是虚函数,所以是多态 virtual void fun2() { cout << "Base::fun2()" << endl; } }; class Derived:public Base { public: virtual void fun2() { cout << "Derived:fun2()" << endl; } }; int main() { Derived d; Base * pBase = & d; // 指向 Derived 的对象 pBase->fun1(); return 0; } 输出结果为:Derived:fun2()
• 在非构造函数,非析构函数的成员函数中调用虚函数,是多态!!!
• 在构造函数和析构函数中调用虚函数,不是多态。编译时即可确定,调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数
• 派生类中和基类中虚函数同名同参数表的函数,不加virtual也自动成为虚函数 -
虚函数的访问权限
class Base { private: virtual void fun2() { cout << "Base::fun2()" << endl; } }; class Derived:public Base { public: virtual void fun2() { cout << "Derived:fun2()" << endl; } }; Derived d; Base * pBase = & d; pBase -> fun2(); // 编译出错
• 编译出错是因为 fun2() 是Base的私有成员。即使运行到此时实际上调用的应该是
Derived的公有成员 fun2()也不行,因为语法检查是不考虑运行结果的。• 如果将Base中的 private换成public,即使Derived中的fun2() 是private的,编译依然能通过,也能正确调用Derived::fun2()。
就是说基类指针指向派生类对象的指针可以调用的派生类的私有函数,但是不能调用多态使用中基类被设置为私有的成员函数。
• “多态”的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定 ---- 这叫“动态联编”。
-
虚函数表
每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着虚函数表的指针。虚函数表中列出了该类的虚函数地址。多出来的4个字节就是用来放虚函数表的地址的。
• 多态的函数调用语句被编译成一系列根据基类指针所指向的(或基类引用
所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的指令。 -
虚析构函数
• 通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数
• 但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数。
• 解决办法:把基类的析构函数声明为virtual
• 派生类的析构函数可以virtual不进行声明
• 通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数
• 一般来说,一个类如果定义了虚函数,则应该将析构函数也定义成虚函数。或者,一个类打算作为基类使用,也应该将析构函数定义成虚函数。
• 注意:不允许以虚函数作为构造函数 -
纯虚函数和抽象类
• 纯虚函数: 没有函数体的虚函数
virtual void Print( ) = 0 ; //纯虚函数
• 包含纯虚函数的类叫抽象类
• 抽象类只能作为基类来派生新类使用,不能创建抽象类的对象
• 抽象类的指针和引用可以指向由抽象类派生出来的类的对象
• 在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部不能调用纯虚函数。
• 如果一个类从抽象类派生而来,那么当且仅当它实现了基类中的所有纯虚函数,它才能成为非抽象类。 -
第六周作业
/* 看上去像多态 程序填空产生指定输出 D::Fun B::Fun D::Fun nBVal=2 nBVal=24 nDVal=8 B::Fun nBVal=12 输入 无 输出 D::Fun B::Fun D::Fun nBVal=2 nBVal=24 nDVal=8 B::Fun nBVal=12 样例输入: 无 样例输出 D::Fun B::Fun D::Fun nBVal=2 nBVal=24 nDVal=8 B::Fun nBVal=12 */ #include <iostream> using namespace std; class B { private: int nBVal; public: void Print() { cout << "nBVal="<< nBVal << endl; } void Fun() {cout << "B::Fun" << endl; } // 构造函数 B ( int n ) { nBVal = n;} }; //your code starts here class D :public B { private : int nDVal; public: void Print() { B::Print(); cout << "nDVal="<<nDVal<<endl; } // 构造函数 基类用初始化列表 D( int n) : B(3*n) { nDVal = n; } void Fun() { cout << "D::Fun" << endl; } }; //your codes ends here int main() { // D 继承于 B B * pb; D * pd; D d(4); d.Fun(); // D::Fun pb = new B(2); pd = new D(8); pb -> Fun(); pd->Fun(); // B::Fun D::Fun pb->Print (); // 2 pd->Print (); // 24(由初始化列表初始化) 8 pb = & d; // 或者 pb = new B(4) pb->Fun(); // 非多态 B::Fun pb->Print(); // 非多态B::Print 4 * 3 = 12 return 0; }
/* 程序填空输出指定结果 输出: A::Fun C::Do */ #include <iostream> using namespace std; class A { private: int nVal; public: void Fun() { cout << "A::Fun" << endl; }; void Do() { cout << "A::Do" << endl; } }; class B:public A { public: virtual void Do() { cout << "B::Do" << endl;} // 虚函数会被覆盖成多态 // 若为 viod Do() { cout << "B::Do" << endl;} 则就算B & b = c 引用的是B的子类对象c, 调用的仍然是B自身的Do函数,因为不是多态 }; class C:public B { public: void Do( ) { cout <<"C::Do"<<endl; } void Fun() { cout << "C::Fun" << endl; } }; //your code starts here void Call( B & p ) { // B & p = c 基类 引用 派生类对象; p 是子类对象,但是p的行为是基类行为 // (只能调用子类继承来的基类部分的方法,不能调用子类附加部分的方法); p.Fun(); // p是子类对象但是是基类行为,所以不能调用 C 的Fun函数,只能调用 B,而 B 没有Fun函数,故调用 B 的基类 A 的Fun函数 p.Do(); } int main() { // A -> B -> C C c; Call(c); system("pause"); return 0; }
/* 程序填空输出指定结果 destructor B destructor A */ #include <bits/stdc++.h> using namespace std; class A { public: A() { } //your code starts here virtual ~A() { cout << "destructor A" << endl; } // 基类的析构函数定义为虚函数就可以 //your code ends here }; class B:public A { // B -> A public: ~B() { cout << "destructor B" << endl; } }; int main() { A * pa; pa = new B; // 基类指针指向子类对象;子类对象,基类行为 delete pa; return 0; }
/* 程序填空,输出: A::Fun A::Do A::Fun C::Do */ #include <iostream> using namespace std; class A { private: int nVal; public: void Fun() { cout << "A::Fun" << endl; }; virtual void Do() { cout << "A::Do" << endl; } // 虚函数会被覆盖 }; class B:public A { public: virtual void Do() { cout << "B::Do" << endl;} }; class C:public B { public: void Do() { cout <<"C::Do"<<endl; } void Fun() { cout << "C::Fun" << endl; } }; //your code starts here void Call(A * p) { // 创建一个 A 类对象 自然就是调用自身的Fun 和 Do p->Fun(); // 创建一个 C 类对象,用间接基类的指针指向基类对象表现出的是子类对象,基类行为 p->Do(); // 由于 A 有Fun函数故调用的是 A 的Fun ,A 的Do是虚函数,表现为多态形式故调用的是 C 的Do函数 } int main() { // C -> B -> A Call( new A()); // 创建一个 A 类对象 Call( new C()); // 创建一个 C 类对象 system("pause"); return 0; }
-
输入和输出相关的类
• istream是用于输入的流类,cin就是该类的对象。
• ostream是用于输出的流类,cout就是该类的对象。
• ifstream是用于从文件读取数据的类。
• ofstream是用于向文件写入数据的类。
• iostream是既能用于输入,又能用于输出的类。
• fstream 是既能从文件读取数据,又能向文件写入数据的类。 -
标准流对象
• 输入流对象: cin 与标准输入设备相连
• 输出流对象:cout 与标准输出设备相连
• cerr 与标准错误输出设备相连
• clog 与标准错误输出设备相连
• 缺省情况下cerr << "Hello,world" << endl; clog << "Hello,world" << endl; 和 cout << “Hello,world” << endl; 一样
• cin对应于标准输入流,用于从键盘读取数据,也可以被重定向为从文件中读取数据
• cout对应于标准输出流,用于向屏幕输出数据,也可以被重定向为向文件写入数据
• cerr对应于标准错误输出流,用于向屏幕输出出错信息,
• clog对应于标准错误输出流,用于向屏幕输出出错信息,
• cerr和clog的区别在于cerr不使用缓冲区,直接向显示器输出信息;而输出到clog中的信息先会被存放在缓冲区,缓冲区满或者刷新时才输出到屏幕。
• 如果是从文件输入,比如前面有freopen(“some.txt”,”r”,stdin);那么,读到文件尾部,输入流就算结束
• 如果从键盘输入,则在单独一行输入Ctrl+Z代表输入流结束 -
istream类的成员函数
•
istream & getline(char * buf, int bufSize);
从输入流中读取bufSize-1个字符到缓冲区buf,或读到碰到‘\n’为止(哪个先到算哪个)。
•istream & getline(char * buf, int bufSize,char delim);
从输入流中读取bufSize-1个字符到缓冲区buf,或读到碰到delim字符为止(哪个先到算哪个)。两个函数都会自动在buf中读入数据的结尾添加\0’。,
• ‘\n’或delim都不会被读入buf,但会被从输入流中取走。 如果输入流中‘\n’或delim之前的字符个数达到或超过了bufSize个,就导致读入出错,其结果就是:虽然本次读入已经完成,但是之后的读入就都会失败了。
• 可以用 if(!cin.getline(…)) 判断输入是否结束
•bool eof();
判断输入流是否结束
•int peek();
返回下一个字符,但不从流中去掉.
•istream & putback(char c);
将字符ch放回输入流
•istream & ignore( int nCount = 1, int delim = EOF );
从流中删掉最多nCount个字符,遇到EOF时结束。
• 因为getline读到留在流中的’\n’就会返回
•freopen("test.txt","w",stdout);
//将标准输出重定向到 test.txt文件 -
流操纵算子
• 整数流的基数:流操纵算子
dec,oct,hex,setbase
• 浮点数的精度(precision,setprecision)
• 设置域宽(setw,width)
• 用户自定义的流操纵算子
使用流操纵算子需要 #include < iomanip > -
控制浮点数精度的流操纵算子
• precision, setprecision
• precision是成员函数,其调用方式为:cout.precision(5);
• setprecision 是流操作算子,其调用方式为:cout << setprecision(5); // 可以连续输出
指定输出浮点数的有效位数(非定点方式输出时)
指定输出浮点数的小数点后的有效位数(定点方式输出时)
定点方式:小数点必须出现在个位数后面 -
文件读写
创建文件
• #include // 包含头文件
•ofstream outFile(“clients.dat”, ios::out|ios::binary);//创建文件
–clients.dat”
要创建的文件的名字
–ios::out
文件打开方式
•ios:out
输出到文件, 删除原有内容
•ios::app
输出到文件, 保留原有内容,总是在尾部添加
–ios::binary
以二进制文件格式打开文件• 也可以先创建ofstream对象,再用 open函数打开
ofstream fout; fout.open("test.out",ios::out|ios::binary); // 判断打开是否成功: if(!fout){ cout << “File open error!”<<endl; }
• 文件名可以给出绝对路径,也可以给相对路径。没有交代路径信息,就是在当前文件夹下找文件
-
文件的读写指针
• 对于输入文件,有一个读指针;
• 对于输出文件,有一个写指针;
• 对于输入输出文件,有一个读写指针;
• 标识文件操作的当前位置, 该指针在哪里,读写操作就在哪里进行。ofstream fout("a1.out",ios::app); //以添加方式打开 long location = fout.tellp(); //取得写指针的位置 location = 10; fout.seekp(location); // 将写指针移动到第10个字节处 fout.seekp(location,ios::beg); //从头数location fout.seekp(location,ios::cur); //从当前位置数location fout.seekp(location,ios::end); //从尾部数location
• location 可以为负值
-
显式关闭文件
ifstream fin(“test.dat”,ios::in); fin.close(); ofstream fout(“test.dat”,ios::out); fout.close();
-
字符文件读写
• 因为文件流也是流,所以流的成员函数和流操作算子也同样适用于文件流。
• 写一个程序,将文件 in.txt 里面的整数排序后,输出到out.txt
例如,若in.txt 的内容为:1 234 9 45 6 879
则执行本程序后,生成的out.txt的内容为:1 6 9 45 234 879#include <iostream> #include <fstream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; ifstream srcFile("in.txt",ios::in); ofstream destFile("out.txt",ios::out); int x; while( srcFile >> x ) v.push_back(x); sort(v.begin(),v.end()); for( int i = 0;i < v.size();i ++ ) destFile << v[i] << " "; destFile.close(); srcFile.close(); return 0; }
• 二进制读文件:
ifstream 和 fstream的成员函数:istream& read (char* s, long n);
将文件读指针指向的地方的n个字节内容,读入到内存地址s,然后将文件读指针向后移动n字节 (以ios::in方式打开文件时,文件针开始指向文件开头) 。
• 二进制写文件:
ofstream 和 fstream的成员函数:istream& write (const char* s, long n);
将内存地址s处的n个字节内容,写入到文件中写指针指向的位置,然后将文件写指针向后移动n字节(以ios::out方式打开文件时,文件写指针开始指向文件开头, 以ios::app方式打开文件时,文件写指针开始指向文件尾部 ) 。
-
二进制文件和文本文件的区别
Linux,Unix下的换行符号:‘\n’ (ASCII码: 0x0a) Windows 下的换行符号:‘\r\n’ (ASCII码: 0x0d0a) endl 就是 '\n' Mac OS下的换行符号: ‘\r’ (ASCII码:0x0d)
• 导致 Linux, Mac OS 文本文件在Windows 记事本中打开时不换行
• Unix/Linux下打开文件,用不用 ios::binary 没区别
• Windows下打开文件,如果不用 ios::binary,则:读取文件时,所有的 ‘\r\n’会被当做一个字符’\n’处理,即少读了一个字符’\r’。写入文件时,写入单独的’\n’时,系统自动在前面加一个’\r’,即多写了一个’\r’ -
第七周作业
/* 填写模板 PrintArray,使得程序输出结果是: TomJackMaryJohn 10 不得编写SumArray函数 */ #include <iostream> #include <string> using namespace std; template <class T> //your code starts here T SumArray(T * begin, T * end) { // 模板类 SumArray T tmp = * begin; ++ begin; for(; begin != end ; ++ begin) tmp += * begin; return tmp; //your code ends here } int main() { string array[4] = { "Tom","Jack","Mary","John"}; cout << SumArray(array,array+4) << endl; // a + 4 = 4 int a[4] = { 1, 2, 3, 4}; // 提示:1+2+3+4 = 10 cout << SumArray(a,a+4) << endl; system("pause"); return 0; }
/* 编写MyForeach模板,使程序按要求输出 不得编写 MyForeach函数 输入: 多组数据 每组数据第一行是两个整数 m 和 n ,都不超过 50 第二行是 m 个不带空格的字符串 第三行是 n 个整数 输出: 对每组数据 第一行输出所有输入字符串连在一起的结果 第二行输出输入中的每个整数加1的结果 样例输入: 3 4 Tom Mike Jack 1 2 3 4 1 2 Peking 100 200 样例输出: TomMikeJack 2,3,4,5, Peking 101,201, */ #include <iostream> #include <string> using namespace std; //your code starts here template <class T1, class T2 > void MyForeach( T1 start, T1 end, T2 op) { // 起始和操作 for(; start != end; ++start) { op(*start); // 操作(值) } } //your code ends here 两个操作函数 void Print(string s) { cout << s; } void Inc(int & n) { ++ n; } string array[100]; int a[100]; int main() { int m,n; while(cin >> m >> n) { for(int i = 0;i < m; ++i) cin >> array[i]; for(int j = 0; j < n; ++j) cin >> a[j]; MyForeach(array,array+m,Print); cout << endl; MyForeach(a,a+n,Inc); for(int i = 0;i < n; ++i) cout << a[i] << ","; cout << endl; } system("pause"); return 0; }
/*编写Filter模板,使得程序产生指定输出不得编写 Filter函数 输入样例 无 输出样例 MikeJackLucy 3,4,5, */ #include <iostream> #include <string> using namespace std; //your code starts here template <class T1, class T2> T1 Filter( T1 start,T1 end, T1 start2, T2 op) { for(; start != end; ++start) { if( op(*start)) { // 剩下的字符串长度 > 值 * start2 = * start; // 存储start 的位置存放的值 ++ start2; // 将指针start2 前移 } } return start2; } //your code ends here 两个操作函数 bool LargerThan2(int n) { return n > 2; } bool LongerThan3(string s) { return s.length() > 3; // 若字符串的长度 > 3 } string as1[5] = {"Tom","Mike","Jack","Ted","Lucy"}; string res1[5]; int a1[5] = { 1, 2, 3, 4, 5}; int res2[5]; int main() { string * p = Filter(as1, as1+5, res1, LongerThan3); for(int i = 0; i < p - res1; ++i) cout << res1[i]; cout << endl; int * p2 = Filter(a1, a1+5, res2, LargerThan2); for(int i = 0;i < p2 - res2; ++i) cout << res2[i] << ","; system("pause"); return 0; }
/* 读入两个整数,输出两个整数 ,直到碰到-1 输入 多组数据,每组一行,是两个整数 输出 对每组数据,原样输出 当碰到输入中出现-1 时,程序结束 输入中保证会有 -1 输入样例 12 44 344 555 -1 2 3 输出样例 12 44 344 555 */ #include <iostream> using namespace std; class MyCin { //your code starts here bool valid; public: // 构造函数 MyCin():valid(true) { } // 重载 bool operator bool( ) { //重载类型强制转换运算符 bool return valid; } // 重载 >> MyCin & operator >> (int & n) { cin >> n; if( n == -1 ) valid = false; return * this; } //your code ends here }; int main() { MyCin m; // m是一个自定义Mycin的对象 int n1,n2; while( m >> n1 >> n2) // 此处m >> 由于重载了操作符>> 等价于cin cout << n1 << " " << n2 << endl; system("pause"); return 0; }
/* 程序填空,按要求输出 输入 第一行是整数t,表示有t组数据 每组数据一行,三个整数加两个字符串。字符串是不含空格的 输出: 对每组数据,输出二行 在第一行输出第一个数 第二行原样输出输入的内容 输入样例 2 79 90 20 hello me 12 34 19 take up 输出样例: 79 79 90 20 hello me 12 12 34 19 take up 提示: C++标准模板库 istream_iterator模版使用说明: 其构造函数执行过程中就会要求输入,然后每次执行++,则读取输入流中的下一个项目,执行 * 则返回上次从输入流中读取的项目。例如,下面程序运行时,就会等待用户输入数据,输入数据后程序才会结束: #include <iostream> #include <iterator> using namespace std; int main() { istream_iterator<int> inputInt(cin); return 0; } 下面程序运行时,如果输入 12 34 程序输出结果是: 12,12 #include <iostream> #include <iterator> using namespace std; int main() { istream_iterator<int> inputInt(cin); cout << * inputInt << "," << * inputInt << endl; return 0; } 下面程序运行时,如果输入 12 34 56程序输出结果是: 12,56 #include <iostream> #include <iterator> using namespace std; int main() { istream_iterator<int> inputInt(cin); cout << * inputInt << "," ; inputInt ++; inputInt ++; cout << * inputInt; return 0; } */ #include <iostream> #include <string> using namespace std; template <class T> class CMyistream_iterator { //your code starts here istream & r; // 输入流对象 r T v; public: T operator *() { return v; } // 构造函数 CMyistream_iterator( istream & rr):r(rr) { r >> v ; } // 重载 ++ void operator ++ (int) { r >> v ; } //your code ends here }; int main() { int t; cin >> t; while( t -- ) { CMyistream_iterator<int> inputInt(cin); int n1,n2,n3; n1 = * inputInt; //读入 n1 int tmp = * inputInt; cout << tmp << endl; inputInt ++; n2 = * inputInt; //读入 n2 inputInt ++; n3 = * inputInt; //读入 n3 cout << n1 << " " << n2<< " " << n3 << " "; CMyistream_iterator<string> inputStr(cin); string s1,s2; s1 = * inputStr; inputStr ++; s2 = * inputStr; cout << s1 << " " << s2 << endl; } system("pause"); return 0; }
/* 程序填空,输出指定结果 输入: 多组数据。每组第一行是一个不含空格的字符串 第二行是整数n 第三行是n个整数 输出: 对每组数据,先依次输出输入字符串的每个字母,并且在每个字母后面加逗号 然后依次再输出输入的n个整数 ,在每个整数后面加逗号 输入样例 Tom 3 3 4 5 Jack 4 1 2 3 4 输出样例: T,o,m, 3,4,5, J,a,c,k, 1,2,3,4, */ #include <iostream> #include <string> #include <cstring> using namespace std; template <class T> class myclass { // 抽象类 //your code starts here T * p;; int size; public: myclass ( T a [], int n) { // 构造函数 p = new T[n]; for( int i = 0;i < n;i ++ ) p[i] = a[i]; size = n; } //your code ends here ~myclass( ) { // 析构函数 delete [] p; } void Show() { for( int i = 0;i < size;i ++ ) { cout << p[i] << ","; } cout << endl; } }; int a[100]; int main() { char line[100]; while( cin >> line ) { myclass<char> obj(line, strlen(line));; obj.Show(); int n; cin >> n; for(int i = 0;i < n; ++i) cin >> a[i]; myclass<int> obj2(a,n); obj2.Show(); } system("pause"); return 0; }
/* 3) 自己编写一个能对任何类型的数组进行排序的mysort函数模版,可以如下使用: 4 8 10 11 123 123 11 10 8 4 1.400000 1.200000 1.800000 3.100000 3.200000 2.100000 提示: 1> an 和 an + NUM 的类型显然是一样的,都是 int * 2> mysort里用第二个参数减去第一个参数,就能知道要排序的元素有几个 3> mysort(an, an+NUM, Greater1) 会导致 Greater1在mysort内部被调用,用来比较元素大小,因此回忆一下函数指针的用法 */ #include <iostream> using namespace std; bool Greater2(int n1,int n2) { return n1 > n2; } bool Greater1(int n1,int n2) { return n1 < n2; } bool Greater3(double d1,double d2) { return d1 < d2; } template <class T1,class T2> void mysort( //your code starts here T1 * start , T1 * end, T2 myless ) { int size = end - start; for( int i = size -1;i >= 0 ; --i ) { for( int j = 0; j < i ; ++j ) { if( myless( start[j+1],start[j] )) { T1 tmp = start[j]; start[j] = start[j+1]; start[j+1] = tmp; } } } } /*答案2 : template<class T> void Swap( T & a, T & b) { T tmp; tmp = a; a = b; b = tmp; } template <class T1,class T2> void mysort( T1 start , T1 end, T2 myless ) { int size = end - start; for( int i = size -1;i >= 0 ; --i ) { for( int j = 0; j < i ; ++j ) { if( myless( * ( start + j+1), * (start+j) )) { Swap(* ( start + j+1), * (start+j) ); } } } } 答案 3 template <class T1,class T2> void mysort( T1 start , T1 end, T2 myless ) { int size = end - start; for( int i = size -1;i >= 0 ; --i ) { for( int j = 0; j < i ; ++j ) { if( myless( * ( start + j+1), * (start+j) )) { auto tmp = * ( start+j); * ( start +j ) = * ( start + j+1); * ( start + j+1) = tmp; } } } } */ //your code ends here #define NUM 5 int main() { int an[NUM] = { 8,123,11,10,4 }; mysort(an, an+NUM, Greater1); //从小到大排序 for( int i = 0;i < NUM; i ++ ) cout << an[i] << ","; mysort(an, an+NUM, Greater2); //从大到小排序 cout << endl; for( int i = 0;i < NUM; i ++ ) cout << an[i] << ","; cout << endl; double d[6] = { 1.4,1.8,3.2,1.2,3.1,2.1}; mysort(d+1, d+5, Greater3); //将数组从下标1到下标4从小到大排序 for( int i = 0;i < 6; i ++ ) cout << d[i] << ","; system("pause"); return 0; }