IO流与单例模式
单例模式
单例模式是指一个类只能有一个对象。
饿汉模式
在单例模式下,在程序开始(main函数运行前)的时候创建一个对象,这之后就不能再创建这个对象。
class HungryMan
{
public:static HungryMan* getinstance(){return &inst;}
private:HungryMan(){}HungryMan(const HungryMan& it) = delete;const HungryMan& operator=(const HungryMan& it) = delete;static HungryMan inst;//这里是声明,并不是定义,所以可以在类里面包含//实际上是全局变量与类封装融合的结果
};HungryMan HungryMan::inst;//在类外定义
饿汉模式的实现方式较为简单,但存在线程安全(如果没加锁,可能会出现new了几个单例对象出来),可能导致进程启动慢(main函数启动之前需要创建对象),无法控制单例的初始化顺序(如果不同的单例模式在不同的文件,就无法确认初始化顺序,包括单例之间有可能存在依赖,也可能存在问题)的问题。
懒汉模式
在第一次使用的时候创建。
class LazyMan
{
public:static LazyMan* getinstance(){if (inst == nullptr){inst = new LazyMan();//new出来的数据是需要释放的,但懒汉对象大部分不需要手动释放//但这个对象并不是在程序层面上释放的,而是在系统回收资源的时候释放的//那如果我们需要保存一些数据的话,要保证main函数之后要自动调用懒汉类的析构函数}return inst;}static void delinstance(){if (inst == nullptr){delete inst;inst=nullptr;}}private:LazyMan(){}LazyMan(const LazyMan& it) = delete;const LazyMan& operator=(const LazyMan& it) = delete;static LazyMan* inst;class gc{~gc(){delinstance();}};static gc delgc;
};LazyMan* LazyMan::inst = nullptr;LazyMan::gc LazyMan::delgc;
IO流
c++的IO流
C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类,c++这个IO流实际上是和C语言保持一致的,比如说istream对标的就是scanf和printf,fstream对标的是fscanf和fprintf,sstream对标的是sscanf和sprintf。
在类型转换中,对于内置类型,相近的类型可以互相转换,其他的可以由构造函数来支持转换,就像自定义类型可以转化为自定义类型,内置类型也可以转换为自定义类型,自定义类型也可以转化为自定义类型。
class tmp
{
public:operator int(){if(/*输入为ctrl+z*/){return 0;}else{return /*非0*/}}
};int main()
{tmp t;int i = t;return 0;
}
标准读写
所以cin可以直接使用while(cin>>tmp),当输入其他字符,返回值就为真,如果输入ctrl+z,返回值就是假,循环就退出了。
c++默认是兼容C语言的,当c++的cin和C语言的scanf混用的时候,并不会因为缓冲区的问题而导致读取混乱,如果想关掉这种兼容,可以调用下图的函数。
cin.sync_with_stdio(false);
cout.sync_with_stdio(false);
文件读写
在C语言中,fgetc和fputc是用来读单个字符的,fread和fwrite是用来读写文件的,fprintf和fscanf是用来格式化读写的,但这些只能用来对内置类型做操作,在c++里,ifstream是从文件里读,ofstream是写到文件里,可以应付内置类型和自定义类型的输入和输出。
二进制读写:如果要把内存中的数据存到磁盘里,内存中的数据是由类型的,而磁盘没有,我们可以使用二进制读写,也可以使用文本读写。比如说我们要把一个结构体按二进制的形式写到磁盘里:
class A
{
private:int a;char b;string c;
};class Bin
{
public:Bin(const char* filename="./info.bin"):_filename(filename){}void Write(){ofstream ofs(_filename, ios_base::out | ios_base::binary);A data;ofs.write((const char*)&data, sizeof(data));}void Read(A & rA){ifstream ifs(_filename, ios_base::in | ios_base::binary);ifs.read((char*)&rA, sizeof(rA));}private:string _filename;
};
我们可以用read再次读取,但这样是把数据原封不动的读回来,包括地址,但这样在同一个进程就会发生浅拷贝问题,析构两次,而如果是不同的进程读取,就会出现野指针问题,因为到后来的进程读取的时候,前面进程的空间早就销毁了,根据文件拷贝回来的地址就是一个野指针,所以只要是容器都要注意不能用二进制进行读取,因为容器底层都相对复杂。
文本读写:如果要正常读写到文件里和从文件里读出来,都要和字符串相互转化。但这样转化太麻烦,所以在C语言中,我们可以使用sscanf和sprintf转化完后用fwrite和fread写到文件里和读取。而在c++中重载了流插入和流提取,所以也不需要自己去做转换。
class Text
{
public:Text(const char* filename = "./info.text"):_filename(filename){}void Read(A& rA){ifstream ifs(_filename);ifs >> rA._a >> rA._b >> rA._c;}void Write(){A data(1, 'a', "hello world");ofstream ofs(_filename);ofs << data._a;ofs << data._b;ofs << data._c;//可以使用getline}
private:string _filename;
};
stringstream
自定义类型不方便转为字符串,所以可以调用stringstream。
class A
{
public:A(int a=0,char b='\0', string c=""):_a(a),_b(b),_c(c){}int _a;char _b;string _c;
};ostream& operator << (ostream & os, A aa)//必要手动写格式
{os << aa._a << '/' << aa._b << '/' << aa._c;return os;
}int main()
{A tmp(20, 'a', "hello world");ostringstream oss;oss << tmp;string out = oss.str();cout << out << endl;return 0;
}
但stringstream并不会用于比较复杂的情景,比较复杂的情景可以使用json。
class Date
{
public:Date(int _year,int _month,int _day):year(_year),month(_month),day(_day){}int year;int month;int day;
};
istream& operator>>(istream& is, Date& d)
{is >> d.year >> d.month >> d.day;return is;
}ostream& operator<<(ostream& os, Date& d)
{os << d.year <<' ' << d.month<<' ' << d.day << ' ';return os;
}
class Parent
{
public:Parent(int a1,char b1,Date c1):a(a1),b(b1),c(c1){}int a;char b;Date c;
};ostream& operator<<(ostream& os, Parent& p)
{os << p.a <<' ' << p.b<<' ' << p.c << ' ';return os;
}istream& operator>>(istream& is, Parent& p)
{is >> p.a >> p.b >> p.c;return is;
}int main()
{Date d1(2025, 8, 16);Parent p1(10, 'a', d1);ostringstream os;os << p1;Date d2(2024, 9, 18);Parent p2(20, 'b', d2);istringstream is(os.str());is >> p2;return 0;
}