6.11运算符重载

友元

友元的设置是因为这样就可以访问类中的private成员

设置某一个类或者是函数为友元的,用friend关键字声明友元

友元的三种形式:普通函数、成员函数、友元类

普通函数:在类内部声明然后再类外部定义。 

class Point{
public:
    friend float distance(const Point & rhs, const Point & rhs1);
private:
    int _ix;
    int _iy;
};

float distance(const Point & rhs, const Point & rhs1){
    return (rhs._ix - rhs1._ix );
}

成员函数:另一个类的成员函数访问该类的私有成员

Point设置为前向声明,这样line中可以知道有Point,但是不知有_ix,所以说将函数声明和实现拆开,实现放在这两个对象的后面。

【注意】一个friend只能声明一个友元函数,所以可以将friend放在前面

当需要申明的友元函数非常多的时候每次都要在这个类中设置友元,同时又在原类中声明在类下面进行实现很麻烦,所以说这样直接将一个类申明为另外一个类的友元类

//友元类的实现就是
class Point;
class Line{
函数声明;
};
class Point{
friend class Line;
};
函数定义;

友元的特点

友元:使得使用类内部的成员使用更加灵活。

但是破坏了类的封装性。

友元是单向的,如果说A类是B类的友元类,A可以访问B的私有成员。

class B{

        friend class A;

};

友元不具备传递性。

友元不能被继承。

运算符重载

这样的话就可以和自定义类型进行一样的操作,非常的方便。

不能重载的运算符:有5个

可以进行重载的运算符有42个

【了解】sizeof int 可以这样写

1234点运算符和sizeof是不能重载的

运算符重载的规则以及形式:

1. 操作数据的类型必须要有自定义类型或者枚举类型(如果都是int那么1+1 = 0乱套了)

2. 优先级和结合性固定不变的

3.操作符的个数是保持不变的,运算符重载的时候不能设置默认参数,这样的话操作数的个数就发生了变化

4. &&与||不再具有短路求值特性,不能造一个根本就不存在的运算符

运算符重载的形式有三种:

  1. 采用友元函数的重载形式
  2. 采用普通函数的重载形式
  3. 采用成员函数的重载形式

友元函数实现的重载形式 

这样就可以借助于friend关系来访问私有元素,比第二种更加安全,只能在这个函数才能访问私有元素,不像是通过类中外开放接口。

//友元的方式实现运算符重载
class Complex{
    //...
    friend Complex operator+(const Complex & lhs, const Complex & rhs);
    //...
};

//友元函数中的普通函数形式实现重载
Complex operator+(const Complex & lhs, const Complex & rhs){
    //...
}

void test0(){
    Complex cx(1,2);
    Complex cx2(3,4);
    Complex cx3 = cx + cx2; //看上去和内置类型的计算一样了
    //Complex cx3 = operator(cx,cx2);   //本质上是调用了operator+函数
}

普通的函数实现的重载形式

对于私有成员的保护不够,因为在外面直接使用getReal和getImage就可以获取私有元素

class Complex {
public:
	//...
	double getReal() const { return _real; }
	double getImage() const { return _image; }
	//...
};

Complex operator+(const Complex & lhs, const Complex & rhs)
{
	return Complex(lhs.getReal() + rhs.getReal(),
			lhs.getImage() + rhs.getImage());
}

void test0()
{
	Complex c1(1, 2), c2(3, 4);
	Complex c3 = c1 + c2;//ok
}

还可以通过成员函数来实现

class Complex{
public:
	//...
	Complex operator+(const Complex & rhs)
	{
		return Complex(_real + rhs._real, _image + rhs._image);
	}
};

【注意】在类中实现的时候就需要注意只是需要传递一个对象就可以,因为本身还有一个this指针指向的对象。

像加号这一类不会修改操作数的值的运算符,倾向于采用友元函数的方式重载。参数中会标记const,这样就不能对于函数中的数

p3 = p1 + p2
//如果是成员函数
p3 = p1.operator+(p2);

//友元函数
p3 = operator+(p1,p2);

进行修改。如果是定义在类内部,那样的话就可以直接访问并且修改私有成员的内容。

【注意】友元函数和成员函数的表面都是+,本质上有区别

+=运算符

对于需要修改最终的对象的时候倾向于采用成员函数的方式实现。

重载形式的选择(重点)

  • 不会修改操作数的值的运算符,倾向于采用友元函数的方式重载
  • 会修改操作数的值的运算符,倾向于采用成员函数(有this提供就不写该对象)的方式重载

赋值=、下标[ ]、调用()、成员访问->、成员指针访问->* 运算符必须是成员函数形式重载

考虑函数的返回值,尽量和内置类型对象保持一致

【了解】在类内部只有=赋值运算符函数是类是默认提供的,其他的赋值运算符函数是不会默认提供的

++运算符重载

难点在于有前置++和后置++之分,借助于函数参数区别虽然不需要参数但是在后置++的参数中写int就可以

前置++是返回修改以后的对象,使用引用。参数是无参的。

后置++是返回修改修改以前的副本,返回副本。参数设置为int,只是为了区分。

//前置++的形式
    Complex& operator++(){
        cout << "Complex & operator++()" << endl;
        ++_real;
        ++_image;
        return *this;
    }

    //后置++的形式
    //参数列表中要多加一个int
    //与前置形式进行区分
    Complex operator++(int){
        cout << "Complex operator++(int)" << endl;
        Complex tmp(*this);
        ++_real;
        ++_image;
        return tmp;
    }

[]运算符

class CharArray{
public:
    CharArray(const char * pstr)
    : _capacity(strlen(pstr) + 1)
    , _data(new char[_capacity]())
    {
        strcpy(_data,pstr);
    }

    ~CharArray(){
        if(_data){
            delete [] _data;
            _data = nullptr;
        }
    }

    //"hello"来创建
    //capacity = 6
    //下标只能取到 4
    char & operator[](size_t idx){
        if(idx < _capacity - 1){
            return _data[idx];
        }else{
            cout << "out of range" << endl;
            static char nullchar = '\0';
            return nullchar;
        }
    }

    void print() const{
        cout << _data << endl;
    }
private:
    size_t _capacity;
    char * _data;
};

【注意】前面的const是不能修改指向,后面的那个const是不能修改其中的内容,但是如果是存的是一个指针的话,那么是不能修改这个指针指向,但是其中的内容也是可以进行改动的,如果不想让内容后序改动只能初始化的话,那么就在private那个指针的前面加上const

【注意】只需要提供一个idx就可以,因为会有this指针已经指向这个对象了

输入输出流重载

输出是一个cout(ostream对象),因为cout(cin)是不能进行复制的所以使用引用,同理形参的位置也是使用引用

cout和cin不能复制是因为在内核代码中对于拷贝构造函数是删除的。

成员函数因为隐含的this指针是在第一个,所以只能写成很奇怪的方式

p1 << cout;

所以说只能是使用友元函数的方式

class Point {
public:
	//...
	friend ostream & operator<<(ostream & os, const Point & rhs);

private:
	int _x;
	int _y;
};

ostream & operator<<(ostream & os, const Point & rhs)
{
	os << "(" << rhs._x << "," << rhs._y << ")";
	return os;
}

void test0(){
    Point pt(1,2);
    cout << pt << endl; //本质形式: operator<<(cout,pt) << endl;
}

输入流

仍然是用友元函数的方式实现

cin的返回对象仍然是cin本身,需要修改对象本身传入一个引用

class Complex {
public:
	//...
	friend istream & operator>>(istream & is, Complex & rhs);
private:
	int _real;
	int _image;
};

istream & operator>>(istream & is, Complex & rhs)
{
	is >> rhs._real;
	is >> rhs._image;
	return is;
}

成员访问运算符(->这个可以重载,但是.不可以重载)

本节的例子感觉有点奇特,要求明白意思。

class Data
{
public:
    Data(){}
    ~Data(){}

    int getData() const{ return _data; }
private:
    int _data = 10;
};

class MiddleLayer
{
public:
    MiddleLayer(Data * p)
    : _pdata(p)
    {}

    ~MiddleLayer(){
        if(_pdata){
            delete _pdata;
            _pdata = nullptr;
        }
    }
private:
    Data * _pdata;
};

 要求使用MiddleLayer ml;来实现Data * _pdata的功能。理由是因为M中只有一个指针Data * _pdata,要求ml对象实现完成指针的所有功能。

为什么需要进行封装:将一个栈上的指针封装成一个对象的时候,因为栈上的对象被销毁的时候,会主动调用析构函数,可以回收堆上的空间。

Data * p = new Data();
p->getData();
(*p).getData();
delete p;
p = nullptr;

//要求可以实现
ml->getData();
(*ml).getData();
ml = nullptr;

如果使用下面的方式进行初始化的时候,不能进行delete pdata操作,会出现double free问题。因为如果使用了delete pdata;但是M中的指针依然是指向这个位置,当M销毁的销毁的时候调用析构函数清理堆上空间的时候就出问题了。

如果是让pdata单独管理一块堆上的空间的话,就不会出现问题。

重载类型没得选,只能成员函数形式(特定规定->)。

返回类型是Data 类型的指针。

在getData()前面会自动添加->指针,即使是已经对于->指针进行了重载。

智能指针的最大的效果就是自动回收。

要返回一个引用,这样的话不是对于副本进行操作,而是对于本身。 

Data & operator *(){
    return *_pdata;
}

 额外再加一层:

Data *p = new Data();
MiddleLayer ml(p);
ThirdLayer tl(ml);

上述这种会出现tl管理栈上对象的效果,这样的话当tl(最后被调用)先销毁的时候销毁了栈上的空间,当程序结束的时候程序销毁栈上空间的时候出现问题。

想法就是将这个栈上的空间放到堆上去,同时借鉴于避免浅拷贝的问题所以都是使用匿名

ThirdLayer tl(new MiddleLayer(new data));

在构造的时候先构造thL然后Ml然后是data构造函数。

析构函数的调用也是同样的顺序。

这个地方在thl对象的->返回的是ml对象,ml的又会调用->获取到的data *pdata然后就是正常的->

【注意】->只能是写一次,下面的*可以一次次的调用,也可以一次调用

【理解】这个地方需要注意->和*函数和返回值是一样的,因为都是需要返回一个middleLayer对象,然后借助于middleLayer对象的运算符重载进行处理。

//*可以多次调用

cout << (*(*tl)).getData() << endl;

//*的实现直接一步到位的使用

*tl.getData();

//一步到位的实现

//方式一:借助于跨层的实现,*pml得到ml对象,*ml得到data
Data & operator*(){
    return *(*pml);
}

//方式二:借助于友元函数的实现,因为ml中的_pdata是一个私有元素
//所以在ml类中设置TL为友元类,这样的话就可以在tl中访问_pdata指针
Data & operator*(){
    return *(*pml)._pdata;
}

//在函数的中调用方式都是这样
cout << (*tl).getData() << endl;

//但是两者的本质是不同的
//方式一:借助于跨层重载
cout << (tl.operator*()).operator*().getData() << endl;

//方式二:借助于友元类
cout << tl.operator*().getData() << endl;

可调用实体

普通函数,函数指针,成员函数指针,类中的成员函数,函数对象都被称为可调用实体。

函数对象

重载了函数调用运算符的类的对象称为函数对象,可以像对象一样使用,也可以像函数一样使用

背景需求:希望对象像函数一样被调用。查看函数被调用多少次,如果是直接定义全局变量不安全,如果是定义局部静态变量出了大括号就不能随便访问了。所以说一个想法是将这个计数器放在类中的私有区域中,通过一个接口在访问访问次数。

所以说:函数对象可以携带函数的状态(函数被调用的次数)。

对于运算符()进行重载

class FunctionObject{
public:
    void operator()(){
        cout << "FunctionObject operator()()" << endl;
        ++ _count;
    }

    int operator()(int x, int y){
        cout <<"operator()(int,int)" << endl;
        ++ _count;
        return x + y;
    }
    
    int _count = 0;//携带状态
};

void test0(){
    FunctionObject fo;
  
    cout << fo() << endl;
    cout << fo.operator()() << endl;//本质

    cout << fo(5,6) << endl;
    cout << fo.operator()(5,6) << endl;//本质

    cout << "fo._count:" << fo._count << endl;//记录这个函数对象被调用的次数
}

函数指针

void print(int x){
    cout << "print:" << x << endl;
}

void display(int x){
    cout << "display:" << x << endl;
}

int main(void){
    //省略形式
    void (*p)(int) = print;
    p(4);
    p = display;
    p(9);
    
    void (*)(int) p = print;//error
    
    //完整形式
    void (*p2)(int) = &print;
    (*p2)(4);
    p2 = &display;
    (*p2)(9);
}

p和p2可以抽象出一个函数指针类型void(*)(int) —— 逻辑类型,不能在代码中直接以这种形式写出

使用typedef定义:

Function类的“对象”可以这样使用,这个类的“对象”都是特定类型的函数指针,只能指向一种函数(这种函数的类型在定义函数指针类时就决定了)

typedef void(*Function)(int);

    Function f;
    f = print;
    f(19);
    f = display;
    f(27);

成员函数指针

定义一个成员函数信息,确定属于哪一个类,函数返回类型,函数信息

非静态类的函数

typedef void (*Function)(int); //定义函数指针类型

typedef void (FFF::*MemberFunction)(int); //定义成员函数指针类型

.*是成员指针运算符的一种形式

//只能这么写,定义时需要使用完整形式即&即取地址
//还需要指明类的命名,因为在成员函数在存储的时候也会有类的标记
void (FFF::*p)(int) = &FFF::print;
FFF ff;
(ff.*p)(4);

//使用的时候进行解引用
//通过对象调用
//需要解引用,和定义配套的使用。同时加上*也不会从这个类中找相应的函数
//又优先级比较高所以加()
//.*是配套的成员指针运算符一种形式

->*成员指针运算符的一种形式

FFF * fp = new FFF();

(fp->*mf)(65);//通过指针调用成员函数指针

函数指针的意义:就是将函数视为一个变量存在,也可以将函数指针作为参数传给别的函数。

成员函数也是同样的效果,就是记得加上类名。

【注意】注意一个问题,一个空指针也可以调用得到结果

现象:空指针没有指向有效的对象。对于不涉及数据成员的成员函数,不需要实际的对象上下文,因此就算是空指针也可以调用成功。对于涉及数据成员的成员函数,空指针无法提供有效的对象上下文,因此导致错误。

原因:因为在查找的时候就直接从程序代码区就可以找到相应的类名对应的成员函数,不会用到这个对象,但是当访问对象中的成员的时候,需要借助于这个对象指针。

类型转换函数

以前又int向double这种转换,现在讨论自定义类对象向系统类型转换,以及转换回来。

以前见到过类似的隐式转换

因为默认参数:Point p = 1;

string str = "hello";将const char *转换为string类型

class Point{
public:
//将返回类型写在中间
 operator int(){
     cout << "operator int()" << endl;
     return _ix + _iy;
 }
	//...
};

自定义类型转换内置类型

Point pt(3,4);

pt = 1;

一般是先进行隐式转换,发现1可以通过默认参数构造成一个point,然后再调用到赋值运算符函数

如果将默认构造函数的隐式转换关掉,那么就出错。

这个时候可以修改赋值运算符函数传入一个int数据得到一个point对象

num = pt使用类型转换函数,不然只能去int去修改int的函数。

自定义类型向自定义类型进行转换

多种方式

1.可以在一个类中实现类型转换

class Complex
{
 //...
 operator Point(){
     cout << "operator Complex()" << endl;
     return Point(_real,_image);
 }
};

2.也可以进行隐式转换

注意前向声明(Point可以知道又com),声明为friend(com中可以访问),以后后来的定义

3.也可以使用赋值运算符修改的方式

如果当前2条内容都存在的时候那么会使用类型转换函数,而不是使用隐式转换函数,不要系统自己隐式转换了。类型转换的优先级高于隐式转换。

如果当这些3条内容都存在的时候那么会使用赋值运算符函数,因为赋值直接就可以使用,不用再进行类型转换。

string类型的+=

其实const函数和非const函数其中的内容是一样的,但是必须要再定义一遍,因为const只能使用const函数。

const函数的形式

const char & operator[](String &s) const{}前面的const 用来限制不能修改这一片区域的内容,后面的这个const是用来限制只是被const对象来使用。

输入流的实现

耍流氓做法一

//TODO这个地方也就是耍流氓,只能动态扩容,勉强用vector实现
istream &operator>>(std::istream &is, String &s){
    string word;
    is >> word;
    if(s._pstr){
        delete [] s._pstr;
        s._pstr = new char[word.size() + 1]();
        strcpy(s._pstr, word.c_str());
    }
    return is;
}

耍流氓做法二

//TODO这个地方也就是耍流氓,只能动态扩容,勉强用vector实现
istream &operator>>(std::istream &is, String &s){
    vector<char> vec;
    char ch;
    while((ch = is.get()) != '\n'){
        vec.push_back(ch);
    }
    s._pstr = new char[vec.size() + 1]();
    strncpy(s._pstr, &vec[0], vec.size());
    return is;
    /*
    string word;
    is >> word;
    if(s._pstr){
        delete [] s._pstr;
        s._pstr = new char[word.size() + 1]();
        strcpy(s._pstr, word.c_str());
    }
    return is;
    */
}

string的函数重载

//string.hpp
#include <iostream>
using namespace std;

class String 
{
public:
	String();
	String(const char *);
	String(const String &);
	~String();
	String &operator=(const String &);
	String &operator=(const char *);

	String &operator+=(const String &);
	String &operator+=(const char *);
	
	char &operator[](std::size_t index);
	const char &operator[](std::size_t index) const;
	
	std::size_t size() const;
	const char* c_str() const;
	
	friend bool operator==(const String &, const String &);
	friend bool operator!=(const String &, const String &);
	
	friend bool operator<(const String &, const String &);
	friend bool operator>(const String &, const String &);
	friend bool operator<=(const String &, const String &);
	friend bool operator>=(const String &, const String &);
	
	friend std::ostream &operator<<(std::ostream &os, const String &s);
	friend std::istream &operator>>(std::istream &is, String &s);

private:
	char * _pstr;
};

String operator+(const String &, const String &);
String operator+(const String &, const char *);
String operator+(const char *, const String &);
//string.cc
#include "string.hpp"
#include <string.h>

String::String()
:_pstr(new char[1]()) 
{}
String::String(const char * pstr)
:_pstr(new char[strlen(pstr) + 1]())
{
    strcpy(_pstr, pstr);
}
String::String(const String &pstr)
:_pstr(new char[pstr.size() + 1]())
{
    strcpy(_pstr, pstr.c_str());
}
String::~String(){
    if(_pstr){
        delete [] _pstr;
        _pstr = nullptr;
    }
}
String & String::operator=(const String &pstr){
    if(&pstr != this){
        //REM 判断的是指针!!
        if(_pstr) delete [] _pstr;
        _pstr = new char[strlen(pstr._pstr) + 1]();
        strcpy(_pstr, pstr._pstr);
    }
    return *this;
}

String &String::operator=(const char *pstr){
    if(_pstr) delete [] _pstr;
    _pstr = new char[strlen(pstr) + 1]();
    strcpy(_pstr, pstr);
    return *this;
}

String & String::operator+=(const String & rhs){
    //这样的话了解底层空间的分配问题
    //程序块结束的时候会回收指针但是不会回收堆上的空间
    char *temp = new char[strlen(_pstr) + strlen(rhs._pstr)+ 1]();
    strcpy(temp, _pstr);
    strcat(temp, rhs._pstr);
    delete [] _pstr;
    _pstr = temp;

    return *this;
    /*
    char *temp = new char[strlen(_pstr) + 1]();
    strcpy(temp, _pstr);
    delete [] _pstr;
    if(this != &rhs) _pstr = new char[strlen(temp) + strlen(rhs._pstr) + 1]();
    else _pstr = new char[strlen(temp) + strlen(temp) + 1]();
    strcpy(_pstr, temp);
    if(this != &rhs) strcat(_pstr, rhs._pstr);
    else strcpy(_pstr, temp);
    delete [] temp;
    return *this;
    */
}

String &String::operator+=(const char * pstr){
    char *temp = new char[strlen(_pstr) + 1]();
    strcpy(temp, _pstr);
    delete [] _pstr;
    _pstr = new char[strlen(temp) + strlen(pstr) + 1]();
    strcpy(_pstr, temp);
    strcat(_pstr, pstr);
    delete [] temp;
    return *this;
}

char &String::operator[](std::size_t index){
    return _pstr[index];    
}

const char & String::operator[](std::size_t index) const{
    return _pstr[index];
}

size_t String::size() const{
    return strlen(_pstr);
}

const char* String::c_str() const{
    return _pstr;
}
	
bool operator==(const String &lhs, const String &rhs){
    return !strcmp(lhs._pstr, rhs._pstr);
}

bool operator!=(const String &lhs, const String &rhs){
    return strcmp(lhs._pstr, rhs._pstr);
}

bool operator<(const String & lhs, const String & rhs){
    for(size_t idx = 0; idx < lhs.size(); idx ++){
        //只有相等才能继续往后比较
        if((lhs._pstr[idx] - rhs._pstr[idx] < 0 )){
            return true;
        }else if(lhs._pstr[idx] - rhs._pstr[idx] > 0 ){
            return false;
        }
    }
    if(rhs.size() > lhs.size()) return true;
    return false;
}

bool operator>(const String &lhs, const String &rhs){
    if(lhs == rhs) return false;
    if(lhs < rhs) return false;
    return true;
}
bool operator<=(const String &lhs, const String &rhs){
    if(lhs > rhs) return false;
    return true;
}
bool operator>=(const String &lhs, const String &rhs){
    if(lhs < rhs) return false;
    return true;
}
ostream &operator<<(std::ostream &os, const String &s){
    if(s._pstr) os << s._pstr;
    //REM 需要尽量避免万一是空指针
    return os;
}
//TODO
istream &operator>>(std::istream &is, String &s){
    string word;
    is >> word;
    if(s._pstr){
        delete [] s._pstr;
        s._pstr = new char[word.size() + 1]();
        strcpy(s._pstr, word.c_str());
    }
    return is;
}

String operator+(const String &lhs, const String &rhs){
    String temp = lhs;
    temp += rhs;
    return temp;
}
String operator+(const String &lhs, const char *s){
    String temp(s);
    String res = lhs + temp;
    return res;
}
String operator+(const char *s, const String &rhs){
    String temp(s);
    temp += rhs;
    return temp;
}
//stringTest.cc
#include "string.hpp"
#include <iostream>

void test(){
    String s1("h");
    String s2(s1);
    cout << "s1: " << s1 << endl;
    cout << "s2: " << s2 << endl;

    cout << endl << endl;
    s2 += s1;
    cout << "+= s2: " << s2 << endl;
    s2 += "u";
    cout << "+= s2: " << s2 << endl;
    cout << endl << endl;

    s2[0] = 'u';
    cout << "[] s2[0]: " << s2 << endl;
    cout << "s2.size(): " << s2.size() << endl;
    cout << "s2.c_str(): " << s2.c_str() << endl;

    cout << endl << endl;
    //REM 闇€瑕佸姞鎷彿
    cout << "s1 == s2: " << (s1==s2) << endl;
    cout << "s1 != s2: " << (s1!=s2) << endl;

    cout << "s1 > s2 " << (s1 > s2) << endl;
    cout << "s1 < s2 " << (s1 < s2) << endl;

    cout << "s1 >= s2 " << (s1 >= s2) << endl;
    cout << "s1 <= s2 " << (s1 <= s2) << endl;

    String s3;
    cin >> s3;
    cout <<"s3: " <<  s3;
    String s4;
    cin >> s4;

    cout << "s3 + s4: " << s3 + s4 << endl;
    cout << "s3 + h" << s3 + "h" << endl;
    cout << "h + s3" << "h" + s3 << endl;
}

void test1(){
    String s1 = "h";
    s1 += s1;
    cout << s1 << endl;
}
int main()
{
    test1();
    return 0;
}

1.6 嵌套类

类作用域:

在类作用域内,成员可以相互访问,无论它们在类定义中的声明顺序如何。因为编译器会读完所有的成员然后再访问。

类名作用域:

使用类名作用域可以访问静态成员还有嵌套类型可以使用::访问,也就是不依赖对象但也可以通过对象访问

在函数和其他类定义的外部定义的类称为全局类,绝大多数的 C++ 类都是全局类。

与之对应的,一个类A还可以定义在另一类B的定义中,这就是嵌套类结构。A类被称为B类的内部类,B类被称为A类的外部类

//嵌套类
class Line
{
public:
    class Point{
    public:
        Point(int x,int y)
        : _ix(x)
        , _iy(y)
        {}
    private:
        int _ix;
        int _iy;
    };
public:
    Line(int x1, int y1, int x2, int y2)
    : _pt1(x1,y1)
    , _pt2(x2,y2)
    {}
private: 
    Point _pt1;
    Point _pt2;
};

Point pt(1,2);//error
Line::Point pt2(3,4);//ok

Line ll(1,2,3,4);

实现cout << ll <<endl;

如果将point设为私有类的话,那么不仅需要在point中声明友元,而且需要在line中 申明为友元。

嵌套类结构的访问权限:

外部类对内部类的成员进行访问

内部类对外部类的成员进行访问

//嵌套类的访问方式 

#include <iostream>
class Line;


class Line{
public:
    class Point{
    public:
        Point(){}
        void test(){
            //即便是Line的私有成员也可以在内部类中通过对象访问
            Line ll;
            ll._pt1;
     //如果是静态成员那么可以直接写名称,或者通过类作用域限定访问
            _x = 12;
            Line::_x = 21;
        }
        friend class Line;
    private:
        static int _ix;
        int _iy;
    };
    //外部类不能直接不借助对象或者是类作用域限定符访问内部类成员
    void test1(){
        Point::_ix = 45;
    }
private:

    Point _pt1;
    Point _pt2;
    static int _x;
};
int Line::Point::_ix  = 12;
int Line::_x  = 12;

int main()
{
    return 0;
}

不依赖对象直接访问就是啥也不加,直接访问。

1. 外部类在访问内部类中的私有成员(无论是否是静态成员)的时候需要在内部类中将外部类声明为友元形式。

2. 内部类的私有静态成员的初始化只能在外部类的外部进行初始化

3. 内部类可以直接访问外部类的私有成员。甚至可以这样写:_line可以只写名字。

理解这个地方因为不加东西表示直接使用this指针访问,因为理解就是_line是在line类的作用域中可以直接使用,同时Point又是在Line这个类中的作用域中,所以说不用加类作用域限定符。

pimpl模式

背景需求:给甲方的时候不希望直接将源代码直接交出去。

1. 头文件中只是给出接口

//Line.hpp
class Line{
public:
    Line(int x1, int y1, int x2, int y2);
    ~Line();
    void printLine() const;//打印Line对象的信息
private:
    class LineImpl;//类的前向声明
    LineImpl * _pimpl;
};

2. 具体的所有的实现都放在LineImpl中实现,实现文件加载为动态库

//LineImpl.cc
class Line::LineImpl
{
    class Point{
    public:
        Point(int x,int y)
            : _ix(x)
              , _iy(y)
        {}
		//...
    private:
        int _ix;
        int _iy;
    };
    //...
};

//Line.cc
void test0(){
    Line line(10,20,30,40);
    line.printLine();
}

3. 打包库文件,将库文件和头文件交给第三方

sudo apt install build-essential
g++ -c LineImpl.cc
ar rcs libLine.a LineImpl.o

生成libLine.a库文件
编译:g++ Line.cc(测试文件) -L(加上库文件地址) -lLine(就是库文件名中的lib缩写为l,不带后缀)
此时的编译指令为 g++ Line.cc -L. -lLine

1.7单例对象自动释放(重点)

单例对象以前的回收都是通过destroy函数手动回收。

想到以前的middleLayer对象,通过包装可以通过析构函数将堆上的对象销毁

(1)如果还手动调用了Singleton类的destroy函数,会导致double free问题,所以可以删掉destroy函数,将回收堆上的单例对象的工作完全交给AutoRelease对象。处理比较简单就是将destroy函数删除掉就好。

(2)不能用多个AutoRelease对象托管同一个堆上的单例对象。这种情况只能在使用的时候避免两个同时托管同一片空间。

【注意】需要避免第二种情况,这两种情况都会导致double free问题。

智能指针最大的问题就是不要将两个对象赋给同一个对象。

【继续】想到对于第二种情况的解决办法,避免多个对象接管同一片内存。想到因为一个类创建就是为了另一个类的使用想到使用嵌套类,同时保证唯一性的方式可以通过单例的方式想到也可以通过static静态成员的方式实现。

如果是将au作为普通的成员的话,那么他们就是放在一起,当si销毁的时候先销毁堆上的空间然后销毁对象_ar因为ar接管的是si所以又销毁si,循环了。

所以说设置为静态对象

这个地方即使再有destroy也无所谓,因为都是判断_pInstance

class Singleton
{
    class AutoRelease{
    public:
        AutoRelease()
        {}
        ~AutoRelease(){
          //...
        }
    };
    //...
private:
   //...
    int _ix;
    int _iy;
    static Singleton * _pInstance;
    static AutoRelease _ar;
};
Singleton* Singleton::_pInstance = nullptr;
//使用AutoReleas类的无参构造对_ar进行初始化
Singleton::AutoRelease Singleton::_ar;
//这个地方本来是一个有参构造直接改为无参使用外部类的_pinstance



void test1(){
    Singleton::getInstance()->print();
    Singleton::getInstance()->init(10,80);
    Singleton::getInstance()->print();
}

方式三:atexit + destroy

atexit函数可以在程序正常结束的时候会自动调用注册的函数。

注册的函数可以被调用多次。

先注册的函数后被调用。

前面三种都存在的问题就是无法保证多线程的安全性。 

就是当多个线程同时进入if语句的时候会出现内存泄漏的问题,_pinstance只会保存一个singleson

在饿汉式的方式下一定是最开始的在编译的时候就把_pInstance初始化为getInstance,

后序都不能进入到_pinstance == nullptr中

【注意】这个地方理解一下将静态变量写在外面表示初始化但是并没有赋初值的时候,是会有默认值的,int类型就是0,char *默认为nullptr类型。这样才能够实现直接进入到getInstance函数中。

饱汉式就是在使用的时候,才进行初始化。饿汉式就是无论是否使用,都会初始化占用空间。但是在多线程情况下就使用饿汉式来实现。

方式四:atexit + pthread_once

  • 34
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值