STL之操作符重载(operator)

C++中规定不能重载的操作符有:运算符 **::**(作用域解析)、**.**(成员访问)、**.\***(通过成员指针的成员访问)及 **?:**(三元条件)。

不能创建新运算符,例如 ***\*****<>****&|**

运算符 **&&****||** 的重载失去短路求值。

重载的运算符 **->** 必须要么返回裸指针,要么(按引用或值)返回同样重载了运算符 **->** 的对象。

不可能更改运算符的优先级、结合方向或操作数的数量。

**&&****||****,**(逗号)在被重载时失去其特殊的定序性质,并且即使不使用函数调用记法,也表现为与常规的函数调用相似。

除了上述限制外,语言对重载运算符的所作所为或返回类型(它不参与重载决议)上没有其他任何制约,但通常期待重载的运算符表现尽可能与内建运算符相似:期待 operator+ 对其实参进行相加而非相乘,期待 operator= 进行赋值,如此等等。期待相关的运算符之间的表现也相似(operator+ 与 operator+= 做同一类加法运算)。返回类型为期待使用该运算符的表达式所限制:例如,令赋值运算符按引用返回,以使写出 a = b = c = d 可行,因为内建运算符允许这样做。

转换函数的名称是类型转换的目标类型,因此,不必再为它指定返回值类型;转换函数是被用于本类型的数值或变量转换为其他的类型,也不必带参数

在写这篇文章之前,让我们先回顾一下编译器通过匹配过程确定调用哪一个函数的匹配顺序:
(1)寻找和使用最符合函数名和参数类型(包括返回值)的函数,若找到则调用;

(2)否则,寻找一个函数模板,将其实例化产生一个匹配的重载函数,若找到则调用;

(3)否则,寻找可以通过类型转换进行参数匹配的重载函数,若找到则调用它。

如果以上步骤均未找到匹配函数,则这个调用是错误的;如果这个调用有多于一个的匹配选译,则调用匹配出现二义性,也是错误的。

类型转换是将一种类型的值映射为另一种类型的值。类型转换实际上包含有自动隐含和强制的两种。

**\C语言编译系统提供的内部数据类型的自动隐式转换规则如下:

****1.程序在执行算术运算时,低类型可以转换为高类型。

2.在赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给它。

3.当在函数调用时,将实参值赋给形参,系统隐式地将实参转换为形参的类型后,赋给形参。

4.函数有返回值时,系统将自动地将返回表达式类型转换为函数类型后,赋值给调用函数。

在以上情况下,系统会进行隐式转换的。当在程序中发现两个数据类型不相容时,又不能自动完成隐式转换,则将出现编译错误。例如:
   int* p = 100;
在这种情况下,编译程序将报错,为了消除错误,可以进行如下所示的强制类型转换:
   int* p = (int *)100;
将整型数100显式地转换成指针类型。

构造函数具有类型转换功能

在实际应用中,当类定义中提供了单个参数的构造函数时,该类便提供了一种将其他数据类型的数值或变量转换为用户所定义数据类型的方法。因此,可以说单个参数的构造函数提供了数据转换的功能。下面通过一个例子进一步说明单参数构造函数的类型转换功能。

代码如下:

#include
classA
{
public:
A(){ m=0; }
A(doublei) { m=i; }
voidprint() { cout<<M<<endl}
private:
doublem;
};

voidmain()
{
Aa(5);
a=10; //a与10是不同的数据类型
a.print();
}

程序的输出结果为:
  10
在该程序中,赋值语句a=10;中,赋值号两边数值10和对象a是两上不相容的数据类型,可是它却能顺利通过编译程序,并且输出显示正确结果,其主要原因是得益于单参数的构造函数。编译系统选通过标准数据类型转换,将整型数值10转换成double型,然后,再通过类中定义的单参数构造函数将double型数值转换为A类类型,最后把它赋值给a。这些转换都是自动隐式完成的。

**关于上面的程序,补充一点:

**Aa = 10;

Aa;
a= 10;
两者是不同的,前者对a进行初使化,编译器会尝试将10隐式转换为A类型,这样将引起a的A(doublei)构造函数直接被调用。
后者属于赋值语句,编译器将建立一个临时对象,并将10隐式转换为A类型。如果我们显示调用
(A)10;
这也将建立一个临时对象,引起A的构造函数被调用。

还有一点要注意,编译器只会进行一次隐式转换(C时刻库的内置类型如intshort char等)除外,下面的语句说明了这点:
m_rst->GetFields()->GetItem(nCol)->Value= (_bstr_t)sValue;
上面Value是COM的变体类型,“Value=”将引起operator= (_bstr_t)被调用。如果上面省略(_bstr_t),编译器将发生错误,因为没有operator= (char*)这样的重载,编译器不会为我们进行两次以上的隐式转换。

在函数调用过程中,运算符重载和构造也是一个函数调用,如果匹配的函数如无二义性,那么将可以产生一次隐式转换。如果上句的Value变体类只有一个operate= (_bstr_t),那么既使这样写->Value= sValue; 编译器也会试图将sValue隐式转换为_bstr_t类型。

还有一种情况

代码如下:

1
2
3
4
5
6
7
8
classA
{
inta;
public:
A(){ };
A(int_a) { a = _a; };
Operator int() { return a; }
}

有如下调用:

代码如下:

1
2
Aa(10);
Aa2 = (int)(int)a; //只相当于Aa2 = (int)a; 因为第一个就近已经转成了int,第二//个就不用再转了

比较有意思吧,A类既有将int隐式转换A的构造,也有int()转换函数供强制转换,(int)(int)a将以就近原则的方式进行。如果就近转换失败,编译器将报错。比如:

代码如下:

1
2
3
4
5
6
7
8
classB
{
};
Aa2 = (B)a;

Aa2 = (B)10;


编译器报这样的错误:”errorC2440: “类型转换”:无法从“int”转换为“B””
可知,我们自己编写的构造和转换函数多么重要。

转换函数
转换函数又称类型强制转换成员函数,它是类中的一个非静态成员函数。它的定义格式如下:

代码如下:

1
2
3
4
5
6
7
8
   class<类型说明符1>
    {
     public:
      operator<类型说明符2>();
      …
    }


这个转换函数定义了由<类型说明符1>到<类型说明符2>之间的映射关系。可见,转换函数是用来将一种类型的数据转换成为另一种类型。下面通过一个例子说明转换函数的功能。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
\#include

classRational
{
public:
Rational(intd, int n)
{
den= d;
num= n;
}
operatordouble();//类型转换函数
private:
intden, num;
};

Rational::operatordouble()
{
returndouble(den)/double(num);
}

voidmain()
{
Rationalr(5, 8);
doubled = 4.7;
d+= r; //这句将调用隐式转换,相当于d= (double)r;
cout<<d<<ENDL;
}


程序输出结果:
5.325

由程序可知,d是一个double型数值,r是Rational类的对象,这两个不同类型的数据进行加法之所以能够进行是得益于转换函数operatordouble()。为使上述加法能够进行,编译系统先检查类Rational的说明,看是否存在在下转换函数能够将Rational类型的操作数转换为double类型的操作数。由于Rational类中说明了转换函数operatordouble(),它可以在程序运行时进行上述类型转换,因此,该程序中实现了d=r;的操作。

\定义转换函数时应注意如下几点:
**
(1)转换函数是用户定义的成员函数,但它要是非静态的。
(2)转换函数的不可以有返回值。(意思是声明中不可以有返回值)
(3)转换函数也不带任何参数。
(4)转换函数函数还不能定义为友元函数。

转换函数的名称是类型转换的目标类型,因此,不必再为它指定返回值类型;转换函数是被用于本类型的数值或变量转换为其他的类型,也不必带参数。

类中的构造函数完成其他类型到类类型的转换,而重载强制转换完成类类型到其他类型的转换。

1. operator 用于类型转换函数:

类型转换函数的特征:

1) 型转换函数定义在源类中;
2) 须由 operator 修饰,函数名称是目标类型名或目标类名;
3) 函数没有参数,没有返回值,但是有return
语句,在return语句中返回目标类型数据或调用目标类的构造函数。

类型转换函数主要有两类:

1) 对象向基本数据类型转换:

对象向不同类的对象的转换:

例程1:
//通过类型转换函数求半径为5的圆的面积
//并将其存储在float型变量中打印输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#i nclude <iostream>
using namespace std;

class CArea
{
float area;
public:
CArea()
{
area=0;
}
CArea(float a) //重载含有一个参数的构造函数
{
area=a;
}
void getArea()
{
cout<<area<<endl;
}
void setArea(float a)
{
area=a;
}
operator float() //类型转换函数
{
//将面积类对象转换为float型数据
return area;
}
};

class CCircle
{
float R;
public:
void getR()
{
cout<<R<<endl;
}
void setR(float r)
{
R=r;
}
operator CArea() //类型转换函数
{ //将圆类对象转为面积类对象
float area=3.1415926*R*R;
return (CArea(area));
}
};

void main()
{
CCircle cir;
CArea are;
float a;

cir.setR(5);
cir.getR(); //打印圆的半径
are.getArea(); //打印转换前的面积

are=cir; //将圆类对象转为面积类对象
are.getArea(); //打印转换后的面积
a=are; //将面积类对象转换为float型数据
cout<<a<<endl;
}


2. operator 用于操作符重载:

操作符重载的概念:

将现有操作符与一个成员函数相关联,并将该操作符与其成员对象(操作数)一起使用。

注意事项:

1) 重载不能改变操作符的基本功能,以及该操作符的优先级顺序。

2) 重载不应改变操作符的本来含义。

3) 只能对已有的操作符进行重载,而不能重载新符号。

4) 操作符重载只对类可用。

5) 以下运算符不能被重载:

. 原点操作符(成员访问符)

* 指向成员的指针

:: 作用域解析符

? : 问号条件运算符

sizeof 操作数的字节数

操作符函数的一般格式:

1
return_type operator op(argument list);

return_type:返回类型(要得到什么)

​ op:要重载的操作符

argument list:参数列表(操作数有哪些)

例程2:
//重载大于号操作符比较两个人的工资

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#i nclude <iostream>
using namespace std;

class employee
{
int salary;
public:
void setSalary(int s)
{
salary=s;
}
void getSalary()
{
cout<<salary<<endl;
}
bool operator >(const employee & e)//重载大于号操作符
{
if(salary > e.salary)
return true;
else
return false;
}
};
void main()
{
employee emp1,emp2;
emp1.setSalary(1000);
emp2.setSalary(2000);
if (emp1 > emp2)
{
cout<<"emp1比emp2工资高"<<endl;
}
else
{
cout<<"emlp1没有emp2工资高"<<endl;
}
}


3:操作手册对operator的描述(加注解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <memory>
#include <string>
#include <algorithm>
using namespace::std;

struct A
{
int n;
std::string s1;
// 用户定义的复制赋值,复制并交换形式
A& operator=(A other)//交换数据
{
std::cout << "copy assignment of A\n";
std::swap(n, other.n);
std::swap(s1, other.s1);
return *this;
}
void print(){
cout<<"the string s1 value is "<<s1<<"\n";
}
};

struct B : A//c++中特性,其中不声明继承类型struct默认public继承,class默认private继承
{
std::string s2;
// 隐式定义的复制赋值
};

struct C
{
std::unique_ptr<int[]> data;
std::size_t size;
// 非复制并交换的赋值
C& operator=(const C& other)
{
// 检查自赋值
if(&other == this)
return *this;
// 可能时复用存储
if(size != other.size)
{
data.reset(new int[other.size]);
size = other.size;
}
std::copy(&other.data[0], &other.data[0] + size, &data[0]);
return *this;
}
// 注意:复制并交换始终导致重分配
};

int main()
{
A a1, a2;
std::cout << "a1 = a2 calls ";
a2.s1 = "hell0";
a1.print();
a1 = a2; // 用户定义的复制赋值
a2.print();
B b1, b2;
b2.s1 = "foo";
b2.s2 = "bar";
std::cout << "b1 = b2 calls ";
b1 = b2; // 隐式定义的复制赋值
std::cout << "b1.s1 = " << b1.s1 << " b1.s2 = " << b1.s2 << '\n';
}

声明:operator XX(); 无返回值,XX就是某个类型;

用法:XX a = (XX)obj; 假设上边SocketAddress非abstract类,例如:SocketAddress sa; int a = (socklen_t)sa; 也就是此时会调用原成员函数operator XX(); 一般返回XX类型值,可以理解成类型转换的重载!

另外:一个自定义类的 构造函数可以用作隐形的类型转换,例如:Class A { A(int i) { val = i}; private int val;} , A a = 5; 解:5 首先通过构造函数A(int)隐形的转换为A类型,然后调用默认的operator=赋值函数,赋值给a;