编程题
1、实现单例模式的自动释放(4种方式)
1.1友元类实现单例模式的释放。
知识点:
要实现自动释放,可以考虑用一个栈对象,一个类在栈上创建一个对象,在程序执行结束后,栈对象空间被自动释放,会去调用该类的析构函数。把释放单例模式指针的代码放在该类中就可以实现自动释放。
#include <iostream>
using namespace std;
class AutoRelease;//1.要在单例模式中作为友元,所以先进行声明
class Singleton//2.单例模式类。
{
friend class AutoRelease;//自动释放友元
public:
static Singleton *get_Instance()//获取单例指针的接口,静态
{
if(nullptr==_pInstance) //指针为空的时候才创建,一开始设单例指针为nullptr,在多线程的时候会出问题,这里不考虑多线程的情况,不过也好解决。
{
_pInstance = new Singleton();//单例指针不存在的情况下就创建
}
return _pInstance;//不空直接返回当前指针
}
private://构造函数和析构函数放在私有权限下,为了防止创建多个对象
Singleton()//构造函数
{
cout<<"Singleton()"<<endl;
};
~Singleton()//析构
{
cout<<"~Singleton()"<<endl;
}
static Singleton *_pInstance;//静态单例指针,类内声明,类外定义
};
Singleton *Singleton::_pInstance = nullptr;//饱汉
class AutoRelease
{
public:
AutoRelease()
{
cout<<"AutoRelease()"<<endl;
}
~AutoRelease()
{
if(Singleton::_pInstance) //栈对象销毁时自动调用,释放单例指针的函数放在友元类的析构函数里
{
delete Singleton::_pInstance;
Singleton::_pInstance = nullptr;
cout<<"delete _pInstance"<<endl;
}
cout<<"AutoRelease"<<endl;
}
};
void test01()
{
Singleton *ps1 = Singleton::get_Instance();
Singleton *ps2 = Singleton::get_Instance();
cout<<ps1<<endl;
cout<<ps2<<endl;
AutoRelease ar;//自动释放。
}
int main()
{
test01();
return 0;
}
1.2 嵌套类释放单例模式指针
注意点:
1.把释放类放在单例类里面,单例模式类里面多一个释放类对象,必须是静态变量,为何?
释放类的对象不能作为普通成员函数,如果作为普通成员函数,单例指针无法进行释放,释放类对象和单例指针作为同一个类的成员,由于在同样的作用域下,Singleton和AutoRelease的析构函数形成死锁(这种情况下,Release类对象的释放要Singleton的析构函数执行才能销毁,而Singleton的销毁又需要Release的析构函数执行才能完成)。
因此,释放类的对象不能和单例指针在同一作用域下,而作为静态变量,程序执行结束后,释放类对象被销毁,其析构函数被调用,单例类才能被销毁。
#include <iostream>
using namespace std;
class Singleton
{
public:
class AutoRelease //嵌套类
{
public:
AutoRelease()
{
cout<<"AutoRelease()"<<endl;
}
~AutoRelease()
{
if(_pInstance)//释放函数写在自动类的析构函数里,程序执行结束自动释放
{
delete _pInstance;
_pInstance = nullptr;
cout<<"Delete _pInstance succeed"<<endl;
}
cout<<"~AutoRelease()"<<endl;
}
};
static Singleton* get_Instance()//获取单例指针
{
if(nullptr==_pInstance)//指针为空才创建
{
_pInstance = new Singleton();
}
return _pInstance;
}
private:
Singleton()
{
cout<<"Singleton()"<<endl;
}
~Singleton()
{
cout<<"~Singleton()"<<endl;
}
static Singleton* _pInstance;//单例指针
static AutoRelease _autoRe;//1.不能作为普通成员函数,如果作为普通成员函数,单例指针无法进行释放,
//AutoRelease对象和_pInstance指针作为同一成员,在同样的作用域,
//Singleton和AutoRelease的析构函数形成死锁.因此AutoRelease的对象只能作为静态变量
};
Singleton *Singleton::_pInstance=nullptr;//单例指针一开始为空
Singleton::AutoRelease Singleton::_autoRe;//实例化释放类对象,
int main()
{
Singleton *ps1 = Singleton::get_Instance();
Singleton *ps2 = Singleton::get_Instance();
cout<<ps1<<endl;
cout<<ps2<<endl;
return 0;
}
1.3 用atexit自动释放单例模式
知识点:
1.atexit在stdlib.h头文件下,用于对一个函数进行注册,等main()中其他函数执行完毕后才执行这个经过atexit注册的函数。
-
在初始化单例指针的时候,要把指针设置成饿汉模式-即程序一开始就给指针申请空间(在main函数开始之前,单例指针不空),否则,在多线程下,饱汉模式(指针设为nullptr),每个线程的单例指针都是nullptr,都会跑去新创建。
-
实现:1.在单例类设一个销毁函数,在创建完指针后,用atexit注册一下即可。
atexit示例:
#include <iostream>
#include <stdlib.h>
using namespace std;
void func()
{
cout<<"void func()"<<endl;
}
int main()
{
cout<<"enter main...."<<endl;
atexit(func);
cout<<"end main....."<<endl;
return 0;
}
/*
输出:
enter main....
end main.....
void func()
*/
用atexit自动释放单例指针
#include <iostream>
using namespace std;
#include<stdlib.h>
class Singleton
{
public:
static Singleton* get_Instance()
{
if(!_pInstance)
{
_pInstance = new Singleton();
atexit(Destory); //单例指针初始化后,立即给指针注册释放函数,等程序运行结束进行释放
}
return _pInstance;
}
private:
Singleton()
{
cout<<"Singleton()"<<endl;
}
static void Destory()
{
if(_pInstance)
{
delete _pInstance;
_pInstance = nullptr;
cout<<"自动释放单例指针.....完成"<<endl;
}
}
~Singleton()
{
cout<<"~Singleton()"<<endl;
}
static Singleton *_pInstance;
};
Singleton *Singleton::_pInstance = Singleton::get_Instance();
//如果这里是nullptr,在多线程环境下,这代码是极其不安全的多线程环境下,设为nullptr,每个线程都会在get_Instance申请空间解决方法,在main函数之前,保证_pInstance不为空
int main()
{
Singleton *ps1 = Singleton::get_Instance();
Singleton *ps2 = Singleton::get_Instance();
cout<<ps1<<endl;
cout<<ps2<<endl;
return 0;
}
1.4. pthread+atexit自动释放单例指针。
知识点:
#include <pthread.h>里面的pthread_once(&once,init)函数
1.static pthread_once_t once;//控制pthread_once 函数中的初始变量
2.pthread_once_t Singleton::once = PTHREAD_ONCE_INIT;//设置宏标记,init执行一次后,会变
3.pthread_once功能:保证放在init函数里面的函数体,在多线程的条件下,里面的代码只执行一次。
#include <iostream>
#include <pthread.h>
#include <stdlib.h>
using namespace std;
class Singleton
{
public:
static Singleton* get_Instance()
{
pthread_once(&once,init);
//init里面的函数,在多线程环境下,只去执行一次
return _pInstance;
}
static void init()
{
_pInstance = new Singleton();//创建
atexit(Destory);//注册销毁函数
}
private:
Singleton()
{
cout<<"Singleton()"<<endl;
}
static void Destory()
{
if(_pInstance)
{
delete _pInstance;
_pInstance = nullptr;
cout<<"自动释放单例指针.....完成"<<endl;
}
}
~Singleton()
{
cout<<"~Singleton()"<<endl;
}
static Singleton *_pInstance;
static pthread_once_t once;//控制pthread_once 函数中的初始变量
};
Singleton *Singleton::_pInstance = Singleton::get_Instance();//如果这里是nullptr,在多线程环境下,这代码是极其不安全的
pthread_once_t Singleton::once = PTHREAD_ONCE_INIT;
int main()
{
Singleton *ps1 = Singleton::get_Instance();
Singleton *ps2 = Singleton::get_Instance();
cout<<ps1<<endl;
cout<<ps2<<endl;
}
2、实现COW的String,让其operator[]能够区分出读写操作
COW原理:
string发生拷贝构造或者赋值的时候,不会复制字符串的内容,而是增加一个引用计数,字符串指针进行浅拷贝,当需要对字符串内容进行修改或者写操作时,才进行深拷贝。
2.实现。
1.给管理字符串指针多申请4个字节的空间大小,前4个字节用来存储引用计数,用解引用方式来访问引用计数值--重要
2.字符串发生拷贝构造操作的时候和赋值操作的时候,只进行浅拷贝,执行引用计数+1
3.发生取下标操作的时候,先判断引用计数的值,再进行操作,目前operator[]不能够区分出读写操作,对下标操作的读写都会进行深拷贝
4.析构函数:在引用计数为0的情况才释放。
3.String运算符重载总结
1.左移运算符<<,在String类声明为友元:
1.返回类型是 ostream&,两个形参:第一个ostream &os,第二个String对象的引用2.赋值运算符重载:
1.返回类型: String对象的引用;一个参数:String对象的引用或者const char *
2.步骤:自复制、释放左操作数、深拷贝、返回*this
3.+=运算符重载:
1.返回类型:String对象的引用;一个参数:String对象的引用或者const char *
2.步骤:记录新字符串大小、请一个临时堆空间,存储接起来的内容、释放左操作数、建立联系、返回*this
4.[]下标运算符重载:
1.返回类型:char类型的引用:char&,一个参数:索引位置 int pos;
2.步骤:下标小于字符串大小才允许返回,否则返回一个静态char变量'\0'5.关系运算符重载:==,>=,<=,>,<,友元形式存在
1.返回类型bool, 两个参数:都是类对象的引用。
6.右移运算符重载:>>输入,友元形式存在
1.返回类型:istream的引用;两个参数:istream对象的引用,string对象的引用
2.步骤:
1.先清空原堆区内容
2.弄个临时堆区或者用vector,来存取从键盘输入的数据
3.申请新空间,进行深拷贝
4.返回istream对象
7.+加号运算符重载进行字符串拼接:--友元形式存在
1.返回类型:String对象,两个参数:都是String类对象的引用或者一个对象的引用+const char*
2.步骤:
1.记录新字符串大小
2.申请一个临时堆空间,存储接起来的内容
3.实例化新的String对象,2的内容作为初始值,删除临时堆空间
4.返回String临时对象。
版本1:不区分读写操作
头文件:05_cow.h
#include <iostream>
#include <stdlib.h>
using namespace std;
#include <string.h>
class String
{
public:
//1.无参构造函数
String();
//2.拷贝构造函数
String(const char *_pstr);
//3.拷贝构造函数
String(const String &rhs);
//4.赋值运算符函数
String &operator=(const String &rhs);
//5.析构函数
~String();
//6.左移运算符重载输出
friend ostream& operator<<(ostream &os, const String &rhs)
{
if(rhs._pstr)
{
os<<rhs._pstr;
return os;
}
}
//7.打印对象的堆空间地址
void print_Address()
{
printf("%p\n", _pstr);
}
//8.重载下标运算符,进行读写访问都会进行深拷贝
char &operator[](int pos);
//9.返回c语言风格字符串
const char *c_str()
{
return _pstr;
}
private:
void InitRefcount()
{
//初始化引用计数
*(int *)(_pstr-4)=1;
}
int getRefcount()
{
//获取引用计数
return *(int *)(_pstr-4);
}
//引用计数加1
void IncreseRefcount()
{
++*(int *)(_pstr-4);
}
//引用计数减1
void DecreaseRefcount()
{
--*(int *)(_pstr - 4);
}
//字符串长度
int size()
{
return strlen(_pstr);
}
char *_pstr;
};
cou.cc文件
#include "05_cow.h"
//1.无参构造函数
String::String()
:_pstr(new char[5]()+4)
{
cout<<"String()"<<endl;
//1.初始化引用计数
// ++*(int *)(_pstr-4);
InitRefcount();
}
//2.拷贝构造函数
String::String(const char *str)
:_pstr(new char[strlen(str)+5]() + 4)
{
cout<<"String(const char *str)"<<endl;
InitRefcount();
strcpy(_pstr, str);
}
//3.拷贝构造函数
String::String(const String &rhs)
:_pstr(rhs._pstr)
{
cout<<"String(const String &rhs)"<<endl;
IncreseRefcount(); //引用计数++
}
//4.赋值运算符函数
String& String::operator=(const String &rhs)
{
cout<<"String &operator=(const String&rhs)"<<endl;
if(this!=&rhs)//自复制
{
//2.释放左操作数
delete _pstr;
_pstr = nullptr;
//3.进行浅拷贝
_pstr = rhs._pstr;
//4.引用计数加1
IncreseRefcount();
}
return *this;
}
//5.析构函数
String::~String()
{
DecreaseRefcount(); //引用计数减1
if(0==getRefcount()) //引用计数为0,则删除
{
delete [] (_pstr-4);
}
}
//重载下标运算符函数,读写都会进行深拷贝
char &String::operator[](int pos)
{
if(pos<size())
{
if(this->getRefcount()>1) 贝
{//引用计数值大于1,指向这内存的不止一个指针,重新申请空间,进行深拷
DecreaseRefcount();
char *temp = new char[strlen(_pstr)+5]()+4;
strcpy(temp,_pstr);
_pstr = temp;
InitRefcount();
}
//不大于1,说明指向堆空间的指针只有一个,可以直接进行操作
return _pstr[pos];
}
else
{
static char charnull='\0';
return charnull;
}
}
main.cc
#include "05_cow.h"
int main()
{
String p1("hello world");
String p2(p1);
cout<<p1<<endl; //打印对象
cout<<p2<<endl;
p1.print_Address();//获取堆内存地址
p2.print_Address();
cout<<p2[0]<<endl;//下标进行读取
p2.print_Address();//获取地址,地址变了
cout<<p2.c_str()<<endl;
return 0;
}
版本2:operator区分读写操作
待完成。