C++ primer 薄片系列之重载运算符

重载运算符函数也是函数,所以参数数量和该运算符的运算对象数量一样多。当运算符是成员函数时候,它的第一个(左侧)运算对象绑定到隐式的this指针上,所以成员运算符函数的显式参数数量比运算符的运算对象总数少一个。
硬性规定:对于一个运算符函数来说,它或者是类的成员函数,或者至少含有一个类类型的参数。
不能重载的运算符 一、:: 二、.* 三、 . 四、?:
通常情况下,不应该重载逗号,取地址,逻辑与和逻辑或

对于如何为类设计运算符的准则:
1. 类需要IO操作的话,则应该定义移位运算符(>> <<)
2. 类如果需要相等性检查, 则定义==, 如果有了==,通常应该也有!=
3. 如果类包括内在的单序比较操作,则定义operator<, 如果有了operator<,则通常定义其他关系操作
4. 重载运算符的返回类型应与内置版本相符合。如果逻辑运算符应返回bool等。

运算符作为成员还是非成员:
1.赋值(=), 下标([]),调用(())和成员访问箭头 (->)运算符必须是成员。
2. 复合赋值运算符一般来说是成员,但并非必须
3. 改变对象状态的运算符,如递增,递减,解引用,通常应该是成员
4. 具有对称性的运算符可能转换任意一端的运算对象,如算术(加减乘除等),相等性(==),关系运算符(>,<),和位运算符(位与,或,非等)。它们通常是非成员函数。

string s = "world";
string t = s + "!"; //无论+对于 string是成员还是非成员都能通过,成员的话等价于s.operator+("!")
string u = "!" +s; //"!"的类型是const char*,内置类型。它没有成员函数。因此这里产生错误。
然后加运算符是对称性运算符,左右可以交换的,为了杜绝 这种不合理的错误。string的+被定义成普通的非成员函数。"!"可以被转换为string类型

输入输出运算符

必须是非成员函数

//首先来看成员函数版本的输出运算符
class A
{
public:
    A(int k) { i = k; }
    std::ostream& operator<<(std::ostream& os)
    {
        os << i;
        return os;
    }
    int i;
};

int main()
{
    A s(10);
    s << std::cout;
}
首先这样写是完全可以通过的,但是这样写的话,一来别扭,通常都是std::cout << s;再者由于每次只能s<< std::cout 的形式调用,就无法实现std::cout << a << b << c 这样的链式调用了。
如果要像通常调用的方式即std::cout << s  这样,那就只能让我们的类继承于std::ostream.然而很可惜,标准库不让这样继承发生。。。所以最终只能以非成员函数的方式重载这样的运算符

输入运算符必须考虑失败的情况,输出不需要
很简单,输入的内容由用户控制,而输出是编译器预先定好的,如果输出类型不对,编译都过不去,更别提执行了。

class A
{
public:
    int i;
};
istream &operator<<(istream& is, A &data)
{
    int i;
    is >> i;
    if(is) //判断输入是否成功
    {
        data.i = i;
    }
    else
    {
        data = A();
    }
    return is;
}

复合赋值运算符和算术运算符

通常复合赋值运算符被定义为类的成员函数。算术运算符作为非成员函数,调用复合赋值运算符实现

class A
{
public:
    friend A& operator+(const A&a1, const A & a2);
    A& operator+=(const A& a)
    {
        i+= a.i;
        return *this;
    }
private:
    int i;
};
A& operator+(const A&a1, const A & a2)
{
    A sum = a1;
    sum += a2;
    return sum;
}

下标运算符

必须是成员函数。一般返回引用,这样可以 将返回值放在赋值运算符的任意一端。另外下标运算符返回两个版本,常引用和普通引用

class A
{
public:
    std::string & operator[](int i){
   
   return ele[n];}
    std::string & operator[](int i) const{
   
   return ele[n];}
private:
    std::string *ele;
};

递增运算符

这个运算符只要区分前置和后置,后置和前置相比,唯一的区别是后置运算符提供一个int类型的形参。

class A
{
public:
    A(int k = 10) { i = k; }
    A(A&) {
    }
    A & operator++()//前置++ ,前置可以引用
    {
        cout << "forward++" << endl;
        if (i > 10)
        {
            throw std::exception("dsds"); //假设有个小异常
        }
        i += 1;
        return *this;
    }
    A operator++(int)//后置++,后置没有返回引用,这是因为后置返回的是变化前的值,而这个 值在代码中是个临时变量
    {
        cout << "back++" << endl;
        A s = *this;
        ++*this; //调用前置++
        return s;
    }
    int i;
};


int main()
{
    A a;
    a++, ++a;//普通调用方式
    a.operator++();//显示调用前置版本
    a.operator++(0);//显式调用后置版本
}

成员访问运算符

class A
{
public:
    A(int k = 10) { i = k; }
    A(A&) {
        cout << "copy construct" << endl;
    }
    int  operator*()
    {
        return 10;
    }
    /*
    int operator->() //这是个怪异的函数,也能正常返回,但是丢掉了箭头操作符是用来获取成员这个基本的含意.所以通常箭头操作符必须 返回类的指针或者自定义了箭头运算符的某个类的对象
    {
        cout << "stupid" << endl;
        return this->operator*();
    }
    */
    A* operator->()
    {
        return this;
    }

    int i;
};

int main()
{
    A a;
    cout << a.operator*() << endl;
    cout << *a << endl;
    cout << a.operator->()->i << endl;
    cout << a-> i << endl;

}

函数调用运算符

必须是成员函数。如果类定义了调用运算符,则该类的对象称作函数对象。 lambda就是一个函数对象,当我们编写一个lambda后,编译器将该表达式 翻译成一个未命名类的未命名对象。lambda表达式产生的类中含有一个重载的函数调用运算符。

[sz](const std::string &a){
   
   return a.size()>sz};
这个lambda表达式被编译器翻译成
class SizeCmp
{
public:
    SizeCmp(size_t n):sz(n){}
    bool operator()(const std::string &a)const
    {
        return s.size()> sz;
    }
private:
    std::string sz;
};

function的作用

标准库定义了std::function,那么为啥要有它。
首先 ,C++定义了几种可调用的对象,函数,函数指针,lambda,bind创建的对象,重载了函数运算符的类。
对象就会有类型。函数对象 也是。
不同的函数对象会有相同的形式。

int(int,int) 是个函数类型,接受两个int,返回一个int
1. int add(int a, int b)
2. auto mod = [](int a, int b){
   
   return a+b;};
3. struct s{
      int operator()(int a, int b){ return a+b;}
   };
4. auto bb = std::bind(add, std::placeholders::_1, std::placeholders::_2)
//假设现在做个函数表
map<string, int(*)(int,int)> binops;
binops.insert({
   
   "+",add});
binops.insert({
   
   "+2",mod});//错误,mod不是一个函数指针。同样divide和bb也不能塞在这个map表里。

为解决这个囧境,从而有了 function 这个类型

function<int(int,int)> f1 = add;
function<int(int,int)> f2 = mod;
function<int(int,int)> f3 = s;
function<int(int,int)> f4 = bb;
std::map<std::string, function<int(int,int)> binops;//搞定

function 类型的对象中不能直接存入重载函数的名字

int add(int a,int b){
   
   return a+b;}
double add(double a, double b){
   
   return a+b;}
map<string, function<int,int>> binops;
binops.insert({
   
   "+", add});//错误,到底调用哪个add
//两个解决办法
1.函数指针
    int (*fp)(int,int) = add;
    binops.insert({
   
   "+", fp});//正确,fp指向正确的版本
2.lambda表达式
    binops.insert({
   
   "+",[](int a,int b){
   
   return add(a,b);}});//指定了add的参数是int,也能解决

类型转换运算符

负责将类类型的值转换成其他类型。
类型转换函数必须是类的成员函数,它不能声明返回类型,形参列表也必须为空,类型转换函数通常是const
operator type() const;

class A
{
public:
    explicit operator int() const
    {
        return val;
    }
    explicit operator bool() const
    {
        return val>=0;
    }
private:
    std::size_t val;
};
int main()
{
    A a;
    a = 4;
    static_cast<int>(a)+3;//会先将a转换int,然后执行整数的加法
    if(a)//在if,while,do 语句, for语句头的条件表达式,逻辑非,逻辑或,逻辑与运算符,条件运算符的条件表达式中, 尽管转换bool是explicit的,但是这里不需要调用static_cast<bool>()进行转显式转换,编译器会将显式的类型转换自动应用于它
    {
    }
}

类型转换的二义性

struct B;
struct A
{
    A()=default;
    A(const B&);
    operator int()const{}
    operator double()const{}
};
struct B
{
    operator A()const{}
};
A f(const A &);//普通函数
B b;
A a = f(b);//傻叉了,这里应该是f(B::operator A()) 还是f(A::A(const B&))
A a1 = f(b.operator A()); //正确
A a2 = f(A(b));//正确

void f2(long dou);
f2(a);//傻叉了,是将a转换为int还是转换为double,因为不管是int还是double,都能被转换成double

猜你喜欢

转载自blog.csdn.net/jxhaha/article/details/78452539
今日推荐