文章目录
- 4 类和对象(类属性【成员属性】,类函数【成员函数】)
- 4.5 运算符重载(对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型)
- 4.5.0.1 可重载运算符和不可重载运算符
- 4.5.0.2 重载运算符基本语法规则
- 4.5.1 加号运算符重载(operator+)(可以用成员函数实现和全局函数实现,注意区别,注意有的有限制,只能眼红成员函数或者只能用全局函数实现)
- 示例
- 用成员函数和全局函数(或类外友元函数)实现运算符重载的区别(成员函数运算符重载使用隐式的 this 指针作为左操作数)(全局函数运算符重载需要将左操作数和右操作数作为参数传递)(成员函数的优先级更高)
- 4.5.2 左移运算符重载(输出自定义数据类型)(ostream& operator<<)(注意全局函数实现时,如果用到类中私有成员,不要忘记在类中声明友元)
- 示例
- 4.5.3 递增运算符重载(前置递增和后置递增)(++)
- 示例
- 4.5.4 赋值运算符重载(对属性值进行拷贝)(这个用得多不多啊,搞得语法有点乱不是么?用得很多哎,特别是自己实现一个类的时候!)
- 示例
- 4.5.5 关系运算符重载(等号=与不等号!=)
- 示例
- 4.5.6 函数调用运算符重载(仿函数)(把对象名,当作函数那样去调用)
- 示例
黑马程序员C++教程
4 类和对象(类属性【成员属性】,类函数【成员函数】)
4.5 运算符重载(对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型)
C++中的运算符重载是一种特性,允许重新定义已存在的运算符的行为,以适应自定义类型的操作。通过运算符重载,可以为用户定义的类创建自定义的运算符行为,使其与内置类型的操作一样自然和直观。
4.5.0.1 可重载运算符和不可重载运算符
4.5.0.2 重载运算符基本语法规则
运算符函数必须是类的成员函数或友元函数。如果想将运算符重载为类的成员函数,则运算符函数必须在类定义中声明和定义。如果想将运算符重载为友元函数,则需要在类定义中声明友元函数,但在类外部定义该函数。
运算符函数的名称必须遵循特定的命名约定。运算符函数的名称由关键字operator后面跟随要重载的运算符组成。例如,如果要重载加法运算符+,则函数名称为operator+。
运算符函数的参数列表取决于运算符的操作数。对于二元运算符(例如加法、减法),运算符函数应该有一个参数,该参数表示运算符的右操作数。对于一元运算符(例如取负、自增),运算符函数不带参数。
运算符函数可以作为成员函数或非成员函数进行定义。如果将运算符函数定义为成员函数,那么左操作数将自动成为调用对象,右操作数作为函数的参数传递。如果将运算符函数定义为非成员函数(友元函数),则需要在参数列表中显式地传递两个操作数。
运算符函数的返回类型通常是该运算符操作结果的类型。对于赋值运算符,通常返回引用以支持连续赋值。
以下是一个示例,演示了如何重载加法运算符+:
#include <iostream>
#include <string>
using namespace std;
class MyNumber
{
public:
int value;
public:
MyNumber(int val) : value(val) {}
MyNumber operator+(const MyNumber &other)
{
int sum = value + other.value;
return MyNumber(sum);
}
};
int main()
{
MyNumber num1(5);
MyNumber num2(10);
MyNumber sum = num1 + num2; // 调用重载的+运算符
cout << sum.value << endl; // 15
return 0;
}
在上述示例中,operator+函数被定义为MyNumber类的成员函数。它接受一个MyNumber类型的参数作为右操作数,并返回一个新的MyNumber对象,表示两个操作数的和。
请注意,不是所有的运算符都可以被重载,例如.
(成员访问运算符)和::
(作用域解析运算符)不能被重载。此外,一些运算符的优先级和关联性是固定的,无法通过重载进行更改。因此,在重载运算符时,应该遵循C++的规则和约定,以保持代码的可读性和可维护性。
4.5.1 加号运算符重载(operator+)(可以用成员函数实现和全局函数实现,注意区别,注意有的有限制,只能眼红成员函数或者只能用全局函数实现)
作用:实现两个自定义数据类型相加的运算
示例
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(){};
// Person(int a, int b)
// {
// this->m_A = a;
// this->m_B = b;
// }
Person(int a, int b) : m_A(a), m_B(b)
{
}
// 成员函数实现 + 号运算符重载(调用它的对象(this)+(p)?)
Person operator+(const Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
Person operator+(int val)
{
Person temp;
temp.m_A = this->m_A + val;
temp.m_B = this->m_B + val;
return temp;
}
public:
int m_A;
int m_B;
};
// 全局函数实现 + 号运算符重载(如果与成员函数有相同的重载,则优先成员函数)
// Person operator+(const Person& p1, const Person& p2) {
// Person temp(0, 0);
// temp.m_A = p1.m_A + p2.m_A;
// temp.m_B = p1.m_B + p2.m_B;
// return temp;
// }
Person operator+(const Person &p2, int val)
{
Person temp;
temp.m_A = p2.m_A + val;
temp.m_B = p2.m_B + val;
return temp;
}
void test()
{
Person p1(10, 10);
Person p2(20, 20);
// 成员函数方式
Person p3 = p2 + p1; // 相当于 p2.operaor+(p1)
cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
// 全局函数方式
Person p4 = p3 + 10; // 相当于 operator+(p3,10)
cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
mA:30 mB:30
mA:40 mB:40
用成员函数和全局函数(或类外友元函数)实现运算符重载的区别(成员函数运算符重载使用隐式的 this 指针作为左操作数)(全局函数运算符重载需要将左操作数和右操作数作为参数传递)(成员函数的优先级更高)
-
成员函数实现运算符重载:
- 成员函数运算符重载是将重载函数定义为类的成员函数。在重载函数内部,可以直接访问类的成员变量和成员函数。
- 成员函数运算符重载使用隐式的 this 指针作为左操作数,而右操作数作为函数的参数。
- 成员函数运算符重载对于类的私有成员变量和函数有直接的访问权限,无需使用访问器或者修改类的封装性。
- 在代码中,
Person operator+(const Person &p)
和Person operator+(int val)
是成员函数运算符重载的例子。
-
全局函数实现运算符重载:
- 全局函数运算符重载是将重载函数定义为全局函数或者类外的友元函数。它们与类的成员函数没有直接的关联。
- 全局函数运算符重载需要将左操作数和右操作数作为参数传递。
- 全局函数运算符重载对于类的私有成员变量和函数没有直接的访问权限,如果需要访问私有成员,需要使用类的公有接口(访问器或者友元函数)。
总结:
- 成员函数运算符重载可以直接访问类的成员变量和成员函数,但对于私有成员的访问权限比较方便。
- 全局函数运算符重载无法直接访问类的私有成员,需要通过公有接口访问,但可以用于扩展类的操作符功能或处理与类无关的类型。
- 如果同时定义了成员函数和全局函数的运算符重载,成员函数的优先级更高,会优先调用成员函数的重载运算符。
4.5.2 左移运算符重载(输出自定义数据类型)(ostream& operator<<)(注意全局函数实现时,如果用到类中私有成员,不要忘记在类中声明友元)
https://www.bilibili.com/video/BV1et411b73Z?p=122
看了视频教程才看明白,
作用:可以输出自定义数据类型
示例
#include <iostream>
#include <string>
using namespace std;
class Person
{
friend ostream &operator<<(ostream &out, Person &p);
public:
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
// 成员函数 实现不了 p << cout 不是我们想要的效果
// void operator<<(Person& p){
// }
private:
int m_A;
int m_B;
};
// 全局函数实现左移重载
// ostream对象只能有一个
ostream &operator<<(ostream &out, Person &p) // arnold:对ostream对象重载了左移运算符
{
out << "a:" << p.m_A << " b:" << p.m_B;
return out;
}
void test()
{
Person p1(10, 20);
// cout << p1 << "hello world" << endl; // 链式编程
// 相当于:
cout << p1;
cout << "hello world";
cout << endl;
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
a:10 b:20hello world
总结:重载左移运算符配合友元可以实现输出自定义数据类型
4.5.3 递增运算符重载(前置递增和后置递增)(++)
详细分析可见此:C++运算符重载,前置自增(前置++)和后置自增(后置++)代码分析
在C++中,前置递增(++)和后置递增(++)运算符可以通过函数重载进行区分。重载函数可以根据参数的不同来实现不同的行为。
-
前置递增运算符使用单个参数,并且不需要额外的参数来区分前置和后置递增。因此,通过重载函数的名称和参数列表就可以区分前置递增运算符的重载。
-
后置递增运算符重载函数需要使用一个额外的(并且没有实际意义的)int参数来区分它们与前置递增运算符的重载。这个额外的int参数是一个占位符,用于在编译器中区分前置和后置递增。因为后置递增运算符在递增操作之前返回对象的副本,所以它需要一个区分标记来指示是使用后置递增重载。
示例
#include <iostream>
using namespace std;
class MyInteger
{
friend ostream &operator<<(ostream &out, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
// 前置++
MyInteger &operator++()
{
// 先++
m_Num++;
// 再返回
return *this;
}
// 后置++
MyInteger operator++(int)
{
// 先返回
MyInteger temp = *this; // 记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream &operator<<(ostream &out, MyInteger myint)
{
out << myint.m_Num;
return out;
}
// 前置++ 先++ 再返回
void test01()
{
MyInteger myInt;
cout << ++myInt << endl; // 1
cout << myInt << endl; // 1
}
// 后置++ 先返回 再++
void test02()
{
MyInteger myInt;
cout << myInt++ << endl; // 0
cout << myInt << endl; // 1
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
总结: 前置递增返回引用,后置递增返回值
4.5.4 赋值运算符重载(对属性值进行拷贝)(这个用得多不多啊,搞得语法有点乱不是么?用得很多哎,特别是自己实现一个类的时候!)
c++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
示例
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(int age)
{
// 将年龄数据开辟到堆区
m_Age = new int(age);
}
// 重载赋值运算符
Person &operator=(Person &p)
{
// if(this->m_Age != NULL)
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
// 编译器提供的代码是浅拷贝
// m_Age = p.m_Age;
// 提供深拷贝 解决浅拷贝的问题
m_Age = new int(*p.m_Age);
// 返回自身
return *this;
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
// 年龄的指针
int *m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1; // 赋值操作
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main()
{
test01();
int a = 10;
int b = 20;
int c = 30;
c = b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
system("pause");
return 0;
}
运行结果:
p1的年龄为:18
p2的年龄为:18
p3的年龄为:18
a = 10
b = 10
c = 10
4.5.5 关系运算符重载(等号=与不等号!=)
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
示例
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
};
bool operator==(Person &p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) // string类型能用=比较,那是因为string类型也重载了等号运算符
{
return true;
}
else
{
return false;
}
}
bool operator!=(Person &p) // 为啥不直接写个equal函数?(因为本来就是要重载=运算符啊!)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
string m_Name;
int m_Age;
};
void test01()
{
// int a = 0;
// int b = 0;
Person a("孙悟空", 18);
Person b("孙悟空", 18);
if (a == b)
{
cout << "a和b相等" << endl;
}
else
{
cout << "a和b不相等" << endl;
}
if (a != b)
{
cout << "a和b不相等" << endl;
}
else
{
cout << "a和b相等" << endl;
}
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果:
a和b相等
a和b相等
4.5.6 函数调用运算符重载(仿函数)(把对象名,当作函数那样去调用)
- 函数调用运算符 () 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
示例
#include <iostream>
#include <string>
using namespace std;
class MyPrint
{
public:
void operator()(string text)
{
cout << text << endl;
}
};
void test01()
{
// 重载的()操作符 也称为仿函数
MyPrint myFunc;
myFunc("hello world");
}
class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
};
void test02()
{
MyAdd add;
int ret = add(10, 10);
cout << "ret = " << ret << endl;
// 匿名对象调用
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
运行结果:
hello world
ret = 20
MyAdd()(100,100) = 200