十二、操作符重载

十二、运算符重载

函数重载(Function Overloading)

  • 不同的函数有着一样的名字(多态)

  • 在C++中,一个函数的表示不仅取决于它的名称,还取决于参数的数量、类型,以及是否带有关键字 const

    示例

    cpp 复制代码
    void swap(unsigned long&,unsigned long&);
    void swap(double&,double&);
    void swap(char&,char&);
    void swap(Point&,Point&);

    它们都是不同的函数。同名函数有着相似的功能。

为什么运算符需要重载

cpp 复制代码
class complex(){
public:
    complex(double x = 0,double y = 0)
    {re = x; im = y;}
    const complex Add(const complex& c){
        double t1 = re + c.re;
        double t2 = im + c.im;
        return complex(t1,t2);
    }
private:
    double re,im;
};
void main(){
    complex c,c1,c2(5.5,2);
    c = c1.Add(c2);
}

这串代码是没错,但我们希望类之间的+,-可以像内建类型一样,比如:

cpp 复制代码
int a = 9;int b = 3;
int c = a + b;

我们希望可以将c = c1.Add(c2); 改成 c = c1 + c2; 这里就需要用到操作符重载。

12.1 引言

  • 运算符重载是另外一种调用函数的方式。
  • 它的目的是让涉及你的类的代码更容易编写,尤其更容易阅读。
  • 在表达式中,凡是只包含内建数据类型 的运算符,都是无法被改变的。只有包含用户自定义类型的表达式,才能有重载的运算符。
  • 不能定义新的运算符符号,但是当现有运算符集合不够用时,可以使用函数调用的形式来代替。

12.2 语法格式

一般语法格式

  • 运算符函数的名称是关键字operator 加上实际的运算符本身。

    cpp 复制代码
    type operator@(参数列表)
    {
        //实现代码
    }
  • 定义一个重载运算符就像定义一个函数,但是函数的名字是operator@ ,其中@ 表示被重载的运算符

  • 运算符重载函数中的参数个数,取决于两个因素:

    • 一元(unary)运算符(一个操作数)还是二元(binary)运算符(两个操作数)
    • 是定义为全局函数还是成员函数:
      • 如果是全局函数:
        • 一元运算符:一个参数
        • 二元运算符:两个参数
      • 如果是成员函数:
        • 一元运算符:无参数(隐含this)
        • 二元运算符:一个参数(另一个操作数)

示例:

cpp 复制代码
//C12:OperatorOverloadingSyntax.cpp
#include <iostream>
using namespace std;

class Integer {
    int i;
public:
    Integer(int ii) :i(ii) {}
    void print() { cout << i << endl; }
    const Integer operator+(const Integer& rv) const;
};
const Integer Integer::operator+(const Integer& rv) const {
    return Integer(i + rv.i);
}

void main() {
    int i = 1, j = 2, k = 0;
    k = i + j;
    Integer ii(1), jj(2), kk(0);
    kk = ii + jj;
    kk.print();
}
  • kk = ii + jj; 这一步会先计算右边的ii + jj,然后正好匹配

    cpp 复制代码
    const Integer operator+(const Integer& rv) const;

    因为这是一个成员函数,所以ii + jj会被翻译为:

    cpp 复制代码
    ii.operator+(jj)

    也就是说,编译器看到ii + jj,就会转化为

    cpp 复制代码
    const Integer temp = ii.operator+(jj);

    这时ii是隐式的thisjj是参数rv

    然后再

    cpp 复制代码
    kk  = temp;

    所以表达式

    cpp 复制代码
    kk = ii + jj;

    等于编译器内部操作

    cpp 复制代码
    const Integer temp = ii.operator+(jj);//调用operator+
    kk = temp;//把结果赋值给kk

成员函数

这里讲述,当运算符是重载函数是成员函数时的语法

格式:
cpp 复制代码
type operator@(argument){/*......*/}
  • 表示你要重载一个运算符@ (比如 +-++ 等),其返回类型是type,参数列表根据运算符类型不同而不同。
用法:
  • 一元前缀运算符:

    • 例子:@a
    • 对应函数:a.operator@()
    • 举例:-a会转化为a.operator-()
  • 一元后缀运算符:

    • 例子:a@

    • 对应函数:a.operator@(int)

    • 说明:后缀运算符需要增加一个int 类型的占位参数来区分前缀形式(C++为了分辨前缀还是后缀而决定的,有int 是后缀)

      举例:a++ 会转为 a.operator++(int) ,而++a 则是 a.operator++()

  • 二元运算符:

    • 例子:a@b
    • 对应函数:a.operator@(b)
    • 举例:a+b会转为a.operator+(b)
注意:
  • 成员函数都会隐式包含一个this 指针,指向左侧对象a
  • 前缀一元运算:无参数(如++a
  • 后缀一元运算:只有一个整型参数int(占位符)(如a++
  • 二元运算符:只有一个参数(右操作数),而this指向左操作数。
示例

complex.h

cpp 复制代码
class complex{
public:
    complex (double r = 0,double i = 0);
    const complex operator+(const complex& c); //二元运算符
    const complex operator-();//一元前缀运算符
    complex& operator+=(const complex& c);//二元运算符
    
    void print() const;
private:
    double real,imag;
};

complex.cpp

cpp 复制代码
#include <iostream>
#include "complex.h"

using namespace std;

complex::complex(double r, double i) {
    real = r; imag = i;
}
const complex complex::operator+(const complex& c) {
    double r = real + c.real;
    double i = imag + c.imag;
    return complex(r, i);
}
const complex complex::operator-() {
    return complex(-real, -imag);
}
complex& complex::operator+=(const complex& c) {
    real += c.real;
    imag += c.imag;
    return *this;
}

void complex::print() const {
    cout << "(" << real << "," << imag << ")" << endl;
}

user.cpp

cpp 复制代码
#include <iostream>
#include "complex.h"
void main()
{
    complex c1(3.5,5.5),c2(1.5,3.5);
    complex c;
    c = c1 + c2; // 对应着c = c1.operator(c2)
    c.print();
    c += c1 += c2; //c1.operator+=(c2);c.operator+=(c1);
    c.print();
    c = -c1;   //c = c1.operator-()
    c.print();
}

输出

cpp 复制代码
(5,9)
(10,18)
(-5,-9)

友元函数

格式:

friend type operator@(argument){/*......*/}

用法:
  • 前缀一元运算符:@a -> operator@(a)
  • 后缀一元运算符:a@ -> operator@(a,int)
  • 二元运算符:a@b -> operator@(a,b)
注意:

友元函数和成员函数不同的原因在于,友元函数没有隐形指针。

示例

complex.h

cpp 复制代码
//complex.h
class complex {
public:
    complex(double r = 0, double i = 0);
    friend const complex operator+(const complex& c1, const complex& c2); //二元运算符
    friend const complex operator-(const complex& c);//一元前缀运算符
    friend complex& operator+=(complex& c1,const complex& c2);//二元运算符

    void print() const;
private:
    double real, imag;
};

complex.cpp

cpp 复制代码
//complex.cpp
#include <iostream>
#include "complex.h"

using namespace std;

complex::complex(double r, double i) {
    real = r; imag = i;
}

const complex operator+(const complex& c1, const complex& c2) {
    double r = c1.real + c2.real;
    double i = c1.imag + c2.imag;
    return complex(r, i);
}
const complex operator-(const complex& c) {
    return complex(-c.real, -c.imag);
}
complex& operator+=(complex& c1, const complex& c2) {
    c1.real += c2.real;
    c1.imag += c2.imag;
    return c1;
}
void complex::print() const {
    cout << "(" << real << "," << imag << ")" << endl;
}

user.cpp

cpp 复制代码
//user.cpp
#include <iostream>
#include "complex.h"

void main() {
    complex c1(3.5, 5.5), c2(1.5, 3.5);
    complex c;
    c = c1 + c2; //c = operator(c1,c2)
    c.print();
    c += c1 += c2; //operator+=(c1,c2);operator+=(c,c1);
    c.print();
    c = -c1; //operator-(c1)
    c.print();
}

输出

cpp 复制代码
(5,9)
(10,18)
(-5,-9)

运算符重载函数与普通函数

  • 运算符函数的声明和调用函数与普通函数相同。
  • 运算符函数的参数可以是一个或2个,而普通函数可以有多个参数
  • 运算符函数必须是成员函数或者至少有一个参数是用户自定义类型(类或结构)

12.3 可重载的运算符(Overloaded Operators)

12.3.1 一元运算符

  • 可重载的一元运算符包括:+-!~++--&*

12.3.2 二元运算符

  • 可重载的二元运算符包括:+-*/&==!=
  • <><=>=&&||<<>>
  • =+=-=*=/=&=|=^=<<=>>=

12.3.3 参数与返回值

  • 就像处理任何函数参数一样,如果你只需要读取参数 而不修改它,默认应该将其作为常量引用 传递。
  • 我们选择哪种类型的返回值,取决于该运算符的预期含义。(例如二元加法运算符operatoa+ ,就应该返回一个常量值------两个操作数的和)
  • 所有赋值运算符 都会修改左值,因此应该返回一个非常量引用 (便于链式调用)。例如:a += b
  • 对于逻辑运算符,通常期望返回的最差情况 是一个int最理想 的是一个bool

示例,依旧是12.2当中成员函数的例子,来表现当返回值是const的情况

cpp 复制代码
//成员函数
const complex complex::operator+(const complex& c){
    double r = real + c.real;
    double i = imag + c.imag;
    return complex(r,i);
}

complex& complex::operator+=(const complex& c){
    real = r.real;
    imag = c.imag;
    return *this;
}

(a+b)按值返回一个const

  • f(a+b) :表达式a+b 的结果变成了一个临时的const对象 ,在调用函数f() 时使用这个临时对象。
  • (a+b).g():由于 a+b 的返回值是const ,这意味着只能调用const成员函数

示例,来表现临时对象

示例1

cpp 复制代码
#include <iostream>
using namespace std;
class A{
    int i;
public:
    A(int x = 0){i = x;cout << "Constructor." << endl;}
    A(const A&x){i = x.i ; cout << "Copy Constructor." << endl;}
    ~A() { cout << "Destructor." << endl; }
};
A f(){return A(1);}
void main(){
    f();
    cout << "main() End." << endl;
}

输出

cpp 复制代码
Constructor.
Destructor.
main() End.

示例2

此时我们将示例1中的A f(){return A(1);} 替换成A f(){A a(1);return A(a);}

cpp 复制代码
#include <iostream>
using namespace std;
class A {
    int i;
public:
    A(int x = 0) { i = x; cout << "Constructor." << endl; }
    A(const A& x) { i = x.i; cout << "Copy Constructor." << endl; }
    ~A() { cout << "Destructor." << endl; }
};
A f() {
    A a(1); 
    return A(a); // 显式调用拷贝构造
}
void main() {
    f();  // 接收返回值,确保 Copy Constructor 被触发(C++14)
    cout << "main() End." << endl;

}

输出

cpp 复制代码
Constructor.
Copy Constructor.
Destructor.
Destructor.
main() End.

解析:

cpp 复制代码
return A(a)

这一行:

  • 创建了一个临时对象(记作temp),它是a的拷贝。
  • A(a)是一个 临时的 unnamed 对象 ,将作为函数f()的返回值。

然后main()里面调用:

cpp 复制代码
f();

这个临时返回值没有被捕获,所以:

  • 临时对象仍然被创建(必须被创建,因为return A(a)是显示拷贝),这里不用return a,是因能这样可以会被编译器自动优化,导致看不到返回值有临时对象这一过程。
  • 它(临时返回值 temp )会在这条语句结束后立即析构。
建议
  • 如果运算符的第一个操作数是某个类的对象 ,那么这个运算符应该被声明为该类的成员函数。(例如 a + b 、 a + 5)
  • 否做 ,它应该被声明为友元函数。(例如 6 + a)

错误样例

cpp 复制代码
#include <iostream>
using namespace std;
class complex
{
public:
    complex(double r = 0, double i = 0) {
        real = r;
        imag = i;
        cout << "Constructor." << r << "." << i << endl;
    }
    const complex operator+(const complex& c);
    void print() const {
        cout << "(" << real << "," << imag << ")" << endl;
    }
private:
    double real, imag;
};

const complex complex::operator+(const complex& c) {
    double r = real + c.real;
    double i = imag + c.imag;
    return complex(r, i);
}
void main() {
    complex c, c1(3.5, 5.5);
    c = c1 + 1.5;//ok: c = c1.operator+(1.5)
    c.print(); //(5,5.5)
    //c = 1.5 + c1; //error: c = 1.5.operator+(c1)
}

输出

cpp 复制代码
Constructor.0.0     //complex c
Constructor.3.5.5.5   //complex c1(3.5,5.5)
Constructor.1.5.0   // c1.operator.(1.5)中的1.5被隐式转换为complex类型
Constructor.5.5.5   //return complex(r,i),创建了一个新的临时complex(5.5,5.5)
(5,5.5)

正确样例1

cpp 复制代码
#include <iostream>
using namespace std;
class complex
{
public:
    complex(double r = 0, double i = 0) {
        real = r;
        imag = i;
        cout << "Constructor." << r << "." << i << endl;
    }
    friend const complex operator+(const complex& c1,const complex& c2);
    void print() const {
        cout << "(" << real << "," << imag << ")" << endl;
    }
private:
    double real, imag;
};

const complex operator+(const complex& c1,const complex& c2) {
    double r = c1.real + c2.real;
    double i = c1.imag + c2.imag;
    return complex(r, i);
}
void main() {
    complex c, c1(3.5, 5.5);
    c = c1 + 1.5;//ok: c = operator(c1,1.5)
    c.print(); //(5,5.5)
    c = 1.5 + c1; //ok: c = operator(1.5,c1)
}

输出

cpp 复制代码
Constructor.0.0
Constructor.3.5.5.5
Constructor.1.5.0
Constructor.5.5.5
(5,5.5)
Constructor.1.5.0
Constructor.5.5.5
    

正确样例2

cpp 复制代码
#include <iostream>
using namespace std;
class X {
public:
    void operator+(int) {}
    X(int) {}
};
void operator+(X, X) {}
void operator+(X, double) {}
void f(X a)
{
    a + 1;//a.operator+(1)
    1 + a;//::operator+(X(1),a)
    a + 1.0;//::operator+(a,1.0)
}
int main() {
    X a(1);
    f(a);
    return 0;
}

12.3.4特殊运算符

下标运算符[ ]
  • 操作符[]可以为一个类进行重载。
  • 注意:它必须被重载为一个成员函数
  • 因为它的语法必须是对象[索引]
cpp 复制代码
#include <iostream>
using namespace std;
class vect{
public:
    vect(int size){v = new int[size];}
    ~vect(){delete []v;}
    int& operator[](int i);
private:
    int* v;
};

int& vect::operator[](int i){
    cout << "Subscripting" << endl;
    return v[i];
}
int main(){
    vect a{5};
    a[2] = 12;		//a.operator[](2) = 12
    cout << a[2] << endl;
    return 0;
}

输出

cpp 复制代码
Subscripting
Subscripting
12
解引用操作符:->
  • ->运算符必须作为成员函数进行重载。
  • 该函数应该返回一个指向类的指针。
cpp 复制代码
#include <iostream>
using namespace std;
struct Student {
    int age;
    int ID;
};
class X {
public:
    X() { S = new Student; S->age = 0; S->ID = 0; }
    Student* operator->() { return S; }
    ~X() { delete S; }
    void print() { cout << S->age << "." << S->ID << endl; }
private:
    Student* S;
};

int main() {
    X x;
    x->age = 20;//(x.operator->())->age = 20
    x->ID = 001;//(s.operator->())->ID = 001
    x.print();
    return 0;
}

如果没有 S->age = 0; S->ID = 0; 那么刚开始S里面的 age , ID 会被初始化为垃圾值

输出

cpp 复制代码
20.1
自增(++)和自减(--)运算符
  • 自增(++)和自减(--)运算符可以作为前缀(例如++x)或后缀(例如x++)使用。重载时需要分别定义前缀版本和后缀版本(互相区分的方法是后缀多一个整型的占位符)。通常,这两个运算符重载为类的成员的函数

  • 前缀形式的函数原型:T operator++();后缀形式:T operator++(int)

    这里要说明一般来说前缀形式写成 T& operator++(),包括下面的T& operator--(),因为这样可以优化掉拷贝函数,并且返回值还可以作为左值(有确切地址的变量)来调用成员函数。

  • 前缀形式的函数原型:T operator--();后缀形式:T operator--(int)

  • 上述int 不参与实际使用,仅用于与前缀形式区分后缀形式

cpp 复制代码
#include<iostream>
using namespace std;
class Increase {
public:
    Increase(int val = 0) { value = val; }
    Increase(const Increase& i) { cout << "Copy Constructor" << endl; value = i.value; }
    void display() const { cout << value << endl; }
    Increase operator++();  //前缀
    Increase operator++(int);  //后缀
private:
    int value;
};
Increase Increase::operator++()
{
    cout << "++value" << endl;
    ++value;
    return *this;
}
Increase Increase::operator++(int)
{
    cout << "value++" << endl;
    int temp = value;
    value++;
    return Increase(temp);
}

void main() {
    Increase a(10), b(10), c;
    c = ++a;	//a.operator++()
    c.display();
    c = b++;	//b.operator++(int)
    c.display();
}

输出

cpp 复制代码
++value
Copy Constructor
11
value++
10

除此之外,还有=<<>>

下表则展现了函数表达式

12.3.5无法重载的运算符

  • . 对象成员访问运算符
  • .* 指针到成员运算符
  • ::作用域解析运算符
  • ?: 三元条件运算符
  • sizeof
  • typeid
  • 注意:C++中没有内置的指数(幂)运算符;也无法引入新的运算符符号;重载运算符不会改变原有的优先级和结合性规则。

12.4成员还是非成员

  • 在重载运算符时,可以选择将其实现为类的成员函数或非成员函数(通常以友元函数形式)。

  • 一般来说,当运算符需要访问类的内部实现细节 ,或该运算符本身必须定义为成员(例如,赋值=,下标[],函数调用(),以及->++-- 运算符)时,应使用成员函数形式重载。

  • 对于对称的二元运算符(如算数运算符、比较运算符等),将其定义为非成员的友元函数 通常更为灵活。这样可以允许运算符左右两侧的操作数进行类型转换。例如,假设类X有一个接受int的构造函数:如果operator+ 使X的成员函数,那么a + 1可以执行(调用a.operator+(1)),但 1 + a 将无法匹配;而将 operator+ 定义为友元函数后,表达式 1 + a可以被转换为 operator+(X(1),a) 来执行,使得两种操作数顺序都支持。

  • 总而言之,当运算符主要作用于类本身 且不需要左操作数进行额外转换时,倾向于成员函数实现 ;当运算符涉及不同类型之间的交互,或希望支持交换操作数顺序的调用时,使友元函数会更合适。

  • 也可以参考下图

12.5重载赋值运算符(Overloading assignment)

  • 赋值运算符用于将一个已存在的对象赋值为同类的另一个对象。其重载函数通常声明为:X& X::operator=(const X& other) ,即返回当前对象的引用,参数为待赋值的同类对象的常量引用。

  • 目的:通过重载赋值运算符,可以使已存在的类对象 更新为另一个同类对象的内容。

  • 若用户自定义了赋值运算符,则赋值时将调用自定义版本;如果未定义,编译器会尝试生成一个默认的公有赋值运算符(对每个成员执行逐项赋值)。

  • 例如:

    cpp 复制代码
    Date t1;//调用构造函数创建 t1
    Date t2;//调用拷贝构造函数,用t1初始化t2
    Date t3;//调用构造函数创建 t3
    t3 = t2;//调用赋值运算符,将t2 赋值给已存在的 t3

示例,用来区分拷贝构造函数和赋值运算符

cpp 复制代码
#include <iostream>
using namespace std;

class Location {
public:
    Location(int xx = 0, int yy = 0) { X = xx; Y = yy; }
    Location(const Location& p) {
        X = p.X;
        Y = p.Y;
        cout << "Copy constructor called." << endl;
    }
    Location& operator=(const Location& p);//赋值运算符
    int GetX() { return X; }
    int GetY() { return Y; }
private:
    int X, Y;
};

Location& Location::operator=(const Location& p) {
    X = p.X;
    Y = p.Y;
    cout << "Assignment operator called." << endl;
    return *this;
}
void main() {
    Location A(1, 2), B;
    B = A;
    cout << "B = " << B.GetX() << "," << B.GetY() << endl;
    Location C = A;
    cout << "C = " << C.GetX() << "," << C.GetY() << endl;
}

输出

cpp 复制代码
Assignment operator called.
B = 1,2
Copy constructor called.
C = 1,2
  • 注意:我们通常需要在赋值运算符中防止自赋值。

    cpp 复制代码
    Location& Location::operator=(const Location& p) {
        if (this == &p){
            return *this;
        }
        X = p.X;
        Y = p.Y;
        cout << "Assignment operator called." << endl;
        return *this;
    }
  • 当我们在重载一个运算符时,我们不能改变:

    • 运算符的数量
    • 优先级
    • 语法结构
    • 将运算符组合(比如把=+组合成+=
  • 如果类包含指针等动态分配资源 ,默认的浅拷贝赋值会导致多个对象共享同一块内存资源,这可能造成严重错误:如析构时发生双重释放 (多次释放同一内存),或赋值覆盖时产生内存泄漏

    示例1

    cpp 复制代码
    class A{
        int *p;
    public:
        A(int i){p = new int(i);}
        ~A(){delete p;}
    };
    void main(){
        A a(5);
        A b(a);			//拷贝函数
        A c(6),d(10);
        d = c;		//赋值运算古
    }
    • 这个示例中,因为是 a.pb.p 指向同一内存,5所在的内存区域会被释放两次,因此报错。
    • c.pd.p 指向一样的存储区域。6所在的内存区域将会被释放两次(会报错)。10所在的区域会永远失去。

示例1的修改

cpp 复制代码
class A{
    int *p;
public:
    A(int i){p = new int(i);}
    ~A(){delete p;}
    A(const A&r);
    A& operator=(const A& r);
};
A::A(const A&r){
    p = new int(*r.p);
}
A& A::operator=(const A&r){
    if (this == &r) return *this;
    delete p;		//释放p指向的内存
    p = new int(*r.p);
    return *this;
}

void main(){
    A a(5);
    A b(a);
    A c(6),d(10);
    d = c;
}

这个示例就是正确的

示例2

cpp 复制代码
#include <iostream>
using namespace std;
class A{
    int* p;
public:
    A(int i){cout << "Constructor" << endl;p = new int(i);}
    ~A(){cout << "Destructor" << endl;delete p;}
    A (const A& r)//Copy Constructor
    {cout << "Copy-constructor" << endl;p = new int(*r.p);}
    A& operator=(const A&r);//赋值运算符
    void output() {cout << p << "->" << *p << endl;}
};
A& A::operator=(const A&r){
    cout << "Assignment" << endl;
    if (this == &r) return *this;
    delete p;
    p = nullptr;
    p = new int(*r.p);
    return *this;
}
int main(){
    A a(5);
    A b(a);//拷贝构造函数
    A c(6),d(10);
    d = c;//赋值运算符
    a.output();
    b.output();
    c.output();
    d.output();
    return 0;
}

输出

cpp 复制代码
Constructor
Copy-constructor
Constructor
Constructor
Assignment
000001D298B1C530->5
000001D298B1C870->5
000001D298B1C2F0->6
000001D298B1CCB0->6
Destructor
Destructor
Destructor
Destructor

12.6 自动类型转换(Automatic type conversion)

  • 构造函数可以被用来指定类型转换。
  • 实现自动类型转换的第二种方法是通过转换运算符重载

12.6.1 构造函数类型转换

cpp 复制代码
#include <iostream>
using namespace std;

class complex{
public:
    complex(double r = 0,double i = 0);
    complex(const complex& a);
    friend const complex operator+(const complex& c1,const complex& c2);
private:
    double real,imag;
};

complex::complex(double r,double i)
{
    real = r;
    imag = i;
    cout << "constructor:" << real << "," << imag << endl;
}

complex::complex(const complex& a){
    real = a.real;
    imag = a.imag;
    cout << "copy constructor:" << real << "," << imag << endl; 
}

const complex operator+(const complex& c1,const complex& c2){
    double r = c1.real + c2.real;
    double i = c1.imag + c2.imag;
    return complex(r,i);
}
void main(){
    complex c,c1(3.5,5.5);
    c = c1 + 1.5;
}

输出

cpp 复制代码
constructor:0,0  //c
constructor:3.5,5.5		//c1
constructor:1.5,0	//c = operator+(c1,complex(1.5)),double -> complex
constructor:5,5.5	//return complex(r,i)

限制 :构造函数只能定义从其他类型转换为本类的规则,但无法定义从本类转换为基础类型,或者将本类转换为另一个已有类类型的规则(除非修改那个类的定义来添加相应构造函数)

12.6.2转换运算符

  • 为补充构造函数转换的不足,可以在类中定义类型转换运算符 来将类转换为其他类型。其一般形式为:X::operator type()
  • 上述声明中,目标类型type 是运算符名称的一部分,不在函数返回类型位置出现。该运算符函数没有显式参数,它将类 类型转换为指定的目标类型。
cpp 复制代码
#incldue <iostream>
using namespace std;
class complex
{
    double real,imag;
public:
    complex(double r = 0,double i = 0)
    {
        real = r;
        imag = i;
        cout << "(" << real << "," << imag << ")" << endl;
    }
    operator int(){return int(real);}
};
void main(){
    complex c(3.5,5.5);
    int b = d;
    cout << b << endl;
    cout << int(c) << endl;
}

12.7 重载<<和>>

  • 流插入运算符<<流提取运算符>> 通常重载为友元函数,用于实现自定义类型于输入输出流之间的序列化。

    因为友元函数可以访问类的私有成员变量 ,同时<<>>左边是ostreamistream类型(例如cout << "hello world"<< 的左边就是ostream 类型),因此要把流运算符重载为友元函数

  • 下面示例定义了一个date类,并重载<<>> 运算符以支持日期的输入和输出。

cpp 复制代码
#include <iostream>
using namespace std;

class date {
public:
    date(int x = 0, int y = 0, int z = 0) { year = x, month = y, day = z; }
    friend ostream& operator<<(ostream& ost, const date& d);
    friend istream& operator>>(istream& ist, date& d);
private:
    int year, month, day;
};
ostream& operator<<(ostream& ost, const date& d) {
    //注意ost引用ostream类对象
    ost << d.year << ends << d.month << ends << d.day << endl;
    return ost;
}
istream& operator>>(istream& ist, date& d) {
    //注意ist引用istream类对象
    cout << "请输出年月日的数值的数值,如:2025 5 6" << endl;
    ist >> d.year >> d.month >> d.day;
    return ist;
}

int main() {
    date d;
    cin >> d;	//即调用函数operator>>(cin,d)
    cout << d;	//operator<<(cout,d)
    return 0;
}

12.8 总结

  • 运算符函数的命名方式
  • 按运算元个数分类:一元、二元、三元运算符
  • 可以重载的运算符
  • 不可重载的运算符
  • 重载实现方式
    • 成员函数方式
    • 友元函数方式
  • 特殊运算符:[]++--= 、类型转换、<<>>

以访问类的私有成员变量 ,同时<<>>左边是ostreamistream类型(例如cout << "hello world"<< 的左边就是ostream 类型),因此要把流运算符重载为友元函数

  • 下面示例定义了一个date类,并重载<<>> 运算符以支持日期的输入和输出。
cpp 复制代码
#include <iostream>
using namespace std;

class date {
public:
    date(int x = 0, int y = 0, int z = 0) { year = x, month = y, day = z; }
    friend ostream& operator<<(ostream& ost, const date& d);
    friend istream& operator>>(istream& ist, date& d);
private:
    int year, month, day;
};
ostream& operator<<(ostream& ost, const date& d) {
    //注意ost引用ostream类对象
    ost << d.year << ends << d.month << ends << d.day << endl;
    return ost;
}
istream& operator>>(istream& ist, date& d) {
    //注意ist引用istream类对象
    cout << "请输出年月日的数值的数值,如:2025 5 6" << endl;
    ist >> d.year >> d.month >> d.day;
    return ist;
}

int main() {
    date d;
    cin >> d;	//即调用函数operator>>(cin,d)
    cout << d;	//operator<<(cout,d)
    return 0;
}

12.8 总结

  • 运算符函数的命名方式
  • 按运算元个数分类:一元、二元、三元运算符
  • 可以重载的运算符
  • 不可重载的运算符
  • 重载实现方式
    • 成员函数方式
    • 友元函数方式
  • 特殊运算符:[]++--= 、类型转换、<<>>
相关推荐
字节高级特工1 小时前
【C++】”如虎添翼“:模板初阶
java·c语言·前端·javascript·c++·学习·算法
.Vcoistnt1 小时前
Codeforces Round 1024 (Div. 2)(A-D)
数据结构·c++·算法·贪心算法·动态规划·图论
越甲八千1 小时前
MFC listctrl修改背景颜色
c++·mfc
炯哈哈2 小时前
【上位机——MFC】序列化机制
开发语言·c++·mfc·上位机
隐世12 小时前
C++多态讲解
开发语言·c++
Wooden-Flute2 小时前
十四、继承与组合(Inheritance & Composition)
c++
阳区欠2 小时前
【Linux】线程的同步与互斥
linux·服务器·c++·线程同步·线程互斥·生产者/消费者模型
刚入门的大一新生2 小时前
C++初阶-string类的模拟实现1
开发语言·c++
梁下轻语的秋缘2 小时前
每日c/c++题 备战蓝桥杯(洛谷P1115 最大子段和)
c语言·c++·蓝桥杯
wen__xvn2 小时前
每日一题洛谷P8662 [蓝桥杯 2018 省 AB] 全球变暖c++
c++·职场和发展·蓝桥杯