#include <iostream>
using namespace std;
class Test
{
public:
Test(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
public:
int a;
int b;
public:
//全局函数
Test T_add(Test &t1, Test &t2)
{
Test t3;
t3.a = t1.a + t2.a;
t3.b = t1.b + t2.b;
return t3;
}
Test& add(Test &t2) //面向对象编程
{
a = a + t2.a;
b = b + t2.b;
return *this;
}
};
//全局函数
Test T_add(Test &t1, Test &t2)
{
Test t3;
t3.a = t1.a + t2.a;
t3.b = t1.b + t2.b;
return t3;
}
Test T_add01(Test *pthis, Test &t1, Test &t2)
{
Test t3;
t3.a = t1.a + t2.a;
t3.b = t1.b + t2.b;
return t3;
}
//从成员函数转换为全局函数,只需要加一个this指针(一个指向本类的指针)
//从全局函数转换为类的成员函数,只需要减去一个左操作数
void Demo01()
{
Test t1(2, 3), t2(3, 4);
Test t3 = T_add(t1, t2);
t3 = t1.T_add(t1, t2);
}
void main()
{
system("pause");
}
从成员函数转换为全局函数,只需要加一个this指针(一个指向本类的指针),而从从全局函数转换为类的成员函数,只需要减去一个左操作数即可,正如上面的代码所示。
下面是关于友元函数的一个例子:
#include <iostream>
using namespace std;
//const c 莫安排活
//register cpu身边的小太监
//typedef 混号王
class Test
{
public:
Test(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
int getA()
{
return a;
}
int getB()
{
return b;
}
//友元函数的特点是有一个参数肯定是友元类的指针或者引用
friend int OpMen(Test *p, int a); //函数声明放在哪里都一样
private:
int a;
int b;
};
int OpMen(Test *p, int a) //Test类的好朋友,友元函数
{
p->a = a;
return 0;
}
void main()
{
Test t1;
OpMen(&t1, 5);
system("pause");
}
友元函数并不是类的成员函数,它是一个全局函数,只需要在类里面声明一下就行了,放在public,private,protect任意一个域下都,没关系,使用友元函数唯一的好处是可以在函数里访问类的私有数据成员,就好像类打开了一扇窗一样,友元函数一般用的不多(C+++设计的败笔),可是有时候会用,了解一下即可。
运算符重载练习
#include <iostream>
using namespace std;
class Complex
{
public:
int a;
int b;
public:
Complex(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
void print()
{
printf("%d+%di\n", a, b);
}
};
Complex add(Complex &lop, Complex &rop)
{
Complex result;
result.a = lop.a + rop.a;
result.b = lop.b + rop.b;
return result;
}
Complex operator+(Complex &lop, Complex &rop)//重载
{
Complex result;
result.a = lop.a + rop.a;
result.b = lop.b + rop.b;
return result;
}
void Demo01()
{
Complex c1(1, 2), c2(3, 4);
//c1 = c1 + c2; //编译器压根不知道怎么加,但是编译器会给你提供一种机制,会让你实现自定义加操作
Complex c3 = add(c1, c2); //直接调用函数add
}
void Demo02()
{
Complex c1(1, 2), c2(3, 4);
//操作符重载首先是通过函数实现的
//+操作符有两个参数,左操作数和右操作数,特别留意左操作数
Complex c3 = c1 + c2; //实现了重载
//Complex c3 = operator+(c1, c2);//这样写也不会错
}
void main()
{
Demo02();
system("pause");
}
一般的类变量是设置成private属性的,因此我们一般都用友元函数实现运算符重载。
#include <iostream>
using namespace std;
class Complex
{
private:
int a;
int b;
public:
Complex(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
//通过友元函数实现操作符重载
friend Complex operator+(Complex &lop, Complex &rop);
friend Complex &operator++(Complex &lop);
friend Complex operator++(Complex &lop, int/*占位符*/);
//通过类的成员函数实现减号操作
Complex operator-(Complex &rop) //左操作数隐藏在this指针里面
{
Complex res;
res.a = a - rop.a;
res.a = b - rop.b;
return res;
}
Complex &operator--()
{
a--;
b--;
return *this;
}
Complex operator--(int) //后置--的成员函数实现
{
Complex tmp = *this;
this->a--;
this->b--;
return tmp;
}
void print()
{
printf("%d + %di\n", a, b);
}
};
Complex operator++(Complex &lop, int/*占位符*/)
{
Complex tmp = lop;
lop.a++;
lop.b++;
return tmp;
}
//全局函数原型推导
Complex &operator++(Complex &lop)
{
lop.a++;
lop.b++;
return lop;
}
Complex operator+(Complex &lop, Complex &rop)
{
Complex result;
result.a = lop.a + rop.a;
result.b = lop.b + rop.b;
return result;
}
void Demo01()
{
Complex c1(1, 2), c2(3, 4);
//c1 = c1 + c2; //编译器压根不知道怎么加,但是编译器会给你提供一种机制,会让你实现自定义加
Complex c4 = c1 - c2;
Complex c5 = c1.operator-(c2);
//目标,通过类的成员函数完成操作符重载
//1.要承认操作符操作是一个函数,要写函数原型
//2.写出函数调用语言
//3.完善函数原型
}
void Demo02()
{
Complex c1(1, 2), c2(3, 4);
//Complex c3 = operator+(c1, c2);
//操作符重载首先是通过函数实现的
//+操作符有两个参数,左操作数和右操作数,特别留意左操作数
Complex c3 = c1 + c2;
}
void Demo03()
{
Complex c1(1, 2), c2(3, 4);
c1++;
c1.print();
--c2;
c2++;
//先使用c2的属性,然后让属性+++
//operator++(c2, int/*占位符*/);//后置++
c2.print();
//成员函数实现后置--
}
void main()
{
Demo03();
system("pause");
}
使用友元函数和类的成员函数都可以实现操作符重载,正如同前面所提到的,成员函数由于已经包含了this指针,所以省略了一个操作数,其实两者差别不大。不过也存在差别,成员函数和友元函数重载操作符各有用处。规范的写法是,一般可以用成员函数实现的操作符重载就不用友元函数,所以使用友元函数重载的地方就很少了,一处场景如下下段代码所示。
关于前置++和后置++需要注意几点:
前置++的版本一般是这么写:foo&operator++()//前置++,前置返回引用
后置++一般这么写:foooperator++(int)//后置++,后置返回对象
前置返回引用,后置返回对象,这是通用做法。
class foo
{
public:
foo operator++() //后置++,后置返回对象
{
a++;
return *this;
}
foo& operator++(int) //前置++,前置返回引用,int占位符,只是标记,将前置和后置区分开来
{
a++;
return *this;
}
public:
foo(int a = 0)
{
this->a = a;
}
private:
int a;
};
在实际中,我们一般不用友元函数重载运算符,一般都用成员函数,但是使用友元函数重载运算符也有用武之地,下面是规范的写法:
#include <iostream>
using namespace std;
class Complex
{
private:
int a;
int b;
public:
Complex(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
//通过类的成员函数实现减号操作
Complex operator-(Complex &rop) //左操作数隐藏在this指针里面
{
Complex res;
res.a = a - rop.a;
res.a = b - rop.b;
return res;
}
Complex operator+(Complex &rop) //左操作数隐藏在this指针里面
{
Complex res;
res.a = a + rop.a;
res.a = b + rop.b;
return res;
}
Complex &operator++()
{
a++;
b++;
return *this;
}
Complex operator++(int) //后置--的成员函数实现
{
Complex tmp = *this;
this->a++;
this->b++;
return tmp;
}
Complex &operator--()
{
a--;
b--;
return *this;
}
Complex operator--(int) //后置--的成员函数实现
{
Complex tmp = *this;
this->a--;
this->b--;
return tmp;
}
void print()
{
printf("%d + %di\n", a, b);
}
friend ostream& operator<<(ostream &out, Complex &op); //这里才是friend函数的真正用武之地
protected:
private:
};
ostream& operator<<(ostream &out, Complex &op)
{
out << op.a << " " << op.b << endl;
return out;
}
void Demo01()
{
Complex c1(1, 2), c2(3, 4);
int a = 10;
char *p = "abc";
cout << p << endl;
//没有办法在cout类里面添加函数operator<<只能通过全局函数实现
cout << c1;
cout << c1 << endl << "链式编程测试" << endl;
//若void operator<<(ostream &out, Complex &op);
//operator<<(cout, &c1);
//void << endl;
//函数返回值当左值的时候,需要返回一个对象的引用
}
void main()
{
Demo01();
system("pause");
}
特别需要注意的地方是,如果ostream&operator<<(ostream&out,Complex&op)写成了voidoperator<<(ostream&out,Complex&op),虽然执行cout<<c1;不会出错,但是执行cout<<c1<<endl<<"链式编程测试"<<endl;会出错,这是因为operator=的返回值的缘故,cout<<c1相当于执行operator<<(cout,c1),返回void,接下来执行void <<endl,不错才怪。
同时,上面的ostream&operator<<(ostream&out,Complex&op)是友元函数运用的绝好时期,因为如果要用成员函数实现<<运算符重载,那么必须在cout里面实现,cout并没有在成员函数里实现operator<<(Complex&op),当然它也不可能实现,怎么办呢?恰好运用友元函数可以解决这个问题。
接下来是运算符重载的一个很好的例子:
/*Array.h*/
#ifndef _ARRAY_H_
#define _ARRAY_H_
#include <iostream>
class Array
{
private:
int mLength;
int* mSpace;
public:
Array(int length);
Array(const Array& obj);
int length();
void setData(int index, int value);
int getData(int index);
~Array();
public:
int& operator[](int index);
Array& operator=(Array &rop);
bool operator==(Array &rop);
bool operator!=(Array &rop);
};
//[] = == !=
//
#endif
/*Array.cpp*/
#include "iostream"
#include "Array.h"
using namespace std;
//区别 如果不加引用会构造一个匿名对象(临时对象)
//开销会非常大
Array::Array(int length)
{
if (length < 0)
{
length = 0;
}
mLength = length;
mSpace = new int[mLength];
}
Array::Array(const Array& obj)
{
mLength = obj.mLength;
mSpace = new int[mLength];
for (int i = 0; i<mLength; i++)
{
mSpace[i] = obj.mSpace[i];
}
}
int Array::length()
{
return mLength;
}
void Array::setData(int index, int value)
{
mSpace[index] = value;
}
int Array::getData(int index)
{
return mSpace[index];
}
Array::~Array()
{
mLength = -1;
delete[] mSpace;
}
//需要当左值,返回引用
//返回引用是要注意生命周期
/************************************************************************/
/*
Complex &operator-(operator &rop)
{
Complex tmp;
tmp.a = this.a - rop.a;
tmp.b = this.b - rop.b;
return tmp; //这样返回会出问题
return rop;//这样返回没问题
}
*/
/************************************************************************/
//printf("%d", a[i]);
//a[i] = 10;
int& Array::operator[](int index) //函数返回值当左值 a[i] = 10;
{
if (index > mLength)
{
}
return mSpace[index];
}
//测试用例
//a2 = a3;
//a1 = a2 = a3;连等操作 =从右向左 先执行a2 = a3返回 a2再执行 a1 = a2;
//a1.operator=(a2.operator=(a3)))
//函数返回值当左值
Array& Array::operator=(Array &rop)
{
if (mSpace != NULL)
{
delete[]mSpace;
mLength = 0;
mSpace = NULL;
}
mSpace = new int[rop.mLength];
mLength = rop.length();
for (int i = 0; i < rop.mLength; ++i)
{
mSpace[i] = rop.getData(i);
}
return *this;
}
bool Array::operator==(Array &rop)
{//先判断长度是否相等,在判断数据是否相等
if (rop.length() != mLength)
{
return false;
}
for (int i = 0; i < mLength; ++i)
{
if (mSpace[i] != rop.getData(i))
{
return false;
}
}
return true;
}
bool Array::operator!=(Array &rop)
{
return !this->operator==(rop);
}
要特别注意运算符重载的返回值,就像上面演示的代码一样,int&Array::operator[](intindex) 之所以返回int&,是因为它的性质所决定的,int a = array[5];[]的返回值可以做右边的操作数,这时候返回int是没问题的,不过array[5] = 10;这一句[]的返回值要做左值,就不能返回int了,因为返回的int无法修改,所以要返回int&。
同时也要注意返回类还是类的引用,返回类的时候,如
Complexoperator--(int)//后置--的成员函数实现
{
Complextmp=*this;
this->a--;
this->b--;
return tmp;
}
在主函数里假设这么调用Complex t2= t1--;
开销是比较大的,返回tmp时c++会创建一个临时对象,调用拷贝构造函数用tmp初始化该临时对象,此时C++做了优化,会将t2和该临时对象绑定起来。
若这么调用Complex t2; t2 = t1--;,这时候开支更大了,返回tmp时c++会创建一个临时对象,调用拷贝构造函数用tmp初始化该临时对象,将该临时对象赋给t2,调用t2的=操作,调用该操作之后,该临时对象会立马析构掉。
而返回引用时省略了临时变量,所以你要考量值不值。
并非所有时候都能返回引用,如如上面的代码,如果返回tmp的引用,这是肯定不行的,因为tmp的生命期只在operator—函数里,返回了引用是要出问题的。当然生命期长的,如在堆上分配的,调用函数传过来的,返回它们的引用自然没问题。