1. 友元
1.1 友元的三种形式
1. 友元之普通函数形式
class Point
{
friend double distance(const Point &p1, const Point &p2);
private:
int _ix;
int _iy;
};
double distance(const Point &p1, const Point &p2)
{
return hypot(p1._ix - p2._ix, p1._iy - p2._iy);
}
void test()
{
distance(pt1, pt2);
}
2. 友元之成员函数形式
class Point; // 类的前置声明
// 如果成员函数在Line类内实现的话,因为Point类的具体实现在下面,可能会报不完整类型错误
class Line
{
public:
double distance(const Point &p1, const Point &p2);
};
class Point
{
friend double Line::distance(const Point &p1, const Point &p2);
······
};
double Line::distance(const Point &p1, const Point &p2)
{
return hypot(p1._ix - p2._ix, p1._iy - p2._iy);
}
void test()
{
Line()::distance(pt1, pt2);
}
3. 友元之友元类
class Point; // 类的前置声明
// 如果成员函数在Line类内实现的话,因为Point类的具体实现在下面,可能会报不完整类型错误
class Line
{
public:
double distance(const Point &p1, const Point &p2);
void setPoint(Point &p, int ix);
};
class Point
{
friend class Line;
······
};
double distance(const Point &p1, const Point &p2)
{
return hypot(p1._ix - p2._ix, p1._iy - p2._iy);
}
void setPoint(Point &p, int ix)
{
p._ix = ix;
}
void test()
{
Line line;
line.distance(pt1, pt2);
line.setPoint(pt1, 10);
}
1.2 友元的注意事项
- 友元不受类访问权限的控制
- 友元的关系不能传递(A->B->C)
- 友元的关系是单向的(A是B的友元,但B不一定是A的友元)
- 友元的关系不能继承
- 友元关系最好谨慎使用,否则容易破坏封装性
2. 运算符重载
本质就是函数,函数重载
2.1 不能重载的运算符函数
- 成员访问运算符
.
- 成员指针运算符
.*
- 三目运算符
?:
- 作用域限定符
::
sizeof
2.2 运算符重载的规则
- 至少有一个操作数是类类型或枚举类型
- 运算符重载后,优先级和结合性不变
- 运算符重载时,不能改变参数的个数和顺序,也不能设置默认参数
- 在重载逻辑运算符(&&、||)后,不再具备短路求值特性
- 不能臆造不存在的运算符,如$、@
2.3 运算符重载的三种形式
1. 普通函数形式,但需要get函数作为支撑
Complex operator+(const Complex &lhs, const Complex &rhs)
{
return Complex(lhs.getReal() + rhs.getReal(), lhs.getImag() + rhs.getImag());
}
2. 成员函数形式,但因为有一个隐含的this指针,形式上少了一个参数
class Complex
{
public:
Complex operator+(const Complex &rhs)
{
return Complex(_dreal + rhs._dreal, _dimag + rhs._dimag);
}
};
3. 友元的形式(推荐使用)
class Complex
{
friend Complex operator+(const Complex &lhs, const Complex &rhs);
}
Complex operator+(const Complex &lhs, const Complex &rhs)
{
return Complex(lhs._dreal + rhs._dreal, lhs._dimag + rhs._dimag);
}
2.4 特殊运算符的重载
1. 复合赋值运算符
class Point
{
···
Point &operator+=(Point &rhs)
{
_ix += rhs._ix;
_iy += rhs._iy;
return *this;
}
···
};
// pt1 += pt2;
2. 自增自减运算符
class Point
{
···
// 前置++
Point &operator++()
{
++ _ix;
++ _iy;
return *this;
}
// 后置++
Point operator++(int)
{
Point tmp(*this);
_ix ++;
_iy ++;
return tmp;
}
···
};
// cout << (++pt1);
// cout << (pt2++);
3. 赋值运算符
class Point
{
···
···
};
4. 函数调用运算符
class Point
{
···
int operator()(int a, int b, int c)
{
return a * b * c;
}
···
};
void test()
{
Point pt1;
cout << pt1(1, 3, 5) << endl; // 对象函数
}
5. 下标访问运算符
class CharArray
{
char &operator[](size_t idx)
{
if (idx < _size)
{
return _pstr[idx];
}
else
{
static char nullChar = '\0';
return nullChar;
}
}
};
void test()
{
const char *pstr = "Hello, world!";
CharArray ca(pstr);
for (size_t idx = 0; idx != strlen(pstr); ++ idx)
{
ca[idx] = pstr[idx];
}
ca[8] = '6';
for (size_t idx = 0; idx != strlen(pstr); ++ idx)
{
cout << ca[idx] << " ";
}
cout << endl;
}
6. 成员访问运算符
void test()
{
SecondLayer sl1(new Data(10));
cout << sl1->getData() << endl;
// cout << sl1.operator->()->getData() << endl;
ThirdLayer tl1(new SecondLayer(new Data(30)));
cout << tl1->getData() << endl;
// cout << tl1.operator->().operator->()->getData() << endl;
}
7. 输入输出流运算符
class Point
{
···
friend std::istream &operator>>(std::istream &is, Point &rhs);
friend std::ostream &operator<<(std::ostream &os, const Point &rhs);
···
};
std::istream &operator>>(std::istream &is, Point &rhs)
{
readInt(is, rhs._ix);
readInt(is, rhs._iy);
return is;
}
std::ostream &operator<<(std::ostream &os, const Point &rhs)
{
os << "(" << rhs._ix
<< "," << rhs._iy
<< ")" << endl;
return os;
}
3. 类型转换
3.1 由其他类型向自定义类型转换
修改构造函数
class Complex
{
friend class Point;
};
class Point()
{
Point(const Complex &rhs)
{
_ix = rhs._dreal;
_iy = rhs._dimag;
}
};
3.2 由自定义类型向其他类型转换
特点:
1. 必须是成员函数
2. 参数列表没有参数
3. 没有返回值,但在函数体内必须以return语句返回一个目标类型的变量
// 模板
operator 目标类型()
{
//...
}
// 例子
class Point()
{
···
public:
operator Complex()
{
return Complex(_ix, _iy);
}
···
};
4. 类域
4.1 作用域
注意作用域的屏蔽现象
4.2 内存泄漏检测工具valgrind
g++ memcheck.cc -o memcheck -g
valgrind --tool=memcheck - `` -leak-check=full ./memcheck
4.3 单例模式的自动释放
方式一:友元,在另一个类的析构函数中写
destroy()
函数的代码,实例化一个栈对象自动执行
方式二:内部类 + 静态数据成员,用private修饰,把该辅助类的对象当作数据成员,且设置为静态数据成员
方式三:int atexit(void (*function)(void))
+ 饿汉模式(初始化时直接调用getInstance()函数):
在getInstance()
函数中if判断后调用
方式四:方式三 + 线程安全pthread_once(&init, _once);
// 方式三
class Singleton
{
public:
static Singleton *getInstance()
{
if (_pInstance == nullptr)
{
atexit(destroy);
_pInstance = new Singleton();
}
return _pInstance;
}
static void destroy()
{
if (_pInstance)
{
delete _pInstance;
_pInstance = nullptr;
}
}
private:
Singleton()
{
cout << "Singleton()" << endl;
}
~Singleton()
{
cout << "~Singleton()" << endl;
}
private:
static Singleton * _pInstance;
};
Singleton *Singleton::_pInstance = getInstance();
5. std::string底层实现
5.1 string发展历史
1. 深拷贝
class String
{
public:
String(const String &rhs)
: _pstr(new char[strlen(rhs._pstr) + 1]())
{
strcpy(_pstr, rhs._pstr);
}
private:
char * _pstr;
};
2. 写时复制(cow)
当只进行读操作时进行浅拷贝,如果需要写操作时,再进行深拷贝。
实现方式:浅拷贝 + 引用计数
3. 短字符串优化(sso)
当字符串长度小于16字节时,存放在栈上,当字符串长度大于等于16字节时,存放在堆上
5.2 写时赋值string的实现
class String
{
public:
String()
: _pstr(new char[5]() + 4) // 字符串指针始终指向字符串值开始位置
{
cout << "String()" << endl;
initRefCount(); // 引用计数初始化为1
}
String(const char * pstr)
: _pstr(new char[strlen(pstr) + 5]() + 4)
{
cout << "String(const char *)" << endl;
strcpy(_pstr, pstr);
initRefCount(); // 引用计数初始化为1
}
String(const String &rhs)
: _pstr(rhs._pstr) // 浅拷贝
{
cout << "String(const String &)" << endl;
increaseRefCount();
}
~String()
{
cout << "~String()" << endl;
release();
}
String &operator=(const String &rhs)
{
// 1. 避免自复制
if (&rhs != this)
{
// 2. 释放左值
release();
// 3. 浅拷贝
_pstr = rhs._pstr;
increaseRefCount();
}
// 4. 返回 *this
return *this;
}
#if 0
char &operator[](size_t idx)
{
if (idx < size()) {
if (getRefCount() > 1) // 引用计数大于1,就新开辟一块空间
{
char *tmp = new char[strlen(_pstr)]();
strcpy(tmp, _pstr);
decreaseRefCount();
_pstr = tmp;
initRefCount();
}
return _pstr[idx]; // 引用计数等于1,直接在原空间上修改
}
else
{
static char charNull = '\0';
return charNull;
}
}
#endif
private:
class CharProxy
{
public:
CharProxy(String &self, size_t idx)
: _self(self)
, _idx(idx)
{
}
// s1[3] = 'H',写操作
char &operator=(const char &ch);
// cout << s1[3],读操作
friend std::ostream &operator<<(std::ostream &os, const String::CharProxy &rhs);
private:
String & _self;
size_t _idx;
};
public:
// 重载运算符[],返回一个CharProxy对象
CharProxy operator[](size_t idx)
{
return CharProxy(*this, idx);
}
int getRefCount()
{
return *(int *)(_pstr - 4);
}
char * c_str()
{
return _pstr;
}
friend std::ostream &operator<<(std::ostream &os, String &rhs);
// 在类外能够访问到CharProxy
friend std::ostream &operator<<(std::ostream &os, const CharProxy &rhs);
private:
size_t size()
{
return strlen(_pstr);
}
void initRefCount()
{
*(int *)(_pstr - 4) = 1; // 引用计数初始化为1
}
void increaseRefCount()
{
++ *(int *)(_pstr - 4);
}
void decreaseRefCount()
{
-- *(int *)(_pstr - 4);
}
void release()
{
decreaseRefCount();
if (!getRefCount())
{
delete [] (_pstr - 4);
}
}
private:
char * _pstr;
};
// 写操作 s1[3] = 'H' CharProxy = char
char &String::CharProxy::operator=(const char &ch)
{
if (_idx < _self.size()) {
if (_self.getRefCount() > 1) // 引用计数大于1,就新开辟一块空间
{
// 深拷贝
char *tmp = new char[strlen(_self._pstr) + 5]() + 4; // 注意开辟空间大小和指针指向位置
strcpy(tmp, _self._pstr);
_self.decreaseRefCount();
_self._pstr = tmp;
_self.initRefCount();
}
_self._pstr[_idx] = ch;
return _self._pstr[_idx]; // 引用计数等于1,直接在原空间上修改
}
else
{
static char charNull = '\0';
return charNull;
}
}
// cout << s1[3]
std::ostream &operator<<(std::ostream &os, const String::CharProxy &rhs)
{
os << rhs._self._pstr[rhs._idx];
return os;
}
std::ostream &operator<<(std::ostream &os, String &rhs)
{
os << rhs._pstr;
return os;
}