C++:运算符重载、string类重写、数据类型转换、->操作符重载、virtual继承、virtual函数、typeid

来源:互联网 发布:arraylist 源码 编辑:程序博客网 时间:2024/06/05 04:41
 

1.运算符重载

谁调用了成员函数,谁就是当前对象。在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构造函数在调用函数中开辟一块内存存这个临时变量(虽然你可能不会调用它,但并不影响它的存在)。若返回为输入进来的变量的引用,则返回与输入是同一回事。

2.string类重写

#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;

}

3.数据类型转换

#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);     

}

4.->操作符重载

#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中没有给分配内存空间,则会出现段错误。

5.virtual继承

#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下异常为已放弃)。

7.virtual函数(成员函数多态实现)

#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函数时,要看具体的对象谁覆盖。

8.typeid

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中的结构体不是类。

原创粉丝点击