【C++ 程序设计】第 4 章:运算符重载

目录

一、运算符重载的概念

(1)重载运算符的概念

① 重载运算符的概念

② 可重载的运算符

③ 不可重载的运算符

④ 运算符的优先级 

(2)重载运算符为类的成员函数

(3)重载运算符为友元函数

(4)重载运算符的规则

二、重载赋值运算符

(1)重载赋值运算符

(2)浅拷贝与深拷贝

① 概念 

② 示例 

三、重载流插入运算符和流提取运算符

(1)概念

(2)示例一:使用友元函数重载运算符 <<  和 >>

(3)示例二:使用运算符重载处理复数

四、*重载强制类型转换运算符

(1)概念

(2)示例:重载强制类型转换运算符 double

五、重载自增、自减运算符

(1)概念

(2)示例:重载 ++ 和 - 运算符




一、运算符重载的概念

(1)重载运算符的概念

① 重载运算符的概念

C++ 中的表达式由运算符和操作数按照规则构成。
  • 例如,算术运算符包括加 “+”、减 “-” 、乘 “*” 、除 “/” 和取模 “%” 。
  • 如果不做特殊处理,则这些算术运算符通常只能用于对基本数据类型的常量或变量进行运算,而不能用于对象之间的运算
运算符重载,就是给已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时产生不同的行为。
  • 运算符重载的目的是使得 C++ 中的运算符也能够用来操作对象
用于类运算的运算符通常都要重载。
  • 有两个运算符,系统提供了默认的重载版本:赋值运算符 = 和 地址运算符 &
  • 对于 = ,系统默认重载为对象成员变量的复制。
  • 对于 &,系统默认重载为返回任何类对象的地址。
  • 与其他函数一样,重载运算符有一个返回类型和一个参数列表。这样的函数称为运算符函数
  • 运算符可以被重载为全局函数,也可以被重载为类的成员函数。声明为全局函数时,通常应是类的友元
  • 故运算符函数是一种特殊的友元函数或成员函数

② 可重载的运算符

运算符类型 可重载的运算符
双目算术运算符 +(加),-(减),*(乘),/(除),%(取模)
关系运算符 ==(等于),!=(不等于),<(小于),>(大于),<=(小于等于),>=(大于等于)
逻辑运算符 ||(逻辑或),&&(逻辑与),!(逻辑非)
单目运算符 +(正),-(负),*(指针),&(取地址)
自增自减运算符 ++(自增),--(自减)
位运算符 |(按位或),&(按位与),~(按位取反),^(按位异或),<<(左移),>>(右移)
赋值运算符 =(赋值),+=(加法赋值),-=(减法赋值),*=(乘法赋值),/=(除法赋值),
%=(取模赋值),&=(按位与赋值),|=(按位或赋值),^=(按位异或赋值),
<<=(左移赋值),>>=(右移赋值)
空间申请与释放 new(创建对象),delete(释放对象),new[](创建数组),delete[](释放数组)
其他运算符 ()(函数调用),->(成员访问),,(逗号),[](下标)

③ 不可重载的运算符

运算符类型 不可重载的运算符
成员访问运算符 .
成员指针访问运算符 .*, ->*
域运算符 ::
长度运算符 sizeof
条件运算符 ?:
预处理符号 #

④ 运算符的优先级 


(2)重载运算符为类的成员函数

// 示例:复数类(类的定义、构造函数、成员函数、运算符重载)
#include <iostream>
using namespace std;

// 复数类定义
class myComplex {
    // 私有成员变量
    private:
        double real, imag;
    // 公有成员函数
    public:
        // 构造函数
        myComplex();
        myComplex(double r, double i);
        // 输出函数
        void outCom();
        // 运算符重载,计算两个复数的差
        myComplex operator - (const myComplex &c); 
        // 全局函数运算符重载,计算两个复数的和
        friend myComplex operator+ (const myComplex &c1, const myComplex &c2);
};

// 默认构造函数
myComplex::myComplex() {
    real = 0;
    imag = 0;
}

// 自定义构造函数
myComplex::myComplex(double r, double i) {
    real = r;
    imag = i;
}

// 输出函数实现
void myComplex::outCom() {
    cout<<"("<<real<<","<<imag<<")";
}

// 运算符重载实现,计算两个复数的差
myComplex myComplex::operator - (const myComplex &c) {
    return myComplex(this->real - c.real,this->imag - c.imag);
}

// 全局函数运算符重载实现,计算两个复数的和
myComplex operator+ (const myComplex &c1, const myComplex &c2) {
    return myComplex(c1.real+c2.real,c1.imag+c2.imag);
}

// 主函数
int main() {
    // 创建两个复数类对象和一个用于存放结果的复数类对象
    myComplex c1(1,2),c2(3,4),res;
    // 输出第一个复数
    c1.outCom();
    cout<<" operator+ "; // 运算符 + 的提示
    // 输出第二个复数
    c2.outCom();
    cout<<"="; // 等号的输出
    // 计算两个复数相加并输出结果
    res = c1+c2;
    res.outCom();
    cout<<endl; // 换行字符
    // 输出第一个复数
    c1.outCom();
    cout<<" operator- "; // 运算符 - 的提示
    // 输出第二个复数
    c2.outCom();
    cout<<"="; // 等号的输出
    // 计算两个复数相减并输出结果
    res = c1-c2;
    res.outCom();
    cout<<endl; // 换行字符
    // 程序返回值
    return 0;
}

【代码功能】 

  • 这是一个关于复数类的 C++ 代码,包含了类的定义、构造函数、成员函数和运算符重载

【代码详解】

  • 首先,这个 C++ 代码定义了一个名为 myComplex 的复数类。这个类包含了两个 private 成员变量 real 和 imag,分别表示复数的实部和虚部。
class myComplex //复数类
{
private:
  double real,imag;
public:
  ...
};
  • 接下来,这个类有两个公有的构造函数 myComplex() 和 myComplex(double r,double i)。第一个构造函数没有参数,会将实部和虚部都初始化为 0;第二个构造函数可以在创建对象时指定实部和虚部的值。两个构造函数都没有返回值。
myComplex::myComplex() //构造函数
{ 
  real=0;
  imag=0;
}

myComplex::myComplex(double r,double i) //构造函数
{ 
  real=r;
  imag=i;
}
  • 接着,这个类还有一个公有的成员函数 outCom(),用于输出复数。这个函数没有参数,也没有返回值,它会在标准输出上输出复数的值。
void myComplex::outCom() //成员函数
{ 
  cout<<"("<<real<<","<<imag<<")"; 
}
  • 然后,这个类定义了双目运算符 operator-,用于计算两个复数的差。这个运算符重载函数使用了 this 指针,表示当前实例的地址,同时也用到了 & 运算符来获取一个指向参数对象的引用。它返回一个新的 myComplex 对象,表示两个复数相减的结果。
myComplex myComplex::operator- (const myComplex &c)
{ 
  return myComplex(this->real - c.real,this->imag - c.imag);
}
  • 最后,这个代码使用了 friend 关键字来定义了一个全局的函数 operator+,同样用于计算两个复数的和。这个函数使用了 const 关键字来确保参数是只读的,即不能被修改。与上一个运算符重载函数一样,它返回一个新的 myComplex 对象,表示两个复数相加的结果。
friend myComplex operator+(const myComplex &c1,const myComplex &c2);
  • 在 main() 函数中,创建了两个 myComplex 类型的对象 c1 和 c2,分别被初始化为 (1,2) 和 (3,4)。然后依次调用了 outCom() 函数输出这两个复数。接着使用运算符重载 operator+ 来计算它们的和,将结果保存到 res 对象中,最后输出它们的和。然后又使用运算符重载 operator- 来计算它们的差,同样将结果保存到 res 对象中,最后输出它们的差。
int main()
{
  myComplex c1(1,2),c2(3,4),res;
  c1.outCom();
  cout<<"operator+";
  c2.outCom();
  cout<<"=";
  res=c1+c2;
  res.outCom();
  cout<<endl;
  c1.outCom();
  cout<<"operator-";
  c2.outCom();
  cout<<"=";
  res=c1-c2;
  res.outCom();
  cout<<endl;
  return 0;
}

【执行结果】

(1,2)operator+(3,4)=(4,6)
(1,2)operator-(3,4)=(-2,-2)

(3)重载运算符为友元函数

// 示例:重载运算符为友元函数,使用旧版 C++ 编写的关于复数类的代码
#include<iostream.h>

// 定义复数类
class myComplex {
private:
    double real, imag; // 私有成员变量
public:
    myComplex(); // 默认构造函数
    myComplex(double r, double i); // 有参数构造函数
    void outCom(); // 输出实现
    friend myComplex operator+(const myComplex &c1, const myComplex &c2); // 友元函数运算符重载实现,用于计算两个复数相加
    friend myComplex operator-(const myComplex &c1, const myComplex &c2); // 友元函数运算符重载实现,用于计算两个复数相减
    friend myComplex operator-(const myComplex &c1, double r); // 友元函数运算符重载实现,用于整数和复数的运算,计算一个复数减去一个实数
    friend myComplex operator-(double r, const myComplex &c1); // 友元函数运算符重载实现,用于整数和复数的运算,计算一个实数减去一个复数
};

// 默认构造函数实现
myComplex::myComplex(){
    real = 0;
    imag = 0;
}

// 有参数构造函数实现
myComplex::myComplex(double r, double i){
    real = r;
    imag = i;
}

// 输出实现,将复数输出到屏幕
void myComplex::outCom(){
    cout << "(" << real << "," << imag << ")";
}

// 友元函数运算符重载实现,用于计算两个复数相加
myComplex operator+(const myComplex &c1, const myComplex &c2){
    return myComplex(c1.real + c2.real, c1.imag + c2.imag); // 返回一个临时对象
}

// 友元函数运算符重载实现,用于计算两个复数相减
myComplex operator-(const myComplex &c1, const myComplex &c2){
    return myComplex(c1.real - c2.real, c1.imag - c2.imag); // 返回一个临时对象
}

// 友元函数运算符重载实现,用于整数和复数的运算,计算一个复数减去一个实数
myComplex operator-(const myComplex &c1, double r){
    return myComplex(c1.real - r, c1.imag); // 返回一个临时对象
}

// 友元函数运算符重载实现,用于整数和复数的运算,计算一个实数减去一个复数
myComplex operator-(double r, const myComplex &c1){
    return myComplex(r - c1.real, -c1.imag); // 返回一个临时对象
}

int main(){
    myComplex c1(1, 2), c2(3, 4), res;
    c1.outCom();
    cout << "operator+";
    c2.outCom();
    cout << "=";
    res = c1 + c2;
    res.outCom();
    cout << endl;
    res = c1 - 5;
    res.outCom();
    res = 5 - c1;
    res.outCom();
    return 0;
}

【代码功能】 

  • 这是一个使用旧版 C++ 编写的关于复数类的代码
  • 该代码使用的 I/O 流头文件为 <iostream.h>,在新版 C++ 中已经被弃用

【代码详解】

1. 预处理指令:

  • 这是一个 C++ 标准输入输出流头文件,但这个头文件已经被废弃了,更推荐使用#include<iostream> 来代替。
// 旧代码
#include<iostream.h>
// 新代码
#include <iostream>

using namespace std;

2. 类定义:

  • 这里定义了一个myComplex类,表示复数类。
  • 在这里定义了两个私有成员变量 real 和 imag ,表示实数和虚数部分。
  • 公有部分有一个默认构造函数 myComplex(),一个有参数构造函数 myComplex(double r, double i) 和一个输出函数 outCom()
  • 此外还定义了四个友元函数,分别是 ​​​​​​​operator+()operator-()operator-(const myComplex &c1, double r) 和 operator-(double r,const myComplex &c1)
  • 这四个函数都是用于运算符重载的实现,分别用于计算两个复数相加、两个复数相减、一个复数减去一个实数、一个整数减去一个复数。
class myComplex //复数类
{
private:
    double real, imag; // 私有成员变量
public:
    myComplex(); // 默认构造函数
    myComplex(double r, double i); // 有参数构造函数
    void outCom(); // 输出实现
    friend myComplex operator+(const myComplex &c1, const myComplex &c2); // 友元函数运算符重载实现,用于计算两个复数相加
    friend myComplex operator-(const myComplex &c1, const myComplex &c2);// 友元函数运算符重载实现,用于计算两个复数相减
    friend myComplex operator-(const myComplex &c1, double r);// 友元函数运算符重载实现,用于整数和复数的运算,计算一个复数减去一个实数
    friend myComplex operator-(double r, const myComplex &c1);// 友元函数运算符重载实现,用于整数和复数的运算,计算一个实数减去一个复数
};

3. 实现默认构造函数和有参数构造函数:

  • 默认构造函数 ​​​​​​​myComplex::myComplex() 将实数和虚数部分都初始化为0。
  • 有参数构造函数myComplex::myComplex(double r, double i)则可以实现自定义初始化实数和虚数部分的功能。
myComplex::myComplex() // 默认构造函数实现
{
    real = 0;
    imag = 0;
}

myComplex::myComplex(double r, double i) // 有参数构造函数实现
{
    real = r;
    imag = i;
}

4. 实现输出函数outCom():

  • 这个函数实现了将 ​​​​​​​myComplex ​​​​​​​类的实数和虚数部分输出的功能。
void myComplex::outCom() // 输出实现,将复数输出到屏幕
{
    cout << "("<< real << "," << imag << ")";
}

5. 友元函数运算符重载实现

  • 这里使用了运算符重载语法,实现了四个函数:
  • operator+()用于实现两个复数相加的功能
  • operator-()用于实现两个复数相减的功能
  • operator-(const myComplex &c1, double r)用于计算一个复数减去一个实数的操作(c1-r)
  • operator-(double r, const myComplex &c1)用于计算一个实数减去一个复数的操作(r-c1)
  • 这里的四个运算符重载函数都是友元函数,使用了C++中的友元机制,访问了myComplex类的私有成员变量。
  • 其中,每个函数都返回了一个myComplex类型的临时对象,这样可以保证返回值与左右操作数不冲突。
myComplex operator+(const myComplex &c1, const myComplex &c2) //c1+c2
{
    return myComplex(c1.real + c2.real, c1.imag + c2.imag); //返回一个临时对象
}

myComplex operator-(const myComplex &c1, const myComplex &c2) //c1-c2 新增
{
    return myComplex(c1.real - c2.real, c1.imag - c2.imag); //返回一个临时对象
}

myComplex operator-(const myComplex &c1, double r) //c1-r 新增
{
    return myComplex(c1.real - r, c1.imag); //返回一个临时对象
}

myComplex operator-(double r, const myComplex &c1) //r-c1 新增
{
    return myComplex(r - c1.real, -c1.imag); //返回一个临时对象
}

6. 主函数实现:

  • 主函数中,创建了两个复数对象 c1 和 c2 ,分别初始化为 (1,2) 和 (3,4) 。
  • 接着使用 c1 和 c2 相加的运算符重载函数 operator+(),将相加的结果存放到一个中间临时变量 res 中,再使用 ​​​​​​​outCom() 函数输出。
  • 接下来使用运算符重载的 “减法” 函数,分别计算一个复数减去一个实数(c1-5)和一个实数减去一个复数(5-c1)。
  • 最后使用 outCom() 函数将结果输出到屏幕。
int main()
{
    myComplex c1(1, 2), c2(3, 4), res;
    c1.outCom();
    cout << "operator+";
    c2.outCom();
    cout << "=";
    res = c1 + c2;
    res.outCom();
    cout << endl;
    res = c1 - 5;
    res.outCom();
    res = 5 - c1;
    res.outCom();
    return 0;
}

【执行结果】

  • 第一行输出了(1,2)加上operator+再加上(3,4)的结果,是(4,6),即(1+3,2+4)
  • 接下来一行输出了(1,2)减去5的结果(-4,2),另一行输出了5减去(1,2)的结果(4,-2)
(1,2)operator+(3,4)=(4,6)
(-4,2)(4,-2)

(4)重载运算符的规则

  1. 重载后运算符的含义应该符合原有的用法习惯。例如,重载 “+” 运算符,完成的功能就应该类似于做加法,在重载的 “+” 运算符中做减法是不合适的。
  2. 运算符重载不能改变运算符原有的语义,包括运算符的优先级和结合性。
  3. 运算符重载不能改变运算符操作数的个数及语法结构。
  4. 不能创建新的运算符,即重载运算符不能超出C++语言允许重载的运算符范围。
  5. 重载运算符 “( )” “[ ]” “->” 或者赋值运算符 “=” 时,只能将它们重载为成员函数,不能重载为全局函数。
  6. 运算符重载不能改变该运算符用于基本数据类型对象的含义。

运算符重载有以下几个限制:

  • 运算符只能被重载为类的成员函数或全局函数。
  • 一些运算符不能被重载,例如三元运算符 ?:,成员选择运算符 .,作用域解析运算符 :: 和 sizeof 运算符等。
  • 运算符重载不能改变其基本用途,例如重载运算符 + 时不能使其实现其他功能,如输入。
  • 当使用运算符时,其重载版本与其基本操作有相同的优先级和结合性。


二、重载赋值运算符

(1)重载赋值运算符

  • C++ 中的赋值运算符 “=” 要求左右两个操作数的类型是匹配的,或至少是赋值兼容的。
  • 有时希望 “=” 两边的操作数的类型即使不赋值兼容也能够成立,这就需要对 “=” 进行重载。C++ 规定,“=” 只能重载为成员函数
  • 若有类 CL 的两个对象 s1 和 s2 ,则它们之间的赋值语句通常是下面这样的形式: s1=s2;
  • 当类 CL 中定义了成员函数,重载了赋值运算符后,上述赋值语句将解释为函数调用的形式: s1.operator=(s2);
  • 重载运算符 “()” “[]” “->” 或者赋值运算符 “=” 时,只能将它们重载为成员函数,不能重载为全局函数。
  • 赋值运算符必须重载为成员函数

【示例代码】 

#include <iostream>

using namespace std;

class myComplex {
private:
    double real, imag;
public:
    myComplex();
    myComplex(double r, double i);
    void outCom(string name = "");
    myComplex addCom(const myComplex &c);
    myComplex& changeReal(const double r);
    myComplex operator+(const myComplex &c1, const myComplex &c2);
    myComplex operator+(const myComplex &c1, double r);
    myComplex operator+(double r, const myComplex &c1);
    myComplex& operator=(const myComplex& c1);
    myComplex& operator=(double r);
};

myComplex::myComplex(){
    real = 0;
    imag = 0;
}

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

void myComplex::outCom(string name){
    cout << name << " = (" << real << "," << imag << ")" << endl;
}

myComplex myComplex::addCom(const myComplex &c){
    return myComplex(real + c.real, imag + c.imag);
}

myComplex& myComplex::changeReal(const double r){
    real = r;
    return *this;
}

myComplex operator+(const myComplex &c1, const myComplex &c2){
    return myComplex(c1.real + c2.real, c1.imag + c2.imag);
}

myComplex operator+(const myComplex &c1, double r){
    return myComplex(c1.real + r, c1.imag);
}

myComplex operator+(double r, const myComplex &c1){
    return myComplex(r + c1.real, c1.imag);
}

myComplex& myComplex::operator=(const myComplex &c1){
    real = c1.real;
    imag = c1.imag;
    return *this;
}

myComplex& myComplex::operator=(double r){
    real = r;
    imag = 0;
    return *this;
}

int main(){
    myComplex c1(1,2),c2(3,4),res;

    // 输出c1和c2当前的值
    c1.outCom("\t\t\tC1"); 
    c2.outCom("\t\t\tC2");

    // 使用运算符重载计算c1和c2的和
    res = c1 + c2;
    res.outCom("执行res=c1+c2→\tres");

    // 使用成员函数计算c1和c2的和
    res = c1.addCom(c2);
    res.outCom("执行res=c1.addCom(c2)→\tres");

    // 使用运算符重载计算c1加5的结果
    res = c1 + 5;
    res.outCom("执行res=c1+5→\tres");

    // 使用运算符重载计算5加c1的结果
    res = 5 + c1;
    res.outCom("执行res=5+c1→\tres");

    // 将c1赋值给res,输出c1和res的值
    res = c1;
    c1.outCom("\t\t\tC1");
    res.outCom("执行res=c1→\t\tres");

    // 使用成员函数修改c1的实部为-3,输出c1的值
    c1.changeReal(-3);
    c1.outCom("执行c1.changeReal(-3)→\tc1");

    // 输出res和c1的值
    res.outCom("\t\t\tres");
    res = c1;

    // 输出res和c1的值
    res.outCom("执行res=c1→\t\tres");

    // 使用运算符重载计算7的值,并输出
    res = 7;
    res.outCom("执行res=7→\t\tres");

    // 使用运算符重载计算7 + 8的结果,并输出
    res = 7 + 8;
    res.outCom("执行res=(7+8)→\tres");

    // 将c2的值赋给c1和res,输出c1和c2的值
    res = c1 = c2;
    c1.outCom("\t\t\tc1");
    c2.outCom("\t\t\tc2");
    res.outCom("执行res=c1=c2→\tres");

    return 0;
}

【代码功能】 

  • 这段代码演示了复数类 myComplex 的一些运算符重载和成员函数的实现,以及这些函数如何在主函数中使用。
  • 使用运算符重载和成员函数实现一些常见的复数运算,并在主函数中测试这些运算的效果。

【代码详解】 

1. 运算符重载 operator+(const myComplex & c1, const myComplex & c2)

  • 此函数用于实现两个复数相加。其中,左操作数和右操作数都是 myComplex 类型的常量引用。
  • 函数返回一个临时对象,表示两个复数相加的结果。

2. 运算符重载 operator+(const myComplex& c1, double r)

  • 此函数用于实现一个复数加上一个实数的操作。左操作数是myComplex类型的常量引用,右操作数是 ​​​​​​​double 类型。
  • 函数同样返回一个临时对象,表示复数加上实数的结果。

3. 运算符重载 operator+(double r, const myComplex& c1)

  • 此函数用于实现一个实数加上一个复数的操作。
  • 左操作数是 ​​​​​​​double 类型,右操作数是 ​​​​​​​myComplex 类型的常量引用。
  • 函数返回一个临时对象,表示实数加上复数的结果。

4. 成员函数 myComplex& operator=(const myComplex& c1)

  • 此函数实现复制运算符,用于将一个复数赋值给另外一个复数。
  • 其中,左操作数为当前对象本身,右操作数为 ​​​​​​​myComplex 类型的常量引用。
  • 函数返回左操作数的引用,即当前对象的引用。

5. 成员函数 myComplex& operator=(double r)

  • 此函数实现将一个实数赋值给复数的操作。
  • 左操作数为当前对象本身,右操作数为 ​​​​​​​double 类型。
  • 函数同样返回左操作数的引用。

6. 主函数:

  • 主函数首先创建两个复数对象 ​​​​​​​c1 和 c2,并分别初始化为 (1,2) 和 (3,4)
  • 然后分别使用加法运算符重载、成员函数和加法运算符重载来计算复数的加减法和赋值。
  • 其中,每个函数执行后都会输出执行的结果。
  • 其中主要展示了如何使用myComplex类实现对象定义、初始化、调用函数、使用运算符重载等等操作。
  • 这个代码没有输出不同步骤执行产生的输出结果,但按照代码的意图,会进行如下操作:
  1. 定义两个复数对象 c1(1,2) 和 c2(3,4)。
  2. 输出 c1 和 c2 的值。
  3. 使用运算符重载计算 c1 和 c2 的和,并输出结果。
  4. 使用成员函数计算 c1 和 c2 的和,并输出结果。
  5. 使用运算符重载计算 c1 加 5 的结果,并输出结果。
  6. 使用运算符重载计算 5 加 c1 的结果,并输出结果。
  7. 将 c1 赋值给 res,输出 c1 和 res 的值。
  8. 使用成员函数修改 c1 的实部为 -3,输出 c1 的值。
  9. 输出 res 和 c1 的值。
  10. 将 c2 的值赋给 c1 和 res ,输出 c1 和 c2 的值。

【执行结果】 

  • 主函数的部分:
myComplex c1(1,2),c2(3,4),res;
// 输出c1和c2当前的值
c1.outCom("\t\t\tC1"); 
c2.outCom("\t\t\tC2");
  • 这里定义了两个 myComplex 类型的对象 c1 和 c2,并将它们的初始值分别设为 (1,2) 和 (3,4)。然后使用 c1 和 c2 对象的成员函数 outCom(),输出它们当前的值:
C1 = (1,2)
C2 = (3,4)
// 使用运算符重载计算c1和c2的和
res = c1 + c2;
res.outCom("执行res=c1+c2→\tres");
  • 通过运算符重载,计算 c1 和 c2 的和,将结果保存到 res 变量中,并使用 res 对象的成员函数 outCom() 输出计算结果:
//执行res=c1+c2→	
res = (4,6)
// 使用成员函数计算c1和c2的和
res = c1.addCom(c2);
res.outCom("执行res=c1.addCom(c2)→\tres");
  • 使用 c1 的成员函数 addCom(),计算 c1 和 c2 的和,并将结果保存到 res 变量中,并使用 res 对象的成员函数 outCom() 输出计算结果:
// 执行res=c1.addCom(c2)→	
res = (4,6)
// 使用运算符重载计算c1加5的结果
res = c1 + 5;
res.outCom("执行res=c1+5→\tres");
  • 通过运算符重载,计算复数 c1 加上实数 5 的和,将结果保存到 res 变量中,并使用 res 对象的成员函数 outCom() 输出计算结果:
// 执行res=c1+5→	
res = (6,2)
// 使用运算符重载计算5加c1的结果
res = 5 + c1;
res.outCom("执行res=5+c1→\tres");
  • 通过运算符重载,计算实数 5 加上复数 c1 的和,将结果保存到 res 变量中,并使用 res 对象的成员函数 outCom() 输出计算结果:
// 执行res=5+c1→	
res = (6,2)
// 将c1赋值给res,输出c1和res的值
res = c1;
c1.outCom("\t\t\tC1");
res.outCom("执行res=c1→		res");
  • 将复数对象 c1 赋值给 res 变量,然后分别使用 c1 和 res 变量的成员函数 outCom() 输出它们的值:
// C1 = (1,2)
// 执行res=c1→		
res = (1,2)
// 使用成员函数修改c1的实部为-3,输出c1的值
c1.changeReal(-3);
c1.outCom("执行c1.changeReal(-3)→\tc1");
  • 使用 c1 对象的成员函数 changeReal(),将 c1 对象的实部更改为 -3,并使用 c1 对象的成员函数 outCom() 输出 c1 对象的值:
// 执行c1.changeReal(-3)→	
c1 = (-3,2)
// 输出res和c1的值
res.outCom("\t\t\tres");
res = c1;
res.outCom("执行res=c1→\t\tres");
  • 分别输出 res 和 c1 变量的值,然后将 c1 变量的值赋给 res 变量,并再次输出 res 变量的值:
// res = (1,2)
// 执行res=c1→		
res = (-3,2)
// 使用运算符重载计算7的值,并输出
res = 7;
res.outCom("执行res=7→\t\tres");
  • 将整型数值 7 赋给复数对象 res,然后使用 res 对象的成员函数 outCom() 输出它的值:
// 执行res=7→		
res = (7,0)
// 使用运算符重载计算7 + 8的结果,并输出
res = 7 + 8;
res.outCom("执行res=(7+8)→\tres");
  • 通过运算符重载,计算整型数值 7 加上 8 的和,将结果保存到 res 变量中,并使用 res 对象的成员函数 outCom() 输出计算结果:
// 执行res=(7+8)→	
res = (15,0)
// 将c2的值赋给c1和res,输出c1和c2的值
res = c1 = c2;
c1.outCom("\t\t\tc1");
c2.outCom("\t\t\tc2");
res.outCom("执行res=c1=c2→\tres");
  • 将 c2 对象的值同步赋值给 c1 和 res 变量,然后分别使用 c1、c2 和 res 变量的成员函数 outCom(),输出它们的值:
// c1 = (3,4)
// c2 = (3,4)
// 执行res=c1=c2→	
res = (3,4)
  • 总结:通过上面对程序中的每一行的分析和运行结果的输出,我们可以熟悉了解类、成员函数、运算符重载,以及如何创建对象、调用成员函数等 C++ 的基本概念和语法。

(2)浅拷贝与深拷贝

① 概念 

  • 同类对象之间可以通过赋值运算符 “=” 互相赋值。
  • 如果没有经过重载, “=” 的作用就是将赋值号右侧对象的值一一赋值给左侧的对象
  • 这相当于值的拷贝, 称为 “浅拷贝” 。
  • 重载赋值运算符后,赋值语句的功能是将一个对象中指针成员变量指向的内容复制到另一个对象中指针成员变量指向的地方,这样的拷贝叫 “深拷贝

② 示例 

#include<iostream>
using namespace std;

// 定义pointer类
class pointer
{
public:
    int a;  // 整型变量a
    int *p; // 整型指针变量p

    // 缺省构造函数
    pointer( )
    {
        a = 100;
        p = new int(10); // p指向动态内存分配的10
    }

    // 拷贝构造函数
    pointer(const pointer& tempp)
    {
        // 通过判断内存地址确定是否是同一块内存,而不是拷贝了同一块内存
        if(this !=&tempp)
        {
            a = tempp.a;
            p = tempp.p;
        }
    }
};

// 主函数
int main()
{
    // 声明p1实例
    pointer p1;
    // 声明p2实例并使用p1进行初始化
    pointer p2(p1);
    // 声明p3实例并使用p1进行初始化
    pointer p3 = p1;

    // 输出初始化后,各对象的值及内存地址
    cout<<"\n初始化后,各对象的值及内存地址"<<endl;
    cout<<"对象名\t对象地址 a的值 p中的值 p指向的值 p的地址"<<endl;

    // 输出p1的值
    cout<<"p1:\t"<<&p1<<", "<<p1.a<<", "<<p1.p<<", "<<*p1.p<<", "<<&p1.p<<endl;
    // 输出p2的值
    cout<<"p2:\t"<<&p2<<", "<<p2.a<<", "<<p2.p<<", "<<*p2.p<<", "<<&p2.p<<endl;
    // 输出p3的值
    cout<<"p3:\t"<<&p3<<", "<<p3.a<<", "<<p3.p<<", "<<*p3.p<<", "<<&p3.p<<endl;

    // 改变p1中*p的值,p1.a的值,p2.a的值
    *p1.p = 20;
    p2.a = 300;

    // 输出修改后,各对象的值及内存地址
    cout<<"\n修改后,各对象的值及内存地址"<<endl;
    cout<<"对象名\t对象地址 a的值 p中的值 p指向的值 p的地址"<<endl;

    // 输出p1的值
    cout<<"p1:\t"<<&p1<<", "<<p1.a<<", "<<p1.p<<", "<<*p1.p<<", "<<&p1.p<<endl;
    // 输出p2的值
    cout<<"p2:\t"<<&p2<<", "<<p2.a<<", "<<p2.p<<", "<<*p2.p<<", "<<&p2.p<<endl;
    // 输出p3的值
    cout<<"p3:\t"<<&p3<<", "<<p3.a<<", "<<p3.p<<", "<<*p3.p<<", "<<&p3.p<<endl;

    // 主函数返回0
    return 0;
} 

【代码功能】

  • 这段代码定义了一个指针类 (pointer),通过该类的缺省构造函数(pointer())和拷贝构造函数 (pointer(const pointer&)) 实现了三个指针对象 p1、p2、p3 的初始化和复制操作。
  • 其中,p1 实例采用默认构造函数进行初始化,从缓存池中分配存储空间,并给 a 赋值为100,给 p 动态分配存储空间并初始化为 10;p2 实例拷贝 p1 实例,而 p3 采用 p1 进行初始化。
  • 然后,该程序分别输出了 p1、p2、p3 的内存地址,a 的值,p 指向的内存地址,p 指向的变量值和p的地址。
  • 接着,程序修改了 p1.p 的值为 20,同时也修改了 p1.a 和 p2.a 的值,最后再次输出了 p1、p2、p3 的内存地址,a 的值,p 指向的内存地址,p 指向的变量值和 p 的地址。
  • 最终,程序返回了 0 。

【代码详解】

  1. 定义 pointer 类;
  2. 在类中定义 a 和 p ,其中 a 为整型,p 为整型指针;
  3. 在类中定义缺省构造函数,首先将a的值赋为 100,然后动态分配内存并将 p 指向该内存,并将该内存初始化为 10;
  4. 在类中定义拷贝构造函数,其中先判断要拷贝的内存是否与自身相同,如果不同,则将a 和 p 的值设置为当前拷贝对象的a和p的值;
  5. 定义 main 函数并在该函数中定义三个 pointer 类型的对象 p1、p2、p3 ,其中 p1 采用默认构造函数进行初始化,而p2和p3则分别采用p1对象进行拷贝和赋值初始化;
  6. 输出 p1、p2、p3 的内存地址,a 的值,p 指向的内存地址,p 指向的变量值和 p 的地址;
  7. 修改 p1.p 的值为 20,p2.a 的值为 300,并输出 p1、p2、p3 的内存地址,a 的值,p 指向的内存地址,p指向的变量值和p的地址;
  8. 程序执行完毕并返回 0 。

【执行结果】

  • 在输出结果中,值得注意的是,三个对象的指针成员p指向同样的内存地址,而这部分内存地址通过 new 动态分配而来。
  • 当进行对象拷贝时,会产生两个指针对象 p 和 q ,p 和 q 指向同一块内存,但如果进行修改时,只有一个对象会改变,因为两个指针对象并不是拷贝同一块内存,而是各自维护自己的空间。

初始化后,各对象的值及内存地址
对象名 对象地址 a的值 p中的值 p指向的值 p的地址
p1: 0x61fed0, 100, 0x1a0f010, 10, 0x61fed8
p2: 0x61fec0, 100, 0x1a0f030, 10, 0x61fec8
p3: 0x61fea0, 100, 0x1a0f050, 10, 0x61fea8

修改后,各对象的值及内存地址
对象名 对象地址 a的值 p中的值 p指向的值 p的地址
p1: 0x61fed0, 100, 0x1a0f010, 20, 0x61fed8
p2: 0x61fec0, 300, 0x1a0f030, 20, 0x61fec8
p3: 0x61fea0, 100, 0x1a0f050, 20, 0x61fea8


三、重载流插入运算符和流提取运算符

(1)概念

  • 在 C++ 中,左移运算符 “<<” 可以和 cout 一起用于输出,故常被称为 “流插入运算符” 。
  • 右移运算符 “>>” 和 cin —起用于输入,一般被称为流提取运算符。
  • 它们都是 C++ 类库中提供的。
  • 在类库提供的头文件中已经对 “<<” 和 “>>” 进行了重载,使之分别作为流插入运算符和流提取运算符,能用来输出和输入 C++ 基本数据类型的数据。
  • cout 是 ostream 类的对象,cin 是 istream 类的对象,它们都是在头文件 iostream 中声明的。
  • 因此,凡是用 “cout<<” 和 “cin>>” 对基本数据类型数据进行输出/输入的,都要用 #include 指令把头文件  iostream 包含到本程序文件中。
  • 必须重载为类的友元

(2)示例一:使用友元函数重载运算符 <<  和 >>

// 示例:重载流插入运算符和流提取运算符,使用友元函数重载运算符<<和>>
#include <iostream>
using namespace std;

// 定义test类
class test
{
private:
    int i;
    float f;
    char ch;

public:
    // 缺省构造函数
    test(int a = 0, float b = 0, char c = '\0')
    {
        i = a;
        f = b;
        ch = c;
    }

    // 声明输出运算符的友元函数
    friend ostream &operator<<(ostream &, test);

    // 声明输入运算符的友元函数
    friend istream &operator>>(istream &, test &);
};

// 重载输出运算符
ostream &operator<<(ostream &stream, test obj)
{
    stream << obj.i << ","; //stream是cout的别名
    stream << obj.f << ",";
    stream << obj.ch << endl;
    return stream;
}

// 重载输入运算符
istream &operator>>(istream &t_stream, test &obj)
{
    t_stream >> obj.i;
    t_stream >> obj.f;
    t_stream >> obj.ch;
    return t_stream;
}

// 主函数
int main()
{
    test A(45, 8.5, 'W');
    operator<<(cout, A);
    test B, C;
    cout << "Input as i f ch:";
    operator>>(cin, B);
    operator>>(cin, C);
    operator<<(cout, B);
    operator<<(cout, C);

    // 主函数返回0
    return 0;
}

【代码功能】

  • 这段代码演示了如何使用友元函数重载运算符 <<  和 >>。
  • 友元函数是类外定义的普通函数,但它们可以访问类的私有成员。
  • 友元函数通常被用来重载运算符。

【代码详解】

  1. 在这段代码中,定义了一个 test 类,有三个私有成员变量 i,f 和 ch,分别表示整型、浮点型和字符型数据。该类中还包含了一个缺省构造函数,用来初始化对象数据。
  2. 然后,定义了两个友元函数 operator<< 和 operator>>,用于重载运算符<< 和>>。operator<< 函数的作用是向输出流中输出 test 对象的数据。operator>> 函数从输入流中读入数据,然后将数据存储在 test 对象中。
  3. 在主函数中,创建了一个 test 类型对象 A,调用 operator<< 函数将对象 A 的内容输出到标准输出流中。接下来,创建了两个对象 B 和 C,分别调用 operator>> 函数从标准输入流中读入数据,然后将这两个对象的内容输出到标准输出流中。

【执行结果】

45,8.5,W
Input as i f ch:23 3.14 Q
-858993460,0,Q
23,3.14,Q

(3)示例二:使用运算符重载处理复数

// 示例:重载流插入运算符和流提取运算符,使用运算符重载处理复数
#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

class myComplex     // 定义复数类
{
private:
  double real, imag;

public:
  // 默认构造函数
  myComplex(): real(0), imag(0){}

  // 带参数的构造函数
  myComplex(double r, double i) : real(r), imag(i){}

  // 友元, 插入运算符
  friend ostream &operator<<(ostream &os, const myComplex &c);

  // 友元,提取运算符
  friend istream &operator>>(istream &is, myComplex &c);
};

// 重载插入运算符
ostream &operator<<(ostream &os, const myComplex &c)
{
  if (c.imag >= 0)
    os << c.real << "+" << c.imag << "i";  // 以 a+bi 的形式输出
  else
    os << c.real << "-" << (-c.imag) << "i";
  return os;
}

// 重载提取运算符
istream &operator>>(istream &is, myComplex &c)
{
  string s;
  is >> s;    // 将 a+bi 作为字符串读入,a+bi 中间不能有空格

  // 查找虚部,若不存在 +,则肯定存在 -
  int pos = s.find("+", 0);
  if (pos == -1) pos = s.find("-", 1);    // 虚部为负数时

  // 分离出代表实部的字符串
  string sReal = s.substr(0, pos);  
  c.real = atof(sReal.c_str());           // atof() 能将参数内容转换成浮点数

  // 分离出代表虚部的字符串
  sReal = s.substr(pos + 1, s.length() - pos - 1);
  c.imag = atof(sReal.c_str());

  if (s[pos] == '-')
    c.imag = -c.imag;

  return is;
}

int main()
{
  myComplex c, c1;
  int n;

  cout << "请输入两个复数([-]a±bi)和一个整数,以空格分隔" << endl;
  cin >> c >> c1 >> n;    // 读入两个复数和一个整数

  cout << c << ", " << n << "," << c1;    // 输出两个复数和一个整数

  return 0;
}

【代码功能】 

  • 这段代码演示了如何使用运算符重载处理复数。

【代码详解】

1. myComplex 类表示一个复数,该类有两个私有成员变量 real 和 imag,分别表示复数的实部和虚部。

  • 该类中还包含了两个构造函数:默认构造函数和带参数的构造函数。

2. 然后,定义了两个友元函数 operator<< 和 operator>>,用于重载输出运算符 << 和输入运算符 >> 。

  • operator<< 函数的作用是向输出流中输出 myComplex 对象的数据。
  • operator>> 函数从输入流中读入形如 a+bi 的字符串,将 a 和 b 分别存储在 myComplex 对象的 real 和 imag 成员中。
  • 这两个函数都被定义为类 myComplex 的友元函数,从而可以访问 myComplex 的私有成员变量。

3. 在主函数中,创建了两个 myComplex 对象 c 和 c1,分别表示两个复数,并读入这两个复数和一个整数 n。然后,将 c、n 和 c1 的值依次输出到标准输出流中。需要注意到:

  • 在类的定义中,数据成员 real 和 imag 要有访问控制修饰符(这里是 private)。
  • 在重载提取运算符 >> 中,分离出代表虚部的字符串时,计算 pos 的语句需要增加一个参数 1,以便跳过实部前的字符‘-’ 或‘+’。

【执行结果】

请输入两个复数([-]a±bi)和一个整数,以空格分隔
-1+2i 1-3i 2048
-1+2i, 2048, 1-3i


四、*重载强制类型转换运算符

(1)概念

  • 在 C++ 中,类型的名字(包括类的名字)本身也是一种运算符,即强制类型转换运算符。
  • 强制类型转换运算符是单目运算符,也可以被重载,但只能重载为成员函数,不能重载为全局函数。
  • 经过适当重载后, “(类型名) 对象” 这个对对象进行强制类型转换的表达式就等价于 “象.operator 类型名( )” ,即变成对运算符函数的调用。

(2)示例:重载强制类型转换运算符 double

#include <iostream>
using namespace std;

class myComplex {
  double real, imag;

public:
  myComplex(double r = 0, double i = 0) : real(r), imag(i) {}

  operator double() // 重载强制类型转换运算符 double,返回实部
  {
    return real;
  }
};

int main() {
  myComplex c(1.2, -3.4);
  cout << (double)c << endl; // 输出 1.2

  double n = 12 + c; // 等价于 double n=12+c.operator double()
  cout << n;

  return 0;
}

【代码功能】 

  • 该代码演示了如何重载强制类型转换运算符。

【代码详解】

  1. myComplex 类有两个私有成员变量 real 和 imag,存储实数和虚数部分。该类定义了一个带有默认参数的构造函数。
  2. 该类还重载了一个 double 类型的强制类型转换运算符,将 myComplex 对象转换为 double 类型。注意,该运算符函数没有参数,其返回类型为 double。
  3. 在主函数中,定义了一个 myComplex 对象 c,输出该对象的实部并赋值给一个 double 类型的变量 n。实际上,这个赋值语句相当于将 myComplex 对象 c 隐式地转换为 double 类型,即等价于 c.operator double()。最后将 n 输出。
  4. 需要注意的是,在一个类中只能定义一种特定类型的强制类型转换运算符。强制类型转换运算符虽然可以方便地进行类型转换,但也可能会影响代码的可读性,因此需要谨慎使用。

【执行结果】

1.2
13.2


五、重载自增、自减运算符

(1)概念

  • 自增运算符 “++” 和自减运算符 “--” 都可以被重载,但是它们有前置和后置之分。
  • 以 “++” 为例,对于整数 k, “++k” 和 “k++” 的语义是不一样的。
  • 当 “++” 用于对象时,也应该如此。
  • 例如,obj 是一个类 CDemo 的对象,那么 “++obj” 和 “obj++” 的含义应该是不一样的。按照自增运算符及自减运算符的本来定义, “++obj” 的返回值应该是 obj 被修改后的值,而“obj++” 的返回值应该是 obj 被修改前的值

(2)示例:重载 ++ 和 - 运算符

#include <iostream>
using namespace std;

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;
}

CDemo CDemo::operator++(int k)  // 后置++,多一个参数,返回修改前的对象
{
  CDemo tmp(*this);  // 记录修改前的对象
  n++;
  return tmp;  // 返回修改前的对象
}

CDemo& operator--(CDemo& d)  // 前置--,返回自引用
{
  d.n--;
  return d;
}

CDemo operator--(CDemo& d, int)  // 后置--,多一个参数,返回修改前的对象
{
  CDemo tmp(d);
  d.n--;
  return tmp;
}

int main() {
  CDemo d(10);

  cout << (d++) << ",";   // 等价于 d.operator++(0);输出 10
  cout << d << ",";
  cout << (++d) << ",";   // 等价于 d.operator++();输出 12
  cout << d << ",";
  cout << (d--) << ",";   // 等价于 operator--(d, 0);输出 12
  cout << d << ",";
  cout << (--d) << ",";   // 等价于 operator--(d);输出 10
  cout << d << endl;      // 输出 10

  return 0;
}

【代码功能】 

  • 这段代码演示了如何重载 ++ 和 - 运算符。

【代码详解】

  • CDemo 类实现了前置 ++ 运算符、后置 ++ 运算符、前置 - 运算符和后置 - 运算符,其中前置运算符返回自引用,后置运算符返回修改前的对象。
  • 在主函数中,创建了一个 CDemo 对象 d,并分别进行前置 ++,后置 ++,前置 --,后置 - 操作。
  • 需要注意的是,后置 ++ 和 - 需要多传入一个 int 类型的参数作为区分,其值无意义。

【执行结果】

10,11,12,12,11,10

猜你喜欢

转载自blog.csdn.net/qq_39720249/article/details/131146665
今日推荐