• C++思考和一些代码规范(09/28)


    一、C++的一些思考记录

    1)C++函数进化(函数->函数指针->函数模板->仿函数|函数对象->lambda表达式)

    ①简单的比较函数

    #include 
    
    int countMatchElements(int* beg,int* end)
    {
    	int result = 0;
    	for(;beg!=end;++beg)
    	{
    		if(*beg > 20) 
    			++result;
    	}
    	return result;
    }
    
    int main()
    {
    	int intArray[]= { 11,16,21,19,17,30  };
    	//统计大于20的元素数量
    	std::cout<< countMatchElements(intArray,intArray+6) <<std::endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    ②加入函数指针的优化(改变:传入不同的函数)

    #include 
    
    int countMatchElements(int* beg,int* end,bool(*pred)(const int&))
    {
    	int result = 0;
    	for(;beg!=end;++beg)
    	{
    		if(pred(*beg)) 
    			++result;
    	}
    	return result;
    }
    
    //实际比较函数
    bool isGreater20(const int &val) {return val > 20};
    bool isGreater25(const int &val) {return val > 25};
    bool isLess10(const int &val) {return val < 10};
    
    int main()
    {
    	int intArray[]= { 11,16,21,19,17,30  };
    
    	//优化后的函数(用了函数指针)
    	std::cout<< countMatchElements(intArray,intArray+6,isGreater20) <<std::endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    ③函数模板进一步优化(改变:传入的函数参数为不同类型)

    #include 
    template<typename T>
    int countMatchElements(T* beg,T* end,bool(*pred)(const T&))
    {
    	int result = 0;
    	for(;beg!=end;++beg)
    	{
    		if(pred(*beg)) 
    			++result;
    	}
    	return result;
    }
    
    //实际比较函数
    bool isGreater20(const int &val) {return val > 20;}
    bool isGreater25(const int &val) {return val > 25;}
    bool isLess10(const int &val) {return val < 10;}
    
    bool isTinyStr(const stdLLstring &val) {return val.size() <=3;}
    
    int main()
    {
    	int intArray[]= { 11,16,21,19,17,30  };
    
    	//优化后的函数(用了函数指针)
    	std::cout<< countMatchElements(intArray,intArray+6,isGreater20) <<std::endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    ④用仿函数进一步优化(改变:传入的函数参数为不同值,之前无论是模板还是函数指针都是写死的)

    #include 
    template<typename T,typename Pred>
    int countMatchElements(T* beg,T* end,Pred  pred)
    {
    	int result = 0;
    	for(;beg!=end;++beg)
    	{
    		if(pred(*beg)) 
    			++result;
    	}
    	return result;
    }
    
    //仿函数
    template<typename T>
    struct Greater{
    	T mVal;//
    	//explicit只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示
    	explicit Greater(T value):mVal(value) {} //构造函数初始化mVal
    	bool operator() (const T& val) const { return val > mVal;}//重载函数调用运算符
    };
    
    
    int main()
    {
    	int intArray[]= { 11,16,21,19,17,30  };
    	//阈值设置为20
    	Greater<int> greater20{20};//不过仿函数无法给函数指针赋值
    	//优化后的函数(用了函数指针)
    	std::cout<< countMatchElements(intArray,intArray+6,greater20) <<std::endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    ⑤仿函数代码难以阅读,可以用lambda表达式改造下

    #include 
    template<typename T,typename Pred>
    int countMatchElements(T* beg,T* end,Pred  pred)
    {
    	int result = 0;
    	for(;beg!=end;++beg)
    	{
    		if(pred(*beg)) 
    			++result;
    	}
    	return result;
    }
    
    //仿函数
    template<typename T>
    struct Greater{
    	T mVal;//
    	//explicit只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示
    	explicit Greater(T value):mVal(value) {} //构造函数初始化mVal
    	bool operator() (const T& val) const { return val > mVal;}//重载函数调用运算符
    };
    
    
    int main()
    {
    	int intArray[]= { 11,16,21,19,17,30  };
    	//阈值设置为20
    	//Greater greater20{20};//不过仿函数无法给函数指针赋值
    	auto greater20 = [](auto &val)->bool {return val>20;};
    	//优化后的函数(用了函数指针)
    	std::cout<< countMatchElements(intArray,intArray+6,greater20) <<std::endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    二、代码变量命名

    匈牙利命名方法

    DWORD  dwExStyle,   //dw(DWORD, unsigned long ) 无符号整型
    LPCWSTR lpClassName, // lp(Long  Pointer)长指针
    LPCWSTR lpWindowName, // lp(Long  Pointer)长指针
    DWORD  dwStyle,      //  dw(DWORD, unsigned long ) 无符号整型
    int X,
    int Y,              //  n(Short int )短整型
    int nWidth,         //  n(Short int )短整型
    int nHeight,
    HWND hWndParent,    //  h(handle),Wnd(Window) 窗口句柄
    HMENU hMenu,        //  h(handle) 句柄
    HINSTANCE hInstance, //  h(handle) 句柄
    LPVOID lpParam,     // lp(Long  Pointer)长指针
    
    a      数组(Array)
    b      布尔值(Boolean)
    by     字节(Byte)
    c      有符号字符(Char)
    cb     无符号字符(Char Byte,并没有神马人用的)
    cr     颜色参考值(Color Ref)
    cx,cy  坐标差(长度 Short Int)
    dw     双字(Double Word)
    fn     函数(Function)
    h      Handle(句柄)
    i      整形(Int)
    l      长整型(Long Int)
    lp     长指针(Long Pointer)
    m_     类成员(Class Member)
    n      短整型(Short Int)
    np     近程指针(Near Pointer)
    p      指针(Pointer)
    s      字符串(String)
    sz     以 Null 做结尾的字符串型(String with Zero End)
    w      字(Word)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    三、手写智能指针

    #include 
    using namespace std;
    class CCount
    {
    public:
    	CCount()
    	{
    		m_count = 1;//这里不能省略,不能直接在成员声明的时候赋值为1,如果赋值为1,档SmartPtr sp时,引用技术会错误为1;
    	}
    	void Increase()
    	{
    		m_count++;
    	}
    	void Decrease()
    	{
    		m_count;
    	}
    	int GetCount()
    	{
    		return m_count;
    	}
    private:
    	int m_count = 0;
     };
    
    
    template<typename T>
    class CSmartPtr
    {
    public:
    	CSmartPtr():  
    	p_ptr(nullptr),p_count(nullptr){}  //为了应付SmartPtr sp的情况
    
    	explicit CSmartPtr(T* ptr):
    		p_ptr(ptr),p_count(new CCount()){}
    
    	~CSmartPtr()
    	{
    		if (p_count)
    		{
    			p_count->Decrease();
    			if (0 == p_count->GetCount() )
    			{
    				delete p_ptr;
    				delete p_count;
    				p_ptr = nullptr;
    				p_count = nullptr;
    			}
    		}
    	}
    
    	//拷贝构造函数
    	CSmartPtr(const CSmartPtr<T>& other)
    	{
    		p_ptr = other.p_ptr;
    		p_count = other.p_count;
    		p_count->Increase();
    	}
    
    	//拷贝赋值运算函数
    	CSmartPtr<T>& operator=(const CSmartPtr<T>& other)
    	{
    		if (p_ptr == other.p_ptr)
    		{
    			return *this;
    		}
    
    		p_ptr = other.p_ptr;
    		p_count = other.p_count;
    		p_count->Increase();
    		return *this;
    	}
    
    	T* getPtr()
    	{
    		return p_ptr;
    	}
    
    	T* operator->()
    	{
    		return p_ptr;
    	}
    
    	T& operator*()  //* 返回的是解引用的内容
    	{
    		return *p_ptr;
    	}
    
    	int GetCount()
    	{
    		if (!p_count) return 0;
    		return p_count->GetCount();
    	}
    
    private:
    	T* p_ptr;
    	CCount* p_count;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    四、工作中的注册和回调函数

    1)简单函数注册

    按照容器的顺序触发回调函数

    #include 
    #include 
    #include 
    using namespace std;
    
    typedef std::function<void()> CallbackFunc;
    std::vector<CallbackFunc> g_Callbacks;
    
    
    void RegisterCallback(CallbackFunc callback)
    {
    	g_Callbacks.push_back(callback);
    }
    
    void InvokeCallbacks()
    {
    	for (auto& callback : g_Callbacks)
    	{
    		callback();
    	}
    }
    
    /
    void TestFunc1()
    {
    	std::cout<<"TestFunc1 called!" << std::endl;
    }
    
    void TestFunc2()
    {
    	std::cout << "TestFunc2 called!" << std::endl;
    }
    #endif // !_FIGHTER
    
    
    int main()
    {
    	RegisterCallback(TestFunc1);
    	RegisterCallback(TestFunc2);
    	InvokeCallbacks();
    	return 0;
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    2)类注册

    
    #include 
    #include 
    #include 
    
    using namespace std;
    
    typedef void (*CALLBACKFUN)(std::string std, int data);
    //等同于
    //std::function CALLBACKFUN;
    
    class CBase
    {
    private:
    	std:: string  str_;
    	int data_;
    	static CALLBACKFUN pFunc;
    public:
    	CBase() :str_(""), data_(0) {};
    	void register_callback(CALLBACKFUN fun, std::string str, int data);
    	void excute_callback();
    
    
    };
    
    CALLBACKFUN CBase::pFunc = nullptr;
    
    void CBase::register_callback(CALLBACKFUN fun, 
    			std::string str, int data)
    {
    	pFunc = fun;
    	str_  = str;
    	data_ = data;
    }
    
    void CBase::excute_callback()
    {
    	CBase::pFunc(str_, data_);
    }
    
    /*
    		上层回调注册
    */
    void func_1(std::string str, int data)
    {
    	std::cout << "this is func_1 callback function" << str << "..." << data << std::endl;
    }
    
    void func_2(std::string str, int data)
    {
    	std::cout << "this is func_2 callback function" << str << "..." << data << std::endl;
    
    }
    
    
    int main()
    {
    	CBase c_base;
    	c_base.register_callback(func_1, "lyy", 6);
    	c_base.excute_callback();
    	c_base.register_callback(func_2, "klx", 8);
    	c_base.excute_callback();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    3)类模板注册

    ①CFunc.h

    #ifndef __FIGHTER__
    #define __FIGHTER__
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    
    template<typename T>
    class CRegister
    {
    public:
    	void RegisterFunc(T* obj)
    	{
    		m_RegisterVec.push_back(obj);
    	}
    	void Execute()
    	{
    		for (auto it = m_RegisterVec.begin(); it != m_RegisterVec.end(); ++it)
    		{
    			(*it)->DoOperation();
    		}
    	}
    private:
    	std::vector<T*> m_RegisterVec;
    };
    
    
    class CDoFunc
    {
    public:
    	virtual void DoOperation() = 0;
    };
    
    class CFunc1 :public CDoFunc
    {
    public:
    	void DoOperation()
    	{
    		std::cout << "do test1" << std::endl;
    	}
    };
    
    class CFunc2 :public CDoFunc
    {
    public:
    	void DoOperation()
    	{
    		std::cout << "do test2" << std::endl;
    	}
    };
    #endif // !_FIGHTER
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    ②main函数

    #include 
    #include "Fighter.h"
    
    int main()
    {
    	CRegister<CDoFunc> mRegister;
    	CFunc1* test_1 = new CFunc1();
    	CFunc2* test_2  = new CFunc2();
    
    	mRegister.RegisterFunc(test_1);
    	mRegister.RegisterFunc(test_2);
    
    	mRegister.Execute();
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4)容器注册(不用写一堆类,只需要改写触发函数就行)

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    typedef void (*CALLBACKFUN)(int data);
    //等同于
    //std::function CALLBACKFUN;
    
    class CRegisterMgr
    {
    private:
    	std::map<int, CALLBACKFUN> mRegisterMap;
    public:
    	void register_callback(CALLBACKFUN fun,  int data);
    	void excute_callback(int excuteNum);
    
    
    };
    
    
    
    void CRegisterMgr::register_callback(CALLBACKFUN fun, int data)
    {
    	auto it = mRegisterMap.find(data);
    	mRegisterMap[data] = fun;
    }
    
    void CRegisterMgr::excute_callback(int excuteNum)
    {
    	auto it = mRegisterMap.find(excuteNum);
    	if (it == mRegisterMap.end())
    		return;
    	(it->second)(excuteNum);
    }
    
    /*
    		上层回调注册
    */
    void func_1( int data)
    {
    	std::cout << "this is func_1 callback function" <<  "..." << data << std::endl;
    }
    
    void func_2( int data)
    {
    	std::cout << "this is func_2 callback function" << "..." << data << std::endl;
    
    }
    
    
    int main()
    {
    	CRegisterMgr c_base;
    	c_base.register_callback(func_1,  6);
    	c_base.excute_callback(6);
    	c_base.register_callback(func_2,  8);
    	c_base.excute_callback(8);
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    五、如何定义一个只能在堆上(栈上)生成对象的类

    1)只能在堆上

    做法:析构函数设置为私有
    原因:C++是静态绑定语言,编译器管理栈上对象的生命周期,在为类分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建函数

    2)只能在栈上

    方法:将new和delete重载为私有
    原因:在堆上生成对象,使用new关键词操作,其过程分为两阶段。第一阶段:用new在堆上寻找可用内存,分配给对象;第二阶段:调用析构函数生成对象。new为私有,第一阶段就无法完成

    六、指针常量和常量指针

    1)指针常量
    int *const p1
    p2 = &a; (X)
    *p2 = a; ()
    2)常量指针
    int const* p2
    p2 = &a; ()
    *p2 = a; (X)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    七、内存泄漏的几种方式

    1、堆内存泄露
    2、系统资源泄露(bitmap、handle、socket等)
    3、没有将基类的析构函数定义为虚函数
    4、在释放对象数组时没有使用delete[]而是使用了delete
    5、缺少拷贝构造函数

    Count C1(C2)
    
    • 1

    八、new/delete与malloc/free的区别

    ①new和delete是操作符,可以被重载;malloc和free是C语言的库函数,不能被重载
    ②malloc需要提供分配内存的大小
    ③malloc返回的是void*指针;而new直接就返回了对应类型的指针
    ④new和delete分别调用构造和析构函数,而malloc和free只申请和释放内存。
    备注delete和free只是告诉操作系统,这一块内存被释放了,可以用作其他用途。

    九、运行时多态

    在这里插入图片描述

    10、哈希冲突的解决方法

    1)散列法
    2)再散列法
    3)线性列表法

    11、extern C的作用

    extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。

    12、C++线程池代码

    1)线程函数使用指南

    ①要用std::ref传递引用类型变量
    在这里插入图片描述
    ②记录用类的函数作为线程执行函数(奇怪的用法所以记录下)
    在这里插入图片描述
    ③用友元函数访问类的私有函数作为线程执行函数
    在这里插入图片描述

    2)互斥量解决多线程数据共享问题

    ①lock_guard使用讲解
    在这里插入图片描述
    代码举例:
    在这里插入图片描述
    ②std::unique_lock使用解释
    在这里插入图片描述
    延迟阻塞使用案例:
    在这里插入图片描述
    延迟2秒,拿不到锁就不进入循环:
    在这里插入图片描述
    ③unique_lock的构造函数分类讲解
    在这里插入图片描述

    3)call_once及使用场景

    代码:
    在这里插入图片描述

    在这里插入图片描述

    4)condition_variable及其使用场景

    在这里插入图片描述

    #include 
    #include 
    #include 
    #include 
    
    //#include "Fighter.h"
    using namespace std;
    #include 
    #include 
    #include 
    #include 
    
    
    std::mutex g_mutex;
    std::condition_variable g_cv;
    std::queue<int> g_que;
    void producer()
    {
    	for (int i = 0;i < 10;++i)
    	{
    		std::unique_lock<std::mutex> lock(g_mutex);
    		g_que.push(i);
    		g_cv.notify_one();
    		std::cout << "queue push value:" << i << std::endl;
    	}
    }
    
    
    void consumer()
    {
    	while (1)
    	{
    		std::unique_lock<std::mutex> lock(g_mutex);
    		bool bEmpty = g_que.empty();
    		g_cv.wait(lock, []() {return !g_que.empty(); });
    		int value = g_que.front();
    		g_que.pop();
    		std::cout << " pop value:" << value << std::endl;
    
    	}
    }
    
    int main()
    {	
    	std::thread t1(producer);
    	std::thread t2(consumer);
    	t1.join();
    	t2.join();
    
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    5)C++11线程池实例代码

  • 相关阅读:
    Hash Table Mock
    iOS开发Swift-12-列表UI,TableViewController,动态响应Button勾选-待办事项App(1)
    基于Servlet WebSocket MySQL实现的网络在线考试系统
    用于静电放电敏感器件的包装的性能和要求分类-2
    scp(安全拷贝)和rsync(增量复制)
    2022华为中央媒体技术院AI算法工程师FindStar一面+二面+主管面
    基于移动GIS的环保生态管理系统
    【JavaEE网络】TCP套接字编程详解:从概念到实现
    MobPush开发过程常见问题
    【PAT(甲级)】1072 Gas Station
  • 原文地址:https://blog.csdn.net/weixin_43679037/article/details/133320276