请选择 进入手机版 | 继续访问电脑版

北南南北论坛

 找回密码
 立即注册
查看: 20|回复: 4

C++操作符重载

[复制链接]

563

主题

950

帖子

2694

积分

金牌会员

Rank: 6Rank: 6

积分
2694
发表于 2017-12-15 10:44:46 | 显示全部楼层 |阅读模式
本帖最后由 vxbus 于 2017-12-15 10:48 编辑

赋值操作符
赋值操作符即“=”。赋值操作符为二元操作数,其操作目的是将右操作数的值复制给左操作数。由于左值涉及到写操作,因此左值必须为非const量,而右值在赋值操作中只涉及读操作,因此一般为const量。
赋值操作符通常返回左操作数的引用,这样就不需要创建和撤销运算结果的临时副本。
C/C++编译器支持对内置类型(例如整形int和浮点型double)的赋值运算。
字符数组(字符串)的赋值
对于const变量进行赋值是非法的,例如数组名为不可修改的左值。
    char cstr1[6] = "cstr1";
    char cstr2[6] = "cstr2";
    cstr1 = cstr2; // error C2106: '=' : left operand must be l-value
对于内置类型数组的赋值需要逐个元素进行赋值。对于以上C字符串类型,<string.h>中定义了标准库函数char* strcpy(char *dst, const char *src);用于字符串的赋值。其实现如下:
// strcat.c
char * __cdecl strcpy(char * dst, const char * src)
{
        char * cp = dst;
        while( *cp++ = *src++ );   /* Copy src over dst */
        return( dst );
}
对于非内置类型,如自定义的复数类型complex,我们必须重载赋值操作符“=”,使其支持complex对象之间的赋值。
// complex
    _Myt& operator=(const _Myt& _Right)
    {   // assign other complex
       this->_Val[0] = _Right.real();
       this->_Val[1] = _Right.imag();
       return (*this);
    }
C++标准库string类型封装了C字符串类型的操作,它重载了赋值操作符,但其内部实现同strcpy,也是逐个字符进行(迭代)赋值。
例解CString::operator =
MFC中的字符串处理类CString中对赋值操作符“=”做了多个版本的重载,这样CStirng不仅支持同类对象的赋值,还支持将字符类型(TCHAR)、以及C字符串类型(LPCTSTR、unsigned char*)赋值给CString对象。
// AFX.H
class CString
{
public:
// ……
    // ref-counted copy from another CString
    const CString& operator=(const CString& stringSrc);
    // set string content to single character
    const CString& operator=(TCHAR ch);
#ifdef _UNICODE
    const CString& operator=(char ch);
#endif
    // copy string content from ANSI string (converts to TCHAR)
    const CString& operator=(LPCSTR lpsz);
    // copy string content from UNICODE string (converts to TCHAR)
    const CString& operator=(LPCWSTR lpsz);
    // copy string content from unsigned chars
    const CString& operator=(const unsigned char* psz);
// ……
}
上述返回值的类型为const CString&,即返回指向const CString对象的引用,返回被赋值对象的引用(return *this;),而加const修饰则说明不允许对返回值进行写操作。
    CString afxstr1 = "afxstr1";
    CString afxstr2 = "afxstr2";
    CString afxStr3 = afxstr2 = afxstr1; // afxStr3 = (afxstr2 = afxstr1);
    (afxStr3 = afxstr2) = afxstr1; // error
上述代码中afxstr2 = afxstr1调用const CString& CString::operator=(const CString& stringSrc) 将afxstr1的值赋给afxstr2(AssignCopy)。形如CString::operator=(&afxstr2, afxstr1),其中第一个参数为具体CString对象的this指针。注意CString afxStr3= afxstr2中的“=”赋值运算符将隐式创建对象,调用构造函数CString::CString(const CString& stringSrc)。C++中的explicit关键字用来修饰类的构造函数,以限制这种隐式转换构造。
(afxStr3 = afxstr2) = afxstr1;试图对赋值操作返回值进行二次赋值是不允许的,因为赋值操作返回值受const限定,不可再作为赋值运算的左值。
类的赋值牵涉到深拷贝和浅拷贝问题,牵涉到拷贝构造函数。CString中的引用计数CStringData::nRefs用来实现在线拷贝(浅拷贝),从而提高内存管理和操作的效率。
    CString afxstr1 = "afxstr1"; // CString::CString(LPCTSTR lpsz);
    CString afxstr2 = "afxstr2"; // CString::CString(LPCTSTR lpsz);
    CString afxstr3 = afxstr1;   // CString::CString(const CString& stringSrc)
    afxstr3 = afxstr2; // const CString& CString::operator=(const CString& stringSrc)
上述代码中afxstr3 = afxstr2;只是简单的做afxstr3.m_pchData = afxstr2..m_pchData;的指针赋值操作,即just copy references around。


回复

使用道具 举报

563

主题

950

帖子

2694

积分

金牌会员

Rank: 6Rank: 6

积分
2694
 楼主| 发表于 2017-12-15 10:46:00 | 显示全部楼层
本帖最后由 vxbus 于 2017-12-15 10:48 编辑

算术操作符
+、-、*、/、%是常用的运算操作符,其用法为expr1+expr2、expr1-expr2、expr1*expr2、expr1/expr2、expr1%expr2。它们皆为二元操作符,即它们作用于两个操作数,其中expr1为左操作数,expr2为右操作数。运算结果为同类操作数(对象),一般使用赋值操作符对运算结果进行接收,形如res= expr1+expr2。
“+=、-=、*=、/=、%=”等为复合赋值运算符,它表示把右边的表达式加到左边的操作数的当前值上,因此左操作数又充当了运算结果的接收者。其调用形式与赋值操作符相同,如expr1+=expr2,实际操作为expr1=expr1+expr2。鉴于左操作数既做操作数又做返回值接收器,因此复合赋值运算符通常也返回左操作数的引用。
C/C++编译器支持对内置类型(例如整形int和浮点型double)的算术运算。
    // <1>基本内置类型
    int n1 = 2010;
    int n2 = 2;
    int n3 = n1+n2; // OK. n3 is the sum of n1 and n2.
字符串的+连接操作
我们使用+运算符企图连接两个字符串是错误的,因为C/C++编译器对于字符串类型(char[])没有提供内置的衔接操作。因此,我们必须重载“+”运算符实现期望的操作。<string.h>中定义了标准库函数char* strcat(char *dst, const char *src);用于字符串的连接。
// strcat.c
char * __cdecl strcat (char * dst, const char * src)
{
        char * cp = dst;
        while( *cp )
                cp++;                   /* find end of dst */
        while( *cp++ = *src++ ) ;       /* Copy src to end of dst */
        return( dst );                  /* return dst */
}
    // <2>(字符)数组类型
    char cstr1[6] = "cstr1";
    char cstr2[6] = "cstr2";
    char cstr3[12] = {0};
    cstr3 = cstr1+cstr2; // error C2110: cannot add two pointers
    strcat(cstr3, cstr1);
    strcat(cstr3, cstr2);
C++标准库string类型重载了“+、+=”操作符,但其内部实现同strcat。
例解CString::operator +(=)
MFC中的字符串处理类CString中对赋值操作符“+、+=”做了多个版本的重载,这样CStirng不仅支持同类对象的连接,还支持将字符类型(TCHAR)、以及C字符串类型(LPCTSTR)连接到CString对象上。
// AFX.H
class CString
{
public:
// ……
    // concatenate from another CString
    const CString& operator+=(const CString& string);
    // concatenate a single character
    const CString& operator+=(TCHAR ch);
#ifdef _UNICODE
    // concatenate an ANSI character after converting it to TCHAR
    const CString& operator+=(char ch);
#endif
    // concatenate a UNICODE character after converting it to TCHAR
    const CString& operator+=(LPCTSTR lpsz);
    friend CString AFXAPI operator+(const CString& string1, const CString& string2)
{
// STRCOR.CPP
    CString s; // temporary object for concat result
    s.ConcatCopy(string1.GetData()->nDataLength, string1.m_pchData,
       string2.GetData()->nDataLength, string2.m_pchData);
    return s;
}
    friend CString AFXAPI operator+(const CString& string, TCHAR ch);
    friend CString AFXAPI operator+(TCHAR ch, const CString& string);
#ifdef _UNICODE
    friend CString AFXAPI operator+(const CString& string, char ch);
    friend CString AFXAPI operator+(char ch, const CString& string);
#endif
    friend CString AFXAPI operator+(const CString& string, LPCTSTR lpsz);
    friend CString AFXAPI operator+(LPCTSTR lpsz, const CString& string);
// ……
}


回复 支持 反对

使用道具 举报

563

主题

950

帖子

2694

积分

金牌会员

Rank: 6Rank: 6

积分
2694
 楼主| 发表于 2017-12-15 10:46:33 | 显示全部楼层
本帖最后由 vxbus 于 2017-12-15 10:48 编辑

由于operator+是对两个CString相关的对象的连接操作,不属单对象操作,因此它们应是全局函数(AFXAPI),被设置为CString的友元成员(函数)。而CString对象作为操作数不涉及写访问,因此一般定义const常量;而为避免副本带来的内存开销,一般传入引用,即const CString& string。当然,对于内置类型TCHAR作为操作数,一般不考虑副本内存开销的问题。
    CString afxstr1 = "afxstr1";
    CString afxstr2 = "afxstr2";
    CString afxstr3 = afxstr1+afxstr2;
上述代码中afxstr3 = afxstr1+afxstr2;调用CString operator+(const CString& string1, const CString& string2),即operator+(afxstr1, afxstr2),“afxstr3 =”将存放afxstr1+afxstr2结果的临时对象s拷贝给afxstr3。
另外,可参考MFC中CPoint、CSize和CRect之间的运算操作。
下标操作符
可以从容器中检索单个元素的容器类一般会定义下标操作符,即 operator[]。C/C++编译器定义了对内置类型数组的下标访问。标准库的类 string 和 vector 均定义了下标操作符。
定义下标操作符比较复杂的地方在于,它在用作赋值的左右操作符数时都应该能表现正常。下标操作符出现在左边,必须生成左值,可以指定引用作为返回类型而得到左值。只要下标操作符返回引用,就可用作赋值的任意一方。
下标操作符必须定义为类成员函数,一般需要定义两个版本:一个为非 const 成员并返回引用,另一个为 const 成员并返回 const 引用。
例解一:CString::operator[]
MFC封装的是对C字符串的操作,因此提供了operator[],以对内部字符缓冲区((LPTSTR m_pchData)做char[]索引访问(读和写)。
class CString
{
public:
// ……
    // return single character at zero-based index
TCHAR operator[](int nIndex) const { return m_pchData[nIndex]; }
// ……
}
例解二:CArray::operator[]
MFC中的数组集合类CArray 对operator[]的重载为普通的数组索引访问。
// AFXTEMPL.H
template<class TYPE, class ARG_TYPE>
class CArray : public CObject
{
public:
// ……
    // overloaded operator helpers
    TYPE operator[](int nIndex) const { return GetAt(nIndex); }
    TYPE& operator[](int nIndex) { return ElementAt(nIndex); }
// ……
}
    注意上述CArray::operator[]两个版本的区别在于第一个版本后面的const修饰传递给该函数的this指针,即const对象调用operator[]的第一个版本(传入constCArray* const this指针),非const对象调用operator[]的第二个版本(传入CArray* const this指针)。
如果只定义了第一个版本,没有定义第二个版本,非const对象调用operator[]实际上对传入的非const指针进行了const隐式转换。
例解三:CMap::operator[]
字典映射类CMap对operator[]的重载是哈希查找。实现由key查找value的“索引”访问,同时又可对索引返回值进行赋值(形如map[key] = value,先Get后Set)。
template<class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
class CMap : public CObject
{
// ……
    // Lookup and add if not there
    VALUE& operator[](ARG_KEY key);
// ……
}

回复 支持 反对

使用道具 举报

563

主题

950

帖子

2694

积分

金牌会员

Rank: 6Rank: 6

积分
2694
 楼主| 发表于 2017-12-15 10:47:06 | 显示全部楼层
类型转换符
在对内置类型进行二元运算时,如果两个操作数类型不一致,编译器会进行隐式转换。
       int n = 0;
n = 3.541 + 3; // warning C4244: '=' : conversion from 'const double' to 'int', possible loss of data
以上代码n = 3.541 + 3在编译时检查到该表达式需进行类型转换,将给出double向int截断转换的精度损失警告,其在运行期等效于n = (int)((double)3.541+(double)3);这个是编译器对内置类型的自动隐式转换支持。
例解一:CString::operator LPCTSTR
我们在对字符串进行处理的时候,难免会遭遇char*(char[])与string或CString的混合处理问题。例如我们可能需要将char字符数组(C字符串)拷贝给CString对象,或将CString对象的值拷贝到char字符数组(C字符串)中。
    char cstr[16] = "c string";
CString afxstr = "CString";
strcpy(cstr, afxstr); // right
printf("afxstr = %s", afxstr); // right
strcpy(afxstr, cstr); // error
strcpy((char*)(LPCTSTR)afxstr, cstr); // right
afxstr = cstr; // invoke const CString& CString::operator=(&afxstr, LPCSTR lpsz);
代码strcpy(cstr, afxstr)和printf("afxstr = %s", afxstr)运行时,将试图对CString对象afxstr做向const char*的转换(const char*)(afxstr)以满足形参二的预期类型。在非UNICODE环境下,const *char即LPCTSTR,因此这里将调用CString提供的LPCTSTR转换符,形如CString::operator LPCTSTR(&afxstr),其中&afxstr即为对象的this指针,返回CString对象afxstr内部指向缓冲区的常量指针:this->m_pchData。
代码strcpy(afxstr, cstr);是错误的,因为对CString对象的引用无法直接对其数据区(LPTSTR m_pchData;)进行写访问的,不存在这种转换支持。但若我们对afxstr进行强制char*去cosnt转换(C++中的const_cast) ,即strcpy((char*)(LPCTSTR)afxstr, cstr);编译运行都正确。但一般不提倡这么做,获取CString内部指向缓冲区的非常量指针(LPTSTR m_pchData;)的安全做法是调用CString的GetBuffer()方法。
实际上,CString类重载的operator=赋值运算符,具体为const CString& operator=(LPCSTR lpsz)支持将char字符数组(C字符串)直接对CString对象进行赋值,直接afxstr = cstr;即可。
// AFX.H
class CString
{
public:
// ……
    // return pointer to const string
    operator LPCTSTR() const { return m_pchData; }
    // get pointer to modifiable buffer at least as long as nMinBufLength
    LPTSTR GetBuffer(int nMinBufLength);
// ……
}
例解二:CTypeSimpleList::operator TYPE
MFC线程局部存储中使用到的简单模板链表CTypedSimpleList中TYPE()操作符,使得链表支持直接引用对象,以返回链表首节点。
// AFXTLS_.H
template<class TYPE>
class CTypedSimpleList : public CSimpleList
{
public:
// ……
    operator TYPE()
       { return (TYPE)CSimpleList::GetHead(); }
};
下列代码片段演示了CTypedSimpleList的使用。
    MyThreadData* pData;
    CTypedSimpleList<MyThreadData*> list;
    list.Construct(offsetof(MyThreadData, pNext));
    // 向链表中添加成员
    for(int i=0; i<10; i++)
    {
       pData = new MyThreadData;
       pData->nSomeData = i;
       list.AddHead(pData);
    }
    // …………   // 使用链表中的数据
    // 遍历整个链表,释放MyThreadData对象占用的空间
    pData = list;  // 调用了成员函数 operator TYPE(),相当于“pData = list.GetHead();”语句
    while(pData != NULL)
    {
// ……
    }
上面代码中,pData = list;赋值语句中,左值为数据类型,右值为链表类型,对右值进行隐式类型转换才能实现赋值:pData = (MyThreadData*)(list);编译时检查有无此转换支持(即检查CTypedSimpleList 是否存在TYPE()转换符操作支持)。其中MyThreadData*即TYPE,因此运行期调用CTypedSimpleList的operator TYPE()转换符,即CTypedSimpleList::TYPE(&list)àlist.GetHead(),返回链表的首节点。


回复 支持 反对

使用道具 举报

563

主题

950

帖子

2694

积分

金牌会员

Rank: 6Rank: 6

积分
2694
 楼主| 发表于 2017-12-15 10:47:24 | 显示全部楼层
例解三:CWnd::operator HWND; CAsyncSocket::operator SOCKET;
MFC中封装某种内核对象的类一般都支持对其内核对象做类型转换,典型的如CWndàHWND。
class CWnd : public CCmdTarget
{
// ……
// Attributes
public:
    HWND m_hWnd; // must be first data member
    operator HWND() const;
// ……
}
CWnd只不过封装了窗口对象HWND的操作,因此在调用需要HWND参数的API时,也可直接传入CWnd对象。
类似的还有CAsyncSocketàSOCKET。
class CAsyncSocket : public CObject
{
// ……
// Attributes
public:
    SOCKET m_hSocket;
    operator SOCKET() const;
// ……
}
关系操作符
关系操作符主要指同类对象的值大小比较,包括等于(==)、大于(>)、小于(<)等。关系操作符与算术操作符一样都是二元操作符,不过关系操作符返回的是布尔值。



回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


手机版|北南南北论坛  

GMT+8, 2018-1-24 07:58 , Processed in 0.069347 second(s), 24 queries .

© 2001-2016 VxWorks6 Inc.

快速回复 返回顶部 返回列表