C++之类与对象(3)(下)

简介: C++之类与对象(3)(下)

C++之类与对象(3)(上):https://developer.aliyun.com/article/1624935


2.赋值运算符重载

2.1 运算符重载

• 当运算符被用于类类型的对象时,C++语⾔允许我们通过运算符重载的形式指定新的含义。 C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。

• 运算符重载是具有特殊名字的函数, 它的名字是由operator和后面要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。

重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。

• 如果一个重载运算符函数是 成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。

• 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。

代码展示

#include <iostream>
using namespace std;
 
class Date {
public:
  Date(int year = 1, int month = 1, int day = 1) {
    _year = year;
    _month = month;
    _day = day;
  }
 
  void Print() {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
  /*
  int get_year() const {
    return _year;
  }
  int get_month() const {
    return _month;
  }
  int get_day() const {
    return _day;
  }
  */
  bool operator==( const Date& d2)
  {
    return _year == d2._year
      && _month == d2._month
      && _day == d2._day;
  }
private:
  int _year;
  int _month;
  int _day;
};
/*成员放公有时
bool operator==(const Date& d1, const Date& d2)
{
  return d1._year == d2._year
    && d1._month == d2._month
    && d1._day == d2._day;
}
*/
  // 重载为全局的⾯临对象访问私有成员变量的问题
// 有⼏种⽅法可以解决:
// 1、成员放公有
// 2、Date提供getxxx函数
// 3、友元函数
// 4、重载为成员函数
/*
private:
  int _year;
  int _month;
  int _day;
};
// 友元函数声明
bool operator==(const Date& d1, const Date& d2) {
  return d1.get_year() == d2.get_year()
    && d1.get_month() == d2.get_month()
    && d1.get_day() == d2.get_day();
}
*/
 
 
int main() {
  Date d1(2024, 7, 25);
  Date d2(2024, 7, 26);
  //d1 == d2;
    bool result=d1.operator==(d2);
    cout << "Using explicit call: d1 == d2? " << (result ? "true" : "false") << endl;
  cout << "Using implicit call: d1 == d2? " << (d1 == d2 ? "true" : "false") << endl;
  /*
  // 显式调用运算符重载
  bool result = operator==(d1, d2);
  cout << "Using explicit call: d1 == d2? " << (result ? "true" : "false") << endl;
  // 隐式调用运算符重载
  cout << "Using implicit call: d1 == d2? " << (d1 == d2 ? "true" : "false") << endl;
  */
  return 0;
}

• 不能通过连接语法中没有的符号来创建新的操作符:比如operator@。

  .*  ::  sizeof  ?:  . 注意以上5个运算符不能重载

class A
{
public:
void func()
{
cout << "A::func()" << endl;
}
};
typedef void(A::*PF)(); //成员函数指针类型
int main()
{
// C++规定成员函数要加&才能取到函数指针
PF pf = &A::func;
A obj;//定义ob类对象temp
// 对象调⽤成员函数指针时,使⽤.*运算符
(obj.*pf)();
return 0;
}

•  重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int operator+(int x, int y)

int operator+(int x, int y)
{
  return x - y;
}

错误    C2803    “operator +”必须至少有一个类类型的形参    运算重载    

个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如Date类重载operator-就有意义,但是重载operator+就没有意义。

• 重载++运算符时, 有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。

C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,⽅便区分。

• 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第一个形参位置是左侧运算对象,调用时就变成了 对象<<cout,不符合使用习惯和可读性。

重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第二个形参位置当类类型对

象。

#include <iostream>
using namespace std;
 
class Date {
public:
    // 构造函数
    Date(int year = 1, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }
 
    // 打印日期
    void Print() {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
 
    // 重载相等运算符
    bool operator==(const Date& d) {
        return _year == d._year
            && _month == d._month
            && _day == d._day;
    }
 
    // 重载前置自增运算符
    Date& operator++() {
        cout << "前置++" << endl;
        // 这里简单地将日期加一天
        _day++;
        /*
        // 如果当前天数超过30,简单处理月份
        if (_day > 30) { // 注意:这里没有处理每个月的天数差异
            _day = 1;
            _month++;
            if (_month > 12) { // 处理年份
                _month = 1;
                _year++;
            }
        }
        */
        return *this; // 返回自身的引用
    }
 
    // 重载后置自增运算符
    Date operator++(int) {
        Date tmp = *this; // 保存当前状态
        cout << "后置++" << endl;
        // 增加日期
        ++(*this); // 调用前置运算符,增加一天
        return tmp; // 返回自增前的对象
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main() {
    Date d1(2024, 7, 5);
    Date d2(2024, 7, 6);
    /*
    // 显式调用运算符重载
    d1.operator==(d2);
    // 隐式调用
    if (d1 == d2) {
        cout << "d1 is equal to d2" << endl;
    }
    else {
        cout << "d1 is not equal to d2" << endl;
    }
    */
    // 前置自增调用
    ++d1;
    cout << "After ++d1: ";
    d1.Print();
 
    // 后置自增调用
    d1++;
    cout << "After d1++: ";
    d1.Print();
 
    return 0;
}

2.2 赋值运算符重载

赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。

赋值运算符重载的特点:

1. 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成

const 当前类类型引用,否则会传值传参会有拷⻉

2. 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。

3. 没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。

4. 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。像Stack这样的类,虽然也都是 内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载,也不需要我们显示实现MyQueue的赋值运算符重载。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。

#include <iostream>
using namespace std;
 
class Date {
public:
    // 构造函数
    Date(int year = 1, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }
 
    // 打印日期
    void Print() {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
 
    // 拷贝构造函数
    Date(const Date& d) {
        cout << "Copy constructor called" << endl;
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
 
    // 赋值运算符重载
    Date& operator=(const Date& d) {
        // 自我赋值检查
        if (this != &d) {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
        return *this;
    }
 
    // 重载相等运算符
    bool operator==(const Date& d) const {
        return _year == d._year
            && _month == d._month
            && _day == d._day;
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main() {
    Date d1(2024, 7, 5);
    d1.Print();
 
    // 使用拷贝构造函数创建 d2
    Date d2(d1);
    d2.Print();
 
    // 创建一个新的日期 d3
    Date d3(2024, 7, 6);
    d1 = d3; // 使用赋值运算符
    cout << "After assignment, d1: ";
    d1.Print();
 
    // 比较 d1 和 d3
    if (d1 == d3) {
        cout << "d1 is equal to d3" << endl;
    }
    else {
        cout << "d1 is not equal to d3" << endl;
    }
 
    // 拷贝构造 d4
    Date d4 = d1;
    cout << "Date d4 (copy of d1): ";
    d4.Print();
 
    return 0;
}

3.取地址运算符重载

3.1const成员函数

将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。

• const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const

Date* const this

#include<iostream>
using namespace std;
class Date{
 
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// void Print(const Date* const this) const
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 这⾥⾮const对象也可以调⽤const成员函数是⼀种权限的缩⼩
Date d1(2024, 7, 29);
d1.Print();
const Date d2(2024, 7, 28);
d2.Print();
return 0;
}

3.2取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,一般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非一些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现一份,胡乱返回一个地址。

结束语

本期内容就到此结束,类的基本成员讲解也就差不多了,感谢各位友友的支持,欢迎大家在评论区多多交流!!!

目录
相关文章
|
9天前
|
安全 编译器 程序员
【C++篇】C++类与对象深度解析(六):全面剖析拷贝省略、RVO、NRVO优化策略
【C++篇】C++类与对象深度解析(六):全面剖析拷贝省略、RVO、NRVO优化策略
26 2
|
9天前
|
存储 C++
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
17 2
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
|
6天前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
6天前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
8天前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
20 3
|
8天前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
16 2
|
7天前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
41 1
|
8天前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
11 1
|
8天前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
18 1
|
1天前
|
编译器 C++ 数据库管理
C++之类与对象(完结撒花篇)(下)
C++之类与对象(完结撒花篇)(下)
6 0