// CPPTEST.cpp : 定義控制檯應用程序的入口點。 // #include "stdafx.h" #include<iostream> #include <map> #include<fstream> #include<cassert> #include <sstream> #include"TMyNumOperator.h" #include"abc.h" #include <list> #include<thread> #include <vector> #include <algorithm> #include <cmath> #include <chrono> #include <mutex> using namespace std; using std::cin; using std::cout; //using namespace std; // //class CBase{ //protected://注意,可使用C#風格的定義時初始化 // std::string name = "NoOne"; // int age = -20; // int sex = 1; //public: // float *pData = new float[20]{1, 2, 3, 4, 5}; //public: // virtual ~CBase(){//虛析構函數,防止內存泄漏:對基類指針調用delete時,會從子類一直析構到基類 // cout << "~cbase" << endl; // } //}; // ////基類的私有成員不會被繼承,這和C#徹底同樣 //class CStudent : public CBase{ //public: // std::map<int, std::string> _projs; // CStudent(){ // pData = new float[20]{1, 2, 3, 4, 5}; // } //public: // void SetName(const std::string& name){ // CBase::name = name;//若是CBase.name定義爲私有,這裏就不可訪問 // this->name = name; //等價於上一行 // } // // const string& GetName(){ // return this->name; // } // // ~CStudent(){//若採用淺拷貝,析構函數被調用屢次,pData被刪除屢次,程序崩潰 // //規避方式:判斷pData是否爲空,非空才delete[] pData // //但在不知情的狀況下使用pData仍然會出問題,所以淺拷貝致使的問題不可規避 // cout << "~cstudent" << endl; // delete[] pData; // pData = NULL; // } //}; // //void TestSTL(){ // // auto mp = new std::map<int, std::string>();//c++11新風格 auto // // mp->insert({ 10, ("h你好") });//c++11新風格,不用再使用std::pair()或std::make_pair() // mp->insert({ 20, "el" }); // for (auto var : *mp)//c++11新風格for // { // std::cout << var.first << "," << var.second << "," << std::endl; // } //} // //void TestClass(){ // CBase* pbase = new CStudent(); // auto pst = (CStudent*)pbase; // pst->SetName("xxxx"); // auto name = pst->GetName(); // delete pbase; //} // //int TestAdd(int a, int b){ // return a + b; //} //void TestStdFunc(std::function<int(int,int)> fun, int a, int b){ // auto ret = fun(a, b); //} // //typedef int(*TestAddPtr)(int, int); // //void TestPCall(TestAddPtr func, int a, int b){ // auto ret = func(a, b); //} // //struct Vertex{ // bool isgood; // float x, y, z; // double dx; // bool bx; // int ix; // bool by; //}; //void TestFile(){ // int szChar = sizeof(char); // ofstream ofs; // ofs.open("f:/test.txt"); // ofs << "hello " << 10 << " world " << 20 << endl; // // ofs.flush(); // ofs.close(); // // ifstream ifs; // ifs.open("f:/test.txt"); // string str1, str2; // int num1, num2; // // ifs >> str1 >> num1 >> str2 >> num2; // // //錯誤示例:二進制讀寫,使用std::<<或>>進行的仍是ASCII碼的讀寫 // ofstream ofsb; // ofsb.open("f:/testb", ios::binary); // ofsb << "hellob" << 1022; // // ofsb.flush(); // ofsb.close(); // ifstream ifsb; // // string sx; // int nx; // ifsb.open("f:/testb", ios::binary); // ifsb >> sx >> nx; // ifsb.close(); // // //正確作法 // sx = "binary"; // nx = 978; // ofsb.open("f:/testbx", ios::binary); // ofsb.write(sx.c_str(), sx.length()*sizeof(char)+1); // ofsb.write((const char*)&(nx), sizeof(int)); // ofsb.flush(); // ofsb.close(); // // char sxr[32]; // int nxr; // ifsb.open("f:///testbx", ios::binary);//注意這裏的"///"無論有多少個/都等同於一個 // ifsb.read(sxr, sx.length()+1); // ifsb.read((char*)&nxr, 4); // // //數據轉換的更通用方式 // Vertex vt; // vt.bx = true; // vt.isgood = false; // vt.x = 12; // vt.y = 13; // vt.z = 14; // vt.dx = 3.9; // vt.by = 0; // // ofstream ofsbx; // ofsbx.clear(); // ofsbx.open("f:/testbyx2", ios::binary); // ofsbx.write((const char*)&(vt), sizeof(Vertex)); // ofsbx.flush(); // ofsbx.close(); // // ifstream ifsbx; // Vertex vrt; // ifsbx.clear(); // ifsbx.open("f:/testbyx2", ios::binary); // ifsbx.read((char*)&vrt, sizeof(Vertex)); // // string s1 = "hello"; // string s2 = "wrold"; // s1 = s1 + s2; // auto s3 = s1.substr(1, 2); //} // ////實現較爲高效的字符串分割,限制是分割符只能是一個字符,不能是一個串 //std::list<string> TestStrSplit(string s, char sep){ // std::list<string> lst; // for (int i = 0, j = 0; i < s.length(); ++i){ // if (s[i] == sep){ // lst.push_back(s.substr(j, i - j)); // j = i + 1; // } // } // // //注意臨時對象做爲返回值了,通常狀況下這是錯誤的用法,棧上的臨時對象出了函數域後會被釋放 // //但這裏STL容器內部重載了=運算符,做了值拷貝就沒問題了 // return lst; //} //void TestString(){ // // //g正則表達式實現字符串分割 // string s1 = "a;b;c;dddd;ef;"; // string s2 = "a123b2673cdd4444a"; // std::regex re("(\d+)"); // std::smatch mtch; // // //這個作法效率挺低且浪費內存,產生了不少中間字符串 // while (std::regex_search(s2, mtch, re, std::regex_constants::match_default)){ // cout << mtch.str() << endl; // s2 = mtch.suffix(); // } // // //這個函數效率要高多了 // auto lst = TestStrSplit(s1, ';'); // //} // ////返回棧上的臨時對象測試 //CStudent TestTempObjRet(){ // CStudent ost; //臨時對象 // return ost; //調用對象的拷貝構造函數 //}//出了棧後ost被釋放,析構函數調用,同時成員對象被析構CStudent.name="",但內置類型仍保持原值 // ////經過測試可知,將棧上對象做爲函數返回值使用通常是沒有問題的,但淺COPY時兩個對象中的指針指向同一份 ////內存,當一個對象被刪除時,另外一個對象中的指針就指向非法位置了,成了野指針 //void TestObjConstructorAndDestructor(){ // CStudent ostx; // ostx = TestTempObjRet(); //調用拷貝構造函數(與上面對應) // auto name = ostx.GetName(); // auto px = ostx.pData; //} // //void TestRRef(){ // //} // ////可使用隨機訪問(數組下標)說明vector在內存中是連續存放的 ////這樣,vector在須要擴充容量時就須要將原來內存刪除,再申請一塊新內存 ////但這並不必定,由於內存申請時若用realloc則有可能會在原內存後面增長(原理) //void TestVector(){ // std::vector<string> sv{ "hello", "world" }; // sv[0]; // sv[1]; // // sv.reserve(20); //舊的內容被清除 // int n = sv.capacity(); //20 // sv.push_back("a"); // sv.push_back("b"); // sv.clear(); //舊的內容被清除 // n = sv.capacity(); //20 // // sv.shrink_to_fit(); //內存釋放 // n = sv.capacity(); //0 // //} // //struct CTA{ //private: // virtual void Test(){ // cout << "cta" << endl; // } // //}; // //class CCTA : CTA{//類和結構體能夠相互繼承 //public: // int _id; // void Test() const{ // cout << "ccta-test" << endl; // } //}; // ////C++中字符串有常量和變量之分,字符串遇到\0則結束 ////C#中只有常量字符串,字符串遇到\0不結束,視其爲正常字符 //void TestStr(){ // char* ps = "hello";//字符串常量,不可修改其內容 // ps[0] = 'd'; //運行出錯 // // char arr[] = "hello"; //字符串變量 // char* par = arr; // arr[0] = 'd'; //ok //} // ////C++中指針字符串與數組字符串都是自動以0結尾的 //void TestMemcpy(){ // // char dest[18]; // char src[] = "hell"; //以0結尾,長度爲5,若強制聲明爲 char src[4] = "hell"則編譯報錯 // char* psrc = "hell"; //以0結尾,長度爲5,但測試長度strlen(psrc)爲4,由於它沒算尾0 // // for (int i = 0; i < 10; ++i){ // // } // for (int i = 0, ch; (ch = psrc[i++]) != 0;){ // //這裏發現字符串尾0後有許多個0,不知道緣由 // // } // auto len = strlen(psrc); //4,測試長度,並沒字符串的真實長度(內存中真實串),由於它有尾0 // int len2 = strlen(src); //5,字符串實際長度(內存中存儲的字符串) // int st = sizeof(src); //5,數組大小 // memcpy(dest, psrc, strlen(psrc)+1); //} //template<typename T1, class T2> class MyVector{ // std::vector<int> _lst; // //public: // // void Test2(); //}; // //template<class T1, class T2> void MyVector<T1, T2>::Test2(){ // //} #pragma region 2018.7.7 [module(name = "mytestx")]; void TestIOStream() { std::fstream fs; fs.open("test.txt", ios_base::in | ios_base::out); fs << 12 << "hello"; fs.seekp(0); int ix1; string sx1; char chs[6]; fs >> ix1; fs >> chs; chs[5] = 0; sx1 = chs; cout << ix1 << sx1.c_str() << endl; } void TestMacro() { #define hfunc(x) cout << x << endl; //自定義處起,全局可見 hfunc(124); #undef hfunc //typedf, using等價使用 typedef void(*PFUN)(int); using PFUNC = void(*)(int); using Int = int; using MyType = Int; } //數組和指針 void TestArrayAndPointer() { //1,char* p : char類型指針,指向char數組, p++移動一個char //2,int* p : int型指針,指向int數組,p++移動一個int //3,char(*p)[2] : char[2]類型指針,指向char[2]類型數組,即char[][2]數組,p++移動一個char[2] //總結:X類型的指針指向X類型的數組, p++移動一個數組元素 //如何看指針類型:去除*p剩下的就是類型,如char*p去掉*p爲char,char(*p)[2]去掉*p爲char[2] //======================================================== //指針老是指向數組的,以下,可認爲是指向只有一個元素的數組 //======================================================== int ix = 20; int*pix = &ix; cout << pix[0] << "," << *pix << endl; //======================================================================================== //堆和棧上數組的初始化列表寫法 //======================================================================================== char arr[43] = { 'a','b','c' }; char arr2[10] = { "hello" }; int iarr[] = { 1, 2, 3, 4 }; char*ps = new char[30]{ 0 }; int* ips = new int[30]{}; int* ips2 = new int[30]; //cout << arr << "," << (void*)arr << (void*) ps << endl; char* px; px = arr; //能夠賦值,說明數組名與指針等價 const char* cp;//能夠cp++; char* const cpx = arr; //不能夠 cpx++,不能移動的指針,數組名其實就是這種指針 //這裏以arr與ps做對比,數組名與指針本質上都是指針,只是數組名是不能移動,不能賦值的常指針 //在二維情形時也是如此 stringstream ss; //======================================================================================== //1,棧上二維數組,【內存連續】 //======================================================================================== char a[][3] = {//二維數組初始化列表 { 98, 99, 100 }, { 101, 102, 103 }, }; for (int i = 0; i < 6; ++i) {//驗證 ss << *(*a + i) << ","; } cout << ss.str() << endl; //============================================================================= //2,數組指針(也稱行指針),【內存連續】 //============================================================================= int(*pax)[4] = new int[3][4]; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { pax[i][j] = i * 4 + j + 1; } } ss.str(""); for (int i = 0; i < 12; ++i) {//驗證 ss << *(*pax + i) << ","; } cout << ss.str() << endl; //============================================================================= //3,指針數組,【內存不連續】 //============================================================================= //由於它是一個數組,因此不能用new來給它分配內存,new出來的東西只能賦值給指針 char* arr_p[2]; arr_p[0] = new char[30]{ 'h','e','o','l','l' }; arr_p[1] = new char[10]{ 'a','b','c' }; //============================================================================= //4,多級指針用來分配二維數組,有【連續內存分配法】和【不連續內存分配法】 //這個很是重要,若用一個不連續的二維數組指針進行memcpy操做,則會發生嚴重問題: //(1)數據拷越界,覆蓋了其它變量甚至程序的內存 //(2)dest變量中數據只填充了一部分,其他部分仍是舊數據,致使程序出現莫名其妙的問題 //(3)這種數據拷越界並沒有任何提示,隱蔽性極高,很是難以查找 //============================================================================= int**pi = new int*[3]; int* ptemp = new int[12]; for (auto i = 0; i < 3; ++i) { //------------------------------------------------ //(1)【不連續內存分配法】 //pi[i] = new int[2]; //------------------------------------------------ //(2)【連續內存分配法】 pi[i] = &((ptemp + i * 2)[0]); for (int j = 0; j < 2; ++j) { pi[i][j] = i * 2 + j; } } for (int i = 0; i < 3; ++i) {//驗證 for (int j = 0; j < 2; ++j) { ss << pi[i][j] << ","; } } cout << ss.str() << endl; } void TestInitialist() { class CIn { public: float x, y, z; string name; }; //初始化列表的使用條件: //無自定義構造函數,成員公有,無基類,無虛函數 //這麼多限制,能夠說很雞肋 CIn oin = { 1, 2, 3, "hello" }; //方式1 CIn oin2 { 1, 2 ,3, "world" }; //方式2 } #pragma endregion #pragma region 2018.7.9 class CComplex { float real, image; public: CComplex(float real, float image) { cout << "constructor: " << real << "," << image << endl; this->real = real; this->image = image; } CComplex(const CComplex& other) { cout << "copy constructor: " << other.real << "," << other.image << endl; if (this != &other) { real = other.real; image = other.image; } } ~CComplex() { cout << "~ccomplex" << "(" << real <<"," <<image << ")" << endl; // real = 0; // image = 0; } void PrintInfo() { cout <<"Complex: " << real << "," << image<< endl; } public: //------------------------------------------- //運算符重載 //------------------------------------------- //1,重載爲成員函數 CComplex operator+(const CComplex& other) { cout << "operator+" << endl; return CComplex(real+other.real, image + other.image); } CComplex& operator++() {//前向++ cout << "forward ++ " << endl; real++; image++; return *this; } CComplex& operator++(int) {//後向++ cout << "backward ++ " << endl; real++; image++; return *this; } const CComplex& operator=(const CComplex& other) { this->real = other.real; this->image = other.image; return *this; } //2,重載爲友元函數 friend CComplex operator+(float fx, const CComplex& cp); //3,【運算符重載函數不能定義爲靜態函數】 //這與C#不一樣,C#中全部運算符重載都必須是public和static的 //static CComplex operator+(float fx, const CComplex& cp); //4,類型轉換運算符重載 operator bool() {//使用情景:CComplex oc; if(oc){}或if(oc != NULL){}或 float/int/bool x = oc return real != 0 && image != 0; } operator float() {//使用情景:CComplex oc; if(oc){}或if(oc != NULL){}或 float/int/bool x = oc return real; } // CComplex operator=(const CComplex& other) { // if (this == &other) // return other; // return CComplex(other.real, other.image); // } void Testx() { CComplex* pNewCom = new CComplex(2, 2); pNewCom->real = 20;//能夠訪問私有成員?? } }; // CComplex CComplex::operator+(float fx, const CComplex& cp) { // return CComplex(fx + cp.real, cp.image); // } CComplex operator+(float fx, const CComplex& cp) { return CComplex(fx + cp.real, cp.image); } void TestCComplexOper() { int i = 10; CComplex cpx(1, 2); ++cpx++++; cpx.PrintInfo(); } CComplex TestReturnStackObj() { //----------------------------------------------------------------- //返回棧上的對象 stackObj //返回棧上的對象會致使拷貝構造函數的調用,生成一個 CComplex stackObj(1, 2); return stackObj; return CComplex(1, 2); //這種方式直接調用構造函數,而不調用拷貝構造函數 //----------------------------------------------------------------- } #pragma endregion #pragma region 2018.7.10 void TestRealloc() { cout << "---------------test-realloc---------------" << endl; int szch = sizeof(char); char*pstr = "this is a test str"; int strLen = strlen(pstr); char* pdesc = (char*) malloc((1+strLen)* sizeof(char)); for (int i = 0; i < strLen; ++i) { cout << "," << hex<< (int)pdesc[i]; } cout << endl; cout << strlen(pstr) << endl; strcpy_s(pdesc, strLen+1, pstr); for (int i = 0; i < strLen; ++i) { if(pdesc[i] > 0) cout << (char)pdesc[i]; else cout << "," << (int)pdesc[i] ; } cout << endl; pdesc = (char*)realloc(pdesc, 40); for (int i = 0; i < 40; ++i) { pdesc[strLen + i] = 'a' + i; } for (int i = 0; i < 40 + strLen; ++i) { if (i < strLen) cout << pdesc[i] << ","; else cout << (unsigned short)pdesc[i] << ","; } cout << endl; cout << "---------------test-realloc---------------" << endl; } template<typename T> class CMyNumOperator { T a, b; public: static T Add(T x, T y) { return x + y; } }; #pragma endregion #pragma region 2018.7.11 #pragma region 繼承相關 class A { public: A(int x) { fProtected = x; } float GetFProtected() { return fProtected; } public: float fpublic = 2.3f; //c++11支持了初始化,但不能使用auto string sname = "liqi"; CMyNumOperator<int>* on = new CMyNumOperator<int>(); //對象也能夠 void TestFunc() { cout << "TestFunc " << fProtected << endl; } static void StaticTestFunc() { cout << "Static-TestFunc" << endl; } virtual void ToString() { cout << "A::ToString" << endl; } protected: float fProtected; void ProtectedFunc() { cout << "PRotectedFunc" << endl; } private: void PrivateFunc() { cout << "PrivateFunc" << endl; } }; //只管公有繼承,無論保護繼承和私有繼承,意義不大,也太複雜 //C++能夠直接調用構造函數嗎? //能夠,有兩種 //1,構造函數列表中 //2,new T()構造對象時 class B : public A { public: friend void TestProtectedDerive(); B() :A(1) {} //顯示調用構造函數【1】 void TestForDerive() { //公有繼承下 //1,子類能夠訪問父類的保護成員,不能訪問父類的私有成員 B ob; //PrivateFunc(); //error,子類不能訪問基類的私有成員 ProtectedFunc(); //right fProtected = 10; //right ob.fProtected = 20; //right A* pa = new A(1); //顯示調用構造函數【2】 } //1,c++中只要基類有相同簽名虛函數,則默認爲此基類函數也是虛函數[與C#不一樣],以下情形都成立 // (1) 函數不聲明 virtual // (2) 函數聲明瞭 virtual // (3) 函數聲明瞭 override // (4) 函數聲明瞭 virtual 和 override //2,c++中兩個關鍵詞做用不一樣,能夠同時存在 // virtual僅代表函數是虛函數,override是C++11中出現的,明確說明是對基類的重寫 // 它的好處是當函數聲明不符合規則時,編譯器會報錯 void virtual ToString() override{ cout << "B::ToString" << endl; } }; void TestProtectedDerive() { B ob; ob.ProtectedFunc(); } #pragma endregion #pragma endregion #pragma region 2018.7.18 #pragma region 標準輸入流 void TestCinCout() { float fx; std::string str; while (true) { bool errorNum = false; cin >> str; //1,試讀,看是否是"exit"串 if (str == "exit")//2,如果,結束循環 break; for (int i = str.length() - 1; i >= 0; --i) {//3,若不是,將串放回到流中,注意是反向放回的 cin.putback(str[i]); } cin >> fx; if (cin.fail()) {//4,若是格式錯誤 cout << "格式錯誤:請輸入一個數值" << endl; cin.clear(); //5,清除錯誤標識 while (cin.get() != '\n'); //6,讀掉後面出錯的全部字符,直到回車 errorNum = true; } if (!errorNum) {//7,若前面輸入(數字)是正確的,則繼續後面的解析 cin >> str; if (cin.fail()) { cout << "格式錯誤:請輸入一個字符串" << endl; cin.clear(); } cout << ">>數值= " << fx << ", 描述= " << str << endl; } } } #pragma endregion #pragma region 計算機數據存儲 void TestComputeDataStorage() { //數據轉換:C++,C# 通用 //1,整形數據:短數據類型轉長數據類型時,正數高位補0,負數高位補1 //2,浮點形數據轉整形時,獲得整數部分,捨去了小數部分 cout << hex; cout << (int)(short)1 << endl; //1,即 0x00000001 cout << (int)(short)-1 << endl; //0xffffffff,即負數高位補1 cout << -1 << endl; //0xffffffff,負數表示法,符號位1,真值(1)求補碼 auto sz = sizeof(long);//64位系統,X64編譯器下VS2017測試值爲4 float fx = 83.7f; auto lfx = (long unsigned int)fx; //浮點轉整形, long long x; //8位整形 long unsigned int lui; //8位無符號整形 //浮點數據字節察看 //125.5f = 0x42fb0000 //-125.5f = 0xc2fb0000 //83.7f = 0x42a76666 //浮點數存儲按IEEE754標準: //以float爲例:共4個字節,從高位到低位依次是31,30,...2,1,0 //最高位存放數據符號,接下來8位存放階碼(包括階碼符號位),接下來23位存放尾數 int ifx = *(int*)(&fx); //等價於 int* pfx = (int*)&fx; int ipfx = *pfx; int sz2 = sizeof(x); } #pragma endregion #pragma region 地址與指針 void TestAddrAndPointer() { //------------------------------------------------------------- //1,&p, p, *p的區別: &p是p的地址,p是一個地址,*p是地址中的內容 //2,地址與指針徹底等價,有兩種操做:*地址,地址-> //3,地址就是一個數值,指針也是個地址 int x = 10; *(&x) = 0x100; *((char*)&x) = 1; //小端模式下[低字節存低地址處,高字節存高地址處]:0x101 int* pxt = (int*)10; //直接指向內存地址0x0000000a處 int*px = &x; //px與 &x徹底等價 int adr = (int)(&x); //地址就是個數值,指針也是個地址值 px = (int*)adr; cout << hex; //輸出爲16進制 cout << adr << "," << &x << "," << (int*)&x << "," << px << endl; //四者等價,輸出相同值 cout << dec; //輸出爲10進制 A oa(0); (&oa)->fpublic = 30; //地址與指針等價 (*(&oa)).fpublic = 111; //地址與指針等價 } #pragma endregion #pragma region 函數指針 void TestFuncPtr() { cout << "TestFuncPtr" << endl; } void TestFuncPtrParam(int, int, int) {//注意函數參數能夠不寫變量名 void(*pf)(int, int, int) = TestFuncPtrParam; int*p = (int*)pf; //試圖找出函數實參,失敗,對函數彙編原理不清楚,有時間再查 cout << *(p) << "," << *(p-1) << endl; } void TestFuncPointer() { A oa(0); //1,函數指針與普通指針不兼容,不能相互強轉 //2,函數指針賦值方式有二:pf = func或 pf = &func //3,函數指針pf使用方式有二:pf()或 (*pf)(),由於pf和 *pf的值相同,調試模式下能夠看到 //1,普通成員函數指針 typedef void(A::* PFUNC)(void); //函數指針聲明方式一 using PFunc = void(A::*)(void); //函數指針聲明方式二,C++11新方式 PFunc pf = &(A::TestFunc); int pfsz = sizeof(pf); (oa.*pf)(); //2,全局函數指針 void(*pfg)() = TestFuncPtr; pfg(); (*pfg)(); //3,靜態函數指針 void(*sptf)() = A::StaticTestFunc; sptf(); (*sptf)(); } #pragma endregion #pragma region 虛函數表原理 //每個帶有虛函數的【類】都有一個虛函數表,注意不是對象 void TestVirtualFunctionTable() { cout << hex; typedef void(*PFUNC)(); offsetof(A, fpublic); //利用此函數能夠算函數佈局 A oa(0); B ob; //一,經過內存地址修改不可訪問的保護變量 *(float*)((int*)&oa + 1) = 123.4f; //類的第一個變量fpublic賦值,(int*)&oa + 1是跳過虛函數指針 float fpublic = oa.fpublic; //二,經過內存地址調用虛函數 //A和B的虛函數表地址不同,也就是說父類和子類各有一張虛函數表 int* pvptr = (int*)(*((int*)(&oa))); cout << "A的虛函數表地址:" << pvptr << endl; //000DB0D4 ((void(*)())(*pvptr))(); //A::ToString pvptr = (int*)(*((int*)(&ob))); cout << "B的虛函數表地址:" << pvptr << endl; //000DB128 ((void(*)())(*pvptr))(); //B::ToString cout << "--------------------------" << endl; //最簡寫法 ((void(*)())(*((int*)*(int*)&oa)))(); ((void(*)())(*((int*)*(int*)&ob)))(); } #pragma endregion #pragma region 函數對象,友元函數模板運算符重載 template<class T> class AddTwoNumber { public: T x; AddTwoNumber(T x) { this->x = x; } public: //【經過重載()運算符,實現函數對象】 T operator()(T a, T b) { return a + b; } //一,使用模板類型的友元模板函數 //1, <>表示該友元是一個模板函數,且使用本模板類的類型 // 若不加<>說明符,則找不到模板函數定義,運行時出錯 //2,這裏的T是模板類傳來的類型,所以,這裏不能實現與T不一樣的類型操做 //好比若T爲int,則 2.1f + new AddTwoNumber<int>()不合法 //3,【注意這裏第二個參數是個引用類型,如果AddTwoNumber<T>對象類型則會出錯,不能在類中定義本類對象】 friend void operator+ <>(T os, AddTwoNumber<T>& n); //二,使用模板函數自帶類型的友元模板函數 //這裏的T是一個新的類型,與此模板類的T不要緊,所以沒有上面的限制 template<class T> friend void operator+(T os, A oa); template<class T> T Add(T a, T b); }; template<class T> void operator+ <>(T os, AddTwoNumber<T>& n) { cout << "operator+: n + AddTwoNumber: " << os << endl; } template<class T> void operator+(T n, A o) { cout << "operator+: n + A : " << n << endl; } //================================================== //※※※※※※注意這種多層的模板前置聲明※※※※※※※ template<typename T> //類模板的前置聲明 template<typename T1> //函數模板的前置聲明 T1 AddTwoNumber<T>::Add(T1 a, T1 b) { return a + b; } void TestAdd2Num() { AddTwoNumber<double> NumAdd(1); auto nadd = NumAdd(1, 2); A oa(1); 2.1f + oa; //左操做數任意數值類型,由於使用的是模板函數自帶類型 2.0 + NumAdd;//左操做數必須爲double, AddTwoNumber<string> add2("str"); add2.Add(1, 1); cout << "x: " << add2.x << endl; } #pragma endregion #pragma endregion #pragma region 2018.7.19 #pragma region 智能指針 //---------------------------------------------------------------------------------------------- template<typename T> class SmartPointerx { private: T * _ptr; size_t* _count; public: SmartPointerx(T* ptr = nullptr) : _ptr(ptr) { if (_ptr) { _count = new size_t(1); } else { _count = new size_t(0); } } SmartPointerx(const SmartPointerx& ptr) { if (this != &ptr) {//永遠成立 this->_ptr = ptr._ptr; this->_count = ptr._count; (*this->_count)++; } } SmartPointerx& operator=(const SmartPointerx& ptr) { if (this->_ptr == ptr._ptr) { return *this; } if (this->_ptr) { (*this->_count)--; if (this->_count == 0) { delete this->_ptr; delete this->_count; } } this->_ptr = ptr._ptr; this->_count = ptr._count; (*this->_count)++; return *this; } T& operator*() { assert(this->_ptr == nullptr); return *(this->_ptr); } T* operator->() { assert(this->_ptr == nullptr); return this->_ptr; } ~SmartPointerx() { (*this->_count)--; if (*this->_count == 0) { delete this->_ptr; //數組內存泄漏 int*p = new int[10] delete this->_count; } } size_t use_count() { return *this->_count; } }; void TestSmartPtr() { { SmartPointerx<int> sp(new int(10)); SmartPointerx<int> sp2(sp); SmartPointerx<int> sp3(new int(20)); sp2 = sp3; std::cout << sp.use_count() << std::endl; std::cout << sp3.use_count() << std::endl; } //delete operator } //---------------------------------------------------------------------------------------------- //下面是一個簡單智能指針的demo。智能指針類將一個計數器與類指向的對象相關聯, //引用計數跟蹤該類有多少個對象共享同一指針。每次建立類的新對象時,初始化指針並將引用計數置爲1; //當對象做爲另外一對象的副本而建立時,拷貝構造函數拷貝指針並增長與之相應的引用計數; //對一個對象進行賦值時,賦值操做符減小左操做數所指對象的引用計數(若是引用計數爲減至0,則刪除對象), //並增長右操做數所指對象的引用計數;調用析構函數時,構造函數減小引用計數(若是引用計數減至0,則刪除基礎對象)。 //智能指針就是模擬指針動做的類。全部的智能指針都會重載->和 * 操做符。 //智能指針還有許多其餘功能,比較有用的是自動銷燬。 //這主要是利用棧對象的有限做用域以及臨時對象(有限做用域實現)析構函數釋放內存 template<class T> class SmartPointer final{ //final T* pobj = NULL; __int64* refCnt = 0; public: SmartPointer(T* pobj) {//這裏可能會傳一個棧對象地址 if (pobj) { if (pobj != this->pobj) { if (!this->pobj) this->pobj = new __int64; (*refCnt)++; this->pobj = pobj; } } } SmartPointer(const SmartPointer<T>& rhs) { operator=(rsh); } SmartPointer<T> operator=(const SmartPointer<T>& rhs) { if (this == &rhs || pobj == rhs.pobj) return rhs; (*refCnt)--; (*rhs.refCnt)++; pobj = rhs.pobj; return *this; } ~SmartPointer() { refCnt--; if(refCnt == 0) ReleaseRes(); } T* GetPtr() const { return pobj; } private: void ReleaseRes() { if (pobj) { try { delete[] pobj; pobj = NULL; } catch (const std::exception&) { cout << "智能指針指向的不是一個堆對象" << endl; } } } }; #pragma endregion #pragma endregion #pragma region 2018.7.23 #pragma region 數組傳參方式 //方式一,數組引用傳遞 template<int N> void ArrayRefAsParam(char(&_dest)[N]) {//數組引用的寫法 char chs[] = "hello"; char* pstr = "hello"; cout << sizeof(chs) << endl; cout << strlen(chs) << ", " << strlen(pstr) << endl; strcpy_s(chs, "world"); cout << chs << endl; } //方式二,指針傳遞 void PointerAsParam(const char* pArr, int elemCount) { } void TestAstrPstr() { char chs[] = "world"; //6個字符,自動加了一個尾0 //1,數組引用傳參,如下兩種方式等價 ArrayRefAsParam(chs); //模板不只能夠推導類型,也能夠推導出數組的大小 ArrayRefAsParam<6>(chs); //說明了模板的工做原理,能夠不寫6,模板自動推導出數組大小 //2,指針傳遞 int sz = sizeof(chs); //6 int slen = strlen(chs); //5 PointerAsParam(chs, 1 + strlen(chs)); } #pragma endregion #pragma region 靜態(變量與函數)與常量(常引用,常指針,常函數) class CWithConstStatic { private: static int _privateId; public: string _str = "CWithStatic";//C++11,能夠這樣初始化 static string _sstr; //靜態變量不容許在類內初始化,這與舊C++一致 int _id = 1010; public: static void StaticMethod(){ //1,靜態函數本質上是一個全局函數 //2,靜態函數不能訪問非靜態變量和非靜態函數,包括常函數及常量,由於它不屬於對象,沒有this指針,編譯器翻譯時出錯 // _id = 10; //不訪問非靜態變量,由於沒有this指針,不翻譯爲this->_id //ConstMethod();//不能訪問非靜態函數,由於沒有this指針,不翻譯爲 this->ConstMethod() } void ConstMethod() const {//1 auto id = this->_id; StaticMethod(); //能夠訪問靜態函數,由於靜態函數不可能更改對象 //NormalMethod(); //不能訪問普通函數,由於普通函數可能會更改對象 } void ConstMethod() { //注意1和2的兩個ConstMethod函數是重載關係 } void NormalMethod() {//若函數從【調用1】處進入,則有: cout << "normal method begin" << endl; //輸出,沒問題 //cout << _id << endl; //出錯,由於這裏等價於 this->_id,而this指針爲NULL } }; string CWithConstStatic::_sstr; //靜態變量在類外的CPP中聲明 void NormalMethod(CWithConstStatic* _this) { } void TestCWithStatic() { //1,常對象 const CWithConstStatic ow; //ow._id = 1001; //error, 常對象不能被修改 //ow._str = "dd"; //error, 常對象不能被修改 ow._sstr = "dd"; //ok, 靜態變量不屬於對象 //2,常引用 const CWithConstStatic& owRef = ow; //owRef._str = "hhh"; //error, 常引用不能被修改對象 owRef._sstr = "dd"; //ok, 靜態變量不屬於對象 //3,常量指針,指向常量的指針,指向的內容不可修改 const CWithConstStatic* pcwcs = new CWithConstStatic(); //pcwcs->_id = 20; //error,不可經過常指針更改其指向的內容 //4,指針常量,指針是一個常量,不可被再次賦值 CWithConstStatic* const cpcwcs = new CWithConstStatic(); cpcwcs->_id = 20; //ok //5,類函數原理,this指針 //c++類的成員函數被編譯器翻譯爲了C語言編譯器能夠識別的全局函數,而後用C語言編譯器來處理它 //如下兩條調用等價 CWithConstStatic* pwcs = NULL; pwcs->NormalMethod(); //【調用1】C++的樣子 NormalMethod(pwcs); //【調用2】C語言翻譯出來的結果 } #pragma endregion #pragma region 深刻模板 #pragma region 可變參數模板 void TestVarTemp() {//【無參的重載函數】 //這個函數必須定義,不然編譯器報錯,由於函數參數展開時,最終(可變參數個數爲0時)要調用此函數 } template<typename First, typename... Args > void TestVarTemp(First first, Args... args) { //sizeof...是可變參數模板專用的獲取參數個數的函數 cout << sizeof... (args) << "-" << first << " "; //可變參數展開的惟一方式是遞歸調用,一層層剝離參數,當參數個數爲0時調用無參的重載函數,見【無參的重載函數】 TestVarTemp(args...); } void TestVarTemplate() { TestVarTemp(1, 2, 3, 4, "hello"); } #pragma endregion #pragma endregion #pragma region 構造和拷貝構造 class CNormclass { public: CNormclass() { cout << "constructor" << endl; } CNormclass(const CNormclass& rhs) {//有了複製構造函數後,系統再也不爲類生成無參構造函數 cout << "copy-constructor" << endl; *this = rhs; } }; CNormclass TestConstructorAndCopyCon1() { return CNormclass();//不調用COPY構造函數 } CNormclass TestConstructorAndCopyCon2() { //對象定義:兩種不一樣的定義方式 //方式一,會調用兩次構造函數 CNormclass r0; //constructor r0 = CNormclass(); //constructor,注意不是COPY構造函數 //方式二,只調用一次構造函數 CNormclass rr = CNormclass(); //constructor //COPY構造函數僅在兩種狀況下調用: //1,將一個已存在的對象生成另一個對象 CNormclass r1 = r0; //拷貝構造 //2,將一個已存在的對象做爲參數傳遞給構造函數時 CNormclass r2(r0); //拷貝構造 //不調用構造函數,也不調用拷貝構造函數,也不調用=運算符(由於是同類型),只是進行按位copy r1 = r0; cout << "before return " << endl; return rr; //調用COPY構造函數 } #pragma endregion #pragma region 函數指針複雜嵌套 typedef int(*PF2)(int); typedef PF2(*PF1)(int, int); typedef PF1(*PF)(int); int func2(int) { cout << "func2" << endl; return 0; } PF2 func1(int, int) { cout << "func1" << endl; return func2; } PF1 funcx(int) { cout << "funcx" << endl; return func1; } void TestNestingFuncPtrs() { //1,一次嵌套 PF1 pf1 = func1; pf1(1, 2)(1); //等價方式的直接聲明 int(*(*ptr)(int, int))(int) = func1; ptr(2, 3)(4); cout << "--------------------" << endl; //2,二次嵌套 PF pf = funcx; pf(1)(2, 3)(2); //等價方式的直接聲明 int(*((*((*ptr2)(int)))(int, int)))(int) = funcx; ptr2(1)(2, 3)(2); } #pragma endregion #pragma region 類型轉換構造函數 class CTypeCast { public: int _id; string _name; CTypeCast(int i) {//整形轉換構造函數:將一個整形轉爲對象 _id = i; cout << "integer cast " << i << endl; } CTypeCast(string str) {//字符串轉換構造函數:將一個字符串轉爲對象 _name = str; } //注意,顯示聲明,轉換必須顯式進行 explicit CTypeCast(float fx) {//浮點轉換構造函數:將一個字符串轉爲對象 cout << "float cast " << fx << endl; } }; void TestTypecastContructor() { //CTypeCast otc = 1; //整形轉換構造函數 //CTypeCast otc2 = "otc2"; //字符串轉換構造函數 //otc = 3; //注意,當加了explicit後,類型轉換必須顯示進行,所以下面這個語句不會使用浮點轉換構造函數 //可是,它卻可使用整形轉換構造函數,這會形成數據精度丟失 CTypeCast otc3 = 3.2f; //隱式轉換:整形轉換構造函數 CTypeCast otc4(3.2f); //顯示轉換:浮點轉換構造函數 } #pragma endregion #pragma region 2018.7.24 #pragma region 類型轉換運算符及()[]重載 class CTypeCastOper{ float fx = 0.2f; int arr[3]{ 1,2,3 }; public: //1,類型轉換運算符 explicit operator float() { return fx; } operator string() { } //2,()重載 //()運算符並非用來作類型轉換的,它是當函數用的,即仿函數,或函數對象 bool operator()() { return true; } //3,[]重載 //[]運算符與()差多的用法,都是用於對象以後 int operator[](int idx) { return arr[idx]; } }; void TestTypecastOper() { CTypeCastOper oper; float fx = (float)oper; cout << fx << endl; //1,()運算符 bool b = oper(); //2,[]運算符 cout << oper[0] << "," << oper[1] <<"," << oper[2] << endl; } #pragma endregion #pragma region 模板特化 template<typename T> class CTehuaTemp { public: T px = "abc";//2,被特化爲了一個char*類型指針,故能夠這樣用 }; template<typename T> class CDThhuaTemp : public CTehuaTemp<T*> {//1,將基類模板參數特化爲一個指針類型 public: T ch = 'c'; }; void TestTehuaTemp() { CDThhuaTemp<char> otp; cout << otp.px << endl; cout << otp.ch << endl; } #pragma endregion #pragma region 同類型賦值,常引用修改 class CSimpleclass { public: CSimpleclass() { cout << "cons" << endl; } CSimpleclass(const CSimpleclass& rhs) { cout << "copy cons" << endl; } public: float fx = 0; //默認未初始化,給它來個初始化 }; void TestSameTypeAssign() { CSimpleclass oc, oc1; const CSimpleclass& oc2 = oc; const CSimpleclass& oc3 = oc; cout << "-------------------------" << endl; //【同類型賦值,不調用=運算符,也不調用任何構造函數】 oc1 = oc; //oc2 = oc3; //常引用自己是個常量,也不能被修改 //oc2 = oc1; //常引用自己是個常量,也不能被修改 //oc2.fx = 30; //常引用不能更改引用的對象內容 const std::string ss; //ss = "abc"; //wrong //ss.clear(); //wrong } #pragma endregion #pragma region 堆指針棧指針判斷 class CTestPointerType { public: CTestPointerType(float fx=0) { this->fx = fx; } float fx; }; template<class T, int N> class CHeapDebugger { public: static void Print(const T* p){ int sz = N * sizeof(T); int* ip = (int*)p; int headFlag = *(ip - 1); int endFlag = *(int*)((char*)ip + sz); int orderFlag = *(ip - 2); int szFlag = *(ip - 3); bool isHeapPtr = headFlag == endFlag && headFlag == 0xfdfdfdfd && sz == szFlag; cout << "----------------------------------------------" << endl; //if (isHeapPtr) { cout << hex << "堆大小:" << szFlag << endl; cout << "堆編號: " << orderFlag << endl; cout << "堆首界: " << headFlag << endl; cout << "堆尾界: " << endFlag << endl; //} //else { // cout << "棧指針" << endl; //} cout << "----------------------------------------------" << endl; } }; void TestPointerType() { // const int N = 4; int*p = new int[N]; for (int i = 0; i < N; i++) { p[i] = i; } CNormclass* pn = new CNormclass[N]; CTestPointerType*po = new CTestPointerType[N]; const int*pc = &N; CHeapDebugger<int, 1>::Print(&N); int a = 10, b = 11; float fx = 20, fy = 30; CHeapDebugger<int, 1>::Print(&a); CHeapDebugger<int, 1>::Print(&b); CHeapDebugger<float, 1>::Print(&fx); CHeapDebugger<float, 1>::Print(&fy); delete po; } #pragma endregion #pragma endregion #pragma region 右值引用和MOVE void TestRef(){ int a = 0, b = 1; int& ra = a; cout << ra << endl; //0 ra = b; //此時ra不是a的引用也不是b的引用,而是一個普通變量 b = 300; cout << ra << endl; //1 } #pragma endregion #pragma region C11智能指針 #pragma endregion #pragma region 正則表達式 #pragma endregion #pragma region lambda表達式 #pragma endregion #pragma region unorder_map及hashtable實現 //有沒有無衝突哈希算法 #pragma endregion #pragma region DIJKASTRA最短路徑算法 class Obj { public: Obj(float fx) { x = fx; } float x; }; bool cmpfunc(Obj a, Obj b) { return a.x < b.x; } void TestStlSortFunc() { std::vector<Obj> vec; vec.push_back(Obj(1)); vec.push_back(Obj(12)); vec.push_back(Obj(1.3f)); vec.push_back(Obj(2.31)); vec.push_back(Obj(31)); vec.push_back(Obj(4)); vec.push_back(Obj(0)); int ax = 123; auto iter = max_element(vec.begin(), vec.end(), [ax](Obj obj1, Obj obj2){ cout << "cap addr of ax : " << ax << endl; return obj1.x < obj2.x; }); cout << (*iter).x << endl; } void RemoveVecElem(std::vector<int>& v, int e) { for (auto it = v.begin(); it != v.end();) { if (*it == e) { it = v.erase(it); break; } else it++; } } void Dijkastra() { const int m = 99999; const int n = m; const int nodeCount = 7; int paths[][nodeCount] = { { n, 50, 12, m, 45, m, m }, { m, n, m, m, 2 , m, m }, { m, 10, n, 99, m , m, m }, { m, m, m, n, m , m, m }, { m, m, m, 10, n , m, m }, { m, m, m, m, 0 , n, 1 }, { m, 1, m, m, m , m, n }, }; std::vector<string> sel; std::vector<int> left{ 0, 1, 2, 23, 4, 15, 6 }; sel.reserve(8); left.reserve(8); int startIdx; cout << ">> 選擇一個起點 " << endl; cin >> startIdx; cout << ">> v" << startIdx << endl; if (startIdx >= nodeCount) return; RemoveVecElem(left, startIdx); cout << "after erase : " << left.capacity() << endl; for (auto e:left) { cout << e << ","; } cout << endl; cout << ">> calculating ..." << endl; int tmp[nodeCount]; for (int i = 0; i < nodeCount; ++i) { tmp[i] = paths[startIdx][i]; } std::stringstream ss; //ss >> "v" >> startIdx; auto iter = min_element(tmp, tmp + nodeCount); cout << *iter << "," << iter - tmp << endl; int curMinNode = iter - tmp; int curMinPathLen = *iter; // ss >> "->v" >> curMinNode; //sel.push_back(ss.str()); //ss.clear(); RemoveVecElem(left, curMinNode); while (left.size() > 0) { bool isfind = false; for (int i = 0; i < nodeCount; ++i) { int p1 = paths[startIdx][i]; for (int j = 0; j < nodeCount; ++j) { bool isold = false; for (int i = 0; i < left.size(); ++i) { if (left[i] == j) isold = true; } if (!isold) { int p2 = paths[curMinNode][j]; if (j != curMinNode) { //j != curMinNode if ((curMinPathLen + p2) < p1) { isfind = true; paths[startIdx][i] = (curMinPathLen + p2); } } } } } if (left.size() == 0)break; auto p = paths[startIdx]; auto iter2 = std::min_element(left.begin(), left.end()); curMinPathLen = *iter2; //curMinNode = iter2 - left.be; RemoveVecElem(left, curMinNode); cout << "left: " << left.size() << endl; } // sel.push_back(0); // sel.erase(sel.begin()); // sel.shrink_to_fit(); // cout << "cap: " << sel.capacity() << endl; // for (int d : sel) // { // cout << d << endl; // } // cout << sel.size() << endl; } #pragma endregion #pragma region EffectiveC++ namespace EffectiveCpp { #pragma region 02-以const,enum,inline替代define class CStaticConst { public: //【1】,static const 能夠同時存在,這在C#中是不容許的 //在C#中,常量也是屬於類而不屬於對象,這就等價於C++的 static cosnt 合體了 static const float fx; //【聲明式】 //【2】,浮點類型,不能在定義時初始化 //static float fx2 = 3; //【錯誤】 //【3】,整數類型(整形,char,枚舉),能夠在定義時初始化,且不須要在類外寫定義式 static const int ix = 3; //聲明並初始化,注意,這不是定義,也就是說聲明時能夠賦值 enum {NumTurns = 5}; int scores[NumTurns]; //enum hack //【不安全宏的替代品】,既有宏的高效率和函數的安全性 template<typename T> inline T safe_max(const T& a, const T& b) { return a > b ? a : b; } virtual void VFunc() { } }; const float CStaticConst::fx = 1; //【定義式】:不能寫static //const int CStaticConst::ix = 3; //【錯誤】,已經初始化過了,不能重複 const int CStaticConst::ix; //定義式,聲明時已初始化了。由於是整數類型,這個定義式能夠不寫 //1,【宏是不安全的】任什麼時候候都不要忘了給宏的實參加上() //2 替代方法:使用 template inline #define unsave_max(a, b) (a) > (b) ? (a) : (b) void Test02() { CStaticConst oc; cout << oc.fx << endl; int a(10), b(20); //不安全的宏,下面這樣的致使b被加兩次 max(++a, b++); cout << "a=" << a << ", b=" << b << endl; string s1 = "hello"; string s2 = "hello"; } #pragma endregion } #pragma endregion x #pragma region 2018.8.2 #pragma region 協變逆變 template<typename T> class CAnimal { public: void Print(T info) { cout << info << endl; } }; template<typename T> class CHuman : public CAnimal<T> { }; void TestXiebian() { int a[10], b[10]; float c[10]; int*p = a; int*p2 = b; //int*p3 = c; //error A* pa = new B[10]; //協變 //協變在C++模板中彷佛不能用,不像C# CAnimal<A>* panim = new CHuman<A>[10]; //正確,這個仍是普通的數組協變,而不是像C#那樣真正的泛型協變 //CAnimal<A>* panim = new CAnimal<B>[10]; //錯誤 //CAnimal<A>* panim = new CHuman<B>[10]; //錯誤 } #pragma endregion #pragma endregion #pragma region 2018.8.3 #pragma region 奇怪的默認構造函數和子父間對象賦值 class CA { public: float fx; CA(float x) { fx = x; } virtual void tostring() /*final*/ {//可加final禁止重寫 cout << "ca" << endl; } }; class CB : public CA { public: float fbx = 2; explicit CB() :CA(1) { } //拷貝構造函數能夠有兩個,常量的,很是量的 CB(CB& b) : CA(1) {//拷貝構造函數1, cout << "copy 1" << endl; } CB(const CB& b) : CA(1) {//拷貝構造函數 cout << "copy 2" << endl; } // void tostring() { // cout << "ca" << endl; // } }; CB b() { cout << "....function...b...." << endl; return CB(); //【標記1】調用無參構造函數 } void TestOddConstructor() { CA a(1); CB b(); //調用無參構造函數??錯,實際上是聲明瞭一個返回值爲B的函數o(),對比【標記1】 CB bx; //ok,調用默認構造函數構造了一個對象bx CB* pb = new CB(); //ok,調用默認構造函數構造了一個對象 //C++中,子對象能夠賦值給父對象,C#中也能夠,不過C#操做的是指針 a = bx; //子對象能夠直接賦值給父對象,發生類截斷,子類部分被丟棄 //bx = (CB)a; //error,編譯出錯,不能轉換,除非自定義轉換 //bx = static_cast<CB>(a); CB rb = b(); cout << rb.fx << endl; //正確輸出: 1 //CB* pb1 = new CB; //ok,調用無參構造函數 //CB* pb2 = new CB(); //ok, 調用無參構造函數 CB ob1; const CB ob2; //拷貝構造函數能夠有兩個,常量的,很是量的 CB ob3(ob1); //copy 1 CB ob4(ob2); //copy 2 } #pragma endregion #pragma region 四種類型轉換 class CMemory { public: int x; float fx; char ch = 'a'; }; class ConstFuncForcemodify { float fx = 998; public: void PrintNum() const { cout << --(const_cast<ConstFuncForcemodify*>(this))->fx << endl; //去除常量性,或直接使用舊式強轉 //cout << --((ConstFuncForcemodify*)this)->fx << endl; } }; void Test4Typecast() { CA a(1); CB b; //------------------------------------------------------------------ //一,靜態類型轉換 static_cast, 【任何類型:值,指針或引用】 //不能將const類型轉爲non const類型 //1,普通類型轉換 int i = static_cast<int>('a'); //等價x = (int)'a'; char c = static_cast<char>(i); //等價c = (char)x; //a = b; //自己能夠轉換,子類轉換父類 a = static_cast<CA>(b); //ok a = static_cast<CB>(b); //ok //b = static_cast<CB>(a); //error,編譯錯誤 //2,指針類型轉換,只檢查有無父子關係,不進行動態類型檢查 //動態類型即運行時,指針實際指向的類型 CB* pb = static_cast<CB*>(&a); //ok,但運行時訪問CB類(不屬於CA)的部分將出錯 pb = (CB*)&a; //等價於上式 cout << pb->fbx << endl; //輸出一個未初始化的或不可預料的值 CA* pa = new CB(); pb = static_cast<CB*>(pa); cout << pb->fbx << endl; //ok //------------------------------------------------------------------ //二,動態類型轉換 dynamic_cast,【用於指針或引用】 //【基類必須定義了虛函數,說明動態時類型檢查是根據虛函數表來作的】 pb = dynamic_cast<CB*>(&a); //編譯OK,但運行時轉換失敗pb = Null //pb = (CB*)&a; //等價於上式 if(pb)pb->tostring(); //------------------------------------------------------------------ //三,從新解釋轉換reinterpret_cast, 【任何類型:值,指針或引用】 //依賴於編譯器,不可移植 void (*pf)() = Test4Typecast; int* ipf = (int*)pf; int ix = reinterpret_cast<int>(pf); //將指針轉爲int,或直接轉換 ix = (int)pf; cout <<hex << "ix: " << ix << ", addr: " << pf << endl; int* pix = reinterpret_cast<int*>(102);//將整形轉換爲指針,或直接轉換 pix = (int*)102; //------------------------------------------------------------------- //四,常量指針轉換,移除指針的常量性, 【用於指針或引用】 const int ctx = 10; int* itx = const_cast<int*>(&ctx); //將常指針轉爲int*指針,或直接轉換 itx = (int*)&ctx; //測試經過轉換去除常指針,在類的常函數中修改爲員變量的值 ConstFuncForcemodify ocffd; ocffd.PrintNum(); //997 ocffd.PrintNum(); //996 ocffd.PrintNum(); //995 } #pragma endregion #pragma region 內存佈局-參考[堆指針棧指針判斷] //1,程序總佈局,內存從高地址向低地址分配, //2,對象,結構,數組內部的元素從低地址向高地址分配 //分配順序是變量定義的前後順序 //本機測試中,任何兩個獨立數據間的地址間隔爲12 class CT { public: int x = 101, y = 102, z = 103;//默認公有 void testd() { cout << x << "<" << y << "<" << z << endl; } }; class MyStruct : public CT { public: //=========================================================================== //通用規則,基類指針或引用只能看到基類變量與函數,C++與C#都是這樣 //=========================================================================== //如:CT * pt = new MyStruct(),則pt->只能看到基類數據與函數 int y = 1, z = 2, w = 3, h = 4, k = 5; //與基類重名,pt->x是基類的,pt->w則錯誤,找不到這個變量 }; void TestMemLayout() { //不相關的各元素,內存從高到低分配,不連續 int x, y, z, w, o, p, q, r, s; cout << "addr>>" << &x << "," << &y << "," << &z << "," << &w << "," << &o << "," << &p << "," << &q << "," << &r << "," << &s << endl; //分配完上面的一些獨立數據後,內存地址減12,繼續分配數組 //數組以內,各元素內存地址從低到高,連續,此時數組的最大地址要小於上面已分配的最小地址 int iarr[10]; for (int i = 0; i < 10; ++i) { cout << &iarr[i] << endl; } //分配完上面的數組,內存地址減12字節,繼續分配對象數據 //對象內,各元素內存地址從低到高,連續,最大地址小於上面的最小地址 CMemory om; cout << &om.x << endl; cout << &om.fx << endl; cout << &om.ch << endl; //輸出亂碼,對字符地址操做默認爲是輸出字符串 cout << (char*)&om.ch << endl; //同上輸出亂碼,對字符地址操做默認爲是輸出字符串 cout << (int*)(&om.ch) << endl; //輸出地址 //========================================================================================= //內存覆蓋實驗 //利用上面的12差規律,覆蓋內存數據 //12差規律,也就是說每兩個不相關的數據間有8個字節用來CCCC填充,表示內存分界,內存分界不能寫入,不然崩潰 //========================================================================================= int poorA = 1; int poorB = 2; int poorC = 4; //========================================================================== //CT* pt = new CT(); //堆上數據被默認初始化爲0: x=y=z=0 CT ot;//棧上數據未初始化 //========================================================================== //查看彙編代碼時能夠發現,編譯器每進一個函數時就會行標記一段內存,每一個字節能寫入0XCC,這個值就是21號中斷 //這樣作是爲了給開闢的內存作上標記,只容許編譯器來在這段內存上分配內存,不容許非法的操做 //如,程序定義了 int a=1,則編譯器給 a分配4個字節,並將1寫入,若經過指針訪問未被賦值的內存,則它裏面的內容就是0xcc //,訪問時當即觸發21號中斷,程序報錯,內存不可訪問 MyStruct* pms = static_cast<MyStruct*>(&ot); //或 pms = (MyStruct*)(&ot) //pms->y = 11; //觸發21中斷 //pms->z = 12; //觸發21中斷 pms->w = 0; //不是內存分界,能夠覆蓋,這個就是poorC所在位置 cout << hex << "-----------------------" << endl; int* ptt = (int*)&(pms->x); for (int i = 0; i < 15; ++i) { cout << *(ptt + i) << endl; } //poorC的值被改成了0 cout << poorA << "," << poorB << "," << poorC << endl; } #pragma endregion #pragma region 無題 const int* TestConstarr() { int* iarr = new int[3]{ 1, 2, 3 }; return iarr; } template<int> //無心義的全特化,C#不支持 void TestAllConcreate() { } #pragma endregion #pragma endregion #pragma region 2018.8.4 #pragma region 靜態函數重載 class CSox { public: static void f1(int x) { cout << "f1 int" << endl; } static void f1(float x) { cout << "f1 float" << endl; } }; #pragma endregion #pragma region 有符號無符號賦值 void TestSignedUnsignedAssign() { //1,vs2017c++默認容許進行數據截斷,即double賦值給int可行 int ix = 2.3; //ok, 容許數據截斷 cout << hex; unsigned int ui = 0; //2,有符號無符號混合運算 //計算過程: //ui-1,轉換爲 ui + (-1) //因爲 -1默認爲int, uint + int 類型自動轉換爲uint //-1的補碼爲 0xffffffff,故 0 + 0xffffffff = 0xffffffff,對應無符號值仍是0xffffffff cout << ui - 1 << endl;//0xffffffff //3,越界表現 int x = 65536; //2的16次方 int y = x * x; // 0, 緣由:2的32次方是0x100000000,超出了32位,故截斷爲0 int y1 = (double)x*x*x*x; //0x80000000 int y2 = (double)(x*x); //0,緣由:(x*x)這個單元在計算時是按int算的,自身算完後截斷爲0,再轉double已經晚了 int y3 = double(x)*x; double dx = double(x) * x; int y4 = *(int*)&y3; int z = pow(2, 32);//0x80000000, 緣由同y1 int w = 2 ^ 3; //注意,這是異或,不是2的3次方 cout << hex<< y << "," << y1 << "," << y4 << "," << z << endl; cout << "------------------------" << endl; double a = 0x13edf000000011; cout << "flaot a " << a << dec << "," << 0x01ffffff << endl; unsigned int i = a; //printf("a = %lx\n", a); cout << hex<< "i=" << i << endl; cout << dec << 0x33333333 << endl; } void testcasttype() { int i = 0xffffffff; //-1 int j = 0xfffffffe; //-2 cout << "i=" << i << endl; double di = i; cout << di << endl; auto typ1 = 0x8000; //int auto typ2 = 0x7fffffff; //int auto typ3 = 0x80000000; //unsigned int auto typ4 = 0xffffffff; //unsigned int auto typ5 = 0x100000000; //long long //注意,=號右邊表達式中的數據類型與=號左邊的類型無關,以下計算等價於 //double = uint * int double multype = 0x80000000 * 0x7fffffff; //double 轉int //轉換過程:double = uint * int //=號右邊是按最大類型uint 來算的 //uint 類型的0x80000000左移一位後出了uint範圍,變爲0 double td1 = 0x80000000 * 2; //int轉爲double後,總能正確的轉回來,前提:數據在int型數據範圍內 //當一個數越出了int型表示範圍,轉換爲int時,總會獲得0x80000000 //1,double x = 0x2345e0f0d01f; (int)x ==> 0x80000000 //2,float x = 0x2345e0f0d01f; (int)x ==> 0x80000000 //當一個數越出了uint型表示範圍,轉換爲uint時,獲得被截斷的低32位數據 //1,double x = 0x2345e0f0d01f; (int)x ==> e0f0d01f //2,float x = 0x2345e0f0d01f; (int)x ==> e1000000 //注意,因爲float的尾數只有23位可表示,所以截取數據會有偏差,如上x只能精確到2345e0這24位,所以結果爲e1000000 //雖然float的表示範圍很大,但int轉爲float後再轉回來就可能丟失數據 //即當int數據小於23位時,能夠安全的轉爲float,而後再原樣轉回 int imax = 0x7fffffff; cout << imax << endl; cout << hex << (int)td1 << endl; cout << hex << (unsigned int)td1 << endl; float td2 = float(0x2345e0f0d01f); cout << hex << (int)td2 << endl; cout << hex << (unsigned int)td2 << endl; cout << hex << (short)td2 << endl; cout << hex << (unsigned short)td2 << endl; cout << hex << (int)(char)td2 << endl; cout << hex << (int)(unsigned char)td2 << endl; int x = 65536; int y3 = double(x)*x; double y4 = double(x)*x; //double y5 = cout << *(int*)&y3 << endl; } void tstcast() { int a = 0x60000000; float *p = (float*)&a; cout << *p << endl; printf("%f\n", (float)(*p)); //0.000000 //printf("%f\n", (double(a)));//10.000000 } #pragma endregion #pragma region 數組和指針 void TestArrayAndPtr() { short ac[] = { 1, 2, 4, 0, 3, 5, 7, 9 }; //數組 short* parr = new short[8] { 1, 2, 4, 0, 3, 5, 7, 9 };//指向數組的指針 //----------------------------------------------------------------------------------- //數組比較特別,沒法按照普通指針那樣用 &p,p,*p的模式去通解它,只能分2種狀況去理解,以下1,2 //----------------------------------------------------------------------------------- //1,數組名指向數組首元素, 這與指向數組的指針表現同樣 cout << *(ac) << endl;//1 cout << *(ac + 1) << endl; //2 cout << *(parr) << endl; //1 cout << *(parr+1) << endl;//2 //2,&數組名取得數組內存塊的地址,而&指針取得指針的地址 cout << &ac << endl; //003CF830 cout << &ac + 1 << endl; //003CF840,內存偏移了16字節,即數組大小 cout << &parr << endl; //003CF824 cout << &parr + 1 << endl; //003CF828 //內存偏移了4個字節,任何指針大小都是4個字節 //3,數組指針,也叫行指針 short(*parr1)[8] = ∾ //指向具備8個short類型的數組 } #pragma endregion #pragma region const函數重載與常對象函數調用 class constoverload { char _vals[10] = { 1, 2, 3, 4, 5, 6 }; public: //利用const 進行函數重載 //1,以下 不算重載,算同一個函數,重定義會報錯 //void f2(const float i){} void f2(float i) {} //2,利用const進行重載 void f1(const int i) const { cout << "const f1" << endl; } void f1(int i) { cout << "just f1" << endl; } //3,重載【運算符重載函數】 const char& operator[](int idx) const {//返回值是引用,必須加const修飾,不然違反了常函數規則 cout << "const operator[]" << endl; return _vals[idx]; } char& operator[](int idx) { //不是常函數,能夠返回非const的引用 //注意,這裏的返回值爲引用,是爲了使用 obj[idx] = 'x'操做,若非引用則不合法 cout << "operator[]" << endl; return _vals[idx]; } }; void Print(const constoverload& ctl) { auto x = ctl[0]; //const operator[] } void TestConstOverload() { //----------------------------------------------------------------------- //常對象只能調用常函數,由於很是函數可能會更改了對象內的值 //----------------------------------------------------------------------- const int ix = 10; constoverload od; const constoverload cod; od.f1(1); //just f1 od.f1(ix); //just f1,注意這個調用,並不會由於參數是const int而去調用形參爲const int的那個常函數 cod.f1(2); //const f1 //cod.f2(1); //error, 編譯錯誤 od[0] = 'a'; //由於operator[]返回了引用,故可修改其返回值 //真實使用情形 Print(od);//const operator[] //也能夠這樣 Print(cod);//const operator[] } #pragma endregion #pragma endregion #pragma region 2018.8.6 #pragma region 線程基本使用 void ThreadFunc(int t) { this_thread::sleep_for(chrono::milliseconds(t)); //this_thread::yield(); //獲取線程ID cout << "thread started:" << this_thread::get_id() << "," << _threadid << endl; } void TestThread() { std::thread t(ThreadFunc, 300);//可變參數 t.join(); //t.join();//1已經結束了,不能夠再操做它,不然異常 std::thread t1(ThreadFunc, 1000); //t1.detach(); //解除join,將當前線程做爲後臺線程,主線程結束後將報錯 t1.join(); //再join會異常,已解除了不能再join } #pragma endregion #pragma region 線程同步 int totalNum = 999; std::mutex banklock; void DrawbackMoney() { banklock.lock(); if (totalNum < 0) return; banklock.unlock(); if (totalNum < 0) { throw std::exception("錢的數目小於0");//彈出框不顯示此字符串??? } for (int i = 0; i < 20; ++i) { banklock.lock(); if (totalNum > 0) { cout << "left: " << totalNum << ", thread:" << this_thread::get_id() << endl; totalNum -= 10; this_thread::sleep_for(chrono::milliseconds(100)); } else { banklock.unlock(); return; } banklock.unlock(); } } void TestThreadSync() { thread t1(DrawbackMoney); thread t2(DrawbackMoney); thread t3(DrawbackMoney); thread t4(DrawbackMoney); thread t5(DrawbackMoney); thread t6(DrawbackMoney); thread t7(DrawbackMoney); thread t8(DrawbackMoney); thread t9(DrawbackMoney); this_thread::sleep_for(chrono::seconds(3)); t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); t6.join(); t7.join(); t8.join(); t9.join(); } #pragma endregion #pragma region 智能指針使用 class managedOjbect { public: float fx; double dx; managedOjbect() { cout << "managed object constructor" << this << endl; } ~managedOjbect() { cout << "managed object destructor" << this << endl; } }; void TestShareptrUse() { //1,多個智能指針間能夠相互賦值,智能指針內部自動處理引用計算 std::shared_ptr<managedOjbect> ptr(new managedOjbect); std::shared_ptr<managedOjbect> ptr1(ptr); //ok, ptr1與ptr引用同一個對象 ptr->fx = 10; ptr1 = ptr; ptr = ptr1; std::shared_ptr<managedOjbect> ptr2(); //這是函數聲明式,不是指針!!! //2,不要將一個裸指針同時託管給多個智能指針,不然將發生重複析構 auto* pobj = new managedOjbect(); std::shared_ptr<managedOjbect> ptr3(pobj); //std::shared_ptr<managedOjbect> ptr4(pobj); //析構異常,重複delete auto* pobj1 = new managedOjbect(); auto* pobj2 = new managedOjbect(); std::shared_ptr<managedOjbect> ptr5(pobj1); //3,儘可能不要使用reset,由於它會釋放託管對象,會致使其它智能指針異常 ptr5.reset(); //當即釋放託管堆,即delete pobj1 ptr5.reset(new managedOjbect()); //當即釋放原來的,持有新的對象 //4,智能指針不能用於數組 //std::shared_ptr<managedOjbect> arrptr(new managedOjbect[10]); //析構時異常,緣由見下面異常分析 //std::shared_ptr<int> iarptr(new int[10]); // //----------------------------------------------------------------------------------- //析構異常分析: //釋放數組時應使用 delete[],不該該使用delete //----------------------------------------------------------------------------------- //1,對於基本類型測試 delete與delete[]等價 int* pia = new int[10]; int* piab = pia; //&(pia[0]); for (int i = 0; i < 10; ++i) pia[i] = i; delete pia; //&(pia[0]); //運行時不報錯,數組內存被正確,徹底釋放 pia[1] = 0xff; //運行時出錯,由於指針已經是野指針了 piab[1] = 0xff; //正確運行 for (int i = 0; i < 10; ++i)//查看內存 cout << hex << piab[i] << ","; cout << endl; //2,對於自定義類型,應使用delete[] //智能指針只處理單個類對象,而不處理數組,所以託管數組時析構異常,就是由於調用了delete而不是delete[] auto* pobjs = new managedOjbect[10]; delete pobjs; //運行時崩潰,不是一個有效的堆指針,應使用delete[] pobjs; } #pragma endregion #pragma region 自已實現智能指針 template<typename T> class SmartPtrl { std::size_t* psz = nullptr; T* _ptar = nullptr; public: string name; SmartPtrl(T* p) { //帶裸指針的構造函數只能是初始構造者,不能將同一個裸指針傳遞給兩個不一樣的智能指針 //由於裸指針不知道如何去減小引用計數 psz = new std::size_t; *psz = 1; _ptar = p; } SmartPtrl(SmartPtrl& ptr) { operator=(ptr); } SmartPtrl& operator=(SmartPtrl& ptr) { if (this == &ptr || _ptar == ptr.get()) return *this; if (_ptar) { decrease(); } _ptar = ptr.get(); psz = ptr.psz; ++(*ptr.psz); } T* operator->() { return _ptar; } T& operator*() { return *_ptar; } ~SmartPtrl() { cout << "destructor: " << name << endl; if (_ptar) decrease(); } T* get() { return _ptar; } void decrease() { if (nullptr == psz)return; (*psz)--; if (*psz == 0) { delete _ptar; delete psz; _ptar = nullptr; psz = nullptr; } } }; void TestMySmartPtr() { SmartPtrl<managedOjbect> ptr(new managedOjbect); ptr.name = "ptr"; SmartPtrl<managedOjbect> ptr2(ptr); ptr2.name = "ptr2"; //自賦值及相等指針賦值測試 ptr2 = ptr; ptr = ptr2; ptr = ptr; SmartPtrl<managedOjbect> ptr3(new managedOjbect); ptr3.name = "ptr3"; SmartPtrl<managedOjbect> ptr4(new managedOjbect); ptr4.name = "ptr4"; ptr3 = ptr4; //重載的指針運算符使用起來有點奇怪,以下兩種等價 //按常理,operator->應該返回一個引用而不是指針 ptr3->dx = 30; //從這個來看,->返回的應該是一個引用 ptr3.operator->()->dx = 30; //這纔是它的本質 cout << ptr4->dx << endl; cout << (*ptr4).dx << endl; //operator* } #pragma endregion #pragma region delete和delete[]的表現 void testDeleteStack() { //1,測試1 managedOjbect* pobj = new managedOjbect(); //delete[] pobj; //不報錯,運行時不斷析構 //2,測試2 managedOjbect* pt = &(pobj[100]); //delete pt; //不報錯,運行無錯 //delete[] pt; //不報錯,運行時不斷析構 //3,測試3 managedOjbect mobj; //delete[] & mobj; //不報錯,運行時不斷析構 //4,測試4 managedOjbect* pobjs = new managedOjbect[3]; //delete pobjs; //運行時異常 //5,查看內存頭,數據彷佛也正常 CHeapDebugger<managedOjbect, 1>::Print(pobj); //總結: //對於基本類型,使用 delete[]萬無一失 //對於類對象類型,單對象使用delete,數組對象使用delete[],不然出錯 //對單對象使用delete[],將出現上面的死循環析構 //對數組對象使用delete,將拋出運行時異常 } #pragma endregion #pragma region 模板特化 //------------------------------------------------------------------------------------------ //模板特化的原由是:某些特定類型不適用於模板的通用算法,須要特化出一個專用版原本處理它(寫個特定算法) //同時,因爲模板不支持定義類名相同而類型個數不一樣的模板 //對於模板類型參數個數可變的需求,C++11提供了typename...語法支持可變類型參數 //------------------------------------------------------------------------------------------ //1,模板特化是指在原始模板的基礎上,特化出一個同名的模板來,只是參數被特化了 //注意模板特化的語法是在類名後加<>並指定特化類型 template<typename T, typename T2> class tehuaTemp {//原始版本 public: T Max(T a, T b) { cout << "max a b" << endl; return a > b ? a : b; } }; //2,只能定義一個模板原始版本,不能經過增長或減小模板類型參數個數來定義不一樣的同名模板 // template<typename T, typename T2, typename T3> //不能這樣作, // class tehuaTemp{ // public: // T Max(T a, T b) { // cout << "max a b" << endl; // return a > b ? a : b; // } // }; //3,這不是模板特化 // template<typename T, typename T2, typename T3> // class tehuaTemp<T, T2, T3> {//原始版本 // public: // T Max(T a, T b) { // cout << "max a b" << endl; // return a > b ? a : b; // } // }; template<typename T>//偏特化 class tehuaTemp<T*, int*> {//特化出一個T*版本,語法:類名<類型...>,區別於模板類的定義 public: T Max(T* a, T* b) { cout << "max *a *b" << endl; return *a > *b ? *a : *b; } }; template<> //全特化 class tehuaTemp<string, int> {//特化出一個字符串版本 public: int Max(string a, string b) {//字符串特化版本的Max cout << "stricmp" << endl; return _stricmp(a.c_str(), b.c_str()); } }; //4,模板特化相似於特化繼承,正因如此,兩者不能同時存在 //以下,CTX1與tehuaTemp<int>等價,不能同時存在 class CTX1 : public tehuaTemp<int, string> { }; //5,類型參數個數可變的模板 template<typename t1, typename... others> void xprintx(t1 fst, others... ors) { cout << fst << endl; xprintx(ors...); } void TestVarTemplatex() { xprintx(1, 2, "hello", 4, 5, "world"); } // template<> // class tehuaTemp<int> { // // }; void TestTehuaTemp2() { tehuaTemp<float, int> tht; tht.Max(3, 4); //max a b float a = 1, b = 2; //tht.Max(&a, &b); //注意下面的調用 //1,沒有特化版本時也能夠正常調用,此時用T=float*調用原始版本 //2,有特化版本時,調用指針版本,此時類型參數爲 T=float tehuaTemp<float*, int*> thit; thit.Max(&a, &b); //max *a *b //指針特化版本的實際使用是字符串比較 char* ps1 = "hello"; char* ps2 = "world"; tehuaTemp<char*, float*> scmp; scmp.Max(ps1, ps2);//max a b //另外一個字符串比較的特化版本 tehuaTemp<string, int*> td; cout << td.Max("1", "efg") << endl; //使用字符串特化版本的Max } #pragma endregion #pragma endregion #pragma region 2018.8.7 #pragma region 變量初始化及順序 class CtestInitobj { public: CtestInitobj(float fx) { cout << "CtestInitobj-cons" << endl; } CtestInitobj() { cout << "CtestInitobj-default-cons" << endl; } }; class DefaultConstrutor { class inclass { public: inclass(string s) { cout << "inclass-cons" << endl; } }; public: int fx; //1,基本類型成員默認沒有初始化 static int ix; //2,靜態的數據必須在類外定義一次,就算是int也不例外 inclass oic; //3,變量初始化順序與聲明順序相同 CtestInitobj oti; public: DefaultConstrutor(float fx, string str):oic(str) { cout << "fx: " << hex << *(int*)&this->fx << endl; //cccccccc,未初始化 cout << "default constructor" << endl; //4,對成員對象的初始化最好是放在初始化列表中,由於放在這裏賦值,會有兩次構造函數被調用: //(1),其默認構造函數,(2),有參構造函數 oti = CtestInitobj(1); } }; int DefaultConstrutor::ix; //全局對象不寫初始值默認爲0; void testInitorders() { DefaultConstrutor od(1, "a"); } #pragma endregion #pragma region sealed與final及override //sealed 與 final本質上是同樣的,MSDN:在標準類上使用final,在ref類上使用sealed //大概可理解爲c++中使用final,c#中使用sealed //但實際情形是C++中可使用final或sealed或同時混合使用,C#中只認sealed class Csealfinal /*final sealed*/ {//1,修飾類時,能夠一塊兒使用,或單個使用,該類不能被繼承 public: virtual void out() /*final sealed*/ {//2,修飾虛函數時,能夠一塊兒使用,或單個使用,該函數不能被override cout << "virtual-out-func" << endl; } void funx() /*final*/ {//3,不能修飾非虛函數 } }; class dcsealfinal sealed //4,注意這個sealed的寫法位置 : public Csealfinal { private: void out() override sealed { cout << "deriseal-out-" << endl; } }; #pragma endregion #pragma region 在類外調用私有函數 class CallPrivateVirtualFunc /*final sealed*/ {//修飾類時,能夠一塊兒使用,或單個使用,該類不能被繼承 public: virtual void out() { cout << "virtual-out-func" << endl; } }; class Dcallprivate : public CallPrivateVirtualFunc { private: void out() {//注意,這是個私有的重寫虛函數,它也能實現多態調用 cout << "deriseal-out-" << endl; } }; void testCallPrivateFuncOutside() { Dcallprivate osa; CallPrivateVirtualFunc* psa = new Dcallprivate(); //利用虛函數調用機制,能夠在類外調用私有函數 psa->out(); //調用私有的函數 } #pragma endregion #pragma region 靜態類 static class CStaticClass { public: CStaticClass() { cout << "cstaticclass-constructor" << endl; } static int fx; string name = "static class"; }; int CStaticClass::fx; //默認爲0 void Teststaticclass(){ //從如下測試看來,C++中的靜態類與普通類沒有任何區別 CStaticClass os; os.name = "os"; auto* pcs = new CStaticClass(); cout << pcs->name << ", " << pcs->fx << endl; cout << CStaticClass::fx << endl; } #pragma endregion #pragma region mutable變量在const函數中的使用 class cConstclassMutable { float fx; mutable float fy; public: void Print() const { fy = 10; //ok //fx = 10; //error } }; #pragma endregion #pragma region const&返回值並不絕對安全 class CConstRefcls { char str[10] = "hello"; public: const char& getStr(int idx) { return str[idx]; } char& getStrc(int idx) const { } void out() { cout << str << endl; } }; void TestModifyCOnstref(){ CConstRefcls ocr; char* pc = (char*) &(ocr.getStr(0)); *(pc) = 'x'; ocr.out(); } #pragma endregion #pragma region 跨編譯單元的初始化問題 //遊戲引擎設計中,須要一個gGame的全局對象來供全部地方方便的使用 //【有問題的設計】會有跨編譯單元的初始化順序問題 //game.h,實現以下,使用者可能在game.cpp編譯前已經要使用gGame了,這時候將報錯 // class CGame { // }; // extern CGame gGame; //game.cpp中實現定義 //CGame gGame; //【正確的設計】 CGame& theWorld() {//在使用前總能保證明例對象已初始化,且不用便不初始化 static CGame sgame; //利用了局部靜態變量的性質:只會在初次使用時定義一次,且一直存在 return sgame; } void testSingleton() { gGame.InitGame();//使用,沒問題 //但這個設計存在一個問題,如何保證gGame在全部使用者使用前已初始化完成? //在跨編譯單元時,這種順序是沒法保證的,所以,引出了另外一種設計: theWorld().InitGame(); } #pragma endregion 利用local static實現單例 #pragma region 一個空類有什麼 //當你寫出一個空時,編譯器會爲它生成: //public inline的默認構造函數 //public inline的拷貝構造函數 //public inline的析構函數 //public inline的operator= //class CEmpty{}; //空類 class CEmpty {//編譯器生成的類,但這些東西,只有在被須要時才產生 public: CEmpty(){} CEmpty(const CEmpty& rhs){} ~CEmpty(){} //若基類是虛析構函數,則編譯器在這裏也會生成一個默認的虛析構函數 CEmpty& operator=(const CEmpty& rhs) { return *this; } }; void TestCompilerClass() { CEmpty od; //生成默認構造函數,析構函數 CEmpty od1(od); //copy constructor od1 = od; //operator= } #pragma endregion #pragma region 阻止類對象被拷貝 //方法一: 將拷貝構造函數與operator=聲明爲private且不實現,錯誤只能在連接期間被發現【見effective C++】 //實際上,對於這種狀況,現代編譯器也能在編譯期直接發現錯誤,測試版本[VS2017] class CUncopyableCls { public: CUncopyableCls(){} ~CUncopyableCls() {} private: CUncopyableCls(const CUncopyableCls& rhs); CUncopyableCls operator=(const CUncopyableCls& rhs); }; //方法二,生成一個基類,將拷貝構造函數與operator=聲明爲private且不實現 //同時,生成一個子類,不寫拷貝構造函數和operator= //這樣,編譯器在編譯時就會嘗試爲子類生成兩者 class CUncopyable { protected://使能夠繼承 CUncopyable() {} ~CUncopyable() {} private: CUncopyable(const CUncopyableCls& rhs); CUncopyable operator=(const CUncopyable& rhs); float fx; }; class CUncopyableClass : public CUncopyable {//不實現拷貝構造函數和operator= }; void testUncopyableobj() { CUncopyableCls ou1, ou2; //ou1 = ou2; //編譯錯 CUncopyableClass oup, oup1; //oup = oup1;//編譯錯:編譯器發現有operator=需求,嘗試爲CUncopyableClass生成operator= //並調用基類operator=,但因爲它爲私有的,不能調用,所以,編譯出錯 } #pragma endregion #pragma region 抽象類及虛析構 //1,當聲明一個虛析構時,意味着類爲基類 //2,繼承別人的類時,要注意它有沒有虛析構,如標準庫函數string class CMystr : public string {//string類並無虛析構函數,這樣作將有內存泄漏的風險 }; class CMyVec : public std::vector<int> {//vector也沒有 }; void testVirtualMemLeak() { string* pstr = new CMystr(); delete pstr; //內存泄漏 CMystr mstr; } //3,使用純虛析構函數實現抽象基類的trick class CMyxxxAbastractcls { float fx; string name; public: void instFunc(){} virtual ~CMyxxxAbastractcls() = 0; //這樣就聲明瞭一個純虛析構函數,有純虛函數的類即是抽象類,不能被實例化 }; //4,但必須實現它,不然連接報錯: 由於析構子類對象時最終要調用基類析構函數 //抽象類也須要被析構,由於抽象類並不像C#的接口,抽象類中有成員數據須要釋放 CMyxxxAbastractcls::~CMyxxxAbastractcls(){ cout << "pure virtual function " << endl; } class CDxxObj : public CMyxxxAbastractcls { public: CDxxObj() {} ~CDxxObj() {} }; void testpurevirtualfunc() { CMyxxxAbastractcls* pyx = new CDxxObj(); delete pyx; //pure virtual function } #pragma endregion #pragma endregion #pragma region 2018.8.8 #pragma region 結構體&聯合體&字節對齊&大小端 void testEndian() { //1, 聯合體使用測試 union {//1.1,C風格定義並初始化 char ch; char ch1; long l; } un = { 1 }; union ux {//1.2,C++風格定義 char ch[4]; long l; ux(){} ux(long l) {//能夠有構造函數 this->l = l; } void fx() {//能夠有函數,默認公有,同結構體 cout << "union func: " << endl; cout << ch[0] << endl; } }; //1.3 聯合體無論字節對齊,只取成員中最大的尺寸 union UN //size = 1 { char ch; char ch1; char ch2; }; union UN1 //size = 3 { char ch[3]; }; union UN2 //size = 4 { char ch; int it; }; //2,結構體字節對齊 #pragma pack(push, 4)//老是按4的整數倍分配內存,寧多很多 struct ST { char ch; //4 double d; //8 char ch1; //4 char ch2; short srt; int ix;//4 char ch3;//4 }; #pragma pack(pop) //恢復原來對齊 cout << sizeof(ST) << endl; //3,大小端模式 //經常使用機型均爲小端模式: //(1) X86系統架構 (2) arm系列:IOS,android等,默認爲小端模式,可設置爲大端 //3.1 使用聯合體測試機型大小端 union { char ch[4]; long l; } uni = { 0x64636261 }; cout << (uni.ch[0] == 0x61 ? "小端" : "大端") << endl; } #pragma endregion #pragma region 舊式轉換的隱患 class cax { public: float fax = 1; }; class cbx : public cax { public: float fbx = 2; }; class ccx { public: float fcx = 3; }; void testForceCasttype() { cax* pax = new cbx(); cax* pa = new cax(); //舊式轉型在類對象中無安全保證 cbx* pbx = (cbx*)pax; cbx* pb = (cbx*)pa; //不是子類型,運行時不報錯,直到經過pb指向內部成員 ccx* pcx = (ccx*)pax; //沒有繼承關係編譯器不報錯,由於任何指針間均可以強轉 cout << pb->fax << endl; //1,輸出的是fax cout << pb->fbx << endl; //不肯定值,由於pb並非一個cbx類型 cout << pcx->fcx << endl; //1,輸出的是fax的值 } #pragma endregion #pragma region STL-VECTOR void testvector() { vector<int> vec{ 1, 2, 3 }; vector<string> vecStrs(1); vecStrs.emplace_back("hello"); cout << vecStrs.size() <<", " << vecStrs.capacity() << endl; } #pragma endregion #pragma region 2018.8.14 #pragma region int型數值邊界 void testIntLimit() { //C++中不容許使用-2147483648這個值,由於它把符號與數值當成兩部分來看了 //首先把2147483648當作uint32,再加上前面的負號時,編譯器報錯:一元負運算符應用於無符號類型,結果仍爲無符號類型 //int32的表示範圍是 -2147483648 ~ 2147483647,C++不讓用,有點奇怪,C#是能夠的 //因上在C++中,只能這樣用: int ix = -2147483647 - 1; //系統宏INT_MIN 就是這樣定義出來的 cout << ix - 1 << endl; //0x80000000 + 0xffffffff = 0x7fffffff //C#中能夠這樣用 //int ix = -2147483648; //ok,2147483648被看做uint32 //WriteLine(ix - 1); //0x80000000 + 0xffffffff = 0x7fffffff } #pragma endregion #pragma region 彙編測試 void testASM() { int i = 10; int j = 20; int bp = 0; _asm { mov dword ptr[i], 10h //mov dword ptr[bp], 10h //編譯出錯,變量bp與寄存器同名,被認爲是16位的bp寄存器 mov dword ptr[j], edi lea eax, [ebp] mov DWORD ptr[j], eax } cout << i << "," << j << endl; } #pragma endregion #pragma endregion #pragma endregion void xprintx() { } int _tmain(int argc, _TCHAR* argv[]) { //TestVarTemplatex(); TestTehuaTemp2(); //testInitorders(); //testASM(); //testIntLimit(); //testvector(); //testForceCasttype(); //testEndian(); //testpurevirtualfunc(); //testVirtualMemLeak(); //testSingleton(); //TestModifyCOnstref(); //testInitorders(); //testCallPrivateFuncOutside(); //Teststaticclass(); //TestTehuaTemp2(); //testDeleteStack(); //TestMySmartPtr(); //TestShareptrUse(); //TestThreadSync(); //TestThread(); //TestConstOverload(); //testcasttype(); //TestArrayAndPtr(); //TestSignedUnsignedAssign(); //TestPointerType(); //TestMemLayout(); //Test4Typecast(); //TestOddConstructor(); //TestXiebian(); //a = b; //EffectiveCpp::Test02(); //TestStlSortFunc(); //Dijkastra(); //TestPointerType(); //TestSameTypeAssign(); //TestRef(); //TestTehuaTemp(); //TestCComplexOper(); //TestTypecastOper(); //TestTypecastContructor(); //TestNestingFuncPtrs(); //TestArrayAndPointer(); ///TestRealloc(); //TestComputeDataStorage(); //TestVirtualFunctionTable(); //TestAdd2Num(); //TestAstrPstr(); //TestCWithStatic(); //TestThread(); //TestVarTemplate(); return 0; }