一、
1、运算符重载
2、成员函数重载
3、非成员函数重载
4、运算符重载规则
运算符重载允许把标准运算符(如+、—、*、/、<、>等)应用于自定义数据类型的对象
直观自然,可以提高程序的可读性
体现了C++的可扩充性
运算符重载仅仅只是语法上的方便,它是另一种函数调用的方式
运算符重载,本质上是函数重载
不要滥用重载、因为它只是语法上的方便,所以只有在涉及的代码更容易写、尤其是更易读时才有必要重载
成员函数原型的格式:
函数类型 operator 运算符(参数表);
成员函数定义的格式:
函数类型 类名::operator 运算符(参数表){
函数体;
}
#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、运算符重载不允许发明新的运算符。
2、不能改变运算符操作对象的个数。
3、运算符被重载后,其优先级和结合性不会改变。
4、不能重载的运算符:
作用域运算符 :: 条件运算符?: 直接成员访问运算符.一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。
以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。(会破坏原有的功能)
类型转换运算符只能以成员函数方式重载
流运算符只能以友元的方式重载
前置++运算符重载
成员函数的方式重载,原型为:
函数类型 & 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 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;
}