《C++运算符重载深度解析:从加法、流操作到仿函数与类型转换》
目录
1、加法运算符的重载
类的初定义:
实现对象与对象之间的加法:
实现对象与变量的加法:
实现变量与对象的加法:
2、将对象转化为所需的内置类型(以int为例)
3、输出流和输入流的重载
输出流的重载
输入流的重载
4、单目运算符“++”和“--”的重置
5、重载函数调用(仿函数)
6、当常对象需要修改(以仿函数为例)
存在问题分析
1. const成员函数中非法修改成员变量
解决方案(mutable)
修复const成员函数的未定义行为
关键修改说明
总结
1、加法运算符的重载
类的初定义:
class Int
{
private:
int value;
public:
Int(int x = 0) :value(x)
{
cout << "create Int : " << value << " " << this << endl;
}
Int(const Int& it) :value(it.value)
{
cout << "copy create Int: " << value << " " << this << endl;
}
~Int()
{
cout << "destroy Int: " << value << " " << this << endl;
}
Int& operator=(const Int& it) //用const引用右值
{
if (this == &it) //防止自己给自己赋值
{
return *this;
}
value = it.value;
cout << "operator=" << endl;
return *this;
}
}
实现对象与对象之间的加法:
Int operator+(const Int &it)const
{
return Int(this->value + it.value);
}
例:c = a + b--------> c = a.operator+(b) ----> c = operator+(&a,b)
实现对象与变量的加法:
Int operator+(const int x)const
//用内置类型在形参时一般不用引用,引用需要访问内存两次
{
return Int(this->value + x);
}
例:c = a+10------>c = a.operator+(10) ----> c = operator+(&a,110)
思考:为什么用内置类型做形参时不使用引用?
因为引用在系统的底部是以指针来实现,相当于把实参的地址给形参,当在函数体内部使用该形参时,首先是将该形参变量的值找到,即找到变量的地址,这是第一次访问内存,接着再在该地址将变量的值找到,这是第二次访问内存;
如果不使用引用,以值进行传递时,只需要访问一次内存。
实现变量与对象的加法:
因为加法是一个双目运算符,如果在类里面实现加法运算符重载实现变量与对象的加法,将出现三个参数,所以应该在全局进行定义,但是因为该函数为全局函数,无法直接访问对象的私有属性,所以有三种方法解决:
第一种方法:
Int operator+(const int x, const Int& it)
{
return Int(x + it.Value());
//调用类中可以返回所需值的方法获取value值
}
例:c = 10 + a------> c = operator+(10,a)
第二种方法:
class Int
{
......
//类内将该方法设置为友元
friend Int operator+(const int x, const Int& it);
}
Int operator+(const int x, const Int& it)
{
return Int(x + it.value);
//调用类中可以返回所需值的方法获取value值
}
第三种方法:
将变量与对象相加转变为对象与常量相加:
Int operator+(const int x, const Int& it)
{
return it + x;
}
2、将对象转化为所需的内置类型(以int为例)
operator int() const{return value}
上述代码的功能是将对象强转为int类型,当需要将对象转化为int类型时,便会发生隐式转换,如果用explic关键字声明,隐式转换将不会发生
此处可以参考学习关于拷贝构造函数的类型转换
C++构造函数详解:从基础到类型转换机制_c++構造函數-CSDN博客
3、输出流和输入流的重载
输出流的重载
class Int
{
private:
int value;
public:
ostream& operator<<(ostream& out) const
{
out << value;
return out;
}
};
int main()
{
Int a(10);
a << cout;
//相当于a.operator<<(cout)
}
因为a << cout不符合常用习惯,改进如下:
class Int
{
private:
int value;
public:
//friend ostream& operator<<(ostream& out, const Int& it);
ostream& operator<<(ostream& out) const
{
out << value;
return out;
}
};
ostream& operator<<(ostream& out, const Int& it)
{
//out << it.Value();//使用成员方法访问类的属性
//out << it.value;//将全局函数定义为友元
it << out;//使用类内的输出流重载函数
return out;
}
在类外定义一个运算符重载函数的原因:
-
需要符合输出流在前变量在后的书写习惯
-
输出流运算符为双目运算符,需要两个参数,设计为成员方法会天然的多出一个this指针
输入流的重载
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Int
{
private:
int value;
public:
//friend istream& operator>>(ostream& in, const Int& it);
istream& operator>>(istream& in)
{
in >> value;
return in;
}
};
istream& operator>>(istream& in,Int& it)
{
//in >> it.Value();//使用成员方法访问类的属性
//in >> it.value;//将全局函数定义为友元
it >> in;//使用类内的输出流重载函数
return in;
}
4、单目运算符“++”和“--”的重置
前置++和后置++的区别
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Int
{
private:
int value;
public:
Int(int x):value(x)
{
return;
}
Int& operator++()//前置++的重载
{
value += 1;
return *this;
}
Int operator++(int)//后置++的重载
{
//第一种方法
//Int tmp(*this);
//this->value += 1;
//return tmp;
//第二种方法
//int x = this->value;
//++ *this; //调用前置++的重置函数
//return Int(x);
//第三种方法
return Int(this->value++);
}
};
在前置++的方案中,不需要重新创建对象;在后置++的方案中,每一次运算都需要创建一个亡值对象用于值的传递。
减号运算符重载与加号运算符相当
5、重载函数调用(仿函数)
两个重载的operator()
,允许对象像函数一样被调用(仿函数),替代c语言中的函数指针
class Add
{
private:
int value;
public:
Add(int x = 0):value(x){}
~Add() {}
int operator()(int x)
{
value = ++x;
return value;
}
int operator()(int x, int y)//仿函数
{
value = x + y;
return value;
}
};
int main()
{
Add x(0);
int a = 10, b = 20;
int c = x(10,20);
cout << c <<endl;//运算结果为30
int x = Add()(10, 20);//先构建一个不具名对象,然后调用双参数的函数调用重载
int y = Add{}(10);//先构建一个不具名对象,然后调用单参数的函数调用重载
return 0;
}
-
类定义:
-
私有成员:
int value
用于存储计算结果。 -
构造函数:
Add(int x = 0)
初始化value
,默认值为0。 -
析构函数:默认析构函数,无特殊操作。
-
-
成员函数:
-
单参数
operator()
: -
双参数
operator()
:
-
6、当常对象需要修改(以仿函数为例)
class Add
{
private:
int value;
public:
Add(int x = 0):value(x){}
~Add() {}
int operator()(int x)
{
value = ++x;
return value;
}
int operator()(int x, int y) const
{
*(int*)&value = x + y; //强转,首先用强转为int*去掉值的常性,然后再解引用就是value值的本身
//还有一种做法,如下:
//((Add*)this)->value = x +y;
return value;
}
};
int main()
{
const Add add(0);
int z = 0;
z = add(12,23);
return 0;
}
存在问题分析
1. const成员函数中非法修改成员变量
int operator()(int x, int y) const {
*(int*)&value = x + y; // 未定义行为
return value;
}
-
直接问题:
const
成员函数承诺不修改对象状态,但此处通过*(int*)&value
强制去掉const
属性并修改value
,违反const语义,属于未定义行为 (UB)。 -
后果: 可能引发程序崩溃、数据不一致或编译器优化导致的意外行为。
2. 类设计const正确性缺失
-
双参数
operator()
标记为const
,但实际修改了成员变量,破坏了const契约。
解决方案(mutable)
修复const成员函数的未定义行为
若需允许const
对象修改value
,应声明value
为mutable
:
class Add {
private:
mutable int value; // 允许const成员函数修改
public:
int operator()(int x, int y) const {
value = x + y; // 合法操作
return value;
}
};
关键修改说明
原代码问题 | 修改方案 | 作用 |
---|---|---|
const 函数内非法修改成员 | 声明value 为mutable | 允许const 函数修改特定成员 |
强制类型转换去const | 直接赋值(依赖mutable 合法性) | 消除未定义行为,确保代码安全 |
总结
-
避免强制类型转换去const:优先使用
mutable
或调整设计保证const正确性。 -
严格区分const/非const成员函数:非const函数用于修改状态,const函数用于只读操作。