谁调用了成员函数,谁就是当前对象。在C++中运算符操作就是函数。含有const或者引用成员变量时,构造函数要人为的初始化。Int i=9; cout<<i.相当于operator<<(cout,i);cout是ostream的单例,只能有这一个实例,因此在重载<<时必须要用引用。如有一个类person,实现<<的代码如下:ostream & operator<< (ostream &os,const person &p)const {os<<p.id<<p.name;return os;} >>代码如下:istream & operator>> (istream &ism, person &p){ism>>p.id>>p.name; return ism;} 引用的名字不能和变量相同,如int &i=i是不行的。若返回函数值为引用类型,表示可以出现在=左边,程序可以修改其值,函数返回值为const类型,可以防止其出现在=的左边。当构造函数只有一个成员变量时,可以用=直接赋值,如person p=100.表示p(100)。当实现2目运算符时,最好写成友元函数,以-为例,如果 100-一个对象时,如果是类的成员函数,100无法调用-这个操作符。当在1目运算符时,最好写成内部成员函数。前++和后++操作符是通过(int)中的哑元函数来区别的。前++由于是返回对象本身本身的引用,所以可以放在=的左边。后++由于是返回一个中间值来保存old值,所以不能放在=的左边。类的友元函数是指在类的整个函数中都可以访问类对象的私有成员。不允许重载的操作符: :: 。 .* ?: sizeof typeid .在做运算符重载时,要注意以下:不允许对基本数据类型重载,如int + int 不可以定义为 int *int 。不允许创造运算符。不能违反语法定义的形式,如不能将%定义为一元运算符,单目运算符不可以变成双目运算符。 只能定义为类成员函数的运算符(包含有=的运算符,如== != )= 《=等包含=的操作符和 []下标运算符)。强制类型转换是间接的。编译器将=赋值运算符已经写好了,当类里面有指针之类的操作时,=运算符的重载要自己写。赋值运算符重写的步骤:判断是否与自己相等,若是自己,则返回*this (if(this==传过来变量的地址)),则表示是自己,此时返回自己。释放原有空间 。申请新的空间。复制数据,返回当前对象。 当函数返回 *this时,而函数不是引用时,此时返回的仍是一个临时值,只有加上了&,才真正意义上的返回了自己。不仅函数可以是友元,一个类也可以是另一个类的友元。不可以返回一个局部变量的引用,如果返回了一个局部变量的引用,会产生一个临时对象,这个临时对象会调用copy构造函数在调用函数中开辟一块内存存这个临时变量(虽然你可能不会调用它,但并不影响它的存在)。若返回为输入进来的变量的引用,则返回与输入是同一回事。
#include <iostream>
using namespace std;
class CStr
{
public:
CStr(){ps=NULL;len=0;}
CStr(char *ps);//构造函数
CStr(const CStr &s);//COPY构造函数
~CStr();//析构函数
CStr &operator=(const CStr &s);//重载=
void show(){cout<<ps<<endl;}
int getlength()const {return len;}
friend void strcpy(CStr &s1,const CStr &s2);
private:
char *ps;
int len;
};
void strcpy(CStr &s1,const CStr &s2)//完成两个字符的copy
{
if(s1.ps==s2.ps) return;
if (s1.ps) {delete []s1.ps;}
s1.len=s2.getlength();
s1.ps=new char[s1.len+1];
int i;
for (i=0;i<s1.len;i++)
{*(s1.ps+i)=*(s2.ps+i);}
*(s1.ps+s1.len)=0;
}
CStr &CStr::operator=(const CStr &s)
{
int i;
if (s.ps==ps)
{return *this;}
else
{
len=s.getlength();
ps=new char[len+1];
for (i=0;i<len;i++)
{*(ps+i)=*(s.ps+i);}
*(ps+len)=0;
return *this;
}
}
CStr::CStr(const CStr &s)
{
int i;
if (s.ps==NULL)
{ps=NULL;len=0;}
else
{
len=s.getlength();
ps=new char[len+1];
for (i=0;i<len;i++)
{*(ps+i)=*(s.ps+i);}
*(ps+len)=0;
}
}
CStr::~CStr()
{
if(len) delete[] ps;
}
CStr::CStr(char *p)
{
int i=0;
if (p==NULL)
{ps=NULL;len=0;}
else
{
len=0;
while (*(p+len)!=0) len++;
ps=new char[len+1];
for (i=0;i<len;i++)
{*(ps+i)=*(p+i);} *(ps+len)=0;
}
}
int main()
{
CStr s1("abcdefg");
CStr s2(s1);
CStr s3="12345";
strcpy(s3,s1);
//s1=s3;
s1.show();
s2.show();
s3.show();
return 0;
}
#include <iostream>
using namespace std;
class CDouble
{
double d1,d2;
public:
CDouble(const CDouble &d){d1=d.d1;d2=d.d2;cout<<"copy CDouble"<<endl;}
CDouble(double d1=0.0,double d2=0.0):d1(d1),d2(d2) {cout<<"CDouble"<<endl;}
void show(){cout<<d1<<' '<<d2<<endl;}
~CDouble(){cout<<"~CDouble()"<<endl;}
//operator CInteger(){return d;}//CDouble->CInteger
};
class CInteger
{
int x,y;
public:
CInteger(const CInteger &i){x=i.x;y=i.y;cout<<"copy CInteger"<<endl;}
CInteger(int x=0,int y=0):x(x),y(y) {cout<<"CInteger"<<endl;}
CInteger(CDouble d){cout<<"CInteger(CDouble d)"<<endl;}//CDouble->CInteger 前面加explicit,此函数必须要显式调用,不能进行自动转换,隐匿调用。
operator CDouble(){cout<<"operator CDouble()"<<endl;return x;}//CInteger->CDouble
void show(){cout<<x<<' '<<y<<endl;}
~CInteger(){cout<<"~CInteger()"<<endl;}
};
int main()
{ CDouble d(1.4,2.3);
CInteger i(3,4);
//d=i;
i=d;
d.show();
i.show();
return 0;
}
/*结果为:
单独d=i的结果:
CDouble
CInteger
operator CDouble()
CDouble
~CDouble()
~CInteger
~CDouble
由此可见,在转换的时候中间有一个临时变量来转换的。*/
当类CDouble中有向CInteger转换的函数时,而CInteger中也有向CDouble类型转换的函数时,会产生歧义操作,所以不要出现这种情况。
另外一个例子:
#include <iostream>
using namespace std;
class Double{
double d;
public:
Double(double d=0.0):d(d){}
};
class Integer{
int x;
public:
//构造函数也有类型转换功能
Integer(int x=0):x(x){}
friend ostream& operator<<(ostream&o,const Integer&i){
return o << i.x;
}
friend istream& operator>>(istream&is,Integer&i){
return is >> i.x;
}
//类型转换运算符
operator int(){
cout << "operator int()" << endl;
return x;
}
operator Double(){
cout << "operator Double()" << endl;
return x;//因为Double类中只有一个数据变量,所以才可以返回x.事实上上将x的值转化成了d.
}
};
void fa(Double d){}
void fb(Integer i){}
int main()
{
Integer i = 100;
int x = (int)i;
x = int(i);
x = i.operator int();
x = i;
Double d;
d = i;
fa(i);
i = 200;
int y = 300;
i = y;
fb(y);
}
#include <iostream>
using namespace std;
class emp{
static int id;
public:
static void show(){cout<<id++<<endl;}
};
int emp::id=0;
class emplist{
emp *es[100];
int sz;
public:
emplist():sz(0){}//:sz()表示sz=0这种方法在windows下不能用。
void add(emp *e){es[sz]=e;sz++;}
friend class pointer;//一个类也可以是另外一个类的友元类。
};
class pointer{
emplist &e;
int index;
public:
pointer(emplist &e):e(e),index(0) {}
emp* operator-> (){return e.es[index];}//由于->操作的重载会导致两个->,所以要返回一个指针,实际得到的数据是返回指针的->。
bool operator++(){//判断是否越界
if (index<0 || index>=e.sz) return false;
index++;
return true;
}
bool operator++(int){//后++与前++是一样的,因为只是判断越界与否
return operator++();
}
emp &operator*(){return *e.es[index];}//取值
};
//将类pointer直接当作指针emp对象的来用
int main()
{
emp e1;
emp e2;
emplist e;
e.add(&e1);
e.add(&e2);
pointer p(e);
p->show();
p++;
p->show();//等同于(*p).show();
return 0;
}
附 :new和delete的重载函数形式为:
void *operator new(size_t sz) {cout<<"operator new"<<endl;return data;}
void operator delete(void *p){cout<<"operator delete"<<endl;}
这只是函数形式,具体内容还要自己增加。若new中没有给分配内存空间,则会出现段错误。
#include <iostream>
using namespace std;
class goods{
double price;
public:
goods(double price=0):price(price) {cout<<"goods construct"<<endl;}
double getprice(){cout<<"goods price:"<<price<<endl;return price;}
~goods(){cout<<"goods deconstruct"<<endl;}
};
class camera: virtual public goods{
double price;
public:
camera(double price=0):price(price),goods(price) {cout<<"camera construct"<<endl;}
//double getprice(){cout<<"camera price:"<<price<<endl;return price;}
~camera(){cout<<"camera deconstruct"<<endl;}
};
//虽然camera是virtual继承goods的,但是也可以给它传值,virtual继承是为了给上一层调用都用的,如果光是camera和goods之间,是没有什么作用的,virtual并不会发挥它的作用。
class mp3: virtual public goods{
double price;
public:
mp3(double price=0):price(price) {cout<<"mp3 construct"<<endl;}
//double getprice(){cout<<"mp3 price:"<<price<<endl;return price;}
~mp3(){cout<<"mp3 deconstruct"<<endl;}
};
class phone: virtual public goods{
double price;
public:
phone(double price=0):price(price) {cout<<"phone construct"<<endl;}
//double getprice(){cout<<"phone price:"<<price<<endl;return price;}
~phone(){cout<<"phone deconstruct"<<endl;}
};
class cellphone:public camera,public mp3,public phone{
//mp3 m1;camera c1;phone p1;
double price;
public://在构造函数时,先按基于类顺序构造,再按成员变量来构造。
cellphone(double p1=0,double p2=0,double p3=0):camera(p1),
mp3(p2),phone(p3)//,goods(p1+p2+p3)
{cout<<"cellphone construct"<<endl;}
//double getprice(){double ptemp;
//ptemp=c1.getprice()+m1.getprice()+p1.getprice();
//cout<<"phone price:"<<ptemp<<endl;return ptemp;}
~cellphone(){cout<<"cellphone deconstruct"<<endl;}
};
int main()
{
cellphone cp(100,200,300);
//对于cellphone来说,goods的price由cellphone的构造函数直接决定,与camera、mp3、phone无关系。
camera c1(20);
c1.getprice();
cp.getprice();//当用虚函数继承以后,cp.camera::getprice()=cp.getprice()。因为goods类只有一份。
return 0;
}
//如果任何一个camera、mp3、phone没有virtual继承,则cellphone中有两份goods,在调用的时候会有歧义。系统无法分辨。
名字隐藏是指子类重新实现了父类中函数,名字隐藏只要是函数名字相同就可以实现名字隐藏,与函数的参数列表及返回值无关。
当private继承父类时,父类的东西是继承下来了,只是不能用。当有父类时,子类的构造过程:分配内存空间,递归的构造父类,构造成员变量,调用构造函数,构造成员变量,调用构造函数。
子类不会继承父类的构造函数,析构函数,及赋值运算符等。当父类中有相同名字的函数时,在子类中就将父类中的函数隐藏起来了,除非调用父类::将它调出来用。名字隐藏与函数的参数列表,返回值没关系,只要函数名字相同就可以。
当从父类private 继承下来的函数时,在外部不能调用,如果要调用,要重新写一个public函数就可以了(名字不能和父类的相同,名字相同会出现段错误,在此函数中调用私有函数)。多继承构造顺序:先按继承顺序,再按成员顺序。若继承三个,有成员三个,则调用构造函数6次,析构函数6次。
当有继承和组合两种组合成员时,优先组合,因为继承容易使数据不稳定。
Virtual继承不存在函数隐藏问题,它是直接override.
类的多态应用在指针和引用上面,调用的函数取决于指针,当有态呈现时,取决于指向的对象。前者是在编译的时候决定的,后者是在运行时候决定。如果子类中的参数表和父类中的不同,则不是覆盖,而和父类中继承来的函数形成重载关系。用引用时,引用必须初始化,一旦初始化,后面不能再强制类型转换,(就算是强制类型转换,也没有作用)。含有纯虚函数的类为抽象类,抽象类不能定义对象,如果子类中没有实现纯虚函数,则子类也有抽象类,抽象类除了不能定义对象,在其它方面使用和其它类一样。如果一个类中只有纯虚函数,此类为接口类。动态绑定的实现是通过虚函数表vtable实现的。只要函数中有虚函数(哪怕只有一个),此类就有一个指向vtable的指针,此指针的地址一般在前面(和类是同样的地址),然后才是成员变量。所以如果一个类中含有一个虚函数和另一个int型变量,其sizeof为8,而如果没有虚函数,sizeof为4.在运行时,为了实现多态,要查看vtable. 这样会导致程序效率低下。
指针子向父赋值时,可以直接赋值,不会出警告。转换时有dynamic_cast,此转换只用在父子之间有虚函数,用在多态时实现。如果转换不成功,指针返回NULL,引用为抛出异常(UNIX下异常为已放弃)。
#include <iostream>
using namespace std;
class CAnimal{
public:
virtual void eat(){cout<<"animal eat"<<endl;}
void sleep(){cout<<"animal sleep"<<endl;}
};
class CDog{
public:
virtual void eat(){cout<<"dog eat"<<endl;}
//void sleep(){cout<<"animal sleep"<<endl;}
};
int main()
{ CAnimal a;
CDog d;
CAnimal *pa;
CDog *pd;
pa=(CAnimal *)&d;
//pd=(CDog *)&a;
pa->eat();
//pd->eat();
return 0;
}
//如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。不能实现函数多态,所以派生类也要加virtual.如果不加virtual,会出现段错误。
//实现多态首先要在空间中有两个可以实现多态的函数,然后一个覆盖另一个。
实现覆盖的条件:1。 函数名字、参数列表、返回值都要一样 2.函数的访问权限不能更小。 3. 抛出的异常不能更多。
注意覆盖同隐藏的区别。
静态绑定:编译期绑定。 动态绑定:运行期绑定。
指针调用的函数由指针的类型来决定(包括强制类型转化也是这样),当有virtual函数时,要看具体的对象谁覆盖。
Typeid用在运行时的指针类型或者引用指向的类型,它只关心数据类型,和sizeof一样的性质。可以typeid(类名或者对象或者指针),返回值为type_info对象的引用,如const type_info &r = typeid(类名),cout<<r.name()<<endl; 输出类名(在unix下先输出类名的长度,然后是类名)。若此类中无virtual不能实现多态,则typeid为编译时的类型,否则为运行时类型,它实际指向对象的类型。Typeid主要用在多态时看指针指向的对象的类型,(引用和指针一样使用)。
多态时的析构函数:当类中有虚函数时,析构函数也要是虚函数,以保证父类指针或引用到子类时,能正常的析构,不然会有问题的。
Cin.clear()是清除错误标志,不会清空缓冲区。
Int x; cin>>x; 当输入不是int型数据时,c++ 会按逻辑假来处理数据,此时C++会在错误情况下,拒绝工作,要cin.clear()清一下缓冲区才能正常工作。如:
If( !cin )
{ cin.clear(); cin.ignore(100,’\n’);} 第二条语句是清除缓冲区,清100个字符,直到’\n’。要先清除错误标志,才可以清空缓冲区。
Istringstream 和 ostringstream 。istringstream is(str);
在进行格式控制时,实际是通过对设置的某一位进行置 0 或置 1来实现的。
异常处理:
Throw的作用是导致一系列回退,直到找到一个catch。Void fa() throw(此函数抛出的异常类型),try{可能出现异常操作的代码}catch(此函数中处理的异常类型){对异常的处理} throw(此函数重新抛出的异常)。 当异常处理后,程序只是恢复到正常工作的状态,但是不会从异常那里继续执行代码。若在构造或者析构函数中出现异常,则在构造函数中用new分配的空间不会释放。
当using namespace 定义时,此定义的代码不能写在函数体中,因为其中的变量都等于是全局变量。若在一个函数中,有同其中名字相同的局部变量,则在此函数中用局部变量,其它的全局变量都在::匿名空间里面。当用={}给变量直接赋初值时,只适用于其中变量都为public的情况而且其中没有显式的构造函数,此时class和struct是一样的。 默认的形参有this指针。 类对象声明的位置:若在全局中,则其中的基本数据类型为默认值,成员类要用构造函数。若在局部,基本数据类型为随机值,成员类也要调用构造函数。 (foo bar::f(0)对类型为foo 的bar的类成员 f 赋初值)。
若一个类中有string成员变量,则构造函数:noname(string s){pstring=new string(s);}
Copy构造函数:noname(const noname &n){pstring =new string(*n.pstring);};析构函数:
~noname(){if(pstring) delete pstring;}。
若狗是从动物类继承而来的,则一个狗对象可以转化为动物对象,因为狗对象本来就发生动物,但是将一个动物对象转化为狗对象是错的,因为动物对象不能转化为狗对象。
Typeid在#include <typeinof> typeid()中的参数不能为指针本身,要为指针指向的对象或类型。
当一个类中有虚函数时,其析构函数也要写成虚类的。不然将父类指针new指向子类对象时,不能正确的构构。比如animal *pa=new dog;会构造狗类(先构造动物类),然后析构动物类。dog *pd=new animal;时,先构造动物类,再析构狗类(然后析构动物类),这样根据编译的时候的指针的指向类型来析构,是不对的。当析构函数为虚析构函数时,会正确的析构它所构造和析构的对象。
当 void fa() throw() throw()中什么也没有表示不抛任何异常,若为void fa() 表示抛任何异常。 Try{ 表示要处理此段代码中的异常(如果有的话) } catch(异常的类型(一般在这里可以是对象,如果是对象的话,要为引用)) {在此中为对异常的处理} throw() 表示这个函数还要抛什么类型的异常。
C++中对struct的处理和c中的不一样,在C++中,struct是当作一个类来处理的,它里面有默认的构造函数,析构函数,赋值构造函数等一个类应该有的默认的函数,只是其中数据都是公有的。而在C中的结构体不是类。