21天学通C++读书笔记(十一:运算符类型与运算符重载)

1. C++运算符

  • C++运算符分两大类:单目运算符与双目运算符
  • 从语法层面看,除使用关键字operator外,运算符与函数几乎没有差别
// 其中operator_symbol可以是+、&&等
return_type operator operator_symbol (...parameter list...);
  • C++在支持函数的情况下为何还要提供运算符呢?
    • 如果要将这个Date对象指向下一天,下面两种方法哪种更方便、更直观呢?
    • 显然,方法1优于方法2:基于运算符的机制更容易使用,也更直观。
    Date holiday (12, 25, 2016);
    
    ++ holiday; // 方法1:使用递增运算符
    holiday.Increment(); // 方法2:使用成员函数Increment()
    

2. 单目运算符

  • 单目运算符只对一个操作数进行操作
2.1 单目运算符的类型
运算符名称运算符名称
++递增&取址
递减~求反
*解除引用+
->成员选择-
逻辑非转换运算符转换为其他类型
2.2 单目递增与单目递减运算符
  • 如果只想执行递增运算,可使用++object,也可使用object++,但应选择前者,这样避免创建一个未被使用的临时拷贝
#include <iostream>
using namespace std;

class Date {
private:
    int day, month, year;
public:
    Date (int inMonth, int inDay, int inYear)
        : month (inMonth), day(inDay), year (inYear) {};
    
    Date& operator ++ () { // 前置递增
        ++day;
        return *this;
    }
    Date& operator -- () { // 前置递减
        --day;
        return *this;
    }
    Date operator ++ (int) { // 后置递增
        Date copy(month, day, year);
        ++day;
        return copy;
    }
    Date operator -- (int) { // 后置递减
        Date copy(month, day, year);
        --day;
        return copy;
    }
    void DisplayDate() {
        cout << month << " / " << day << " / " << year << endl;
    }
};

int main () {
    Date holiday (12, 25, 2016);
    holiday.DisplayDate ();
    
    ++holiday;
    holiday.DisplayDate ();
    
    --holiday;
    holiday.DisplayDate ();
    return 0;
}
2.3 转换运算符
  • 要让cout能够显示Date对象,只需添加一个返回const char*的运算符
#include <iostream>
#include <sstream> // new include for ostringstream
#include <string>
using namespace std;

class Date {
private:
    int day, month, year;
    string dateInString;
public:
    Date(int inMonth, int inDay, int inYear)
        : month(inMonth), day(inDay), year(inYear) {};
   
    // 声明转换运算符时,务必使用关键字explicit,以禁止隐式转换
    explicit operator const char*() {
        ostringstream formattedDate; // 将整型成员转换成了一个std::string对象
        formattedDate << month << " / " << day << " / " << year;
        
        // 因为formattedDate是一个局部变量,将在运算符返回时被销毁,
        // 因此运算符返回时,通过str()获得的指针将无效。将其拷贝存储在私有成员Date::dateInString中可避免
        dateInString = formattedDate.str(); 
        return dateInString.c_str();
    }
};
   
int main () {
    Date Holiday (12, 25, 2016);
    cout << "Holiday is on: " << Holiday << endl;
    
    string strHoliday (static_cast<const char*>(Holiday));
    cout << "Holiday is on: " << Holiday << endl;

    strHoliday = static_cast<const char*>(Date(11, 11, 2016));
    cout << "Holiday is on: " << Holiday << endl;
    return 0;
}
2.4 解除引用运算符(*)和成员选择运算符(->)
  • 解除引用运算符(*)和成员选择运算符(->)在智能指针类编程中应用最广
  • 智能指针是封装常规指针的类,旨在通过管理所有权和复制问题以简化内存管理
  • 智能指针能够在指针离开作用域后释放其占用的内存
// 使用智能指针 std::unique_prt 管理动态分配的 Date 对象
#include <iostream>
#include <memory>  // new include to use unique_ptr
using namespace std;

class Date {
private:
    int day, month, year;
public:
    Date(int inMonth, int inDay, int inYear)
        : month(inMonth), day(inDay), year(inYear) {};
    void DisplayDate() {
        cout << month << " / " << day << " / " << year << endl;
    }
};

int main() {
    // 智能指针类 std::unique_ptr 实现了运算符*和->
    unique_ptr<int> smartIntPtr(new int); // 智能指针类unique_ptr的模板初始化
    *smartIntPtr = 42;
    cout << "Integer value is: " << *smartIntPtr << endl;
    
    unique_ptr<Date> smartHoliday (new Date(12, 25, 2016));
    cout << "The new instance of date contains: ";
    smartHoliday->DisplayDate();
    return 0;
}

3. 双目运算符

  • 对两个操作数进行操作的运算符称为双目运算符
3.1 双目运算符的类型
运算符名称运算符名称
,逗号<小于
!=不等于<<左移
%求模<<=左移并赋值
%=求模并赋值<=小于或等于
&按位与=赋值、复制赋值和移动赋值
&&逻辑与==等于
&=按位与并赋值>大于
*>=大于或等于
*=乘并赋值>>右移
+>>=右移并赋值
+=加并赋值^异或
^=异或并赋值
−=减并赋值按位或
->*指向成员的指针按位或并赋值
/逻辑或
/=除并赋值[]下标运算符
3.2 双目加法与双目减法运算符
#include <iostream>
using namespace std;
   
class Date {
private:
    int day, month, year;
public:
    Date (int inMonth, int inDay, int inYear)
        : month(inMonth), day(inDay), year(inYear) {};

    Date operator + (int daysToAdd) {
        Date newDate (month, day + daysToAdd, year);
        return newDate;
    }
    Date operator - (int daysToSub) {
        return Date (month, day - daysToSub, year);
    }
    void DisplayDate() {
        cout << month << " / " << day << " / " << year << endl;
    }
};

int main() {
    Date Holiday (12, 25, 2016);
    cout << "Holiday on: ";
    Holiday.DisplayDate ();
    
    Date PreviousHoliday (Holiday - 19);
    cout << "Previous holiday on: ";
    PreviousHoliday.DisplayDate();
    
    Date NextHoliday(Holiday + 6);
    cout << "Next holiday on: ";
    NextHoliday.DisplayDate ();
    return 0;
}
3.3 实现运算符+=与−=
    void operator+= (int daysToAdd) {
        day += daysToAdd;
    }
    void operator-= (int daysToSub) {
        day -= daysToSub;
    }
3.4 重载等于运算符(==)和不等运算符(!=)
    bool operator== (const Date& compareTo) {
        return ((day == compareTo.day)
                && (month == compareTo.month)
                && (year == compareTo.year));
    }
    bool operator!= (const Date& compareTo) {
        return !(this->operator==(compareTo));
    }
3.5 重载运算符<、>、<=和>=
    bool operator< (const Date& compareTo) {
        if (year < compareTo.year)
            return true;
        else if ((year == compareTo.year) && (month < compareTo.month))
            return true;
        else if ((year == compareTo.year) && (month == compareTo.month) 
                && (day < compareTo.day))
            return true;
        else
            return false;
    }
    bool operator<= (const Date& compareTo) {
        if (this->operator== (compareTo))
            return true;
        else
            return this->operator< (compareTo);
    }
    bool operator > (const Date& compareTo) {
        return !(this->operator<= (compareTo));
    }
    bool operator== (const Date& compareTo) {
        return ((day == compareTo.day)
            && (month == compareTo.month)
            && (year == compareTo.year));
    }
    bool operator>= (const Date& compareTo) {
        if(this->operator== (compareTo))
            return true;
        else
            return this->operator> (compareTo);
    }
3.6 重载复制赋值运算符(=)
  • 有时候,需要将一个类实例的内容赋给另一个类实例
  • 与复制构造函数一样,为确保进行深复制,需要提供复制赋值运算符
Date holiday(12, 25, 2016);
Date anotherHoliday(1, 1, 2017);
anotherHoliday = holiday;

ClassType& operator= (const ClassType& copySource) {
    if(this != &copySource) {
        // copy assignment operator implementation
    }
    return *this;
}
  • 如果编写的类管理着动态分配的资源(如使用new分配的数组),除构造函数和析构函数外,请务必实现复制构造函数和复制赋值运算符
  • 要创建不允许复制的类,可将复制构造函数和复制赋值运算符都声明为私有的
3.7 下标运算符
  • 下标运算符让您能够像访问数组那样访问类
return_type& operator [] (subscript_type& subscript);
  • 编写封装了动态数组的类(如封装了char* buffer的MyString)时,通过实现下标运算符,可轻松地随机访问缓冲区中的各个字符
// 第一个const,可禁止从外部通过运算符[]直接修改成员
// 第二个const,可禁止修改类的成员属性
class MyString {
// ... other class members
public:
    const char& operator [] (int index) const {
    // return the char at position index in buffer
    }
};

一般而言,应尽可能使用const,以免无意间修改数据,并最大限度地保护类的成员属性

4. 函数运算符operator()

#include <iostream>
#include <string>
using namespace std;

class Display {
public:
    void operator () (string input) const { // 这个运算符称为operator()函数
        cout << input << endl;
    }
};

int main () {
    Display displayFuncObj; // Display对象称为函数对象
    
    // displayFuncObj.operator () ("Display this string!"); // 与下行代码等价
    displayFuncObj ("Display this string!"); //编译器隐式地将它转换为对函数operator()的调用
    return 0;
}

5. 用于高性能编程的移动构造函数和移动赋值运算符

  • 旨在避免复制不必要的临时值(当前语句执行完毕后就不再存在的右值)
  • 对于那些管理动态分配资源的类,如动态数组类或字符串类很有用
5.1 不必要的复制带来的问题
  • 这个加法运算符(+)让您能够式轻松地拼接字符串,但也可能导致性能问题
    • 创建sayHello时,需要执行加法运算符两次,而每次都将创建一个按值返回的临时拷贝,导致执行复制构造函数,复制构造函数执行深复制,而生成的临时拷贝在该表达式执行完毕后就不再存在
MyString Hello("Hello ");
MyString World("World");
MyString CPP(" of C++");
MyString sayHello(Hello + World + CPP); // operator+, copy constructor

解决方法:编译器意识到需要创建临时拷贝时,将转而使用移动构造函数和移动赋值运算符

5.2 声明移动构造函数和移动赋值运算符
  • 在需要创建临时右值时,遵循C++的编译器将使用移动构造函数和移动赋值运算符
  • 移动构造函数和移动赋值运算符的实现中,只是将资源从源移到目的地,而没有进行复制
class Sample {
private:
    Type* ptrResource;
public:
    // 由于输入参数是要移动的源对象,因此不能使用const进行限定,因为它将被修改
    // 输入参数的类型为 Sample&&
    Sample(Sample&& moveSource) { // 移动构造函数
        ptrResource = moveSource.ptrResource; // take ownership, start move
        moveSource.ptrResource = NULL; // free move source of ownership
    }
    Sample& operator= (Sample&& moveSource) { // 移动赋值运算符
        if(this != &moveSource) {
            delete [] ptrResource; // free own resource
            ptrResource = moveSource.ptrResource;
            moveSource.ptrResource = NULL;
        }
    }

    Sample(); // 默认构造函数
    Sample(const Sample& copySource); // 复制构造函数
    Sample& operator= (const Sample& copySource); // 复制赋值运算符
};
  • 在没有移动构造函数时,将调用复制构造函数,它对指向的字符串进行深复制
  • 移动构造函数避免了不必要的内存分配和复制步骤,从而节省了大量的处理时间
  • 移动构造函数和移动赋值运算符是可选的
  • 如果没有提供移动构造函数和移动赋值运算符,编译器并不会添加默认实现

6. 用户定义的字面量

  • 在下述代码中,10000、3.14、‘a’和“Hello!”都是字面量常量
int bankBalance = 10000;
double pi = 3.14;
char firstAlphabet = 'a';
const char* sayHello = "Hello!";

C++增大了对字面量的支持力度,让您能够自定义字面量

  • 参数ValueType 只能是下面几个之一,具体使用哪个取决于用户定义字面量的性质
    • unsigned long long int:用于定义整型字面量
    • long double:用于定义浮点字面量
    • char、wchar_t、char16_t 和char32_t:用于定义字符字面量
    • const char*:用于定义原始字符串字面量
    • const char*和size_t:用于定义字符串字面量
    • const wchar_t*和size_t:用于定义字符串字面量
    • const char16_t*和size_t:用于定义字符串字面量
    • const char32_t*和size_t:用于定义字符串字面量
    ReturnType operator "" YourLiteral(ValueType value) {
    // conversion code here
    }
    
#include <iostream>
using namespace std;

struct Temperature {
    double Kelvin;
    Temperature(long double kelvin) : Kelvin(kelvin) {}
};

// 
Temperature operator"" _C(long double celcius) {
    return Temperature(celcius + 273);
}

Temperature operator "" _F(long double fahrenheit) {
    return Temperature((fahrenheit + 459.67) * 5 / 9);
}

int main() {
    Temperature k1 = 31.73_F;
    Temperature k2 = 0.0_C;
    
    cout << "k1 is " << k1.Kelvin << " Kelvin" << endl;
    cout << "k2 is " << k2.Kelvin << " Kelvin" << endl;
    return 0;
}

7. 不能重载的运算符

运算符名称运算符名称
.成员选择?:条件三目运算符
.*指针成员选择sizeof获取对象/类类型的大小
::作用域解析
Q&A
  • 自己编写智能指针类时,至少需要实现哪些函数和运算符?

    • 智能指针必须能够像常规指针那样使用,如 *pSmartPtr 或pSmartPtr->Func()。为此,需要实现运算符 *和->。要确保它足够智能,还需合理地编写析构函数,以自动释放/归还资源;另外,还需实现复制构造函数和复制赋值运算符,以明确定义复制和赋值的方式(也可将复制构造函数和复制赋值运算符声明为私有的,以禁止复制和赋值)
  • 编写的类封装了一个动态整型数组,请问我至少应该实现哪些函数和方法?

    • 编写这样的类时,必须明确定义下述情形下的行为:通过赋值直接复制对象或通过按值传递给函数间接复制对象。通常,应实现复制构造函数、复制赋值运算符和析构函数。另外,如果想改善这个类在某些情况下的性能,还应实现移动构造函数和移动赋值运算符。要能够像访问数组一样访问类实例存储的元素,可重载下标运算符operator[]
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值