C++类细节梳理之运算符重载

本文详细解释了C++中运算符重载的概念,包括一元和二元运算符的重载方法,不可重载的运算符,以及如何通过友元函数处理输入输出。文章还举例说明了自增、自减、赋值和下标运算符的重载,并讨论了友元函数在处理类私有变量的应用以及字符串操作的注意事项。
摘要由CSDN通过智能技术生成

运算符重载可以看做是一个有特殊名称的函数,由operator和要重载的运算符构成

return_type class_name::operator op(operand2) {}
return_type & class_name::operator op(operand2) {}

类成员函数运算符重载

重载一元运算符时没有参数,操作的是该对象本身,通过this指针隐式传递;重载二元运算符时,只需显示传递一个参数(作为二元运算符的右操作数),而左操作数是对象本身,用this指针隐式传递

可以重载的运算符
运算符操作符
二元算术运算符+,-,*,/,%
关系运算符==,!=,<,>,<=,>=
逻辑运算符||,&&,!
位运算符|,&,~,……,<<,>>,
单目运算符+(正),-(负),*(指针),&(取地址))
自增自减运算符++,--
赋值运算符=,+=,-=,/=,%=,&=,|=,^=,<<=,>>=
其他运算符()(函数调用),->(成员访问),[](下标)
空间申请释放new,delete,new[],delete[]

不可以重载的运算符:

  • ::(作用域解析)
  • .(成员访问)
  • .*(通过指针成员的访问)
  • ? :(三元运算符)
  • 除new,delete外的关键字运算符
  • # (预处理符号)

此外还有其他限制:

  1. 不能创建新运算符
  2. ->要么返回裸指针,要么按引用或值返回同样重载了运算符->的对象
  3. 不能更改运算符优先级,结合方向和操作数数量
  4. &&与||的重载失去短路求值

&&与||的重载失去短路求值

先回顾之前的知识,对于A||B,我们知道当A为真时编译器就不会再检测B是否为真;同理对于A&&B,A为假时也不会检查B是否为真。而&&与||重载之后便会失去上述特性,不管A是否为真,编译器都会检查B的值。

我们来看一个例子:

class Boolean {
public:
	Boolean() {}
	Boolean(int val) :val(val) {}
	bool operator && (const Boolean& other);
	bool operator || (const Boolean& other);
	int getVal() { return val; }
private:
	int val;
};
bool Boolean::operator && (const Boolean& other){
    if(val && other.val){
        return true;
    }
    else{
        return false;
    }

}
bool Boolean::operator || (const Boolean& other){
    if(val || other.val){
        return true;
    }
    else{
        return false;
    }
}
Boolean booleanTest(Boolean i){
    std::cout << "Boolean func(Boolean i) : i.val = " << i.getVal() << std::endl;
    return i.getVal();

}
bool boolTest(int i){
    std::cout << "bool func(int i) : i = " << i << std::endl;
    return i;
}

这里为了方便观察在booleanTest和boolTest函数中加入了输出,然后用以下代码检测:

#include <iostream>
#include "Boolean.hpp"
using namespace std;

int main() {
	const char* TRUE = "Result is true";
	const char* FALSE = "Result is false";
	int x, y;
	cin >> x >> y;
	Boolean a = Boolean(x);
	Boolean b = Boolean(y);
	if (boolTest(x) && boolTest(y)) {
		cout << TRUE << endl;
	}
	else {
		cout << FALSE << endl;
	}
	if (boolTest(x) || boolTest(y)) {
		cout << TRUE << endl;
	}
	else {
		cout << FALSE << endl;
	}
	if (booleanTest(a) && booleanTest(b)) {
		cout << TRUE << endl;
	}
	else {
		cout << FALSE << endl;
	}
	if (booleanTest(a) || booleanTest(b)) {
		cout << TRUE << endl;
	}
	else {
		cout << FALSE << endl;
	}
	return 0;
}

当x,y为3和4时,得到这样的输出:

bool func(int i) : i = 3
bool func(int i) : i = 4
Result is true
bool func(int i) : i = 3
Result is true
Boolean func(Boolean i) : i.val = 3
Boolean func(Boolean i) : i.val = 4
Result is true
Boolean func(Boolean i) : i.val = 3
Boolean func(Boolean i) : i.val = 4
Result is true

分析结果我们发现,在执行if (boolTest(x) || boolTest(y))语句的时候只输出了bool func(int i) : i = 3这是因为在boolTest(x) || boolTest(y)中boolTest(x)为真,所以并未执行boolTest(y)。然而,到了运算符重载的时候,我们发现两者都有输出,这就是我们所说的失去短路求值。

使x,y为0和1得到下面的输出:

bool func(int i) : i = 0
Result is false
bool func(int i) : i = 0
bool func(int i) : i = 1
Result is true
Boolean func(Boolean i) : i.val = 0
Boolean func(Boolean i) : i.val = 1
Result is false
Boolean func(Boolean i) : i.val = 0
Boolean func(Boolean i) : i.val = 1
Result is true

双目,单目的实现例子

Integer operator - (const Integer& Int){
    return Integer(x - Int.x);
}
Integer operator - (){
    return Integer(-x);    
}

要注意的是左操作数必须是*this。对于双目运算中加减时,可以注意到我们是重新创建了一个新的类,然后将加减后的结果赋值给这个类,也就是说,双目运算符的加减时是不会改变类本身的值的。

自增运算符

前缀自增运算符()内无东西,后缀自增运算符()内有一个int的虚拟形参,但是这个虚拟形参并不会被使用,只是告诉编译器在后缀模式下被重载

实现例子:

Integer &Integer::operator ++ (){
    x++;
    return *this;
}//前缀自增
Integer Integer::operator ++ (int){
    Integer a(x);
    x++;
    return a;
}//后缀自增

前缀一般增加后返回自身,所以习惯上在前面添加一个引用,然后返回*this;后缀一般先拷贝一个复制体,然后再自增,最后返回复制体。

自减与自增一样

赋值运算符

当有指针成员时要记得深拷贝赋值

Matrix& Matrix::operator=(const Matrix& other){
    rows = other.rows;
	cols = other.cols;
	int* new_data = new int[rows * cols];
    for (int i = 0; i < rows; i++) {
		for (int j = 0; j < cols; j++) {
            int idx = i * cols + j;
			new_data[idx] = other.data[idx];
		}
	}
    delete [] data;
    data = new_data;
    return *this;
}

下标运算符

值得注意的是下标运算符[ ]和指针的成员->不适用于在类外定义

声明格式如下

Integer &operator[](int i);
const Integer &operator[](int i) const;

我们最好把两种形式都写了,这样做是为了适应const对象,因为通过const对象只能调用const成员函数,如果不提供const的形式则无法访问const对象的任何元素

在重载下标运算符时我们有时候可能会遇到类似于二维数组的情况:

int main() {
    int m, n;
    cin >> m >> n;
    Matrix matrix(m, n);
    cin >> matrix;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            matrix[i][j] += i;
        }
    }
    cout << matrix;
    return 0;
}

class Matrix {
public:
	Matrix() :rows(0), cols(0), data(NULL) {}
	Matrix(int rows, int cols) :rows(rows), cols(cols) {
		data = new int[rows * cols];
	}
	~Matrix() {
		delete[] data;
	}
	int* operator[](int );
	const int* operator[](int ) const;
	friend istream& operator>>(istream& is, Matrix& matrix);
	friend ostream& operator<<(ostream& os, const Matrix& matrix);
private:
	int rows, cols;
	int* data;
};

由于下标运算符重载的返回类型为 int* 这时编译器只会对第一个[ ]进行重载,因为在对第一个[ ]重载了之后返回的int*与[ ]结合不会重载。如果返回类型为Matrix的话应该会与第二个[ ]进行重载。所以对于实际情况,在写下标运算符重载的时候我们应该倍乘矩阵的列数

int* Matrix::operator[](int i){
    return data + cols * i;
}
const int* Matrix::operator[](int i) const{
    return data + i * cols;
}

 类外定义运算符重载

使用类外定义运算符重载,用户自定义的对象x也可以cin>>x读入,cout<<x输出;对于第一操作数不是该类的类型的左值对象(Integer c = 4 + a)也可以执行

struct Integer{
    int x;
};
Integer operator+(const Integer&a, const Integer&b){
    return Integer(a.x + b.x);
}
int main(){
    Integer a = 3, b;
    b = 4 + a;
}

在主函数中先会构造一个类并将4赋值给x,后进行运算符重载

但是上面的代码有一个问题,对于class来说,x是私有变量,这样外部函数operator+就无法访问x了,为解决这个问题,我们需要了解友元函数

友元函数

友元函数在类的内部声明friend普通函数,然后在类外实现该函数。友元函数不属于这个类,但是可以访问类的全部变量及函数

其他类的成员函数也可以是友元函数,其他类也可以是友元函数

#include <iostream>
using namespace std;
class Box
{
    double width;
public:
    friend void printWidth(Box box);
    friend class BigBox;//友元类
    friend bool BigBox::operator()(const Box &a, const Box &b);
    void setWidth(double wid);
};
class BigBox
{
public :
    bool operator()(const Box &a, const Box &b);
    void Print(int width, Box &box)
    {
        // BigBox是Box的友元类,它可以直接访问Box类的任何成员
        box.setWidth(width);
        cout << "Width of box : " << box.width << endl;
    }
};
// 成员函数定义
void Box::setWidth(double wid)
{
    width = wid;
}

// 请注意:printWidth() 不是任何类的成员函数,不要写成Box::printWidth()
void printWidth(Box box)
{
    /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
    cout << "Width of box : " << box.width << endl;
}

int main()
{
    Box box;
    BigBox big;
    // 使用成员函数设置宽度
    box.setWidth(10.0);
    // 使用友元函数输出宽度
    printWidth(box);
    // 使用友元类中的方法设置宽度
    big.Print(20, box);
    return 0;
}

当重载函数被声明为友元函数时,=, ->, [], ()由于不可以是全局域中的重载,故也不能重载为友元函数

输入/输出运算符重载

我们需要把运算符重载函数声明为类的友元函数,这样可以避免创建对象并且直接对其私有变量进行输入输出

class Fraction {
    friend istream& operator>>(istream& is, Fraction& f);
    friend ostream& operator<<(ostream& out, const Fraction& f);
};
istream& operator>>(istream& is, Fraction& f){
    is >> f.numerator >> f.denominator;
    int gcd = findGCD(f.numerator, f.denominator);
        f.numerator /= gcd;
        f.denominator /= gcd;
    return is;
}
ostream& operator<<(ostream& out, const Fraction& f){
    out << f.numerator << "/" << f.denominator;
    return out;
}

对于上面的这句out << f.numerator << "/" << f.denominator;编译器会先输出f.numerator的值,后返回一个输出流对象out,这就是为什么我们可以一直输出下去的原因,所以一定要记得返回输出流对象。然后记得从流中提取输入的值和将要输出的值输出到流中

get函数

get()函数是cin输入流对象的成员函数,可以从输入流中提取一个字符并且赋值给括号内的字符

istream& operator>>(istream& is, String& s){
    s.clear();
    char c;
    for( ; is.get(c) && isspace(c) == 0 ; ){
        s += c;
    }
    return is;
}

String StringJoiner::operator()(const String& a, const String& b) const{
    char short_time[a.getSize() + b.getSize() + 1];//没有'/0'的空间,内存测试不通过
    char short_time[a.getSize() + b.getSize() + 2];//可以通过
    strcpy(short_time, a.c_str());
    strcat(short_time, ",");
    strcat(short_time, b.c_str());
    String res(short_time);
    return res;
}

最后一点补充:对于字符串一定要留够末尾'/0'的空间,如没有则程序无法检测到字符串的结尾最后会导致内存泄露

由于学习不精,文章有不正确的地方望读者指正。

  • 18
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值