一.友元
-
友元函数之全局函数
friend float distance(const Point &lhs, const Point &rhs);
-
友元函数之成员函数
friend float Line::distance(const Point &lhs, const Point &rhs);
-
友元之友元类
friend class Line;
-
友元是单向的,非传递性的,且不能被继承。
-
友元函数不受访问权限的限制
二.运算符重载
-
运算符本身就被重载多个版本:
int opeartor+(int,int); double operator+(double,double);
-
不能被重载的运算符:
. .* ?: :: sizeof
-
运算符重载的规则:
- 为了防止用户对标准类型进行运算符重载,C++规定重载的运算符的操作对象必须至少有一个是自定义类型或枚举类型 ;
- 重载运算符之后,其优先级和结合性还是固定不变的;
- 重载不会改变运算符的用法,原来有几个操作数、操作数在左边还是在右边,这些都不会改变;
- 重载运算符函数不能有默认参数,否则就改变了运算符操作数的个数。
- 重载逻辑运算符(&&,||)后,不再具备短路求值特性。
- 不能臆造一个并不存在的运算符,如@、$等
-
重载的形式:
采用普通函数的重载形式:对于其私有的数据成员要定义get方法来获取。
//运算符重载的形式1:普通函数的形式 Complex operator+(const Complex &lhs, const Complex &rhs) { cout << "Complex operator+(const Complex &, const Complex &)" << endl; return Complex(lhs.getReal() + rhs.getReal(),lhs.getImage() + rhs.getImage()); }
采用成员函数的重载形式:左操作数无需用参数输入,而是通过隐含的this指针。
//运算符重载的形式2:以成员函数的形式 Complex operator+(const Complex &rhs) { cout << "Complex operator+(const Complex &)" << endl; return Complex(_dreal + rhs._dreal,_dimage + rhs._dimage); }
采用友元函数的重载形式
//类内 friend Complex operator+(const Complex &lhs, const Complex &rhs); //类外 //运算符重载的形式3:友元函数的形式(推荐使用友元函数的形式进行重载) Complex operator+(const Complex &lhs, const Complex &rhs) { cout << "friend Complex operator+(const Complex &, const Complex &)" << endl; return Complex(lhs._dreal + rhs._dreal, lhs._dimage + rhs._dimage); }
-
特殊成员函数的重载
-
复合赋值运算符(+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=)
对象本身会发生改变,建议使用成员函数进行重载;//复合赋值运算符一般建议使用成员函数的形式进行重载(对象本身会发生改变) Complex &operator+=(const Complex &rhs) { cout << "Complex &operator+=(const Complex &)" << endl; _dreal += rhs._dreal; _dimage += rhs._dimage; return *this; }
-
自增自减运算符
推荐使用成员函数方式进行重载,后置++、— 函数参数带一个int作为标记
Complex &operator++() { cout << "Complex operator++()" << endl; ++_dreal; ++_dimage; return *this; } Complex operator++(int)//标记作用 { cout << "Complex operator++(int)" << endl; Complex tmp(*this); ++_dreal; ++_dimage; return tmp; }
-
赋值运算符
只能以成员函数进行重载,例如:Complex &operator=(const Complex &rhs)
四步骤:自复制,释放左操作数,深拷贝,返回*this
-
函数调用运算符
-
只能以成员函数重载,因为此时会有一个隐含的this指针,是对象类型,要是以其他函数重载,要是参数没有类类型或者枚举类型,会报错(重载的运算符的操作对象必须至少有一个是自定义类型或枚举类型)。
-
把重载了函数调用运算符的类创建的对象称为函数对象,调用时形式上与函数相似。函数也是一种对象,这是泛型思考问题的方式
class FunctionObject { public: FunctionObject(): _cnt(0){} int operator()(int x, int y) { cout << "int operator()(int, int)" << endl; ++_cnt; return x + y; } int operator()(int x, int y, int z) { cout << "int operator()(int, int, int)" << endl; ++_cnt; return x * y * z; } private: int _cnt;//函数状态 }; int func(int x, int y) { cout << "int func(int, int)" << endl; static int cnt = 0; ++cnt; return x + y; } typedef int (*pf)(int, int); int main(int argc, char **argv) { int a = 3, b = 4, c = 5; FunctionObject fo; /* fo.operator()(a, b); */ cout << "fo(a, b) = " << fo(a, b) << endl;//形式上与函数相似 cout << "fo(a, b, c) = " << fo(a, b, c) << endl;//形式上与函数相似 cout << "func(a, b) = " << func(a, b) << endl; pf f = func; cout << "f(a, b) = " << f(a, b) << endl; return 0; }
-
-
输入输出运算符函数
最好以友元函数形式进行重载(普通函数也可以,但是比较麻烦,不能以成员函数进行重载),因为它有两个参数,第一个是流对象,第二个是要IO的对象,如果以成员函数重载,第一个参数就是默认的this指针,参数顺序会搞错。
friend std::ostream &operator<<(std::ostream &os, const Complex &rhs); friend std::istream &operator>>(std::istream &is, Complex &rhs); //第一个引用和第二个引用不能干掉,因为流对象的拷贝构造函数在源码中=delete了 void test() { Complex c1(1, 2); cout << "c1 = " << c1 << endl;//链式编程 operator<<(cout, "c1 = "); operator<<(cout, c1); cout.operator<<(endl); operator<<(operator<<(cout, "c1 = "), c1).operator<<(endl); Complex c2; cin >> c2; operator>>(cin, c2); cout << "c2 = " << c2 << endl; }
-
下标访问运算符[ ]
char &operator[](size_t idx) { if(idx < _size) { return _data[idx]; } else//越界 { static char nullchar = '\0'; return nullchar; } }
-
成员访问运算符
-
->,只能以成员函数的形式重载,其返回值必须是一个指针或者重载了箭头运算符的对象。[返回值是重载了剪头运算符的对象,那么会递归调用operator->(),直到返回一个指向带有指定成员的的对象的指针]
-
-
举例
class Data { public: int getData() const { return _data; } private: int _data; }; class MiddleLayer { public: MiddleLayer(Data *pdata) : _pdata(pdata) {} Data *operator->() { return _pdata; //返回值是一个指针 } Data &operator*() { return *_pdata; } ~MiddleLayer() { delete _data; } private: Data * _pdata; }; class ThirdLayer { public: ThirdLayer(MiddleLayer * ml) : _ml(ml) {} MiddleLayer & operator->() { return *_ml; //返回一个重载了箭头运算符的对象 } ~ThirdLayer() { delete _ml; } private: MiddleLayer * _ml; }; void test() { MiddleLayer ml(new Data()); cout << ml->getData() << endl; cout << (ml.operator->())->getData() << endl; cout << (*ml).getData() << endl; ThirdLayer tl(new MiddleLayer(new Data())); cout << tl->getData() << endl; cout << ((tl.operator->()).operator->())->getData() << endl; }
-
-
总结
所有的一元运算符,建议以成员函数重载 ;
运算符 = () [] -> ->* ,必须以成员函数重载 ;
运算符 += -= /= *= %= ^= &= != >>= <<= 建议以成员函数形式重载 ;
其它二元运算符,建议以非成员函数重载。
-
三.类型转换
-
由其他类型向自定义类型转换。
隐式转换,若不想隐式转换,则在相应的构造函数前加explicit
Point pt = 1;//隐式转换
-
自定义类型向其他类型转换。
类型转换函数:operator 目标类型{ …}
类型转换函数的特征:
必须是成员函数;
参数列表中没有参数;
没有返回值,但在函数体内必须以return语句返回一个目标类型的变量。
//example: operator double() { cout << "operator double()" << endl; if(0 == _iy) { return 0; } else { return static_cast<double>(_ix)/_iy; } }
若一个类有类型转换函数,但未重载输出运算符,但是该类型转换函数的目标类型能被输出运算符识别,这时cout<<对象时,该对象会自动转换为能被输出运算符识别的类型。
四.类作用域
(作用域和可见域,如果发生“屏蔽”现象,类成员的可见域将小于作用域,但此时可借助this指针或“类名::”形式指明所访问的是类成员,这有些类似于使用::访问全局变量。)
int number = 1;
namespace wd
{
int number = 20;
class Test
{
public:
Test(int value)
: number(value)
{
cout << "Test(int)" << endl;
}
void print(int number)
{
cout << "形参number = " << number << endl;
cout << "数据成员number = " << this->number << endl;
cout << "数据成员number = " << Test::number << endl;
cout << "命名空间中的number = " << wd::number <<endl;
cout << "全局number = " << ::number <<endl;
}
private:
int number;
};
}//end of namespace wd
-
全局作用域
-
类作用域
- 一个类定义在另外一个类中,也叫内部类(嵌套类)。
- 当内部类设计为私有时,不能在类外定义对象,为公有时,可以通过【A::B 对象名】来类外定义内部类的对象。
- 内部类可以直接访问外部类的私有成员变量,但是外部类不能直接访问内部类的私有成员变量
-
块作用域
类的定义在代码块中,这是所谓局部类,该类完全被块包含,其作用域仅仅限于定义所在块,不能在块外使用类名声明该类的对象。
五.设计模式之pimpl
是通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏。PIMPL又称作“编译防火墙”,它的实现中就用到了嵌套类。
优点:
-
提高编译速度;[可将pimpl实现文件打包成动态库(编译好的),原类只占8个字节的空间大小。]
-
实现信息隐藏;
-
减小编译依赖,可以用最小的代价平滑的升级库文件;
-
接口与实现进行解耦;
-
移动语义友好。
//Line.h class Line { public: Line(int x1, int y1, int x2, int y2); void printLine() const; ~Line(); class LinePimpl;//类的前向声明 private: LinePimpl *_pimpl; }; //Line.cc class Line::LinePimpl { public: LinePimpl(int x1, int y1, int x2, int y2) : _pt1(x1, y1) , _pt2(x2, y2) { cout << "LinePimpl(int, int, int, int) "<< endl; } void printLinePimpl() const { _pt1.print(); cout << "---->"; _pt2.print(); } ~LinePimpl() { cout << "~LinePimpl()" << endl; } private: Point _pt1; Point _pt2; }; Line::Line(int x1, int y1, int x2, int y2) : _pimpl(new LinePimpl(x1, y1, x2, y2)) { cout << "Line(int, int, int, int)" << endl; } void Line::printLine() const { _pimpl->printLinePimpl(); } Line::~Line() { cout << "~Line()" << endl; if(_pimpl) { delete _pimpl; _pimpl = nullptr; } } //testLine.cc int main(int argc, char **argv) { Line line(1, 2, 3, 4); line.printLine(); return 0; }
六.单例模式的自动释放
-
友元类形式进行设计:
给实现单例模式的类添加一个友元类,该类析构函数实现调用单例模式的destory()函数或者直接delete。主函数中定义该友元类。
-
内部类+静态数据成员方式
给实现单例模式的类定义个私有的内部类,该类析构函数实现调用单例模式的destory()函数或者直接delete,同时增加个该类的静态数据成员,在类外进行定义初始化。一定要设置成静态的,因为如果不设置成静态的,内部类的析构函数由外部类的析构函数负责调用,而外部类的析构函数不会随着程序结束自动调用,所以该情况下内部类的析构函数不会执行。
-
atexit的方式
用atexit()函数在new时注册destory()函数,当进程退出时会进行自动调用。
-
pthread_once + atexit方式,函数原型:
int pthread_once(pthread_once_t *once_control,void (*init_routine)(void)); pthread_once_t once_control = PTHREAD_ONCE_INIT;
以上三种方式为防止多线程时多次new只能使用饿汉模式,使用pthread_once(),保证了当once_control相同时,init_routine只调用一次,此时用饱汉、饿汉模式均可以。once_control为静态数据成员和init函数为静态成员函数,因为getInstance()函数为静态的,只能访问静态的数据成员和成员函数。
-
饱汉和饿汉模式
饱汉模式是静态数据成员初始化时置为nullptr;
饿汉模式是静态数据成员初始化时就new创建对象,该模式有助于防止当多线程时,if(nullptr == _pInstance)时线程切换,造成多次new。
七.检测内存泄露的工具的使用
valgrind
安装:sudo apt install valgrind
使用:valgrind --tool=memcheck --leak-check=full ./test 【test为程序名】