C++语言定义了大量运算符以及内置类型的自动转换规则,当运算符被用于类类型的对象时,C++语言允许我们为其指定新的含义,但无权发明新的运算符号
可以被重载的运算符
例重载+运算符的
//example1.cpp
class Person
{
public:
int age;
;
string name(const int &age, const string &name) : age(age), name(name) {}
Personoperator+(Person &person);
Person void print()
{
<< age << " " << name << endl;
cout }
};
::operator+(Person &person)
Person Person{
return Person(age + person.age, name + " " + person.name);
}
int main(int argc, char **argv)
{
(19, "me");
Person person1(19, "she");
Person person2
= person1 + person2; //隐式调用
Person person3 .print(); // 38 me she
person3
= person1.operator+(person2); //显式调用
Person person4 .print(); // 38 me she
person4return 0;
}
例如取址运算符与逗号运算符,它们本就有它们的存在的意义,重载它们使得规则变得混乱,一般来说它们不应被重载
而逻辑与、逻辑或涉及到短路求值问题,通常情况下,不应重载逗号、取地址、逻辑与、逻辑或运算符
重载运算符得返回类型通常情况应该与其内置版本得返回类型兼容:逻辑运算符和关系运算符返回 bool、算数运算符返回一个类类型的值,赋值运算符和复合赋值运算符则应该返回左侧运算符对象的一个引用
//example2.cpp
class Person
{
public:
int age;
(const int &age) : age(age){};
Personbool operator<(const Person &person)
{
return age < person.age;
}
&operator+=(const Person &person)
Person {
+= person.age;
age return *this;
}
};
//写为非成员函数 判断 person2>person1关系
bool operator>(const Person &person1, const Person &person2)
{
return person2.age > person1.age;
}
int main(int argc, char **argv)
{
(1);
Person person1(2);
Person person2<< (person1 < person2) << endl; // 1
cout += person2;
person1 << person1.age << endl; // 3
cout
<< (person1 > person2) << endl; // 0
cout //显式调用
<< operator>(person1, person2) << endl; // 0
cout
return 0;
}
可见在example2.cpp
中,语言允许直接重载 operator
相关函数,或者重载类的成员方法,那么什么时候作为成员函数,什么时候作为非成员函数呢
重点:当把运算符定义成成员函数时,它的左侧运算对象必须是运算符所属类的一个对象
//example3.cpp
class Person
{
public:
int age;
(const int &age) : age(age) {}
Person// Person+Person
operator+(int a)
Person {
return Person(age + a);
}
};
//支持Person-int
operator-(const Person &a, int n)
Person {
return Person(a.age - n);
}
//支持 int-Person
operator-(int n, const Person &a)
Person {
return Person(n - a.age);
}
int main(int argc, char **argv)
{
(19);
Person person1= person1 + 1;
Person person2 // Person person3 = 1 + person1;//错误: 因为是调用+前面对象的operator+进行计算
<< person2.age << endl; // 20
cout
<< person1.age << endl; // 19
cout << (person1 - 1).age << endl; // 18
cout << (1 - person1).age << endl; //-18
cout return 0;
}
也就是向某些 IO
类使用<<时右边对象的类型是我们自定义的类型
第一个形参通常为 IO 对象的引用,第二个形参 const
对象的引用,可见输入输出运算符必须是非成员函数,如果是成员函数则因该是
ostream 与 istream 的方法,而不是我我们自定义类本身里的重载
//example4.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
class Person
{
public:
int age;
;
string name(const int &age, const string &name) : age(age), name(name), num(rand() % 10) {}
Personfriend ostream &operator<<(ostream &os, const Person &person); //声明为Person的友元函数
private:
int num;
};
&operator<<(ostream &os, const Person &person)
ostream {
<< person.age << " " << person.name << " " << person.num;
os return os;
}
//想要些类内定义怎么办
class A
{
public:
int n;
(int n) : n(n) {}
A&operator<<(ostream &os) const
ostream {
<< n << endl;
os return os;
}
};
int main(int argc, char **argv)
{
(time(NULL));
srand(19, "me");
Person person<< person << endl; // 19 me 7
cout (999);
A a<< cout; // 999
a return 0;
}
方法与<<运算符类似
//example5.cpp
class Person
{
public:
int age;
;
string name(const int &age, const string &name) : age(age), name(name) {}
Person};
&operator>>(istream &is, Person &person)
istream {
>> person.age >> person.name;
is if (is.fail())
{
= Person(19, "me");
person throw runtime_error("error:input format of person error");
}
return is;
};
int main(int argc, char **argv)
{
(19, "me");
Person person1try
{
>> person1; // 20 she
cin }
catch (runtime_error e)
{
<< e.what() << endl;
cout }
<< person1.age << " " << person1.name << endl; // 20 she
cout return 0;
}
形参通常为常量的引用,返回一个新的结果对象
其他算数运算定义方法都是类似的
//example6.cpp
class Person
{
public:
int age;
;
string name(const int &age, const string &name) : age(age), name(name) {}
Personoperator*(const int &mul)
Person {
return Person(age * mul, name);
}
//复合赋值运算符
&operator*=(const int &mul)
Person {
*= mul;
age return *this;
}
};
operator*(const Person &a, const Person &b)
Person {
return Person(a.age * b.age, a.name);
}
int main(int argc, char **argv)
{
(19, "me");
Person a(20, "as");
Person b<< (a * b).age << endl; // 380
cout << (a * 10).age << endl; // 190
cout *= 11;
a << a.age << endl; // 209
cout return 0;
}
参数为常量引用,返回值类型为布尔型
//example7.cpp
class Person
{
public:
int age;
;
string name(const int &age, const string &name) : age(age), name(name)
Person{
}
bool operator==(const Person &b)
{
return this == &b || (age == b.age && name == b.name);
}
bool operator!=(const Person &b)
{
return age != b.age || name != b.name;
}
};
int main(int argc, char **argv)
{
(19, "me");
Person a(19, "me");
Person b<< (a != b) << endl; // 0
cout << (a == b) << endl; // 1
cout return 0;
}
如果某个类在逻辑上有相等性的含义,则改类应该定义 operator==,这样做可以使得用户更容易使用标准库算法来处理这个类
特别是,关联容器和一些算法要用到小于运算符等,我们通常约定规范,当<或>成立时,==不成立、!=成立,同理==成立时<=与>=成立
//example8.cpp
class Person
{
public:
int age;
;
string name(const int &age, const string &name) : age(age), name(name)
Person{
}
bool operator<(const Person &b)
{
return age < b.age;
}
};
int main(int argc, char **argv)
{
(19, "a");
Person a(20, "b");
Person b<< (a < b) << endl; // 1
cout << (b < a) << endl; // 0
cout return 0;
}
函数参数为等号右侧对象,返回值通常为对象本身的引用
复合赋值运算在 example6.cpp 已经涉及,再次不再描述
//example9.cpp
class Person
{
public:
<int> list;
vector&operator=(initializer_list<int> init_list)
Person {
.clear();
list= init_list;
list return *this;
}
&operator=(const Person &b)
Person {
= b.list;
list return *this;
}
friend ostream &operator<<(ostream &o, const Person &p);
};
&operator<<(ostream &o, const Person &p)
ostream {
for (auto item : p.list)
{
<< item << " ";
o }
return o;
}
int main(int argc, char **argv)
{
;
Person person= {1, 2, 3, 4, 5};
person << person << endl; // 1 2 3 4 5
cout <int> list = {1, 2, 3, 4, 5};
initializer_list// Person b = list; //错误:赋值运算不是赋值构造哦
//当Person有以list类型做参数的构造函数时可以调用,即类型转换构造函数
= person; //赋值拷贝构造只是特殊的情况
Person b << b << endl; // 1 2 3 4 5
cout return 0;
}
下标运算符必须是成员函数,通常返回对象内部数的引用,参数为 size_t 类型表示下标
//example10.cpp
class Person
{
public:
int *arr;
(size_t n) : arr(new int[n])
Person{
for (size_t i = 0; i < n; i++)
{
[i] = i;
arr}
}
//当对象不是const时
int &operator[](const size_t &n)
{
return arr[n];
}
//当对象是const时
const int &operator[](const size_t &n) const
{
return arr[n];
}
~Person()
{
delete[] arr;
}
};
int main(int argc, char **argv)
{
(10);
Person person[0] = 100;
person<< person[0] << endl; // 100
cout const Person b(10);
// b[0] = 99;
// error: assignment of read-only location 'b.Person::operator[](0)'
// cout << b[0] << endl;
<< b[0] << endl; // 0
cout return 0;
}
分为前置版本与后置版本,在 C++中并不要求递增和递减运算符必须为类的成员
//example11.cpp
class Person
{
public:
int age;
(const int &age) : age(age)
Person{
}
&operator++(); //前置版本
Person &operator--();
Person };
&Person::operator++()
Person {
++age;
return *this;
}
&Person::operator--()
Person {
--age;
return *this;
}
int main(int argc, char **argv)
{
(19);
Person person--person; // 18
<< person.age << endl;
cout ++person;
<< person.age << endl; // 19
cout
<< person.operator++().age << endl; // 20 显式调用
cout return 0;
}
重点在于如何区分前置版本与后置版本
为了解决这个问题,后置版本接受一个额外的(不被使用)int
类型形参,当使用后置运算符时编译器自动传递实参 0
//example12.cpp
class Person
{
public:
int age;
(const int &age) : age(age)
Person{
}
operator++(int); //后置版本
Person operator--(int);
Person friend ostream &operator<<(ostream &os, const Person &person);
};
::operator++(int)
Person Person{
//拷贝一份
= *this;
Person person ++age;
return person; //返回原值
}
::operator--(int)
Person Person{
= *this;
Person person --age;
return person;
}
&operator<<(ostream &os, const Person &person)
ostream {
<< person.age;
os return os;
}
int main(int argc, char **argv)
{
(19);
Person person<< person-- << endl; // 19
cout << person << endl; // 18
cout << person++ << endl; // 18
cout << person << endl; // 19
cout
<< person.operator++(0) << endl; // 19 显式调用
cout << person << endl; // 20
cout return 0;
}
迭代器以及智能指针和普通指针等常常用到解引用*与箭头运算符->
//example13.cpp
class Person
{
public:
int age;
;
string name(const int &age, const string &name) : age(age), name(name) {}
Person*operator->()
string {
return &this->operator*();
}
&operator*()
string {
return name;
}
};
int main(int argc, char **argv)
{
(19, "me");
Person person<< person->c_str() << endl; // me
cout (*person).assign("she");
<< person.name << endl; // she
cout return 0;
}
operator*与 operator->有区别,operator*可以完成任何像完成的事情,其返回值不受限制,而 operator->的目的是访问某些成员,其返回值类型有限定,箭头函数获取成员这个事实规则永远不变
//example14.cpp
class Person
{
public:
int age;
;
string name(const int &age, const string &name) : age(age), name(name) {}
Person*operator->()
string {
return &this->operator*();
}
&operator*()
string {
return name;
}
};
int main(int argc, char **argv)
{
(19, "me");
Person person<< (*person).c_str() << endl; // me
cout //下面两个操作是等价的
<< person.operator->()->c_str() << endl; // me
cout << person->c_str() << endl; // me
cout return 0;
}
在 C++总类可以重载函数调用运算符 operator()
//example15.cpp
class Person
{
public:
int age;
;
string name(const int &age, const string &name) : age(age), name(name) {}
Personint operator()(const char *templa) const
{
(templa, age);
printfreturn age;
}
};
int main(int argc, char **argv)
{
(19, "me");
Person person// age is 19
<< person("age is %d \n") << endl;
cout // 19
return 0;
}
这样的调用更像使用一种有状态的函数,我们把这类的对象称作为函数对象
当我们写了一个 lambda 表达式时,编译器将表达式翻译成一个未命名类的未命名对象
//example16.cpp
int main(int argc, char **argv)
{
<int> vec = {3, 7, 5, 4, 2, 4, 4};
vector(vec.begin(), vec.end(), [](const int &a, const int &b)
stable_sort{ return a < b; });
(vec); // 2 3 4 4 4 5 7
printVecreturn 0;
}
//example17.cpp
class IntSortFunc
{
public:
bool operator()(const int &a, const int &b)
{
return a < b;
}
};
int main(int argc, char **argv)
{
;
IntSortFunc intSortFunc<int> vec = {3, 7, 5, 4, 2, 4, 4};
vector(vec.begin(), vec.end(), intSortFunc);
stable_sort(vec); // 2 3 4 4 4 5 7
printVecreturn 0;
}
二者实际上是等价的,通常我们要合理考虑要使用哪一种方式,定义函数对象可以进行复用但需要维护成本,而 lambda 随用随定义更加灵活便捷
经过 lambda 的学习,知道 lambda 想要操控函数外部的数据需要进行 lambda 捕获操作,而在函数对象中则是利用对象的属性来进行类似的操作
//example18.cpp
//lambda捕获
int main(int argc, char **argv)
{
<int> vec = {7, 4, 5, 2, 31};
vectorsize_t i = 0;
(vec.begin(), vec.end(), [&](const int &a, const int &b) -> bool
stable_sort{
++;
ireturn a < b; });
<< i << endl; // 10
cout return 0;
}
同样可以使用函数对象实现类似捕获的功能
//example19.cpp
class IntSortFunc
{
public:
size_t *i;
(size_t *i) : i(i) {}
IntSortFuncbool operator()(const int &a, const int &b)
{
++*i;
return a < b;
}
};
int main(int argc, char **argv)
{
<int> vec = {7, 4, 5, 2, 31};
vectorsize_t i = 0;
(&i);
IntSortFunc intSortFunc(vec.begin(), vec.end(), intSortFunc);
stable_sort<< i << endl; // 10
cout return 0;
}
至此我们又多了一种在函数之间传递函数的方法,以前我们使用函数指针、lambda 表达式现在又可以使用函数对象进行类函数的传递
在 C++标准库中定义了一些运算函数对象,其定义在头文件 functional 中
//example20.cpp
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
#include "print.h"
using namespace std;
int main(int argc, char **argv)
{
<int> p;
plus<< p(1, 2) << endl; // 3
cout <int> vec = {6, 7, 3, 4, 5, 2, 3, 0};
vector(vec.begin(), vec.end(), greater<int>());
sort(vec); // 7 6 5 4 3 3 2 0
printVec(vec.begin(), vec.end(), less<int>());
sort(vec); // 0 2 3 3 4 5 6 7
printVecreturn 0;
}
C++中可调用的对象种类有:函数、函数指针、lambda 表达、bind 创建的对象,重载了函数调用运算符的类
虽然可能具有相同的调用方式,但类型是不同的
//example21.cpp
int add(int i, int j)
{
return i + j;
}
//当lamba显式声明返回值类型为int时 与add同类型
//如果是自动推算不写->int则与add不是同类型
auto mod = [](int i, int j) -> int
{
return i % j;
};
class divide
{
public:
int operator()(int i, int j)
{
return i / j;
}
};
int main(int argc, char **argv)
{
//三者调用形式为int(int,int)但三者不是一个类型
;
divide divideInstance<< add(1, 2) << " " << mod(5, 2) << " " << divideInstance(9, 3) << endl;
cout // 3 1 3
<string, int (*)(int, int)> m_map;
mapm_map.insert({"+", add});
m_map.insert({"%", mod}); // mod显式声明了返回值类型
// m_map.insert({"/", divideInstance});//错误:value类型不匹配
auto fun = m_map.find("%")->second;
<< fun(5, 2) << endl; // 1
cout return 0;
}
其定义在 functional 头文件中
其本质为了解决统一调用方式相同的可调用对象
//example22.cpp
int add(int i, int j)
{
return i + j;
}
//当lamba显式声明返回值类型为int时 与add同类型
//如果是自动推算不写->int则与add不是同类型
auto mod = [](int i, int j) -> int
{
return i % j;
};
class divide
{
public:
int operator()(int i, int j)
{
return i / j;
}
};
int main(int argc, char **argv)
{
<int(int, int)> f = add;
function<< add(1, 2) << endl; // 3
cout = mod;
f << f(3, 2) << endl; // 1
cout = divide();
f << f(4, 2) << endl; // 2
cout <string, function<int(int, int)>> m_map;
mapm_map.insert({"+", add});
m_map.insert({"%", mod});
m_map.insert({"/", divide()});
<< m_map["/"](10, 5) << endl; // 2
cout return 0;
}
目前我们已经有了一种更好地在函数之间传递可调用对象地办法
//example23.cpp
int func(function<int(int, int)> f)
{
return f(100, 3);
}
int main(int argc, char **argv)
{
int result = func([](int a, int b) -> int
{ return a + b; });
<< result << endl; // 103
cout //简直优雅极了是吧
return 0;
}
在将函数赋给 function 时,如果函数有多种重载形式,编译器并不能自动推算出要使用哪一种,所以存在二义性,通常会使用下列方法进行解决
//example24.cpp
int add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
int main(int argc, char **argv)
{
<int(int, int)> f = nullptr;
function// f = add; //错误:有重载 不知道使用那一个
int (*fp)(int, int) = add; //先用函数指针存储指定的函数地址
= fp; //将函数地址赋给function
f << f(9, 5) << endl; // 14
cout
// function向成员类型
<int(int, int)>::result_type a = 12; // int a
function<int(int, int)>::first_argument_type b = 100; // int b
function<int(int, int)>::second_argument_type c = 200; // int c
function<int(int)>::argument_type d = 99; // int d
function<< a << " " << b << " " << c << " " << d << endl; // 12 100 200 99
cout return 0;
}
形式为operator type() const
,type
是任意的,只要可以作为函数的返回值,因此不允许转换成数组或者函数类型
//example25.cpp
class Person
{
public:
int age;
;
string name(int age, string name) : age(age), name(name) {}
Personoperator int() const
{
return age;
}
operator string() const
{
return name;
}
};
int main(int argc, char **argv)
{
(19, "me");
Person personint age = person;
= person;
string name << age << " " << name << endl; // 19 me
cout
double temp_double = person; //隐式自动转换
<< temp_double << endl;
cout << person + 9.99 << endl; // 28.99
cout //强制转换
<< (string)person << endl; // me
cout return 0;
}
在 C++11 中引入了显式的类型转换运算符,即定义的类型转换运算符方法只有在进行显式转换时才被调用
//example26.cpp
class Person
{
public:
int age;
;
string name(int age, string name) : age(age), name(name) {}
Personoperator int() const
{
return age;
}
explicit operator string() const
{
return name;
}
};
int main(int argc, char **argv)
{
(19, "me");
Person person//隐式转换被禁止
= person + "sx";
string name << name << endl; // nothing
cout //只能进行显式调用
= (string)person + "sx";
name << name << endl; // mesx
cout return 0;
}
IO 类型对象的状态为 good 则会返回真,否则函数返回假
//example27.cpp
int main(int argc, char **argv)
{
("./example27.iofile", fstream::app | fstream::in);
ifstream i<< (bool)i << endl; // 1
cout .setstate(std::ios_base::badbit);
iif (i)
{
<< "true" << endl;
cout }
else
{
<< "false" << endl; // false
cout }
return 0;
}
最明显的情况就是在A=B
时,A 定义了 B 的转换构造函数,B
定义了 A 的类型转换运算,则编译器应该用哪一个呢?
编译器的不同,可能处理方法是不同的,但是在必要时可以使用显式调用
//example28.cpp
class B;
class A
{
public:
(const B &b)
A{
<< "A(const B &b)" << endl;
cout }
() = default;
A};
class B
{
public:
operator A()
{
<< "operator A()" << endl;
cout ;
A areturn a;
}
};
int main(int argc, char **argv)
{
;
B b= b; // operator A()
A a = b; // operator A()
a //显式调用
= b.operator A(); // operator A()
a (b); // A(const B &b)
A a1return 0;
}
还有一种常见的二义性,如果两个类型转换都转成不同类型的数字,那么在算数运算时应该采用哪一种呢?
最简单的方法就是使用显式转换调用
//example29.cpp
class Person
{
public:
int age;
;
string name(int age, string name) : age(age), name(name) {}
Personoperator double() const
{
return age;
}
operator long() const
{
return age;
}
};
int main(int argc, char **argv)
{
(19, "me");
Person personlong a = person;
<< a << endl; // 19
cout double b = person;
<< b << endl; // 19
cout // cout << person + 34.3 << endl;//错误:具有二义性 long or double ,ambiguous overloads
return 0;
}
下面的例子当传递 int 给 func 则会触发转换构造函数,有多个构造函数的参数都是 int,所以会产生二义性,不知道何去何从
//example30.cpp
class A
{
public:
(int n) {}
A};
class B
{
public:
(int n) {}
B};
void func(const A &a)
{
<< "void func(const A &a)" << endl;
cout }
void func(const B &b)
{
<< "void func(const B &b)" << endl;
cout }
int main(int argc, char **argv)
{
// func(10); // call of overloaded 'func(int)' is ambiguous
(A(10)); // void func(const A &a)
func(B(10)); // void func(const B &b)
funcreturn 0;
}
还有一种变形情况,并不是只有当 A B 的构造函数接收相同的类型时才会冲突,当 A 与 B 构造函数的参数类型可以进行转换时就会引起二义性
//example31.cpp
class A
{
public:
(int n) {}
A};
class B
{
public:
(double n) {}
B};
void func(const A &a)
{
<< "void func(const A &a)" << endl;
cout }
void func(const B &b)
{
<< "void func(const B &b)" << endl;
cout }
int main(int argc, char **argv)
{
// func(10); // call of overloaded 'func(int)' is ambiguous
(A(10)); // void func(const A &a)
func(B(10)); // void func(const B &b)
funcreturn 0;
}
定义运算符方法有两种形式,一种为类方法,一种为直接重载相关操作方法
运算a sym b
可能由a.operatorsym(b)
或者operatorsym(a,b)
处理,如果两个同时被定义,编译器也不知道要调用那一个
//example32.cpp
class Person
{
public:
int age;
;
string name(int age, string name) : age(age), name(name) {}
Person(int age = 0) : age(age), name("") {}
Personoperator long() const
{
return age;
}
operator+(const Person &person)
Person {
<< "Person operator+(const Person &person)" << endl;
cout (age + person.age, name);
Person preturn p;
}
};
operator+(const Person &a, const Person &b)
Person {
<< "Person operator+(const Person &a, const Person &b)" << endl;
cout (a.age + b.age, a.name);
Person preturn p;
}
int main(int argc, char **argv)
{
(19, "me");
Person person1(19, "me");
Person person2+ person2; // Person operator+(const Person &person)
person1 // 二义性
// 78 + person1; // ambiguous overload for 'operator+' (operand types are 'int' and 'Person')
//编译器不知道将78使用转换构造函数变为Person还是将person1转换为long类型
//最简单的解决办法就是显式调用
78 + person1.operator long(); // int + long
// Person + Person
(78) + person1; // Person operator+(const Person &person)
Personreturn 0;
}
这一节的内容比较多,学习了如何定义运算符规则,有进一步深入了解 lambda 与函数对象、以及标准库的 function 对象、讨论了类型转换运算符以及经常出现的二义性问题