C++基础篇: 06 运算符

1 运算符函数

        在C++中会把运算符当做函数处理,一个表达式,其实是调用了对应运算符函数来完成运算,这种特性对于内建类型是没有多大意义的,但是对于自建类型的数据,可以通过该特性进行一些特性化设计,可以极大地提高代码的可读性、易用性。例如:stirng类

2 运算符函数的基本格式

    假设 # 是运算符  O 运算的类对象

2.1 单目运算符: #O 或者 O#

2.1.1 成员函数

[] O::operator#(void)
{ 
    //  返回值不确定、唯一的参数就是调用者对象自己
}

2.1.2 普通全局函数

[] operator#(O& o)
{
    //  全局函数不属于任何类,因此需要把调用者类型作为参数传递
}

        注意:运算符成员函数、全局函数只能实现一个,不能同时存在,否则产生歧义编译报错

2.2 双目运算符:A # B (A\B 其中至少有一个类类型)

2.2.1 成员函数

[] A::operator#(B& b){}

2.2.2 全局函数

[] operator#(A& a,B& b){}

        注意:双目运算符的运算符函数的调用者一定是左操作数,因此如果实现成员函数的话,需要实现在左操作数类中,如果实现为全局函数的话,第一个参数为调用者,第二个参数为右操作数

3 运算类的双目运算符函数

    假设 O是类名

3.1 成员函数 :o1 + o2

const O operator+(const O& that)const
{
    return O(x+that.x,y+that.y);
}

3.2 全局函数

const O operator+(const O& o1,const O& o2)
{
    return O(o1.x+o2.x,o1.y+o2.y);
}

3.3 友元

        在实现全局的运算符函数时,可能会使用类内的私有成员,此时全局函数没有访问权限

        如果把私有成员改为公开的,会破坏类的封装性

        如果给每个私有成员实现公开的访问接口函数,会很麻烦

        最好的方式是在类内给这个全局函数进行独家授权,这个就是把全局函数声明为友元函数

        方式: 在类中声明该全局函数,并在声明前加 friend 关键字

4 输入输出运算符

        在C++中 << >> 运算符不光是按位左移、右移,同时它们还是cout、cin类对象的输出、输入运算符

    cout的类名:ostream

    cin的类名:istream

4.1 输出运算符:<<

 类内:

friend ostream& operator<<(ostream& os,const Test& t);

类外:

ostream& operator<<(ostream& os,const Test& t)
{
     return os << t.x << t.y;
}

   

4.2 输入运算符:>> ;

类内:

friend istream& operator>>(istream& is,Test& t);

类外:

istream& operator>>(istream& is,Test& t)
{
    return is >> t.x >> t.y;
}

注意:

① 由于 << >>运算符的调用者是cout\cin类对象的类,所以我们无法在该类中去实现一个新的<< >>运算符的重载函数,因此只能实现 << >>运算符的全局函数

② 该全局函数大概率会访问要输出的类的私有成员,所以需要在类中声明为友元函数

③ 返回值为ostream\istream的引用,因为cout可以连续输出,cin可以连续输入

④ 参数 ostream& 、istream& 都不能加const

5 运算类的单目运算符重载

单目:++/--  !  ~  - *  &  sizeof

运算: ! ~ -

5.1 成员函数:  const Test t; ~t;

const Test operator~(void)const
{
    Test t(~x,~y);
    return t;
    return Test t(~x,~y);
}

注意:运算对象可以带常属性,因此该类运算符成员函数需要是常函数,并且运算过程中不改变运算对象的值,而是产生一个临时的计算结果,并且结果是右值,所以返回的不能是引用,只能是带const的对象值

5.2 全局函数

const Test operator~(const Test& t)
{
    return Test(~t.x,~t.y);//   需要声明友元函数
}

5.3 C++的前后自变左右值问题

5.3.1 C++的前自变: ++num = 10;true

        直接修改原对象的内存,然后将原对象以引用的方式返回,所以操作和返回一直都是原内存,为左值

5.3.2 C++后自变:num++ = 10;  false

        先将原对象的数据存储到临时变量中,接着修改原对象的内存,然后把临时变量的值以只读方式返回,并且结束前把临时变量销毁,因此无法访问,为右值

int num = 10;
++num++;    //  后自变优先 所以报错
(++num)++;  //  num 12
cout << num << endl;

    注意:在C语言中自变运算符的结果都是右值!

6 自变运算符的重载 Test类 t类对象

6.1 前自变运算符: ++t/--t

成员函数:

Test& operator++(void)  //不能是常函数,返回值必须是引用
{
    x++,y++;
    return *this;   //必须返回调用对象的引用
}

全局函数:

Test& operator++(Test& t)
{
    t.x++,t.y++;
    return t;
}

6.2 后自变运算符:t++ t--

哑元:在参数列表中增加一个不使用的哑元类型(int),唯一目的就是为了区分前后自变运算符函数

成员函数:

const Test operator++(int)
{
    return Test(x++,y++);   //返回临时对象的值,不能返回引用,而且调用者的值必须后自变
}

    全局函数:

const Test operator++(Test& t,int)
{
    return Test(t.x++,t.y++);
}

7 特殊的运算符函数

7.1 中括号运算符   []

        当想让一个类对象当做数组一样使用时,可以重载[]运算符函数,可以让类对象像数组一样使用,例如:vector容器

7.2 小括号运算符  ()

        重载小括号运算符可以让一个类对象当做函数一样使用

7.3 *解引用和 -> 访问成员运算符

        重载了这两个运算符可以让类对象像指针一样使用,C++智能指针就是通过重载这两个运算符来实现的

7.4 new/delete 可以重载

为什么要重载new、delete运算符?

① 可以在该运算符重载中记录每次分配、释放的地址、次数等信息到日志中,以此检查是否出现了内存泄漏、以及哪里泄漏

② 对于申请字节较小内存的对象,可以在重载运算符中多分配一些内存,从而减少内存碎片的产生

        void* operator new(size_t size)//size是要申请的字节数,编译器会自动计算new要申请的字节数并传递过来

void* operator new(size_t size)//size是要申请的字节数,编译器会自动计算new要申请的字节数并传递过来
{
    void* ptr = malloc(size);
    //  程序员自己设计
    return ptr;//编译器也会把void*强制转换成对应的类型
}

void operator delete(void* ptr)//ptr会由编译器传递要释放内存的首地址
{
    free(ptr);
    //  程序员自己设计
}

注意:

        ① 如果只是针对某个类重载了它的new、delete,那么只有该类对象使用new、delete时才会调用重载函数

        ②  如果想要所有类型(自建、内建)都执行new、delete的重载版本,则实现成全局运算符函数

new[] delete[] 成员函数、全局函数重载格式一致:

void* operator new[](size_t size){}
void operator delete[](void* ptr){}

注意:

        ① new、delete可以同时存在全局、成员重载版本,编译器会让类对象优先调用类内成员函数版本

        ② 如果类中显式地实现了析构函数,那么new[]会在原大小基础上多申请前4个字节,用于存储要析构的次数,否则大小是多少就申请多少

8 运算符重载的限制

① 有哪些运算符不能重载

        ::  域限定符

        .   直接访问成员的运算符

        ? : 三目运算符

        sizeof 计算字节数的运算符

        typeid 获取类型信息的运算符

② 只能重载成全局函数的运算符

        <<  输出运算符

        >>  输入运算符

③ 只能重载成成员函数的运算符

        ()      小括号运算符    Test t(10)

        []      中括号运算符    Test t[10]

        =       赋值运算符

        ->      间接访问成员的运算符 Test* t = new Test;t->

④ 运算符重载可以自定义运算过程,但是无法改变运算符本身的优先级

⑤ 运算符的操作数不能擅自改变

⑥ 不能自己发明新的运算


建议:

① 重载运算符函数的原理要与运算符本身自恰,不要改变运算符本身的规律、方向

② 不要忘记重载运算符的初衷:为了提高代码可读性,不要盲目炫技

你可能感兴趣的:(C++,c++,数学建模,开发语言)