更多参考其他文档菜鸟文档W3C微软C++文档

运算符重载

  • c++预定义的运算符不能满足全部的运算需求,比如:复数的加减运算
  • 希望能有一种特殊的运算,可以使类、等复杂的数据也可以进行运算,易于理解
  • 运算符重载,就是对已有的运算符赋予更多含义的运算,可以使不同类型或者复杂的数据也可以进行运算。(比如类之间的运算)
  • 即,对于自定义的这个运算符,具有唯一性,表达的含义只能作用于对应的运算数据 。 类a + 类 b
  • 实质 函数重载
  • 可以重载为普通函数,也可以重载为成员函数
  • 实现 把含运算符的表达式转换成对运算符函数的调用
  • 重载运算符,多个重载运算符时,根据参数选择

目数:运算所需变量个数

//运算符重载形式

返回值类型 operator 运算符(形参表)
{
....
}


//实例
class Complex
{
public:
double real,imag; //实部,虚部
Complex(double r = 0.0,double i=0.0):real(r),imag(i){}
Complex operator-(const Complex & c); //声明,被重载的 “ - ”
};

Complex operator +(const Complex &a,const Complex & b) //作普通函数时,参数个数为运算符目数
{
return Complex(a.real + b.real,a.imag + b.imag);//返回一个临时对象
}
Complex Complex::operator-(const Complex & c) //做成员函数时,参数个数为运算符目数减一
{
return Complex(real - c.real, imag - c.imag);//返回一个临时对象
}

int main()
{
Complex a(4,4),b(1,1),c;
c = a + b; //等价于 c = operator +(a,b);
cout << c.real << ","<<c.imag <<endl;
cout << (a-b).real <<","<<(a-b).imag << endl; //a-b 等价于a.operator -(b)
return 0;
}

赋值运算符的重载

  • 用于类型不匹配数据之间的赋值

  • 赋值运算符“ = ” 只能重载为成员函数

class String 
{
private :
char * str; //将指向动态分配空间
public:
String():str(new char[1]){str[0] = 0;} //构造
const char * c_str(){return str;}; //访问str
String & operator = (const char *s);//赋值运算符重载
~String(){delete[] str;}
};
//下面重载“ = ” 以使得 obj = "hello" 能够成立
String & String::operator = (const char * s)
{
delete [] str;
str = new char[strlen(s)+ 1];
strcpy(str , s);
return * this;
}

int main()
{
String s;
s = "Good Luck,"; //等价于s.operator("Good Luck,");
cout << s.c_str() << endl;
//String s2 = "hello!"; //这里是初始化,并不是赋值,所以不能这样写,初始化用的是构造函数
s = "Shenzhou 8!"; //等价于s.opeartor = ("Shenzhou 8!");
cout << s.c_str() << endl;
return 0;
}

浅拷贝和深拷贝

class String
{
private:
char * str;
public:
String(): str(new char[1]){str[0] = 0;}
const char * c_str() { return str;}; //返回str
String & operator = (const char * s){ //赋值重载
delete [] str ; //原先的就被删除
str = new char[strlen(s)+1];
strcpy(str,s);
return * this;
};
~String(){delete[] str;}
};

int main()
{
String S1,S2;
S1 = "this";
S2 = "that";
S1 = S2; //注意 这里存在问题!!有隐患
return 0;
}

// S1 = S2;
// 上面直接用 “ = ” 没有重载的话,就是指针指向S2的首地址,使得S1原先指向的内存被丢弃,内存被浪费。
//S2、S1消亡时 ,该空间会析构两次,导致程序出错
// 如果再用于其他的如: S1 = “...” 时,就会delete原先的内存,导致S2指向的空间消失。依据第9行

//所以对于同类型的赋值,也需要在class 中添加成员函数:

String & operator = (const String & s){
delete [] str;
str = new char[strlen(s.str) + 1];
strcpy(str,s.str);
return * this;
}

//但是还会有问题,如果 s = s 是,就会先delete导致地址被提前释放从而报错,所以改成下面

String & operator = (const String & s){
if (this == & this)
return * this;
delete [] str;
str = new char[strlen(s.str) + 1];
strcpy(str,s.str);
return * this;
}

对 operator = 返回值类型的讨论

对运算符进行重载的时候,好的风格是应该尽量保留运算符原本的特性

//返回void 
a = b = c ,导致“ = ” 不能连等
//返回String
(a = b) = c 时,a = b 返回的是a的引用
//为什么返回 String &
a.operator(b.operator=(c));
(a.operator=(b)).operator=(c);

//特别的,为String类编写复制构造函数的时候,会面临和=同样的问题,用同样的方法处理,避免两个字符串指向同一空间

String (String & s)
{
str = new char [strlen(s.str) + 1];
strcpy(str,s.str);
}

运算符重载为友元函数

  • 一般情况下,运算符重载为类的成员函数已经够用,但是有特殊的情况,还需使之重载为普通函数,还可以访问该类的私有成员 ,所以需要重载为友元函数
class Complex
{
double real,imag;
public:
Complex(double r,double i):real(r),imag(i){};
Complex operator(double r);
friend Complex operator+(double r,const Complex & c); //声明为友元
};

Complex Complex::operator+(double r) //成员函数
{
return Complex(real + r, imag);
}

//到这里 c = c + 5; 有定义,相当于c = c.operator + (5)
//但如果写成 c = 5 + c 就会报错,所以要写一个下面的全局函数
Complex operator+(double r,const Complex & c)//全局函数
{
return Complex(c.real + r,c.imag); //这里访问了私有成员,故需要在类中,声明该函数为友元
}

数组和结构

可变长数据类的实现

需要实现的功能

//可变长整型数组类,应该满足下面的需求
int main
{
CArray a; //定义空数组
for( int i=0;i<5;++i)
a.push_back(i); //放置元素
CArray a2,a3;
a2 = a; //将a的元素复制给a
for(int i = 0; i<a.length();++i)
cout<<a2[i]<<""; //输出a2的元素
a2 = a3; //a2是空的,原来的空间被释放掉
for(int i = 0; i<a2.length();++i)//a2.length()返回()
cout <<a2[i]<<" "; //所以这里循环没有输出
cout<<endl;
a[3] = 100; //更改了a 的3的数
CArray a4(a);//复制构造函数初始化
for(int i = 0; i<a4.length();++i)
cout<<a4[i]<<""; //
return 0;
}

//要用动态分配的内存来存放元素,需要一个指针成员变量
//在析构函数中,释放内存
//重载 [] ,
//写复制构造函数

实现

//实现
#include <iostream>
#include <string.h>

using namespace std;

class CArray
{
int size;
int *ptr;

public:
CArray(int s = 0); //构造函数
CArray(CArray &a); //复制构造函数
~CArray(); //析构函数
void push_back(int v); //成员函数,在已有数组的尾部添加一个元素v
CArray &operator=(const CArray &a); //运算符的重载,用于数组对象间的赋值
int length() { return size; } //成员函数,返回数组元素个数
int &CArray::operator[](int i) //函数调用的返回值不能左值,所以返回类型应该是 int& 。
{ //为了使对象数组可以和普通数组一样进行左右值,所以这里将[] 符重载,满足对象的属性
return ptr[i]; //这里返回的是指针对应下标的数,不是对象
}
};

CArray::CArray(int s) : size(s) //构造函数
{
if (s == 0)
{
ptr = NULL;
}
else
{
ptr = new int[s];
}
}

CArray::CArray(CArray &a) //复制构造函数
{
if (!a.ptr) //判断被复制的对象里的指针是不是NULL,如果为NULL则让新的数组也指向空
{
ptr = NULL;
size = 0;
}
ptr = new int[a.size]; //动态分配a的元素个数
memcpy(ptr, a.ptr, sizeof(int) * size); //String 的库函数,将后面元素的值赋给前面,最后是赋值的个数
size = a.size; // 成员size 赋值
}

CArray ::~CArray()
{
if (ptr)
{
delete[] ptr;
}
}
CArray &CArray::operator=(const CArray &a) //返回值 一般是该类型的引用
{
if (ptr == a.ptr) //防止自己赋值给自己出错
{
return *this;
}
if (a.ptr == NULL)
{
if (ptr) //判断被赋值的对象数组是不是空
{
delete[] ptr; //不是空 就删除该对象数组的存储空间
}
ptr = NULL;
size = 0;
return *this;
}
if (size < a.size) //判断原有的内存空间够不够用,如果够用就不需要再分配新的空间
{
if (ptr)
delete[] ptr;
ptr = new int[a.size];
}
memcpy(ptr, a.ptr, sizeof(int) * size); //复制元素
size = a.size;
return *this;
}

void CArray ::push_back(int v)
{
if (ptr)
{
int *tmpPtr = new int[size + 1]; //重新分配空间
memcpy(tmpPtr, ptr, sizeof(int) * size); //拷贝原函数组内容
delete[] ptr; //参见释放数组
ptr = tmpPtr;
}
else
{
ptr = new int[1]; //如果数组本来是空的,则要先new一个空间
}
ptr[size++] = v; //加入新的数组元素
}

流插入选算符和流提取运算符的重载

cout <<  //什么含义
cout 是 ostream类的对象
<< //在iostream里 使用了重载
//如何才能 “this” 和 5 都可以输出

有可能的做法

void ostream::operator << (int n) //重载ostream类的成员函数
{
//输出n的代码
return;
}
void ...仿照上面写char


//为了可以连续输出 cout << a << b 修改如下

ostream & ostream::operator<<(int n)
{
//输出n的代码
return * this;
}
ostream & ostream::operator<<(const char *s)
{
//输出s的代码
return *this;
}

//修改后的对应的函数调用形式为
cout.operator<<(5).operator<<("this");

//例题
class CStudent
{
public : int nAge;
};
int main()
{
CStudent s;
s.nAge = 5;
cout << s<< "hello";
return 0;
}

ostream & operator <<( ostream & o,const CStudent & s)
{
o <<s.nAge;
return o;//使返回值还是cout
}

//例题
#include <iostream>
#include <string>
#include<cstdlib>

using namespace std;

class Complex {
double real ,imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i){};
friend ostream &operator<<(ostream &os, const Complex &c);
friend istream &operator>>(istream &is, Complex &c);
};

ostream &operator<<(ostream &os, const Complex &c)
{
os << c.real << "+" << c.imag << "i";//以"a + bi" 的形式输出
return os;
}

istream &operator>>(istream & is,Complex & c)
{
string s;
is >> s;
int pos = s.find("+", 0);
string sTmp = s.substr(0, pos);
c.real = atof(sTmp.c_str());
sTmp = s.substr(pos + 1, s.length() - pos - 2);
c.imag = atof(sTmp.c_str());
return is;
}

类型转换运算符的重载

#include<iostream>

using namespace std;
class Complex
{
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i){};
operator double() { return real; }
//重载强制类型转换运算符 double
};

int main()
{
Complex c(1.2, 3.4);
cout << (double)c << endl;//输出1.2
double n = 2 + c; //等价于double n = 2+c.operator double()
cout << n; //输出3.2
}

自增自减运算符的重载

  • 自增运算符++、自减运算符–有前置\后置之分,为了区分所重载的是前置运算符还是后置运算符

  • 前置运算 符当作一元运算符重载

    • 重载为成员函数:
    • T & operator++();
    • T & operator–();
    • 重载为全局函数:
    • T1 & operator ++(T2);
    • T1 & operator–(T2);
  • 后置运算 符当作二元运算符重载,多写一个没用的参数:

    • 重载为成员函数:
    • T & operator++(int );
    • T & operator–( int );
    • 重载为全局函数:
    • T1 & operator ++(T2,int);
    • T1 & operator–(T2,int);

    在没有后置运算符重载而有牵制重载的情况下,在VS中,obj也调用前置重载,而dev则令obj编译出错

    int main()
    {
    CDemo d(5);
    cout << (d++)<<","; //等价于d.operator++(0);
    cout << d << ",";
    cout << (++d)<< ","; //等价于d.operator++();
    cout << d <<endl;
    cout << (d--) << ","; //等价于operator--(d,0);
    cout << d << ",";
    cout << (--d) << ","; //等价于 operator(d);
    cout << d << endl;
    }

    //实现
    class CDemo{
    private:
    int n;
    public:
    CDemo(int i =0):n(i){}
    CDemo & operator++(); //用于前置形式
    CDemo operator++(int);
    operator int () {return n;}
    friend CDemo & operator--(CDemo &);
    friend CDemo operator--(CDemo &,int);
    };

    CDemo & CDemo ::operator++()
    {
    //前置++
    ++ n;
    return * this;
    //++s 即为:s.operator();
    }
    //++a 在原生c++中是引用

    CDemo & CDemo ::operator++( int k)
    {
    //后置++
    CDemo tmp(*this); //记录修改前的对象
    n++;
    return tmp; //返回修改前的对象
    //s++ 即为:s.operator++(0);
    }
    CDemo & operator--(CDemo & d)
    {
    //前置--
    d.n --;
    return d;
    //--s 即为:s.operator--(s);
    }
    CDemo operator--(CDemo & d,int)
    {
    //后置--
    CDemo tmp(d); //记录修改前的对象
    d.n--;
    return tmp; //返回修改前的对象
    //s-- 即为:s.operator();
    }

    //通常情况下推荐适用 ++ i

运算符重载的注意事项

  • c++不允许定义新的运算符
  • 重载后运算符的含义应该符合日常习惯
    • complex_a + complex_b
    • word_a > word_b
    • date_b =date_a +n
  • 运算符重载不改变运算符的优先级;
  • 以下运算符不能被重载:“.” “.* ” “ :: ” “ ? : ” sizeof;
  • 重载运算符()、[]、-> 或者赋值运算符 = 时,运算符重载函数必须声明为类的成员函数。