笔记四 操作符重载

人盡茶涼 提交于 2020-02-15 04:33:22
一 操作符重载
1 string类的定义:
#include<iostream>
class String;
istream& operator>>(istream &, String &);
ostream& operator<<(ostream &, const String &);

class String{
public:
//构造函数的重载集合
String(const char *=0);
String(const String &)

~String();

String& operator=(const String &);  //拷贝赋值操作符
String& operator=(const String *);

char & operator[] (int)const;

bool operator==(const char *)const;
bool operator==(const String&)const;

int size()
{
    return _size;
}

char *c_str()
{
    return _string;
}

private:
int size;
char *_string;
}


#include<cstring>
inline String& String::operator+=(const String &rhs)
{
 if(rhs._string)
 {
   String tmp(*this);
   _size+=rhs._size;
   _string=new char(_size+1);
   strcpy(_string,tmp,string);
   strcpy(_string+tmp._size,rhs._string);
 }
 return *this;
}

拷贝构造函数与拷贝赋值操作符的区别
class S
{
};
S s1;
S s1=s2;//s1已经调用自身的默认构造函数,然后才调用拷贝赋值操作符。

=、[]、()和->操作符必须被定义为类成员操作符。任何把这些操作符定义为名字空间成员的定义都会被标记为编译时刻错误。

不能为内置数据类型定义其他的操作符。

程序员只能为类类型或枚举类型的操作数定义重载操作符。我们可以把重载操作符声明为类的成员,或者声明为名字空间成员,同时至少有一个类或枚举类型的参数。
自定义类的<< >>操作符被定义为global函数而不是member函数,因为这两个操作符的左操作数是stream而不是自定义的类。



二 友元
对于String类的等于操作符的定义:
定义为全局函数:
bool operator==(const String &str1,const String &str2)
{
if(str1.size()!=str2.size())//不能够直接引用类型的私有成员,必须间接引用
return false;
return strcmp(str,c_str(),str2.c_str())?false;ture;
}
定义为成员函数:
bool operator==(const String &rhs) const
{
if(_size!=rhs._size)//可以直接引用类型的私有成员
return false;
return strcmp(_string,rhs._string)?false;ture;
}


另外一种可能的实现是:把全局等于操作符声明为String类的友元(friend),通过把函数或操作符声明为友元,一个类可以授予这个

函数或操作符访问其非公有成员的权利。

友元声明以关键字friend开始,只能出现在类定义中。因为友元不是授权类的成员,所以它不受其所在类的声明区域(public、

private和protected)的影响。
class String{
friend bool operator==(const String&,const String &);
friend bool operator==(const char*,const String &);
friend bool operator==(const char*,const char *);
public:
其他部分........
}

2 如果一个函数操纵两个不同类类型的对象而且该函数需要访问这两个类的非公有成员,则这个函数可以被声明为这两个类的友元或

者作为一个类的成员函数并声明为另一个类的友元。

三 操作符=

String &String::operator=(const char *sobj)
{
if(!sobj)
{
_size=0;
delete[] _string;
_string=0;
}
else{
_size=strlen(sobj);
delete[] _string;
_string=new char[_size+1];
strcpy(_string,sobj);
}
return *this;
}

_string引用了参数sobj指向的C风格字符串的拷贝。
_string=sobj;//类型不匹配

sobj是一个指向const的指针,指向const的指针不能被赋给一个指向非const的指针。

_string 指向一个在堆中被分配的字符数组。为了防止内存泄漏,在_string 指向被分配来保存新字符串值的内存之前,_string 

原来引用的C 风格字符串被通过delete 表达式释放。因为_string指向一个字符数组,所以必须使用delete表达式的数组版本。


四 操作符()
#include <vector>
#include<algorithm>
int main()
{
int ia[]={0,1,-1,-2,3,5,-5,8};
vector<int> ivec(ia,ia+8);
transform(ivec.begin(),ivec.end(),ivec.begin(),absInt());
//...................
}

transform()的第一个和第二个实参只是了absInt操作被应用的元素范围。第三个实参指向“被用来存储absInt操作结果的向量”

transform()的第四个参数是一个absInt类的临时对象,它通过调用absInt的缺省构造函数来创建。main()调用的泛型算法

transform(),看起来像如下这样表示:
typedef vector<int>::iterator iter_type;
iter_type transform(iter_type iter,iter_type last,iter_type result,absInt func)
{
while(iter!=last)
*result++=func(*iter++);
return iter;
}

五 操作符->
我们可以为类类型的对象重载成员访问操作符箭头,它必须被定义为一个类的成员函数,它的作用是赋予一个类类型与指针类似的

行为。它通常被用于一个代表“智能指针”的类类型。也就是说,一个类的行为很像内置的指针类型。

class ScreenPtr{
public:
ScreenPtr(Screen &s):ptr(&s){}
//.....
private:
Screen *ptr;
};

ScreenPtr类型的对象的定义必须提供初始值;一个Screen类型的对象,ScreenPtr对象将指向它,否则ScreenPtr对象的定义就是错

误的:
ScreenPtr p1;//error
Screen myScreen(4,4);
ScreenPtr ps(myScreen);//ok
为了使得ScreenPtr类的行为像内置指针,我们必须定义一些重载操作符,解引用操作符(*)和成员访问操作符(->)

class ScreenPtr{
public:
ScreenPtr(Screen &s):ptr(&s){}

Screen& operator*(){return *ptr;}
Screen& operator->(){return ptr;}
//.....
private:
Screen *ptr;
};

重载的成员访问操作符箭头的返回类型必须是一个类类型的指针,或者是“定义该成员访问操作符箭头的类”的一个对象。如果返

回类型是一个类类型的指针,则内置成员访问操作符箭头的予以被应用在返回值上。如果返回值是另外一个类的对象或引用,则递

归应用该过程,知道返回的是指针或语句错误。

当然,这种操纵类对象的指针不像使用内置指针类型一样有效率。所以,智能指针类必须提供其他一些对于我们的程序设计很重要

的功能,以便抵消使用它产生的额外开销。

六 操作符++和--

我们先定义一个被称为size 的新数据成员它含有0(表明ScreenPtr 对象指向单个对象)或含为ScreenPtr 对象所指数组的大小。

再定义一个被称为offset 的数据成员,用来记录ScreenPtr 对象所指数组中的偏移量:

class ScreenPtr {
public:
// ...
private:
int size; // 数组的大小对于单个对象其值为0
int offset; // ptr 在数组中的偏移
Screen *ptr;
};

class ScreenPtr {
public:
ScreenPtr( Screen &s , int arraySize = 0 )
: ptr( &s ), size ( arraySize ), offset( 0 ) { }
private:
int size;
int offset;
Screen *ptr;
};

前置操作符的声明看起来就像你所期望的那样
class ScreenPtr {
public:
Screen& operator++();
Screen& operator--();
// ...
};

为区分后置操作符与前置操作符的声明,重载的递增和递减后置操作符的声明有一个额外的int 类型的参数.

class ScreenPtr {
public:
Screen& operator++(); // 前置操作符
Screen& operator- - ();
Screen& operator++(int); // 后置操作符
Screen& operator- - (int);
// ...
};

重载的递增和递减操作符也可以被声明为友元函数例如我们可以改变ScreenPtr 的定义,把这些操作符声明为友元函数,如下所示
class ScreenPtr {
// 非成员声明
friend Screen& operator++( ScreenPtr & ); // 前置
friend Screen& operator- - ( ScreenPtr & );
friend Screen& operator++( ScreenPtr &, int ); // 后置
friend Screen& operator- - ( ScreenPtr &, int );
public:
// 成员定义
};

七 操作符new和delete
类成员操作符new()的返回类型必须是void*型,并且有一个size_t 类型的参数,这里的size_t 是一个在系统头文件<cstddef>中被

定义的typedef。
class Screen {
public:
void *operator new( size_t );
// ...
};

当new 表达式创建一个类类型的对象时,编译器查看该类是否有一个成员操作符new()。如果有,则选择这个操作符为该类对象分配

内存;否则,调用全局操作符new()。例如,下面的new 表达式
Screen *ps = new Screen;
在空闲存贮区中创建了一个Screen 类型的对象因为Screen 类有一个成员操作符new()所以调用该成员操作符new() 操作符的size_t 

参数自动被初始化为Screen 类的大小。

程序员可以使用全局域解析操作符来选择调用全局操作符new() 例如
Screen *ps = ::new Screen

类成员操作符delete()的返回类型必须是void 并且第一个参数的类型是void*。

class Screen {
public:
void operator delete( void* );
};

delete ps;
释放了ps 所指的screen 类对象的内存因为Screen 类有成员操作符delete(),所以调用了该类成员操作符delete(),操作符的

void*参数自动被初始化为ps 值。


操作符new()和delete()都是类的静态static成员,它们遵从静态成员函数的一般限制。

静态成员函数没有this 指针因此它们只能访问静态数据成员。

其原因是这些操作符被调用的时候要么是在该类对象被创建之前【操作符new()】 要么是在其被销毁之后【操作符delete()】

我们也可以把针对数组分配的操作符new[]()和delete[]()声明为类的成员。类成员操作符new[]()的返回类型必须是void* 并且第一个参数的类型是size_t。

成员操作符delete[]()的返回类型必须是void 它的第一个参数必须是void*类型例如
下面是Screen 类操作符delete[]()的声明
class Screen {
public:
void operator delete[]( void* );
};


创建数组的new 表达式首先调用类操作符new[]()来分配存贮区,然后再调用缺省构造函数依次初始化数组的每一个元素。如果这类定义了构造函数,但是没有缺省构造函数,则相应的new 表达式就是错误的。因为没有任何C++语法可以为数组元素指定初始值,或者在数组版本的new 表达式中为类的构造函数指定实参。



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!