运算符重载

一、

1、运算符重载

2、成员函数重载

3、非成员函数重载

4、运算符重载规则


运算符重载允许把标准运算符(如+、—、*、/、<、>等)应用于自定义数据类型的对象

直观自然,可以提高程序的可读性

体现了C++的可扩充性


运算符重载仅仅只是语法上的方便,它是另一种函数调用的方式

运算符重载,本质上是函数重载

不要滥用重载、因为它只是语法上的方便,所以只有在涉及的代码更容易写、尤其是更易读时才有必要重载


成员函数原型的格式:

函数类型 operator 运算符(参数表);

成员函数定义的格式:

函数类型 类名::operator 运算符(参数表){

函数体;

}


代码:
Complex.h:

#ifndef _COMPLEX_
#define _COMPLEX_

class Complex
{
public:
	Complex(int real,int imag);
	Complex();
	~Complex();
	//运算符重载有一个隐含的this指针,所以没必要写两个参数
	Complex operator+(const Complex& other);
	void Display() const;
private:
	int real_;
	int imag_;
};

#endif
Complex.cpp

#include "Complex.h"
#include <iostream>
using namespace std;
Complex::Complex(int real,int imag):real_(real),imag_(imag)
{
}
Complex::~Complex()
{
}

//运算符重载本质上是函数的重载
Complex Complex::operator+(const Complex& other)
{

	int r = real_ + other.real_;
	int i = imag_+ other.imag_;
	
	//return *this出错 因为自身是不发生改变的
	return Complex(r,i);
}

void Complex::Display()const
{
	cout<<real_<<"+"<<imag_<<"i"<<endl;
}
main.cpp:

#include "Complex.h"

int main()
{
	Complex c1(3,5);
	Complex c2(4,6);

	//等价于c1.operator(c2)
	Complex c3 = c1 + c2;
	c3.Display();
}


友元函数原型的格式:

friend 函数类型 operator 运算符(参数表);

友元函数定义的格式:

friend 函数类型 类名::operator 运算符(参数表){

函数体;

}

如果在类外进行定义

函数类型 operator运算符(参数表){

}

//友元函数不是成员函数,不是在类的作用域,并且没有this指针


注:1、友元函数和成员函数重载在vs2012是可以共存的,但成员函数的调用是优先的
在vc6.0是不允许共存的
         2、只有在友元函数中才可以访问类的私有成员
 
例:
friend Complex operator+(const Complex& c1,const Complex& c2);
Complex operator+(const Complex& c1,const Complex& c2)
{
}

运算符重载的规则:

1、运算符重载不允许发明新的运算符。

2、不能改变运算符操作对象的个数。

3、运算符被重载后,其优先级和结合性不会改变。

4、不能重载的运算符:

作用域运算符  ::  条件运算符?:   直接成员访问运算符.  
类成员指针引用的运算符.*   sizeof()运算符 sizeof
注:怕会引起混乱

运算所需变量为两个的运算符叫做双目运算符.或者要求运算对象的个数是2的运算符称为双目运算符。 
单目运算符与之类似

一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。

以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。(会破坏原有的功能)

类型转换运算符只能以成员函数方式重载

流运算符只能以友元的方式重载


二、++运算符重载、!运算符重载、赋值运算符重载

前置++运算符重载

成员函数的方式重载,原型为:

函数类型 & operator++();

友元函数的方式重载,原型为:

friend 函数类型 & operator++(类类型 &);


为了区分后置++,在后面加上Int ,实际上此参数是无效的

后置自增和后置自减的重载

成员函数的方式重载,原型为:

函数类型 & operator++(int);

友元函数的方式重载,原型为:

friend函数类型 & operator++(类类型 &,int);


注:后置++并不简单,推荐用成员函数进行重载,比较直观


#ifndef _INTEGER_H_
#define _INTEGER_H_
 
class Integer
{
public:
    Integer(int n);
    ~Integer();
    //前置++ 为了避免拷贝构造函数,所以采取引用
    Integer& operator++();
    //friend Integer& operator++(Integer& i);
 
    Integer operator++(int n);
    //friend Integer operator++(Integer& i, int n);
 
    void Display() const;
private:
    int n_;
};
 
#endif // _INTEGER_H_

#include "Integer.h"
#include <iostream>
using namespace std;
 
Integer::Integer(int n) : n_(n)
{
}
 
Integer::~Integer()
{
}
 
Integer& Integer::operator ++()
{
    //cout<<"Integer& Integer::operator ++()"<<endl;
    ++n_;
    return *this;
}
 
//Integer& operator++(Integer& i)
//{
//  //cout<<"Integer& operator++(Integer& i)"<<endl;
//  ++i.n_;
//  return i;
//}
 
//返回值不能是引用,因为临时对象返回时生存期已经结束了
//是无效的引用
Integer Integer::operator++(int n)
{
    //cout<<"Integer& Integer::operator ++()"<<endl;
    //n_++;  error
	//因为Integer n3 = n++; 这时候返回的值应该要是n_
	//而不是+完后的n_
	//所以只能用临时对象 跟拷贝构造函数中的临时对象是不一样的!这个临时对象是在内部的
    Integer tmp(n_);
    n_++;
    return tmp;
}
 
//Integer operator++(Integer& i, int n)
//{
//  Integer tmp(i.n_);
//  i.n_++;
//  return tmp;
//}
 
void Integer::Display() const
{
    cout<<n_<<endl;
}

#include "Integer.h"
#include <iostream>
using namespace std;
 
 
int main(void)
{
    Integer n(100);
    n.Display();
 
    Integer n2 = ++n;
    n.Display();
    n2.Display();
 
    Integer n3 = n++;
    n.Display();
    n3.Display();
    return 0;
}

赋值运算符与!运算符:
#ifndef _STRING_H_
#define _STRING_H_
 
class String
{
public:
    explicit String(const char* str=""); //避免隐式转换
    String(const String& other);
    String& operator=(const String& other);
	//这样子就可以实现s = "aaaa"
    String& operator=(const char* str);
 
    bool operator!() const;
    ~String(void);
 
    void Display() const;
 
private:
    char* AllocAndCpy(const char* str);
    char* str_;
};
 
#endif // _STRING_H_

#include "String.h"
#include <string.h>
#include <iostream>
using namespace std;
 
String::String(const char* str)
{
    str_ = AllocAndCpy(str);
}
 
String::String(const String& other)
{
    str_ = AllocAndCpy(other.str_);
}
 
String& String::operator=(const String& other)
{
    if (this == &other)
        return *this;
    //原来的对象已经存在了
    delete[] str_;
    str_ = AllocAndCpy(other.str_);
    return *this;
}
 
String& String::operator=(const char* str)
{
    delete[] str_;
    str_ = AllocAndCpy(str);
    return *this;
}
 
bool String::operator!() const
{
    return strlen(str_) != 0;
}
 
String::~String()
{
    delete[] str_;
}
 
char* String::AllocAndCpy(const char* str)
{
    int len = strlen(str) + 1;
    char* newstr = new char[len];
    memset(newstr, 0, len);
    strcpy(newstr, str);
 
    return newstr;
}
 
void String::Display() const
{
    cout<<str_<<endl;
}

#include "String.h"
#include <iostream>
using namespace std;
 
 
int main(void)
{
    String s1("abc");
    String s2(s1);
 
    String s3;
    s3 = s1;
    s3.Display();
 
    s3 = "xxxx";
    s3.Display();
 
    String s4;
    bool notempty;
    notempty = !s4;
    cout<<notempty<<endl;
 
    s4 = "aaaa";
    notempty = !s4;
    cout<<notempty<<endl;
 
    return 0;
}

三、String类实现
主要包括:[]运算符重载、+运算符重载、+=运算符重载、<<运算符重载、>>运算符重载、流运算符重载

[]

char &operaotor[](unsigned int index)

{
    return str_[index].

}

//为什么返回为引用呢?? 才可以出现在表达式的左边

s1[2] = 'A';//这样子才能够改变它的值



注:const成员函数代表变量成员不能被更改  所以const对象要调用const成员函数的  

不然会有问题的


const String s2('xyzabd');//如果是const对象 s2[] = 'M'是不允许的

//如果还是按照上述操作  那么编译是不会通过的  因为非const不能赋值给const对象!

//这时候重载另一个函数  因为const就代表这对象的装没有改变 但是重载后s2是可以被更改的

char &operator[](unsigned int index)const

{
    return str_[index].

}

//结果s2[2] = 'M' 还是可以 

// 希望ch = s2[2]允许的  但是s2[2] = 'M'是不行的

//所以上诉函数改为

const char operator&(unsigned int index)const;


//可不可以用一个函数调用另一个函数?当代码很多然后代码基本一致的时候?

//因为当代码很多的时候,两个函数的功能是一样的 没必要设计的那么复杂  所以可以用

//non const 版本调用 const版本,这样子只要写好const版本的函数就可以了

//这样子[]才算是比较完美的

char& String::operator[](unsigned int index)

{

//return str_[index];

//non const 版本调用 const版本 然后再去掉常量性即可

return const_cast<char&>(static_cast<const String&>(*this)[index]);

}


注:static_cast<const String&>(*this)将对象转换为const对象

然后要去掉const属性 使用const_cast

实际上还是两个函数


对于+运算符用友元函数重载:

String operator+(const String& s1, const String& s2)
{
	int len = strlen(s1.str_) + strlen(s2.str_) + 1;
	char* newstr = new char[len];
	memset(newstr, 0, len);
	strcpy(newstr, s1.str_);
	strcat(newstr, s2.str_);//如果是调用return String(newstr)会有问题 因为tmp(newstr)会重新开一个空间构造
				//因为这样子newstr没法删除!所以要采用下列的方法
	String tmp(newstr);
	delete newstr;
	return tmp;
}

//最后调用拷贝构造函数而不是赋值运算符,函数返回为类类型,此时临时对象会自动删除


//如果采用String s6 = "aaa" + s3;

//用友元函数是可以实现的   

//如果以成员函数进行重载  因为成员函数第一个参数是对象自身,所以不能是一个字符串

//所以最好把+重载为友元

//也可以实现为String s6 = "aaa" + s3 + "zzz"; 因为"aaa" + s3返回一个对象

//但是如果为"xxx" + "aaa"那么这两个字符串是不允许的  因为我们没有重载两个char *

//"aaa"+s3+"dsgg"+"xxxx"也是可以的,因为可以返回对象!


注:对于String c = "ss"+a  与String d = "aa"+"bb"

“s”之所以会调用转换构造函数(只有一个参数的构造函数)的原因是因为首先友元函数会识别,其中有一个为String类型的话,就会调用转换函数

但是如果是两个字符串的话就不行了  因为首先友元函数是无法识别的,就不会进行识别







+=运算符用成员函数重载,因为隐含一个参数,有一个小技巧

String& String::operator+=(const String& other)
{
    int len = strlen(str_) + strlen(other.str_) + 1;
    char* newstr = new char[len];
    memset(newstr, 0, len);
    strcpy(newstr, str_);
    strcat(newstr, other.str_);
 
    delete[] str_;//指针所指向的区域被删除了,但是指针并没有被删除
 
    str_ = newstr;//这是一个很高的技巧,直接将新建的区域的指针给str_即可
    return *this;
}


C++的I/O流库的一个重要特性就是能够支持新的数据类型的输出和输入。

cin与cout是流对象!!!

用户可以通过对插入符(<<)和提取符(>>)进行重载来支持新的数据类型。

流运算符的重载只能使用友元函数进行重载


//返回值采用引用,可以进行连续的输出

friend istream& operator>>(istream&, 类类型&);

friend ostream& operator<<(ostream&, const 类类型&);



问题:为什么一定要使用友元函数进行重载?

因为第一个对象不是自身,而是流对象,所以只能以友元的函数进行重载

ostream& operator<<(ostream& os, const String& str)
{
    os<<str.str_;
    return os;
}
 
istream& operator>>(istream& is, String& str)
{
    char tmp[1024];
    cin>>tmp;
    str = tmp;
    return is;
}

四、类型转换运算符->运算符operator new、operator delete


类转换运算符

必须是成员函数,不能是友元函数 (因为参数就是自身 而友元函数是没有this指针的)

没有参数(操作数是什么?)(因为操作数就是类自身  所以没必要指定参数)

不能指定返回类型(其实已经指定了  类型名其实就是返回值

函数原型:operator 类型名();

Integer::operator int()

{

return n_;

}

如果用static_cast<int>(n)调用的也是转换运算符


->运算符

先引入一个数据库类

#include <iostream>
using namespace std;

class DB
{
public:
    DB()
    {
        cout<<"DB"<<endl;
        //在构造函数太多功能
        //只能是构造函数的功能
        Open();
    }
    ~DB()
    {
        cout<<"~DB"<<endl;
        Close();
    }
    void Close()
    {   
        cout<<"Close"<<endl;
    }
    void Open()
    {  
        cout<<"Open"<<endl;
    }
    void Query()
    {
        cout<<"Query"<<endl;
    }
};

int main()
{
    DB *db = new DB;
    //实际上新建的时候就打开数据库了
    //db->Open();
    db->Query();
   // db->Close();
    //实际上很难确定什么时候删除对象
    //删除的时候已经关闭数据库了
    delete db;
    return 0;
}

实际上在构造函数里面打开数据库并不值得提倡,在构造函数里面做了太多的事情了,构造函数的功能仅仅只是构造一个对象,而且很难在一个地方delete 数据库,也就难以关闭数据库。

希望数据库在生命周期结束时能够自动释放


所以采用另一种方案

//利用确定性析构的方法

#include <iostream>
using namespace std;
 
class DBHelper
{
public:
    DBHelper()
    {
        cout<<"DB ..."<<endl;
    }
    ~DBHelper()
    {
        cout<<"~DB ..."<<endl;
    }
 
    void Open()
    {
        cout<<"Open ..."<<endl;
    }
 
    void Close()
    {
        cout<<"Close ..."<<endl;
    }
 
    void Query()
    {
        cout<<"Query ..."<<endl;
    }
};
 
class DB
{
public:
    DB()
    {
        db_ = new DBHelper;   //主要是不能在构造函数内调用db_ = new DB()o 所以不行
    }
 
    ~DB()
    {
        delete db_;
    }
 
    DBHelper* operator->()
    {
        return db_;
    }
private:
    DBHelper* db_;
};
 
 
int main(void)
{
    DB db;
    db->Open();
    db->Query();
    db->Close();
 
 
    return 0;
}

DB就相当于是一个智能指针  巧妙的使用smart point可以避免内存泄漏

DBHelper如果是一个基类指针  还可以指向派生类对象 可以实现多态

VC中ADO也是用这种方法来进行包装的

new有三种用法:new operator、operator new、placement new


Test* p1 = new Test(100); // new operator = operator new + 构造函数的调用

//可以通过跟踪看得更加明白

//如果不想跟踪可以按shift+F11跳出函数


//placement new不分配内存  在已经存在的栈上或者堆上进行创建

char chunk[10];

//是在chunk这块内存上创建的,并没有创建对象 new还有这种用法

Test* p2 = new (chunk) Test(200); //operator new(size_t, void *_Where)

// placement new,不分配内存 + 构造函数的调用

如何验证?将chunk强制转换为Test指针即可

Test* p3 = (Test*)chunk;

//说明P3是在chunk这块内存上进行创建的

Test* p3 = reinterpret_cast<Test*>(chunk);


注:

new operator不能被重载

而operator new可以被重载

注:size由编译器计算好

#include <iostream>
using namespace std;
 
 
 
class Test
{
public:
    Test(int n) : n_(n)
    {
        cout<<"Test(int n) : n_(n)"<<endl;
    }
    Test(const Test& other)
    {
        cout<<"Test(const Test& other)"<<endl;
    }
    ~Test()
    {
        cout<<"~Test()"<<endl;
    }
    //这是类作用域里面的new
    void* operator new(size_t size)
    {
        cout<<"void* operator new(size_t size)"<<endl;
        void* p = malloc(size);
        return p;
    }
 
    void operator delete(void* p)
    {
        cout<<"void operator delete(void* p)"<<endl;
        free(p);
    }
    //为什么Operator可以有两个参数呢?当这两者(与上一个函数)共存时
    //都是与Operator new共存的,优先调用带一个参数的
    void operator delete(void* p, size_t size)
    {
        cout<<"void operator delete(void* p, size_t size)"<<endl;
        free(p);
    }
 
    //这个new操作可以起到跟踪效果
    void* operator new(size_t size, const char* file, long line)
    {
        cout<<file<<":"<<line<<endl;
        void* p = malloc(size);
        return p;
    }
 
    void operator delete(void* p, const char* file, long line)
    {
        cout<<file<<":"<<line<<endl;
        free(p);
    }
 
    void operator delete(void* p, size_t size, const char* file, long line)
    {
        cout<<file<<":"<<line<<endl;
        free(p);
    }
 
    void* operator new(size_t size, void* p)
    {
        return p;
    }
 
    void operator delete(void *, void *)
    {
    }
 
    int n_;
};
 
//这是全局的New而不是类作用域中的new
void* operator new(size_t size)
{
    cout<<"global void* operator new(size_t size)"<<endl;
    void* p = malloc(size);
    return p;
}
 
void operator delete(void* p)
{
    cout<<"global void operator delete(void* p)"<<endl;
    free(p);
}
 
void* operator new[](size_t size)
{
    cout<<"global void* operator new[](size_t size)"<<endl;
    void* p = malloc(size);
    return p;
}
 
void operator delete[](void* p)
{
    cout<<"global void operator delete[](void* p)"<<endl;
    free(p);
}
 
int main(void)
{
    Test* p1 = new Test(100);   // new operator = operator new + 构造函数的调用
    delete p1;
 
    char* str = new char[100];
    delete[] str;
 
    char chunk[10];
 
    //参数是(200,chunk)  所以只要返回指针就可以了
    Test* p2 = new (chunk) Test(200);   //operator new(size_t, void *_Where)
                                        // placement new,不分配内存 + 构造函数的调用
    cout<<p2->n_<<endl;
    p2->~Test();                     // 显式调用析构函数
                                     // 因为P2不是在堆中创建的内存
 
    //Test* p3 = (Test*)chunk;
    Test* p3 = reinterpret_cast<Test*>(chunk);
    cout<<p3->n_<<endl;
 
 
 
    ​#define new new(__FILE__, __LINE__)
    //可以打印出文件的位置
    //所以容易找出内存泄漏的位置
    //Test* p4 = new(__FILE__, __LINE__) Test(300);
    Test* p4 = new Test(300);
    delete p4;
 
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值