2018.8.14-C++复习笔记总
// 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; }