22、友员
一个类的成员函数可以是另一个类的友元。整个类也可以是另一个类的友元,访友元称为友类;友类的每个成员函数都可以访问另一个类中的保护或私有数据成员。
23、如果虚函数在基类与子类中出现的仅仅是名字相同,而参数类型不同,或返回类型不同,即使写上了virtual关键字,也不进行迟后联编。有一个例外,如果基类中的虚函数返回一个基类指针或返回一个基类引用,子类中的虚函数返回一个子类的指针或子类的引用(函数名相同,参数类型也相同),则C++将其视为同名虚函数而进行迟后联编。需要注意以下几点:
只有类的成员函数才能说明为虚函数,普能函数不行;静态成员函数不能是虚函数;内联函数不能是虚函数,即使虚函数在类的内部定义,编译时仍将其视作非内联的;析构函数可以是虚函数,而且通常声明为虚函数。如
void Perfect(Base* pHeap)
{
//...
delete pHeapObject;
}
其中pHeap是传来的对象指针,它或者指向基类对象,或者指向子类对象。将析构函数声明为虚的,则在招执行delete pHeapObject时,会自动调用相应的析构函数。
24、从相似的类中,将共有特征提取出来的过程称为分解(factoring),分解使类的层次合理化和减少冗余。
不能创建一个抽象类的对象,但是可以声明一个抽象类的指针或引用。
C++是强类型语言。当访问一个成员函数时,C++坚持要证明该成员函数在类中存在,否则拒绝接受。
在派生类中允许重载基类的成员函数。如果基类的函数是虚函数,当使用指针或引用访问对象时,将基于实际运行时指针所指向的对象类型来调用派生类的函数。
25、多继承的构造顺序
(1)先是虚拟基类。任何虚拟基类的构造函数按照它们被继承的顺序构造。
(2)接着是非虚拟基类。任何非虚拟基类的构造函数按照它们被继承的顺序构造。
(3)任何成员对象的构造函数按照它们声明的顺序调用。
(4)类自己的构造函数。
如
class Derived:public Base1,virtual public Base2,
public Base3,virtual public Base4
{
//...构造函数等
protected:
OBJ1 obj1;
OBJ2 obj2;
};
则其构造次序为:
base2->base4->base1->base3->obj1->obj2
26、当一个类是两个或多个基类的派生类时,必须在派生类名和冒号之后,列出所有基类的类名,基类间用逗号隔开。
派生类的构造函数必须激活所有基类的构造函数,并把相应的参数传递给它们。
27、重载运算符
运算符是函数,除了运算顺序和优先级不能更改外,参数和返回类型是可以重新说明的,即可以重载。C++规定,运算符中,参数说明都是内部类型时(如都是int),不能重载。此外,".、::、.*、.->、?:"这五个运算符不能重载。
返回类型 operator 运算符号(参数说明);如
operator+ (A&,A&)
operator* (A&,A&)
重载成员函数和非成员函数形式:
class RMB
{
RMB operator+ (RMB&)//将之编译成 RMB operator+ (RMB* this,RMB&)
friend RMB operator*(RMB&,RMB&)
}
RMB operator*(RMB& a,RMB& b) {...}
由上可以,重载成成员函数,可以少写一个参数,这是因为C++对所有的成员函数都隐藏了第一个参数this。
C++规定:=,(),[],->这四种运算符必须为成员形式。
有些运算符重载(如复数的加减运算)一般用非成员形式。因为诸如s=1.5+s的运算中,第一个数据1.5无法与C++隐藏的参数匹配。当然可以将其数据类型转换。
******************************************************
将其它类型的数据转换成类,用转换构造函数(默认转换):
RMB(double value)//将double类型转换RMB类
{
//...
}
转换运算符形式为
operator 类型名(); //在类中定义,它没有返回类型,将对象转换成类型。如
RMB::operator double() {...} 将RMB`类转换成double类型
******************************************************
Myclass newMC=mc; //这是拷贝构造函数
newMC=mc; //这是赋值运算符
由上可见,拷贝构造函数用已存在的对象创建一个相同的新对象,而赋值运算符将一个对象的成员变量赋予一个已存在的同类对象的对象。
赋值运算符定义格式如下:
Name& operator= (Name& s)//这是成员定义,C++将之编译成
Name& operator= (Name* this,Name& s)
如Name s("ok1");
Name t("ok2");
t=s; //C++将之编译成t.operator=s,t赋予this,s赋予s.
#include <iostream>
#include <string>
using namespace std;
class Name
{
public:
Name() //默认构造函数
{
pName=0;
}
Name(char* pn) //构造函数
{
copyName(pn);
}
Name(Name& s) //拷贝构造函数
{
copyName(s.pName);
}
~Name()
{
deleteName();
}
Name& operator= (Name& s) //赋值运算符
{
deleteName();
copyName(s.pName);
return *this;
}
void display()
{
cout<<pName<<endl;
}
protected:
void copyName(char* pN);
void deleteName();
char* pName;
};
void Name::copyName(char* pN)
{
pName=new char[strlen(pN)+1];
if(pName)
{
strcpy(pName,pN);
}//if
}
void Name::deleteName()
{
if(pName)
{
delete pName;
pName=0;
}
}
int main()
{
Name s("perfect");
Name t("Holyshit");
t.display();
t=s; //赋值
t.display();
return 1;
}
28、文件流类
ofstream::ofstream(char*pFileName,intmode=ios::out,int prot=filebuf::openprot);
第一个参数是指向打开的文件名字串,第二个和第三个参数说明文件如何被打开,mode是打开方式,见下说明。
Ios::ate 如果文件存在,输出内容加在末尾
Ios::in 具有输入能力(ifstream默认)
Ios::out 具有输出能力(ofstream默认)
Ios::trunc 如文件存在,清除文件内容(默认)
Ios::nocreate 如果文件不存在,返回错误
Ios::noreplace 如果文件存在,返回错误
Ios::binary 以二进制方式打开文件
Prot是文件保护方式,见下说明:
Filebuf::openprot 兼容共享方式
Filebuf::sh_none 独占,不共享
Filebuf::sh_read 允许读共享
Filebuf::sh_write 允许写共享
一个例子:
#include <iostream>
#include <fstream>
using namespace std;
void fn()
{
ofstream myf("c:\\shit.dat",ios::out|ios::trunc);
if(myf.fail()) //fail()==1表示打开失败
{
cerr<<"error opening file";
return;
}//if
myf<<"in each of the following questions,a related pair\n"
<<"of words or ...";
}//fn
int main()
{
fn();
return 1;
}
29、串流类
istrstream::istrstream(const char* str);
istrstream::istrstream(const char* str,int size);
ostrstream::ostrstream(char*,int size,int=ios::out);
#include <iostream>
#include <strstrea>
#include <string>
using namespace std;
int main()
{
string str="I am a student.\n";
string a;
istrstream ai(str);
ai>>a;
cout<<a<<endl;
return 1;
}
30、输入输出(续)
1)Get 读入一个字符
Char istream::get();
例如:letter=cin.get();
Cin>>letter从输入流中读入字符时将跳过空白字符(空格,tab符,backspace符和回车符),而cin.get()则不跳过空白字符。
2)Get 读入一系列字符
Istream& istream::get(char*,int n,char delim='\n');//直到输入流中出现结束字符或所读字符达到要求的字符个数。
3)用getline输入行
Getline(char* line,int size,char='\n');
int main()
{
char str[20];
cout<<"please input a line character:\n";
cin.getline(str,20,'x');
cout<<str<<endl;
char buffer[20];
cin.get(buffer,20,'x');
cout<<buffer<<endl;
return 1;
}
4)cout.put(letter) //输出一个字符。
5)Ifstream source(argv[1]);
Source.eof //判断是否文件的结尾
31、模板
函数模板的一般定义形式是:
Template<类型形式参数表> 返回类型 函数名(形式参数表)
{//类型形式参数表包括基本数据类型,及类类型,如是是类类型,应当在
//类名前加前缀class
//函数定义体
}
通过如下表述进行函数调用:
函数名(实参表);//系统会自动分析实参数的类型,进而进行模型的应用替换。
类模板的一般定义形式是:
Template <类型形式参数表> class 类名
{//类型形式参数表包括基本数据类型,及类类型,如是是类类型,应当在
//类名前加前缀class
//类声明体
};
//如下是类内成员函数在外部定义的一般形式
Template<类型形式参数表>
返回类型 类名<类型名表>::成员函数名(形式参数表)
{//类型名表,是类型形式参数的使用
//成员函数定义体
}
类名<类型实在参数表> 对象;
如下例子:
template <class T> //T是一个已定义好的类型,也可是int等基本类型
T max(T a,T b)
{
//return the bigger one;
}
max(3,5);
template <class T> class List
{//T是一个早已定义的类类型或其它基本数据类型
//...
T* Find(T&);
}
template <class T>
T* List<T>::Find(T& t)
{
//...
}
List<float> floatList;
*********************************************
#include <iostream>
#include <string>
using namespace std;
template <class T> class Test
{
public:
Test()
{cout<<"ok";}
Test(string strt)
{str=strt;}
T swap(T a,T b)
{cout<<str<<endl; return a>b?a:b;}
private:
string str;
};
int main()
{
Test<int> t("test");
int a=t.swap(2,3);
cout<<a<<endl;
return 1;
}
重载模板函数
#include <iostream>
//#include <string>
using namespace std;
template <class T> T max(T a,T b)
{cout<<"in T max\n";
return a>b?a:b;
}
char* max(char* a,char* b)
{
cout<<"in char max\n";
return (strcmp(a,b)>0?a:b);
}
int main()
{
cout<<"Max(hello,Gold)is"<<max("hello","Gold")<<endl;
return 1;
}