zoukankan      html  css  js  c++  java
  • 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++中字符串有常量和变量之分,字符串遇到则结束
    ////C#中只有常量字符串,字符串遇到不结束,视其为正常字符
    //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() != '
    '); //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
    ", 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
    ", (float)(*p)); //0.000000
                                     //printf("%f
    ", (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] = &ac; //指向具有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;
    }
  • 相关阅读:
    npm install node-echarts npm ERR! code ELIFECYCLE
    MySql-Proxy之多路结果集归并
    Error: Cannot find module 'is-accessor-descriptor'
    如何在Node.js实现兼容ES6
    perl 自动识别编码,转换编码
    Mixin result declared without body
    Python爬虫入门教程 48-100 使用mitmdump抓取手机惠农APP-手机APP爬虫部分
    unexpected token "indent"
    Radware:上周五美国大规模DDoS攻击是如何发生的
    Radware:上周五美国大规模DDoS攻击是如何发生的
  • 原文地址:https://www.cnblogs.com/timeObjserver/p/9473509.html
Copyright © 2011-2022 走看看