winxos 发表于 2009-5-31 13:43:28

c++中的内部类重载问题

对于重载问题,我一直有很多疑惑没有解决,请各位熟悉C++的老大指点一下。
问题一、
如何重载!,并且使用的时候放到右边?
补充:用通常的手段重载后!要放到左边才可以用,上网上找大家都说没有办法,不过我坚信计算机世界没有没有办法的事情。
问题二、
如何重载内部已经定义好的类?
比如说如何重载int?使int可以使用感叹号”!“?
用friend?我试了下好像不行。
先感谢各位的帮助。

mathe 发表于 2009-5-31 17:25:44

问题一指什么?什么左边右边的,举个例子来说明把.
猜测你是指操作符重载(定义)的问题了
比如对于类X,如果定义++x,那么我们定义类X的成员函数
X& operator++();
就可以了.
但是如果要定义x++,那么我们需要定义成员函数
X operator++(int);
当然这些操作符可以定义成非成员函数,那么参数必然显示出现在函数列表中,如第一种情况为
X& operator++(X& x);
而后缀操作符需要定义为
X operator++(X& x, int);
即可.
至于说定义成友元(friend),这个是可选的.只有在我们需要在这些函数内部访问类成员的私有变量时,才需要.(而对于操作符定义,通常需要).

所以如果要重载int的操作符!.显然不能定义成成员函数.同样,也不能申明成友元,只能直接声明为普通函数
int operator!(int x);

mathe 发表于 2009-5-31 17:35:43

其实在C++中,操作符同函数可以看成没有区别,其唯一的区别是对操作符添加了一些特殊的使用方法.
而对于所有的类成员函数(包括类成员操作符),我们可以把this指针(指向类本身)总是看成函数的第一个参数.
那么在这个意义下,所有的操作符总是一个或两个参数:
比如操作符X& X::operator+=(const X& a); 可以看成有两个参数,第一个是this,第二个是a,另外还有一个返回值.
而这个函数如果申明为非类成员,就是
X& operator+=(X& a, const X& b);
实质上同上一个函数是相同的.
而如果我们需要在这个函数内部访问类X的私有成员.那么就需要另外在类X的申明内部添加
friend X& operator+=(X& a, const X& b);

而操作符同普通函数的主要区别在于可以采用操作符的方式调用这个函数.但是实际上,我们也可以采用函数的方式调用,结果没有区别.
比如上面的函数,我们可以采用a+=b来调用,也可以采用a.operator+=(b) (成员函数)或operator+=(a,b)(非成员函数)来调用.

同样,对于后缀形式的函数,比如x++,我们可以采用x.operator++(t)或operator++(x,t)来调用,其中t是任意的已定义整数.(使用那个函数取决于这个操作符是如何定义的)

gxqcn 发表于 2009-6-1 07:57:31

一、一般来说很少直接对内部数据类型直接重载,多半采用新定义个类包装一下;
二、关于运算符重载,你可以参考 HugeCalc 的头文件,那里面的已比较多比较全了。

比如,对“!”的重载可以这样定义:constclass CMyInt
{
public:
    const BOOL operator !( void ) const;
    //...

protected:

private:
    int m_Int;
}

winxos 发表于 2009-6-1 15:36:42

本帖最后由 winxos 于 2009-6-1 15:49 编辑

其实在C++中,操作符同函数可以看成没有区别,其唯一的区别是对操作符添加了一些特殊的使用方法.
而对于所有的类成员函数(包括类成员操作符),我们可以把this指针(指向类本身)总是看成函数的第一个参数.
那么在这个意 ...
mathe 发表于 2009-5-31 17:35 http://bbs.emath.ac.cn/images/common/back.gif
我写了下面的测试代码,麻烦mathe看看对不对?
[*]/*[*]c++ 语言运算符重载以及临时变量问题的测试程序。[*]winxos 2009-06-01[*]*/[*]#include <iostream>[*]#include <string>[*]using namespace std;[*]#define NUM_LEN 10[*]static int idnum=0; //标记对象[*]class Test[*]{[*]public:[*]    Test():name("null"),age(0),id(++idnum)//空参数构造[*]    {[*]      num=new int[NUM_LEN];[*]      for(int i=0;i<NUM_LEN;i++)[*]            num[i]=0;[*]    }[*]    Test(string n):name(n),age(0),id(++idnum)   //1个参数(姓名)的构造[*]    {[*]      num=new int[NUM_LEN];[*]      for(int i=0;i<NUM_LEN;i++)[*]            num[i]=i;[*]    }[*]    Test(string n,int a):name(n),age(a),id(++idnum) //2个参数(姓名,年龄)的构造[*]    {[*]      num=new int[NUM_LEN];[*]      for(int i=0;i<NUM_LEN;i++)[*]            num[i]=i*2;[*]    }[*]    Test(Test &b)   //拷贝构造[*]    {[*]      num=new int[NUM_LEN];   //深拷贝[*]      copy(b.num,b.num+NUM_LEN,num);[*]      age = b.age ;[*]      name =b.name ;[*]      id=(++idnum);[*]    }[*]    ~Test() //析构[*]    {[*]      cout<<"ID: "<<this->id <<" destoryed!"<<endl;[*]      delete []num;[*]    }[*]    /*[*]    mathe所说的:前缀重载使用X& operator++(X& x);[*]    我测试之后发现是否用引用似乎对结果没有影响,不知道具体有什么差别?[*]    */[*]    Test operator ++ (int) //后缀重载,注意括号内一定是一个int而且不能在变量[*]    {[*]      Test ret(*this);    //后缀返回值不变[*]      this->age++;[*]      return ret; //编译时为何这里不会出现返回临时变量的警告呢?是否由于拷贝构造函数的存在使之等价于Test(ret)?[*]    }[*]    Test operator ++ () //前缀[*]    {[*]      this->age++;[*]      return (*this);[*]    }[*]    /*[*]    这里非常重要,如果未重载赋值号的话,将造成返回临时变量,会导致析构出错。尾部附有重载与未重载时的运行结果差别。[*]    */[*]    bool operator = (const Test &b) //[*]    {[*]      copy(b.num,b.num+NUM_LEN,num);[*]      age = b.age;[*]      name =b.name;[*]      return true;[*]    }[*]    /*[*]    如何实现!的后缀运算?[*]    如果加入参数将 error C2808: 一元“operator !”的形参太多[*]    */[*]    int operator ! () //前缀符号[*]    {[*]      return (this->age)*(this->age);[*]    }[*]    friend ostream& operator << (ostream &out,Test &t)//重载输出流[*]    {[*]      out<<"ID: "<<t.id<<"'s name is: "<<t.name<<"\tMy age is: "<<t.age<<"Value:";[*]      for (int i=0;i<5;i++)[*]            out<<t.num[i]<<" ";[*]      out<<endl;[*]      return out;[*]    }[*]private:[*]    string name;[*]    int age;[*]    int *num;[*]    int id;[*]};[*]int main()[*]{[*]    Test a,b("winxos"),c("emath",1);[*]    cout<<a<<b<<c<<endl;[*]    a=b++;[*]    cout<<b<<a<<endl;[*]    a=++b;[*]    cout<<b<<a<<endl;[*]    /*[*]    我还想实现的重载:[*]    比如如何实现 int i=b;[*]    其中b为Test类,这个操作实现将b.age赋值给i,请问如何实现?[*]    */[*]    int i;[*]    i=!b;   //前缀运算没问题,如何重载能够使用 i=b![*]    cout<<i<<endl;[*]    /*[*]    还有一个问题,如何重载 ** 符号?(两个乘法连在一起)[*]    直接照例会出现 error C2143: 语法错误 : 缺少“;”(在“*”的前面)[*]    */[*]    return 0;[*]}

[*]/*[*]测试程序: [*]    Test a,b("winxos"),c("emath",1); [*]    cout<<a<<b<<c<<endl; [*]    a=b++; [*]    cout<<b<<a<<endl; [*]    a=++b; [*]    cout<<b<<a<<endl;[*]之前未重载赋值号时出现奇怪错误,debug模式下运行出错,release模式有结果,但是明显有问题,运行结果如下:[*]ID: 1's name is: null   My age is: 0Value:0 0 0 0 0 [*]ID: 2's name is: winxos My age is: 0Value:0 1 2 3 4 [*]ID: 3's name is: emathMy age is: 1Value:0 2 4 6 8 [*][*]ID: 4 destoryed! [*]ID: 5 destoryed! [*]ID: 2's name is: winxos My age is: 1Value:0 1 2 3 4 [*]ID: 5's name is: winxos My age is: 0Value:-572662307 -572662307 -572662307 -57 [*]2662307 -572662307 [*][*]ID: 6 destoryed! [*]ID: 2's name is: winxos My age is: 2Value:0 1 2 3 4 [*]ID: 6's name is: winxos My age is: 2Value:-572662307 -572662307 -572662307 -57 [*]2662307 -572662307 [*][*]ID: 3 destoryed! [*]ID: 2 destoryed! [*]ID: 6 destoryed! [*]请按任意键继续. . .[*]我们可以发现执行 a=b++;后,输出a,发现a的id变成了5,说明a已经指向临时变量了,所以导致了析构出错。[*]重载之后结果如下:[*]ID: 1's name is: null   My age is: 0Value:0 0 0 0 0 [*]ID: 2's name is: winxos My age is: 0Value:0 1 2 3 4 [*]ID: 3's name is: emathMy age is: 1Value:0 2 4 6 8 [*][*]ID: 4 destoryed! [*]ID: 5 destoryed! [*]ID: 2's name is: winxos My age is: 1Value:0 1 2 3 4 [*]ID: 1's name is: winxos My age is: 0Value:0 1 2 3 4 [*][*]ID: 6 destoryed! [*]ID: 2's name is: winxos My age is: 2Value:0 1 2 3 4 [*]ID: 1's name is: winxos My age is: 2Value:0 1 2 3 4 [*][*]ID: 3 destoryed! [*]ID: 2 destoryed! [*]ID: 1 destoryed![*]请按任意键继续. . .[*]我们可以发现a的id正常[*]*/

winxos 发表于 2009-6-1 15:53:35

本帖最后由 winxos 于 2009-6-1 15:55 编辑

很早以前就对这一堆的东西感到头疼,经过很长时间的摸索以为自己理解了,
直到前几天写个小程序才发现很多问题没解决,经过mathe的说明加上我自己的理解,
感觉现在已经理解很大一部分了,但还是有如上的那么多疑问,
唉,晕了。
各位有时间的话看看我上面注释中分析的对么?
还有我注释里面提了好几个问题涅:tip:

mathe 发表于 2009-6-1 16:35:51

1.
   /*
    mathe所说的:前缀重载使用X& operator++(X& x);
    我测试之后发现是否用引用似乎对结果没有影响,不知道具体有什么差别?
    */
对于前缀操作符,通常我们返会对象的引用而不是值,唯一区别在于性能.
通常对于前缀操作,程序最后返回的其实是*this.那么如果返回的是引用,实际上编译器只需要将this指针返回.
但是如果返回是对象,那么必须做复制操作
比如对于操作a+(++b)(假设operator+被定义过),那么对于++返回引用的情况,这个不需要创建临时变量,做加法时,只需要将a和b的引用直接传入就可以了.但是如果++返回的是对象,那么这里必须创建另外一个临时变量,然后再将其指针传给函数operator+.

mathe 发表于 2009-6-1 16:38:50

   Test operator ++ (int) //后缀重载,注意括号内一定是一个int而且不能在变量
    {
      Test ret(*this);    //后缀返回值不变
      this->age++;
      return ret; //编译时为何这里不会出现返回临时变量的警告呢?是否由于拷贝构造函数的存在使之等价于Test(ret)?
    }
而对于上面的后缀形式代码,我们无法返回引用.引用的本质是返回地址(指针).在这个函数中,变量ret是函数的临时变量,它保存在堆栈上面,在函数返回时,对应堆栈空间的内存被自动释放(而且析购函数也被调用了);所以如果返回这个对象的引用,就相当于将没有意义的地址传送给调用者.

mathe 发表于 2009-6-1 16:43:35

/*
    这里非常重要,如果未重载赋值号的话,将造成返回临时变量,会导致析构出错。尾部附有重载与未重载时的运行结果差别。
    */
    bool operator = (const Test &b) //
    {
      copy(b.num,b.num+NUM_LEN,num);
      age = b.age;
      name =b.name;
      return true;
    }
通常,如果在一个类内部定义了指针并且自己管理指针指向的内存,我们需要重载默认复制构造函数和operator=.
如果不重载operator=,编译器会自动产生一个版本,这个版本会对对象的所有数据成员使用operator=
比如在这里,那么默认的版本就是
    bool operator = (const Test &b) //
    {
      b.num=num;
      age = b.age;
      name =b.name;
      return true;
    }
很显然,问题发生在指针数据成员.这个版本会导致原先的b.num指向的内存空间内存泄漏(没有指针指向),而两个指针b.num和this->num同时指向同一块内存空间,所以在对象b和this的析构函数都被调用时,会重复释放这块内存,从而出错

mathe 发表于 2009-6-1 16:44:48

/*
    如何实现!的后缀运算?
    如果加入参数将 error C2808: 一元“operator !”的形参太多
    */
编译器可能不支持这种重载

   /*
    还有一个问题,如何重载 ** 符号?(两个乘法连在一起)
    直接照例会出现 error C2143: 语法错误 : 缺少“;”(在“*”的前面)
    */
**不是合法的C++操作符,所以我们不能重载它
页: [1] 2
查看完整版本: c++中的内部类重载问题