單例模式
單例模式是指一個類只能有一個對象。
餓漢模式
在單例模式下,在程序開始(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;
}