您的位置:首页 > 编程语言 > C语言/C++

C++ 常见运算符重载及模板

2016-11-22 22:10 253 查看
1. 运算符重载

概念:当运算符作用于类类型的独享是,可以通过运算符重载重新定义该运算符的含义。
他们的名字由关键字operator和要重载的运算符组成。
重载运算符的参与格式和该运算符作用的运算对象一样多。
注:
(1).如果一个运算符函数的成员函数,则它的第一个运算对象绑定到this上。
(2).对于运算符函数俩说,它或是类的成员,或至少含有一个类类型的参数。
(3).只能重载现有的运算符,而不能新发明运算符
(4).对于重载的运算符来说,其优先级和结合律于对应的内置运算符保持一致
(5).一般情况下不重载&,&&,||和逗号运算符,因为他们的求值顺序无法保存,而且无法保留短路效应

不能重载的运算符:
1.  * 指针运算符          2. :: 域运算符        3.sizeof             4.   ? : 条件运算符       5.   . 成员运算符

1.1.重载算术关系运算符

通常情况下,我们把算是关系运算符定义为非成员函数,以允许对左右侧对象进行转换。
但事实上下面成员函数和普通函数都可以实现:(建议还是上述)
这里举一个分数的加法为例子
(1).成员函数
下面是个重载"+"的例子:

int  gcd(int a, int b)
{
return (b ? gcd(b, a%b) : a);//求最大公约数
}

class Fraction
{
public:
Fraction(int num = 1, int deno = 1) { numerator = num; denominator = deno;}
~Fraction(){}
Fraction operator+(const Fraction &oper);
ostream & display(ostream & os);

public:
int numerator;  //分子
int denominator;      //分母

};

//重载+运算符
Fraction Fraction::operator+(const Fraction &oper)
{
Fraction result;
result.denominator = denominator * oper.denominator;
result.numerator = denominator*oper.numerator + oper.denominator*numerator;
int g = gcd(result.denominator, result.numerator);   //求公约数
result.denominator /= g;
result.numerator /= g;
return result;
}

ostream & Fraction::display(ostream & os)
{
if (denominator == 1)
os << "result: " << numerator;
else
os << "result: " << numerator << '/' << denominator;
return os;
}

int main()
{
Fraction *op1 = new Fraction(3, 5);
Fraction *op2 = new Fraction(12, 2);
Fraction op3 = *op1 + *op2;
op3.display(cout) << endl;
getchar();
return 1;
}

注:返回值:该对象的类型(这里这个返回值不能为引用,引用对象在函数失效后就释放)

(2).普通函数
同样以上面+为例

int  gcd(int a, int b)
{
return b?gcd(b,a%b):a;
}

class Fraction 
{
public:
Fraction(int num = 1, int deno = 1) { numerator = num; denominator = deno;}
~Fraction(){}

public:
int numerator;  //分子
int denominator;      //分母

};

//普通函数重载
Fraction operator+(const Fraction &op1, const Fraction &op2)
{
Fraction result;
result.denominator = op1.denominator * op2.denominator;
result.numerator = op1.denominator*op2.numerator + op2.denominator*op1.numerator;
int g = gcd(result.denominator, result.numerator);   //求公约数
result.denominator /= g;
result.numerator /= g;
return result;
}

//重载<<运算符
ostream & operator<< (ostream & os, const Fraction& op)
{
if (op.denominator == 1)
os << "result: " << op.numerator;
else
os << "result: " << op.numerator << '/' << op.denominator;
return os;
}

int main()
{
Fraction *op1 = new Fraction(3, 5);
Fraction *op2 = new Fraction(12, 5);
Fraction op3 = *op1 + *op2;
cout << op3 << endl;
getchar();
 	return 1;
}


注:当类操作符和全局操作符同时被重载时,类操作符被优先调用。

1.2.重载输入输出操作符
做输入输出表示时:重载的必须为非成员函数。
这个主要受第一个参数是istream或ostream的限制。
例子在上一个例子中的第二个中体现

通常情况下,输入输出运算符第一个形参时非常量的引用。因为流的输入会使状态改变。返回值是流的引用。

1.3.重载关系操作符
关系操作符:<,>.<=,>=,==,!=
重载关系运算符要和原有的关系运算符的思维相近
成语函数的写法:

bool operator == (const Object& obj)
{
}


非成语函数的写法:

bool operator == (const Object& obj1,  const Object& obj2)
{
}

注:返回值是一个bool类型。如果定义了算术运算符,则一般也会定义个对应的复合复制运算符。
参数:可以重载(比如和一个固定的字符串相比)

1.4.重载下标运算符

[ ]用做访问一组元素中的某个元素。
也可称为:索引,唯一标识一个元素
下标运算必须是成语函数
成语函数的写法:

Element& operator [ ] (Type index)
{

}


参数:类型可以自己选择,用于指定元素下标
注:为了与普通下标元素兼容,返回值为元素的引用。一般会重载2个版本,一个返回是普通引用,另一个如果类是常量则返回常量引用。

1.5.重载递增递减运算符

递增递减运算符建议设计成成员函数
成语函数的写法:

Object& operator++()
{

}


返回值为元素的引用
可以加参数设计成区分前后置的,只需要在参数列表里加一个区分参数就可以

1.6.重载类型转换操作符()
转换构造函数和类型转换运算符共同定义了类类型转换。
类型转换运算符是类的一个特殊成语函数,他负责将一个类类型转换成其他类型。
一般形式如下:

operator Type( ) const
{
Type result;
return result;
}


类型运算符既没有显示的返回类型也没有形参,且必须定义成类的成员函数。
Type 类似于返回转化目标类型。除void外
不允许转换成数组或者函数类型,但允许转换成指针或引用类型。

1.7.函数调用运算符

如果类重载了函数调用运算符,则我们可以向使用函数一样使用该类的对象。
所以函数调用运算符必须是成员运算符。
举个例子:

struct  absInt
{
int operator() (int val) const
{
return val < 0 ? -val : wal;
}
};

//使用:
int i = -10;
absInt absObj;
int ui = absObj(i);

如果类定义了调用运算符,则该类的对象称作函数对象

2.模板
一个模板就是一个创建类或者函数的蓝图或者说公式
注:函数模板和类模板通常定义在头文件中。模板头文件应该包括模板的定义以及类模板或成员定义中用到的所有名字的声明。

2.1.函数模板
概念:一个函数模板就是一个公式,可以用来生活针对特定类型的函数版本
语法:
template <typename T>
T function(T a,  T b)
{
     T c;
      ...
     return c;
}
模板参数列表是由一个逗号分隔一个或多个模板参数的列表,并用<>包围起来。
用模板类型可替代后面函数中出现所有的同名的类型。
注意:typename是关键字也可以用class代替,但为了不产生歧义,一般用typename

使用:
function <int>(val, 4);
在调用一个函数模板时,编译器会根据函数参数来自动推断模板参数,然后用推断的模板参数进行实例化成一个特定版本。
同时输入参数可以为常量,编译器也会自动转换。(这里还有个非模板类型参数的概念...感觉和动态识别常量有关先略过...)

函数模板用于实现通用算法,有人称之为泛型算法。

2.2.类模板
概念:类模板用来生成类的蓝图。
语法:
template <typename T>
class  temp
{

}
调用方式:
temp<int>  tmp;

temp<string> tmp2;
注:
1.一个模板类生成的每个实例都是一个独立的类
2.和函数模板不同的是,编译器不能为类模板推断模板参数类型

关于类模板名的使用:
下面是一个例子:
template <typename T1, typename T2>
class temp
{
public:
T1 typeTmp1;
T2 typeTmp2;
public:
temp(T1 t1, T2 t2)            //类内模板名和类内声明的写法相同
{
typeTmp1 = t1;
typeTmp2 = t2;
}

void display(ostream &os, T1 t1)
{                                  //类内模板名和类内声明的写法相同
os << "inner output!";
typeTmp1 = t1;
}

};

/*
template <typename T1, typename T2>                //类外模板名
temp<T1, T2>::temp(T1 t1, T2 t2)
{
typeTmp1 = t1;
typeTmp2 = t2;
}
*/

/*
template <typename T1, typename T2>
void temp<T1, T2>::display(ostream &os, T1 t1)          //类外模板名
{
os << "output!";
typeTmp1 = t1;
}
*/

int main()
{
int i = 200;
string j = "aaadaa";
temp<int, string> aal(i, j);
temp<int, string> *p = new temp<int, string>(i, j);    //new一个模板对象
aal.display(cout, i);
getchar();
return 1;
}
上述中,类内模板名和类内声明相同,如果是类外则注意要加模板限制。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ 运算符重载 模板