C++高级课程笔记

C++高级课程

01 对象被优化以后才是高效的C++编程

1. 对象使用过程中背后调用了哪些方法

示例一:

#include <iostream>
using namespace std;

class Test
{
public:
    Test(int a = 10) :ma(a) 
    {cout << "Test()" << endl;}
    ~Test() 
    {cout << "~Test()" << endl;}
    Test(const Test &t) :ma(t.ma) 
    {cout << "Test(const Test&)" << endl;}
    Test& operator=(const Test &t)
    {
        cout << "operator=" << endl;
        ma = t.ma;
        return *this;
    }
private:
    int ma;
};

int main()
{
    Test t1; // 调用 Test()
    Test t2(t1); // 调用 Test(const Test&)
    Test t3 = t1; // 调用 Test(const Test&)
    //  Test(20) 显式生成临时对象,临时对象的生存周期为其所在的语句
    /*
      C++编译器对于对象构造的优化:
      用临时对象拷贝构造生成新对象的时候,临时对象就不产生了,直接构造新对象就可以了。
    */
    Test t4 = Test(20); // 等同于 Test t4(20); 调用 Test()

    cout << "------------------" << endl;
  
		// t4.operator=(t2);
    t4 = t2;
  	/*
      先调用构造函数Test()显式生成临时对象,然后将临时对象赋值给t4,
      最后通过析构函数~Test()释放临时对象。
  	*/
    t4 = Test(30); 
  	/*
      将 int 强制转换为 Test(int), 先调用构造函数Test()生成临时对象,
      然后将临时对象赋值给t4,最后通过析构函数~Test()释放临时对象。
  	*/
    t4 = (Test)30; 
  	/*
      将 int 强制转换为 Test(int), 先调用构造函数Test()隐式生成临时对象,
      然后将临时对象赋值给t4,最后通过析构函数~Test()释放临时对象。 
  	*/
    t4 = 30; 

    cout << "------------------" << endl; 
  	/*
      先调用构造函数Test()显式生成临时对象,该语句执行完成以后,
      通过析构函数~Test()释放临时对象,p指向的是一个已经析构的临时对象。
  	*/
    Test *p = &Test(40);
		/*
       先调用构造函数Test()显式生成临时对象,然后引用临时对象,
       临时对象的生命周期就变为引用变量的生命周期。 
       用指针指向临时对象是不安全的,但是可以使用引用变量引用临时对象。
  	*/
    const Test &ref = Test(50);
    cout << "------------------" << endl; 
    return 0;
}

注意⚠️:

C++编译器对于对象构造的优化:用临时对象拷贝构造生成新对象的时候,临时对象就不产生了,直接构造新对象就可以了

运行结果为:

Test()
Test(const Test&)
Test(const Test&)
Test()
------------------
operator=
Test()
operator=
~Test()
Test()
operator=
~Test()
Test()
operator=
~Test()
------------------
Test()
~Test()
Test()
------------------
~Test()
~Test()
~Test()
~Test()
~Test()

示例二:

#include <iostream>
using namespace std;

class Test
{
public:
    Test(int a = 5, int b = 5) :ma(a), mb(b) 
    {
        cout << "Test(int, int)" << endl;
    }
    ~Test() 
    {
        cout << "~Test()" << endl;
    }
    Test(const Test &src) :ma(src.ma), mb(src.mb)
    {
        cout << "Test(const Test&)" << endl;
    }
    void operator=(const Test &src)
    {
        cout << "operator=" << endl;
        ma = src.ma;
        mb = src.mb;
    }
private:
    int ma;
    int mb;
};

Test t1(10, 10); // 1.Test(int, int)              20. ~Test()
int main()
{
    Test t2(20, 20); // 3.Test(int, int)              17. ~Test()
    Test t3 = t2; // 4.Test(const Test&)              16. ~Test()
    // 等同于static Test t4(30, 30);
    static Test t4 = Test(30, 30); // 5.Test(int, int)        18. ~Test()
    t2 = Test(40, 40); // 6.Test(int, int)、operator=、~Test()
    // (Test)(50, 50) = (Test)50; Test(int) 
    t2 = (Test)(50, 50); // 7.Test(int, int)、operator=、~Test()
    t2 = 60; // Test(int) 8.Test(int, int)、operator=、~Test()
    Test *p1 = new Test(70, 70); // 9. Test(int, int)
    Test *p2 = new Test[2]; // 10. Test(int, int)、Test(int, int)
    Test *p3 = &Test(80, 80); // 11. Test(int, int)、~Test()
    const Test &p4 = Test(90, 90); // 12. Test(int, int)      15. ~Test()
    delete p1; // 13. ~Test()
    delete []p2; // 14. ~Test() ~Test()
}
Test t5(100, 100); // 2.Test(int, int)          19. ~Test()

运行结果为:

Test(int, int)
Test(int, int)
Test(int, int)
Test(const Test&)
Test(int, int)
Test(int, int)
operator=
~Test()
Test(int, int)
operator=
~Test()
Test(int, int)
operator=
~Test()
Test(int, int)
Test(int, int)
Test(int, int)
Test(int, int)
~Test()
Test(int, int)
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()

2. 函数调用过程中对象背后调用的方法太多

注意⚠️:函数调用,实参传递给形参的过程,实际上是初始化形参的过程。

#include <iostream>
using namespace std;

class Test
{
public:
    // Test() Test(20)
    Test(int data = 10) :ma(data)
    {
        cout << "Test(int)" << endl;
    }
    ~Test() 
    {
        cout << "~Test()" << endl;
    }
    Test(const Test &src) :ma(src.ma)
    {
        cout << "Test(const Test&)" << endl;
    }
    void operator=(const Test &src)
    {
        cout << "operator=" << endl;
        ma = src.ma;
    }
    int getData()const {return ma;}
private:
    int ma;
};

// 不能返回局部的或者临时对象的指针或引用
Test GetObject(Test t) // 3. 调用 Test(const Test&),由t1拷贝构造t   7. ~Test()
{
    int val = t.getData();
    Test tmp(val); // 4. Test()  6. ~Test()
    return tmp; // 5. 调用 Test(const Test&),由tmp在main()函数上,拷贝构造一个临时对象
}

int main()
{
    Test t1; // 1. Test()     11. ~Test()
    Test t2; // 2. Test()     10. ~Test()
    t2 = GetObject(t1); // 8. operator=,把临时对象赋值给t2
												// 9. 调用~Test(),析构临时对象
    return 0;
}

运行结果为:

Test(int)
Test(int)
Test(const Test&)
Test(int)
Test(const Test&)
~Test()
~Test()
operator=
~Test()
~Test()
~Test()

3. 总结三条对象优化的原则
  1. 函数参数传递过程中,对象优先按引用传递,不要按值传递。
  2. 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象。
  3. 当调用函数的返回值是对象的时候,优先按初始化的方式接收返回值,不要按赋值的方式接收。

4. CMyString的代码问题/添加带右值引用参数的拷贝构造和赋值函数
#include <iostream>
#include <cstring>
using namespace std;

class CMyString
{
public:
    CMyString(const char *str = nullptr)
    {
        cout << "CMyString(const char*)" << endl;
        if (str != nullptr)
        {
            mptr = new char[strlen(str) + 1];
            strcpy(mptr, str);
        }
        else 
        {
            mptr = new char[1];
            *mptr = '\0';
        }
    }

    ~CMyString()
    {
        cout << "~CMyString" << endl;
        delete []mptr;
        mptr = nullptr;
    }

    // 带左值引用参数的拷贝构造函数 
    CMyString(const CMyString& str)
    {
        cout << "CMyString(const CMyString&)" << endl;
        mptr = new char[strlen(str.mptr) + 1];
        strcpy(mptr, str.mptr);
    }
    // 带右值引用参数的拷贝构造函数 
    CMyString(CMyString&& str)
    {
        cout << "CMyString(CMyString&&)" << endl;
        mptr = str.mptr;
        str.mptr = nullptr;
    }

    // 带左值引用参数的赋值重载函数
    CMyString& operator=(const CMyString &str)
    {
        cout << "operator=(const CMyString&)" << endl;
        if (this == &str)
            return *this;
        delete[] mptr;

        mptr = new char[strlen(str.mptr) + 1];
        strcpy(mptr, str.mptr);
        return *this;
    }
    // 带右值引用参数的赋值重载函数
    CMyString& operator=(CMyString &&str)
    {
        cout << "operator=(CMyString&&)" << endl;
        if (this == &str)
            return *this;
        delete[] mptr;

        mptr = str.mptr;
        str.mptr = nullptr;
        return *this;
    }

    const char* c_str()const {return mptr;}
private:
    char* mptr;
};

CMyString GetString(CMyString &str)
{
    const char* pstr = str.c_str();
    CMyString tmpStr(pstr);
    return tmpStr;
}

int main()
{
    CMyString str1("aaaaaaaaaaaaaa");
    CMyString str2;
    str2 = GetString(str1);
    cout << str2.c_str() << endl;

    return 0;
}

/*
		不引入带右值引用参数的拷贝构造和赋值重载函数的运行结果为:
		
    CMyString(const char*)
    CMyString(const char*)
    CMyString(const char*)
    CMyString(const CMyString&) => tmpStr拷贝构造main函数栈帧上的临时对象
    ~CMyString
    operator=(const CMyString&) => main函数栈帧上的临时对象给t2赋值
    ~CMyString
    aaaaaaaaaaaaaa
    ~CMyString
    ~CMyString
*/

在代码中引入了带右值引用参数的拷贝构造函数和赋值重载函数,减少了内存开辟、内存释放和数据拷贝的开销。

运行结果为:

CMyString(const char*)
CMyString(const char*)
CMyString(const char*)
CMyString(const CMyString&&)
~CMyString
operator=(CMyString&&)
~CMyString
aaaaaaaaaaaaaa
~CMyString
~CMyString

5. move移动语义和forward类型完美转发
  • move:移动语义,得到右值类型。std::move(val),将val的类型强转为右值类型。
  • forward:类型完美转发,能够识别左值和右值类型。std::forward<T>(val),能够识别val的类型T是左值还是右值。

02 体验一下智能指针的强大

1. 基础知识
  • 智能指针:保证能做到资源的自动释放

  • 利用栈上的对象出作用域自动析构的特征,来做到资源的自动释放的。

  • 不能把智能指针定义在堆上,因为把智能指针定义在堆上,就需要手动对智能指针指向的资源进行释放。

 #include <iostream>
  using namespace std;
  
  template<typename T>
  class CSmartPtr
  {
  public:
      CSmartPtr(T *ptr = nullptr):mptr(ptr) {}
      ~CSmartPtr() { delete mptr; }
  
      T& operator* () { return *mptr; }
      T* operator->() { return mptr; }
  private:
      T *mptr;
  };
  
  int main()
  {
      CSmartPtr<int> ptr1(new int);
      *ptr1 = 20;
  
      class Test
      {
      public:
          void test() { cout << "call Test::test" << endl;}
      };
      CSmartPtr<Test> ptr2(new Test());
      ptr2->test();
  
      return 0;
  }

2. 不带引用计数的智能指针(推荐使用unique_ptr)
  • C++库里面:auto_ptr,具有unique_ptr的部分特性,我们不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr,因此不推荐使用auto_ptr。
  #include <iostream>
  #include <memory>
  using namespace std;
  
  int main()
  {
      /*
          不带引用计数的智能指针(推荐使用unique_ptr)
          C++库里面:auto_ptr具有unique_ptr的部分特性,我们不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr。不推荐使用auto_ptr。
      */
      auto_ptr<int> ptr1(new int);
      auto_ptr<int> ptr2(ptr1); // 相当于ptr2(ptr1.release()),将ptr1赋值给ptr2,再将ptr1置为nullptr。
    	
    	*ptr2 = 20;
      // cout << *ptr1 << endl;    ptr1为nullptr,不能进行解引用。
  
      return 0;
  }
  • C++11新标准:scoped_ptrunique_ptr

    • scoped_ptr类中直接删除了拷贝构造函数和赋值重载函数

      scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;
      scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;
      
    • unique_ptr类中也删除了拷贝构造函数和赋值重载函数

      unique_ptr(const unique_ptr<T>&) = delete;
      unique_ptr<T>& operator=(const unique_ptr<T>&) = delete; 
      
    • unique_ptr类中提供了带右值的拷贝构造函数和赋值重载函数

      unique_ptr(unique_ptr<T> &&src)
      unique_ptr<T>& operator=(unique_ptr<T> &&src) 
      
    • 因此,可以如下定义unique_ptr

      unique_ptr<int> p1(new int);
      unique_ptr<int> p2(std::move(p1)); // std::move得到当前变量的右值类型
      

3. 实现带引用计数的智能指针
  • 带引用计数的智能指针 shared_ptrweak_ptr
  • 带引用计数:多个智能指针可以管理同一个资源,给每一个对象资源匹配一个引用计数。
  • 当我们拷贝一个shared_ptr,计数器递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

4. shared_ptr的交叉引用问题
  • C++11新标准:shared_ptrweak_ptr

  • shared_ptr强智能指针,可以改变资源的引用计数。

  • weak_ptr弱智能指针,不会改变资源的引用计数。

  • weak_ptr ==》 shared_ptr ==》 资源(内存)

强智能指针循环引用(交叉引用)是什么?会造成什么结果?怎么解决?
#include <iostream>
#include <memory>
using namespace std;

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	shared_ptr<B> _ptrb; // 指向B对象的智能指针
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	shared_ptr<A> _ptra; // 指向A对象的智能指针
};
int main()
{
	shared_ptr<A> ptra(new A());// ptra指向A对象,A的引用计数为1
	shared_ptr<B> ptrb(new B());// ptrb指向B对象,B的引用计数为1
	ptra->_ptrb = ptrb;// A对象的成员变量_ptrb也指向B对象,B的引用计数为2
	ptrb->_ptra = ptra;// B对象的成员变量_ptra也指向A对象,A的引用计数为2

	cout << ptra.use_count() << endl; // 打印A的引用计数结果:2
	cout << ptrb.use_count() << endl; // 打印B的引用计数结果:2

	/*
	出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和
	B对象的引用计数从2减到1,达不到释放A和B的条件(释放的条件是
	A和B的引用计数为0),因此造成两个new出来的A和B对象无法释放,
	导致内存泄露,这个问题就是“强智能指针的交叉引用(循环引用)问题”
	*/
	return 0;
}

运行结果:

A()
B()
2
2

出现的问题:A和B对象并没有进行析构,通过上面的代码示例,能够看出来“交叉引用”的问题所在,就是对象无法析构,强智能指针循环引用导致new出来的资源无法释放,造成资源泄露的问题。

解决办法:定义对象的地方,使用强智能指针;引用对象的地方,使用弱智能指针。

弱智能指针weak_ptr区别于shared_ptr之处在于:

  1. weak_ptr不会改变资源的引用计数,只是一个观察者的角色,通过观察shared_ptr来判定资源是否存在
  2. weak_ptr持有的引用计数,不是资源的引用计数,而是同一个资源的观察者的计数
  3. weak_ptr没有提供常用的指针操作,无法直接访问资源,需要先通过lock()方法提升为shared_ptr强智能指针,才能访问资源

请添加图片描述
将引用对象的地方,修改为弱指针weak_ptr后的运行结果:

A()
B()
1
1
~B()
~A()

请添加图片描述

注意⚠️:由于对象可能不存在,我们不能使用 weak_ptr 直接访问对象,而必须调用 lock 。该函数检查 weak_ptr 指向的对象是否仍存在。如果存在,lock 返回一个指向共享对象的 shared_ptr 。与任何其他 shared_ptr 类似,只要此 shared_ptr 存在,它所指向的底层对象也就会一直存在。例如:

auto p = make_shared<int> (42);
weak_ptr<int> wp(p); // wp弱共享p,p的引用计数未改变
if (shared_ptr<int> np = wp.lock()) // 如果np不为空,则条件成立
{
  // 在if中,np与p共享对象
}

5. 多线程访问共享对象的线程安全问题

有一个用C++写的开源网络库,muduo库,作者陈硕,大家可以在网上下载到muduo的源代码,该源码中对于智能指针的应用非常优秀,其中借助shared_ptr和weak_ptr解决了这样一个问题,多线程访问共享对象的线程安全问题,解释如下:线程A和线程B访问一个共享的对象,如果线程A正在析构这个对象的时候,线程B又要调用该共享对象的成员方法,此时可能线程A已经把对象析构完了,线程B再去访问该对象,就会发生不可预期的错误。

先看如下代码:

#include <iostream>
#include <thread>
using namespace std;

class Test
{
public:
	// 构造Test对象,_ptr指向一块int堆内存,初始值是20
	Test() :_ptr(new int(20)) 
	{
		cout << "Test()" << endl;
	}
	// 析构Test对象,释放_ptr指向的堆内存
	~Test()
	{
		delete _ptr;
		_ptr = nullptr;
		cout << "~Test()" << endl;
	}
	// 该show会在另外一个线程中被执行
	void show()
	{
		cout << *_ptr << endl;
	}
private:
	int *volatile _ptr;
};
void threadProc(Test *p)
{
	// 睡眠两秒,此时main主线程已经把Test对象给delete析构掉了
	std::this_thread::sleep_for(std::chrono::seconds(2));
	/* 
	当前线程访问了main线程已经析构的共享对象,结果未知,隐含bug。
	此时通过p指针想访问Test对象,需要判断Test对象是否存活,如果Test对象
	存活,调用show方法没有问题;如果Test对象已经析构,调用show有问题!
	*/
	p->show();
}
int main()
{
	// 在堆上定义共享对象
	Test *p = new Test();
	// 使用C++11的线程类,开启一个新线程,并传入共享对象的地址p
	std::thread t1(threadProc, p);
	// 在main线程中析构Test共享对象
	delete p;
	// 等待子线程运行结束
	t1.join();
	return 0;
}

运行上面的代码,发现在main主线程已经delete析构Test对象以后,子线程threadProc再去访问Test对象的show方法,无法打印出*_ptr的值20。可以用shared_ptrweak_ptr来解决多线程访问共享对象的线程安全问题,上面代码修改如下:

#include <iostream>
#include <thread>
#include <memory>
using namespace std;

class Test
{
public:
	// 构造Test对象,_ptr指向一块int堆内存,初始值是20
	Test() :_ptr(new int(20)) 
	{
		cout << "Test()" << endl;
	}
	// 析构Test对象,释放_ptr指向的堆内存
	~Test()
	{
		delete _ptr;
		_ptr = nullptr;
		cout << "~Test()" << endl;
	}
	// 该show会在另外一个线程中被执行
	void show()
	{
		cout << *_ptr << endl;
	}
private:
	int *volatile _ptr;
};
void threadProc(weak_ptr<Test> pw) // 通过弱智能指针观察强智能指针
{
	// 睡眠两秒
	std::this_thread::sleep_for(std::chrono::seconds(2));
	/* 
	如果想访问对象的方法,先通过pw的lock方法进行提升操作,把weak_ptr提升
	为shared_ptr强智能指针,提升过程中,是通过检测它所观察的强智能指针保存
	的Test对象的引用计数,来判定Test对象是否存活,ps如果为nullptr,说明Test对象
	已经析构,不能再访问;如果ps!=nullptr,则可以正常访问Test对象的方法。
	*/
	shared_ptr<Test> ps = pw.lock();
	if (ps != nullptr)
	{
		ps->show();
	}
}
int main()
{
	// 在堆上定义共享对象
	shared_ptr<Test> p(new Test);
	// 使用C++11的线程,开启一个新线程,并传入共享对象的弱智能指针
	std::thread t1(threadProc, weak_ptr<Test>(p));
	// 在main线程中析构Test共享对象
	// 等待子线程运行结束
	t1.join();
	return 0;
}

运行上面的代码,show方法可以打印出20,因为main线程调用了t1.join()方法等待子线程结束,此时pw通过lock提升为ps成功,见上面代码示例。

如果设置t1为分离线程,让main主线程结束,p智能指针析构,进而把Test对象析构,此时show方法已经不会被调用,**因为在threadProc方法中,pw提升到ps时,lock方法判定Test对象已经析构,提升失败!**main函数代码可以如下修改测试:

int main()
{
	// 在堆上定义共享对象
	shared_ptr<Test> p(new Test);
	// 使用C++11的线程,开启一个新线程,并传入共享对象的弱智能指针
	std::thread t1(threadProc, weak_ptr<Test>(p));
	// 在main线程中析构Test共享对象
	// 设置子线程分离
	t1.detach();
	return 0;
}

main函数运行后,最终的threadProc中,show方法不会被执行到。以上是在多线程中访问共享对象时,对shared_ptr和weak_ptr的一个典型应用


自定义删除器

我们经常用智能指针管理的资源是堆内存,当智能指针出作用域的时候,在其析构函数中会delete释放堆内存资源,但是除了堆内存资源,智能指针还可以管理其它资源,比如打开的文件,此时对于文件指针的关闭,就不能用delete了,这时我们需要自定义智能指针释放资源的方式,先看看unique_ptr智能指针的析构函数代码,如下:

~unique_ptr() noexcept
{	// destroy the object
if (get() != pointer())
	{
	this->get_deleter()(get()); // 这里获取底层的删除器,进行函数对象的调用
	}
}

unique_ptr的析构函数可以看到,如果要实现一个自定义的删除器,实际上就是定义一个函数对象而已,示例代码如下:

class FileDeleter
{
public:
	// 删除器负责删除资源的函数
	void operator()(FILE *pf)
	{
		fclose(pf);
	}
};
int main()
{
    // 由于用智能指针管理文件资源,因此传入自定义的删除器类型FileDeleter
	unique_ptr<FILE, FileDeleter> filePtr(fopen("data.txt", "w"));
	return 0;
}

当然这种方式需要定义额外的函数对象类型,不推荐,可以用C++11提供的函数对象functionlambda表达式更好的处理自定义删除器,代码如下:

int main()
{
	// 自定义智能指针删除器,关闭文件资源
	unique_ptr<FILE, function<void(FILE*)>> 
		filePtr(fopen("data.txt", "w"), [](FILE *pf)->void{fclose(pf);});

	// 自定义智能指针删除器,释放数组资源
	unique_ptr<int, function<void(int*)>>
		arrayPtr(new int[100], [](int *ptr)->void {delete[]ptr; });

	return 0;
}

03 C++11中引入的bind绑定器和function函数对象

1. bind1st 和 bind2nd 什么时候会用到
  • C++ STL 中的绑定器
    • bind1stoperator() 的第一个形参变量绑定一个确定的值。
    • bind2ndoperator() 的第二个形参变量绑定一个确定的值。
    • 绑定器是函数对象的一个应用,绑定器将二元函数对象转化为一元函数对象。
  • C++ 11 从 Boost 库中引入了 bind 绑定器和 function 函数对象机制。
  • lambda 表达式:底层依赖函数对象的机制实现的。

2. bind1st 和 bind2nd 的底层实现原理
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <ctime>

using namespace std;
using namespace std::placeholders;

template<typename Container>
void showContainer(Container &con)
{
    typename Container::iterator it = con.begin();
    for (; it != con.end(); ++it)
    {
        cout << *it << " ";
    }
    cout << endl;
}

template<typename Iterator, typename Compare>
Iterator my_find_if(Iterator first, Iterator last, Compare comp)
{
    for (; first != last; ++first)
    {
        if (comp(*first))
        {
            return first;
        }
    }
    return last;
}


template<typename Compare, typename T>
class _mybind1st // 绑定器是函数对象的一个应用
{
public:
    _mybind1st(Compare comp, T val)
        :_comp(comp), _val(val)
    {}
    bool operator()(const T &second)
    {
        return _comp(_val, second);
    }
private:
    Compare _comp;
    T _val;
};

template<typename Compare, typename T>
_mybind1st<Compare, T> mybind1st(Compare comp, const T &val)
{
    // 直接使用函数模版,好处是可以进行类型的推演
    return _mybind1st<Compare, T>(comp, val);
}

int main()
{
    vector<int> vec;
    srand(time(nullptr));
    for (int i = 0; i < 20; ++i)
    {
        vec.push_back(rand() % 100 + 1);
    }

    showContainer(vec);
    sort(vec.begin(), vec.end()); // 默认从小到大排序
    showContainer(vec);

    // greater 二元函数对象
    sort(vec.begin(), vec.end(), greater<int>()); // 从大到小排序
    showContainer(vec);

    int b = 70;
    // 把70按顺序插入到vec容器当中
    // auto i = find_if(vec.begin(), vec.end(), [b](const int &a) { return a < b; }); // 找到第一个小于70的数字
    // vec.insert(i, b);

    // auto i = find_if(vec.begin(), vec.end(), bind(greater<int>(), b, _1)); // 绑定器将二元函数对象转化为一元函数对象
    // vec.insert(i, b);

    // auto i = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), b));
    // vec.insert(i, b);

    auto i = my_find_if(vec.begin(), vec.end(), mybind1st(greater<int>(), b));

    if (i != vec.end())
    {
       vec.insert(i, 70); 
    }

    showContainer(vec);

    return 0;
}

3. function 函数对象类型的应用示例

对于标准库 function 类型

  1. 用函数类型实例化 function
  2. 通过 function 调用 operator() 函数的时候,需要根据函数类型传入相应的参数。

对应C++ Primer P511


4. 模版的完全特例化和部分特例化
#include <iostream>
#include <cstring>
using namespace std;

template<typename T>
class Vector
{
public:
    Vector() {cout << "call Vector template init" << endl;}
};

// 下面这个是对char*类型提供的完全特例化版本
template<>
class Vector<char*>
{
public:
    Vector() {cout << "call Vector<char*> init" << endl;}
};

// 下面这个是对指针类型提供的部分特例化版本
template<typename Ty>
class Vector<Ty*>
{
public:
    Vector() {cout << "call Vector<Ty*> init" << endl;}
};

// 针对函数指针(有一个返回值,有两个形参变量)提供的部分特例化
template<typename R, typename A1, typename A2>
class Vector<R(*)(A1, A2)>
{
public:
    Vector() {cout << "call Vector<R(*)(A1, A2)> init" << endl;}
};

// 针对函数类型(有一个返回值,有两个形参变量)提供的部分特例化
template<typename R, typename A1, typename A2>
class Vector<R(A1, A2)>
{
public:
    Vector() {cout << "call Vector<R(A1, A2)> init" << endl;}
};

int sum(int a, int b) {return a + b;}

int main()
{
    Vector<int> vec1;
    Vector<char*> vec2;
    Vector<int*> vec3;
    Vector<int(*)(int, int)> vec4;
    Vector<int(int, int)> vec5;

    // 注意区分一下函数类型和函数指针类型
    typedef int (*PFUNC1)(int, int);
    PFUNC1 pfunc1 = sum;
    cout << pfunc1(10, 20) << endl;

    typedef int PFUNC2(int, int);
    PFUNC2 *pfunc2 = sum;
    cout << (*pfunc2)(10, 20) << endl;


    return 0;
}



#if 0
template<typename T>
bool compare(T a, T b)
{
    cout << "template compare" << endl;
    return a > b;
}

template<>
bool compare<const char*>(const char*a, const char*b)
{
    cout << "compare<const char*>" << endl;
    return strcmp(a, b) > 0;
}

int main()
{
    compare(10, 20);
    compare("aaa", "bbb");
    return 0;
}
#endif

运行结果:

call Vector template init
call Vector<char*> init
call Vector<Ty*> init
call Vector<R(*)(A1, A2)> init
call Vector<R(A1, A2)> init
30
30

5. function 函数对象类型的实现原理
#include <iostream>
#include <string>
#include <functional>

using namespace std;

void hello(string str) {cout << str << endl;}
int sum(int a, int b) {return a + b;}

template<typename Fty>
class myfunction {};

/*
template<typename R, typename A1>
class myfunction<R(A1)>
{
public:
    using PFUNC = R(*)(A1);
    myfunction(PFUNC pfunc):_pfunc(pfunc){}
    R operator()(A1 arg)
    {
        return _pfunc(arg); // hello(arg)
    }

private:
    PFUNC _pfunc;
};

template<typename R, typename A1, typename A2>
class myfunction<R(A1, A2)>
{
public:
    using PFUNC = R(*)(A1, A2);
    myfunction(PFUNC pfunc):_pfunc(pfunc){}
    R operator()(A1 arg1, A2 arg2)
    {
        return _pfunc(arg1, arg2); 
    }

private:
    PFUNC _pfunc;
};
*/

template<typename R, typename... A>
class myfunction<R(A...)>
{
public:
    using PFUNC = R(*)(A...);
    myfunction(PFUNC pfunc):_pfunc(pfunc){}
    R operator()(A... arg)
    {
        return _pfunc(arg...); // hello(arg)
    }

private:
    PFUNC _pfunc;
};

int main()
{  
    myfunction<void(string)> func1(hello);
    func1("hello world!"); // func1.operator()("hello world!")

    myfunction<int(int, int)> func2(sum);
    cout << func2(10, 20) << endl;
    
    return 0;
}

6. bind 和 function 实现线程池

bind函数可以看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。

调用bind的一般形式为:auto newCallable = bind(callable, arg_list);

其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数。这些参数是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的 “位置”。数值n表示生成的可调用对象中参数的位置:_1newCallable的第一个参数,_2为第二个参数,依此类推。

记得重看实现线程池的视频


7. lambda 表达式的实现原理
  • 可调用对象:对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用的。

  • C++语言中有几种可调用的对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。

  • 一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。

  • lambda表达式的形式:[capture list](parameter list)->return type {function body}

  • capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type:返回类型;parameter list:参数列表;function list:函数体。lambda必须使用尾置返回。

  • 我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。

    如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void

  • lambda不能有默认参数,一个lambda调用的实参数目与形参数目相等。

  • 一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。空捕获列表表明lambda不使用它所在函数中的任何局部变量。

  • 捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。

  • 当定义一个lambda时,编译器生成一个与lambda对应的新的未命名的类类型,可以理解为当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象。

    默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。

  • 变量的捕获方式可以是值或引用。

    采用值捕获的前提是变量可以拷贝,被捕获的变量的值在lambda创建时拷贝。

    采用引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。

  • 隐式捕获:可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。

    在捕获列表中写一个&=&表示编译器采用捕获引用方式,=表示采用值捕获方式。

  • 当混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个&=,此符号指定了默认捕获方式为引用或值,且显式捕获的变量必须使用与隐式捕获不同的方式。

  • 当变量采用值捕获时,如果我们希望能改变一个被捕获的变量的值,就必须在参数列表后加上关键字mutable


8. lambda 表达式的应用实践

既然lambda表达式只能使用在语句当中,如果想跨语句使用之前定义好的lambda表达式,应该如何解决呢?用什么类型来表示lambda表达式?

使用function类型来表示函数对象的类型,也就可以跨语句使用lambda表达式。


04 C++11知识点汇总

1. C++11常用知识点整理总结
  • 关键字和语法

    • auto:可以根据右值,推导出右值的类型,然后左边变量的类型也就已知了。

    • nullptr:给指针专用(能够和整数进行区分)

    • foreach:可以遍历数组、容器等,底层就是通过指针或者迭代器来实现的,也叫作范围遍历。

      for (auto val: container)
      {
      	cout << val << " ";
      }
      
    • 右值引用:move移动语义函数和forward类型完美转发函数

    • 模版的一个新特性:typename... A表示可变参(类型参数)

  • 绑定器和函数对象

    • function:函数对象
    • bind:绑定器
    • lambda 表达式
  • 智能指针

    • shared_ptrweak_ptrunique_ptr
  • 容器

    • unordered_setunordered_map:底层是哈希表,O(1)
    • array:数组
    • forward_list:前向链表
  • C++语言级别的多线程编程:代码可以跨平台

    threadmutexcondition_variablelock_guardunique_locksleep_for

    atomic 原子类型,基于 CAS 操作的原子类型,线程安全的

2. 通过 thread 类编写 C++ 多线程程序
2.1、怎么创建启动一个线程?

std::thread 定义一个线程对象,传入线程所需要的线程函数和参数,线程自动开启。

2.2、子线程如何结束?

子线程函数运行完成,线程就结束了。

2.3、主线程如何处理子线程呢?

假设主线程中处理子线程t,有两种处理方式:

  • 在主线程中调用t.join():主线程等待子线程t结束,主线程继续往下运行。

  • 在主线程中调用t.detach():把子线程t设置为分离线程,主线程结束,整个进程结束,所有子线程都自动结束了。

#include <iostream>
#include <thread>

using namespace std;

void threadHandle1(int time)
{
    // 让子线程睡眠time秒
    std::this_thread::sleep_for(std::chrono::seconds(time));
    cout << "hello thread1!" << endl;
}

void threadHandle2(int time)
{
    // 让子线程睡眠time秒
    std::this_thread::sleep_for(std::chrono::seconds(time));
    cout << "hello thread2!" << endl;
}

int main()
{
    // 创建了一个线程对象,传入一个线程函数,新线程就开始运行了
    std::thread t1(threadHandle1, 2);
    std::thread t2(threadHandle2, 3);

    // 主线程等待子线程结束,主线程继续往下运行
    t1.join();
    t2.join();

    // 把子线程设置为分离线程
    // t1.detach();

    cout << "main thread done!" << endl;

    // 主线程运行完成,如果当前进程还有未运行完成的子线程,进程就会异常终止。
    return 0;
}

3. 线程间互斥:mutex 互斥锁和 lock_guard

多线程程序执行的结果应是一致的,不会随着CPU对线程不同的调用顺序,而产生不同的运行结果。如果每次执行的结果不一致,则说明多线程存在 竞态条件

C++ thread 模拟车站三个窗口卖票的程序

#include <iostream>
#include <thread>
#include <mutex>
#include <list>

using namespace std;
/*
    C++ thread 模拟车站三个窗口卖票的程序

    线程间的互斥,需要用到互斥锁mutex,为了防止解锁语句未运行而发生死锁,可以使用lock_guard封装mutex。
*/

int ticketCount = 100; // 车站有100张车票,由三个窗口一起卖票
std::mutex mtx; // 全局的一把互斥锁

// 模拟卖票的线程函数
void sellTicket(int index)
{
    while (ticketCount > 0) // 锁 + 双重判断
    {
        // mtx.lock();
        {
            lock_guard<std::mutex> lock(mtx); // 保证所有线程都能释放锁,防止死锁问题的发生
            if (ticketCount > 0)
            {
                // 临界区代码段,需要保证为原子操作,线程间互斥,使用mutex
                cout << "窗口:" << index << "卖出第:" << ticketCount << "张票!" << endl;
                ticketCount--;
            }
        }
        // mtx.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main()
{
    list<std::thread> tlist;
    for (int i = 1; i <= 3; ++i)
    {
        tlist.push_back(std::thread(sellTicket, i));
    }

    for (std::thread &t: tlist)
    {
        t.join();
    }

    cout << "所有窗口卖票结束!" << endl;
    
    return 0;
}

4. 线程间的同步通信机制:生产者消费者模型

多线程编程的两个问题:

  1. 线程间的互斥

    竞态条件 =》临界区代码段=》保证原子操作 =》互斥锁mutex、轻量级的无锁实现CAS

  2. 线程间的同步通信

    生产者消费者模型

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue> // C++ STL所有的容器都不是线程安全的

using namespace std;

std::mutex mtx; // 定义互斥锁,做线程间的互斥操作
std::condition_variable cv; // 定义条件变量,做线程间的同步通信操作

// 生产者生产一个物品,通知消费者消费一个物品;消费完了,消费者再通知生产者继续生产物品
class Queue
{
public:
    void put(int val) // 生产物品
    {
        unique_lock<std::mutex> lck(mtx);
        while (!que.empty())
        {
            // que不为空,生产者应该通知消费者去消费物品,消费完了,再继续生产
            // 生产者线程进入等待状态,并且把mtx互斥锁释放掉
            cv.wait(lck);
        }
        que.push(val);
    /*
        notify_one:通知另外的一个线程消费物品
        notify_all:通知其他所有的线程消费物品
        通知其他所有的线程,我生产了一个物品,你们赶紧去消费吧
        其他线程得到通知,就会从等待状态变为阻塞状态,在获取到互斥锁后才能继续执行
    */
    cv.notify_all();
    cout << "生产者 生产:" << val << "号物品" << endl;
    }

    int get() // 消费物品
    {
        unique_lock<std::mutex> lck(mtx);
        while (que.empty())
        {
            // 消费者线程发现que是空的,通知生产者线程生产物品
            // 消费者线程进入等待状态,并且把mtx互斥锁释放掉
            cv.wait(lck);
        }
        int val = que.front();
        que.pop();
        cv.notify_all(); // 通知其他线程我消费完了,赶紧生产吧
        cout << "消费者 消费:" << val << "号物品" << endl;
        return val;
    }

private:
    queue<int> que;
};

void producer(Queue *que) // 生产者线程
{
    for (int i = 1; i <= 10; ++i)
    {
        que->put(i);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void consumer(Queue *que) // 消费者线程
{
    for (int i = 1; i <= 10; ++i)
    {
        que->get();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main()
{
    Queue que; // 两个线程共享的队列

    std::thread t1(producer, &que);
    std::thread t2(consumer, &que);

    t1.join();
    t2.join();

    return 0;
}

5. 再谈 lock_guard 和 unique_lock
  • lock_guard不提供拷贝构造函数和赋值重载函数,因此它不能用在函数参数传递以及返回过程中,只能使用在简单的临界区代码段的互斥操作中。
  • unique_lock提供了右值引用参数的拷贝构造和赋值重载函数,因此它不仅可以使用在简单的临界区代码段的互斥操作中,还能用在函数调用过程中。
6. 基于 CAS 操作的 atomic 原子类型

轻量级的无锁实现CAS,是在硬件层面系统总线实现的加锁和解锁操作。

#include <iostream>
#include <thread>
#include <atomic>
#include <list>

using namespace std;

// 使用volatile关键字修饰变量,编译器不会对变量进行优化,也就不会在CPU中对变量进行缓存,
// 每次使用变量时都从内存中取值,可以确保在多线程环境下正确地进行同步。
volatile std::atomic_bool isReady = false; 
volatile std::atomic_int mycount = 0;

void task()
{
    while (!isReady)
    {
        std::this_thread::yield(); // 线程出让当前的CPU时间片,等待下一次调度
    }

    for (int i = 0; i < 100; ++i)
    {
        mycount++;
    }
}

int main()
{
    list<std::thread> tlist;
    for (int i = 0; i < 10; ++i)
    {
        tlist.push_back(std::thread(task));
    }

    std::this_thread::sleep_for(std::chrono::seconds(3));
    isReady = true;

    for (std::thread &t : tlist)
    {
        t.join();
    }
    cout << "mycount:" << mycount << endl;

    return 0;
}

05 设计模式

1. 单例模式(属于创建型设计模式)

单例模式:一个类不管创建多少对象,永远只能得到该类型一个对象的实例。常用于日志模块、数据库模块等。

单例模式分为饿汉式单例模式懒汉式单例模式

  • 饿汉式单例模式:在还没有获取实例对象之前,实例对象就已经产生了。

  • 懒汉式单例模式:直到第一次获取实例对象时,唯一的实例对象才产生。

饿汉式单例模式代码
#include <iostream>

using namespace std;

// 饿汉式单例模式 一定是线程安全的
class Singleton
{
public:
    static Singleton* getInstance() // 3、获取类的唯一实例对象的接口方法
    {
        return &instance;
    }
private:
    static Singleton instance; // 2、定义类的唯一实例对象
    Singleton() // 1、构造函数私有化
    {

    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};
Singleton Singleton::instance;

int main()
{
    Singleton *p1 = Singleton::getInstance();
    Singleton *p2 = Singleton::getInstance();
    Singleton *p3 = Singleton::getInstance();

    return 0;
}
懒汉式单例模式代码1
#include <iostream>
#include <mutex>

using namespace std;

std::mutex mtx;

// 线程安全的懒汉式单例模式
class Singleton
{
public:
    static Singleton* getInstance() // 2、获取类的唯一实例对象的接口方法
    {
        if (instance == nullptr)
        {
            lock_guard<std::mutex> guard(mtx);
            if (instance == nullptr) // 锁+双重判断
            {
                // 开辟内存、构造对象、给instance赋值
                instance = new Singleton(); // 3、定义类的唯一实例对象
            }
        }
        return instance;
    }
private:
    static Singleton *volatile instance; 
    Singleton() // 1、构造函数私有化
    {

    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};
Singleton*volatile Singleton::instance = nullptr;

int main()
{
    Singleton *p1 = Singleton::getInstance();
    Singleton *p2 = Singleton::getInstance();
    Singleton *p3 = Singleton::getInstance();

    return 0;
}
懒汉式单例模式代码2
#include <iostream>
#include <mutex>

using namespace std;

// 线程安全的懒汉式单例模式
class Singleton
{
public:
    static Singleton* getInstance() // 2、获取类的唯一实例对象的接口方法
    {
        // 函数静态局部变量的初始化,在汇编指令上已经自动添加线程互斥指令了,因此不用担心线程安全的问题。
        static Singleton instance; // 3、定义类的唯一实例对象
        return &instance;
    }
private:
    Singleton() // 1、构造函数私有化
    {

    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

int main()
{
    Singleton *p1 = Singleton::getInstance();
    Singleton *p2 = Singleton::getInstance();
    Singleton *p3 = Singleton::getInstance();

    return 0;
}

2. 工厂模式(属于创建型设计模式)

工厂模式:主要是封装了对象的创建。

工厂模式分为:

  • 简单工厂Simple Factory

    把对象的创建封装在一个接口函数里面,通过传入不同的标识,返回创建的对象。

    优点:客户不用自己负责 new 对象,不用了解对象创建的详细过程。

    缺点:提供创建对象实例的接口函数不闭合,不能对修改关闭。

  • 工厂方法Factory Method

    Factory 基类,提供了一个纯虚函数(创建产品),定义派生类(具体产品的工厂)负责创建对应的产品。

    优点:可以做到不同的产品在不同的工厂里面创建,能够对现有工厂以及产品的修改关闭。

    缺点:实际上,很多产品是有关联关系的,属于一个产品簇,不应该放在不同的工厂里面去创建,这样一是不符合实际的产品对象创建逻辑,二是工厂类太多了,不好维护。

  • 抽象工厂Abstract Factory

    把有关联关系的,属于一个产品簇的所有产品创建的接口函数,放在一个抽象工厂里面AbstractFactory,派生类(具体产品的工厂)应该负责创建该产品簇里面所有的产品。

    相比于工厂方法,这样做可以减少大量工厂类的创建,同时也更加符合实际的产品对象创建逻辑。

简单工厂
#include <iostream>
#include <string>
#include <memory>
using namespace std;

class Car
{
public:
    Car(string name) :_name(name) {}
    virtual void show() = 0;
protected:
    string _name;
};

class Bmw : public Car
{
public:
    Bmw(string name) : Car(name) {}
    void show()
    {
        cout << "获取了一辆宝马汽车:" << _name << endl;
    }
};

class Audi : public Car
{
public:
    Audi(string name) : Car(name) {}
    void show()
    {
        cout << "获取了一辆奥迪汽车:" << _name << endl;
    }
};

// 简单工厂
enum CarType
{
    BMW, AUDI
};

class SimpleFactory
{
public:
    Car* createCar(CarType ct) // 不符合软件设计的“开闭原则”:对拓展开放,对修改关闭
    {
        switch (ct)
        {
        case BMW:
            return new Bmw("X5");
        case AUDI:
            return new Audi("A6");
        default:
            cerr << "传入工厂的参数不正确:" << ct << endl;
            break;
        }
        return nullptr;      
    }
};

int main()
{
    // Car *p1 = new Bmw("X5");
    // Car *p2 = new Audi("A6");

    unique_ptr<SimpleFactory> factory(new SimpleFactory());
    unique_ptr<Car> p1(factory->createCar(BMW));
    unique_ptr<Car> p2(factory->createCar(AUDI));

    p1->show();
    p2->show();

    return 0;
}
工厂方法
#include <iostream>
#include <string>
#include <memory>
using namespace std;

class Car
{
public:
    Car(string name) :_name(name) {}
    virtual void show() = 0;
protected:
    string _name;
};

class Bmw : public Car
{
public:
    Bmw(string name) : Car(name) {}
    void show()
    {
        cout << "获取了一辆宝马汽车:" << _name << endl;
    }
};

class Audi : public Car
{
public:
    Audi(string name) : Car(name) {}
    void show()
    {
        cout << "获取了一辆奥迪汽车:" << _name << endl;
    }
};

// 工厂方法
class Factory
{
public:
    virtual Car* createCar(string name) = 0; // 工厂方法
};
// 宝马工厂
class BmwFactory : public Factory
{
public:
    Car* createCar(string name)
    {
        return new Bmw(name);
    }
};
// 奥迪工厂
class AudiFactory : public Factory
{
public:
    Car* createCar(string name)
    {
        return new Audi(name);
    }
};

int main()
{
    // Car *p1 = new Bmw("X5");
    // Car *p2 = new Audi("A6");

    unique_ptr<Factory> bmwfty(new BmwFactory());
    unique_ptr<Factory> audifty(new AudiFactory());
    unique_ptr<Car> p1(bmwfty->createCar("X6"));
    unique_ptr<Car> p2(audifty->createCar("A8"));

    p1->show();
    p2->show();

    return 0;
}
抽象工厂
#include <iostream>
#include <string>
#include <memory>
using namespace std;

// 系列产品1
class Car
{
public:
    Car(string name) :_name(name) {}
    virtual void show() = 0;
protected:
    string _name;
};
class Bmw : public Car
{
public:
    Bmw(string name) : Car(name) {}
    void show()
    {
        cout << "获取了一辆宝马汽车:" << _name << endl;
    }
};
class Audi : public Car
{
public:
    Audi(string name) : Car(name) {}
    void show()
    {
        cout << "获取了一辆奥迪汽车:" << _name << endl;
    }
};

// 系列产品2
class CarLight
{
public:
    virtual void show() = 0;
};
class BmwLight : public CarLight
{
public:
    void show() {cout << "Bmw light!" << endl;}
};
class AudiLight : public CarLight
{
public:
    void show() {cout << "Audi light!" << endl;}
};

// 抽象工厂(对一组有关联关系的产品簇提供产品对象的统一创建)
class AbstractFactory
{
public:
    virtual Car* createCar(string name) = 0; // 工厂方法 创建汽车
    virtual CarLight* createCarLight() = 0; // 工厂方法 创建汽车关联的产品,车灯
};
// 宝马工厂
class BmwFactory : public AbstractFactory
{
public:
    Car* createCar(string name)
    {
        return new Bmw(name);
    }
    CarLight* createCarLight()
    {
        return new BmwLight();
    }
};
// 奥迪工厂
class AudiFactory : public AbstractFactory
{
public:
    Car* createCar(string name)
    {
        return new Audi(name);
    }
    CarLight* createCarLight()
    {
        return new AudiLight();
    }
};

int main()
{
    // Car *p1 = new Bmw("X5");
    // Car *p2 = new Audi("A6");

    unique_ptr<AbstractFactory> bmwfty(new BmwFactory());
    unique_ptr<AbstractFactory> audifty(new AudiFactory());
    unique_ptr<Car> p1(bmwfty->createCar("X6"));
    unique_ptr<Car> p2(audifty->createCar("A8"));
    unique_ptr<CarLight> l1(bmwfty->createCarLight());
    unique_ptr<CarLight> l2(audifty->createCarLight());

    p1->show();
    l1->show();
    p2->show();
    l2->show();

    return 0;
}

运行结果:

获取了一辆宝马汽车:X6
Bmw light!
获取了一辆奥迪汽车:A8
Audi light!

3. 代理模式(属于结构型设计模式)

结构型模式:这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

代理模式Proxy Pattern):通过代理类,来控制实际对象的访问权限。

#include <iostream>
#include <memory>

using namespace std;

class VideoSite // 抽象类
{
public:
    virtual void freeMovie() = 0; // 免费电影
    virtual void vipMovie() = 0; // vip电影
    virtual void ticketMovie() = 0; // 用券观看电影
};

// 委托类
class FixBugVideoSite : public VideoSite
{
public:
    virtual void freeMovie() // 免费电影
    {
        cout << "观看免费电影" << endl;
    }
    virtual void vipMovie() // vip电影
    {
        cout << "观看VIP电影" << endl;
    }
    virtual void ticketMovie() // 用券观看电影
    {
        cout << "用券观看电影" << endl;
    }
};

// 代理类 代理FixBugVideoSite
class FreeVideoSite : public VideoSite
{
public:
    FreeVideoSite() { pVideo = new FixBugVideoSite(); }
    ~FreeVideoSite() { delete pVideo; }
    void freeMovie() // 免费电影
    {
        pVideo->freeMovie(); // 通过代理对象的freeMovie,来访问真正委托类对象的freeMovie方法
    }
    void vipMovie() // vip电影
    {
        cout << "您现在是普通用户,升级为VIP会员即可观看!" << endl;
    }
    void ticketMovie() // 用券观看电影
    {
        cout << "您目前没有券,购买电影券即可观看!" << endl;
    }
private:
    VideoSite* pVideo;
};

// 代理类 代理FixBugVideoSite
class VipVideoSite : public VideoSite
{
public:
    VipVideoSite() { pVideo = new FixBugVideoSite(); }
    ~VipVideoSite() { delete pVideo; }
    void freeMovie() // 免费电影
    {
        pVideo->freeMovie(); // 通过代理对象的freeMovie,来访问真正委托类对象的freeMovie方法
    }
    void vipMovie() // vip电影
    {
        pVideo->vipMovie();
    }
    void ticketMovie() // 用券观看电影
    {
        cout << "您目前没有券,购买电影券即可观看!" << endl;
    }
private:
    VideoSite* pVideo;
};

void watchMovie(unique_ptr<VideoSite> &ptr)
{
    ptr->freeMovie();
    ptr->vipMovie();
    ptr->ticketMovie();
}

int main()
{
    unique_ptr<VideoSite> p1(new FreeVideoSite());
    unique_ptr<VideoSite> p2(new VipVideoSite());

    watchMovie(p1);
    watchMovie(p2);

    return 0;
}

运行结果:

观看免费电影
您现在是普通用户,升级为VIP会员即可观看!
您目前没有券,购买电影券即可观看!
观看免费电影
观看VIP电影
您目前没有券,购买电影券即可观看!

4. 装饰器模式(属于结构型设计模式)

装饰器模式Decorator Pattern):主要是增加现有类的功能,但是增加现有类的功能还有一个方法就是新增加一个子类。

通过子类实现功能增强的问题:为了增强现有类的功能,通过实现子类的方式,重写接口,是可以完成功能拓展的,但是有太多的子类被添加到代码中。

#include <iostream>
#include <memory>

using namespace std;

class Car // 抽象基类
{
public:
    virtual void show() = 0;
};
// 三个汽车实体类
class Bmw : public Car
{
public:
    void show()
    {
        cout << "这是一辆宝马汽车,配置有:基类配置";
    }
};
class Audi : public Car
{
public:
    void show()
    {
        cout << "这是一辆奥迪汽车,配置有:基类配置";
    }
};
class Benz : public Car
{
public:
    void show()
    {
        cout << "这是一辆奔驰汽车,配置有:基类配置";
    }
};

// 装饰器1 定速巡航
class ConcreteDecorator01 : public Car
{
public:
    ConcreteDecorator01(Car *p):pCar(p){}
    void show()
    {
        pCar->show();
        cout << ",定速巡航";
    }
private:
    Car *pCar;
};
// 装饰器2 自动刹车
class ConcreteDecorator02 : public Car
{
public:
    ConcreteDecorator02(Car *p):pCar(p){}
    void show()
    {
        pCar->show();
        cout << ",自动刹车";
    }
private:
    Car *pCar;
};
// 装饰器3 车道偏离
class ConcreteDecorator03 : public Car
{
public:
    ConcreteDecorator03(Car *p):pCar(p){}
    void show()
    {
        pCar->show();
        cout << ",车道偏离";
    }
private:
    Car *pCar;
};

int main()
{
    Car *p1 = new ConcreteDecorator01(new Bmw());
    p1 = new ConcreteDecorator02(p1);
    p1 = new ConcreteDecorator03(p1);
    p1->show();
    cout << endl;
    Car *p2 = new ConcreteDecorator01(new Audi());
    p2->show();
    cout << endl;

    Car *p3 = new ConcreteDecorator01(new Benz());
    p3->show();
    cout << endl;
    return 0;
}

5. 适配器模式(属于结构型设计模式)

适配器模式:让不兼容的接口可以在一起工作。

#include <iostream>
using namespace std;

// VGA接口类
class VGA 
{
public:
    virtual void play() = 0;
};

// TV01表示支持VGA接口的投影仪
class TV01 : public VGA
{
public:
    void play()
    {
        cout << "通过VGA接口连接投影仪,进行视频播放" << endl;
    }
};

// 实现一个电脑类(只支持VGA接口)
class Computer
{
public:
    // 由于电脑只支持VGA接口,所以该方法的参数也只能支持VGA接口的指针或引用
    void playVideo(VGA *pVGA)
    {
        pVGA->play();
    }
};

/*
    现在进了一批新的投影仪,但是新的投影仪都只支持HDMI接口,而电脑又只支持VGA接口,该如何解决接口不兼容的问题呢?
    方法1:换一个支持HDMI接口的电脑,这个叫代码重构。
    方法2:买一个转换头(适配器),能够把VGA信号转成HDMI信号,这个叫添加适配器类。
*/

// 进了一批新的投影仪,但是新的投影仪都只支持HDMI接口
class HDMI
{
public:
    virtual void play() = 0;
};

// TV02表示支持HDMI接口的投影仪
class TV02 : public HDMI
{
public:
    void play()
    {
        cout << "通过HDMI接口连接投影仪,进行视频播放" << endl;
    }
};

// 由于电脑(VGA接口)和投影仪(HDMI接口)无法直接相连,所以需要添加适配器类
class VGAToHDMIAdapter : public VGA
{
public:
    VGAToHDMIAdapter(HDMI *p) :pHdmi(p) {}
    void play() // 该方法相当于转换头,做不同接口的信号转换的
    {
        pHdmi->play();
    }
private:
    HDMI *pHdmi;
};

int main()
{   
    Computer comp;
    comp.playVideo(new TV01());
    comp.playVideo(new VGAToHDMIAdapter(new TV02()));

    return 0;
}

6. 观察者模式(属于行为型设计模式)
  • 行为型设计模式:主要关注的是对象之间的通信。

  • 观察者设计模式(Observer Pattern)也称作观察者-监听者模式、发布-订阅模式。

  • 观察者模式:主要关注的是对象的一对多的关系,也就是多个对象都依赖一个对象,当该对象的状态发生改变时,其他对象都能够接收到相应的通知。

    比如:有一组数据(数据对象),通过这一组数据对象可以绘制出曲线图(对象1)、柱状图(对象2)、圆饼图(对象3),当数据对象改变时,对象1、对象2、对象3应该及时的收到相应的通知!

现在有个主题Subject和三个观察者Observer1Observer2Observer3,假设主题有更改,应该及时通知相应的观察者,去处理相应的事件。

#include <iostream>
#include <string>
#include <unordered_map>
#include <list>

using namespace std;

// 观察者抽象类
class Observer
{
public:
    // 处理消息的接口
    virtual void handle(int msgid) = 0;
};
// 第一个观察者实例
class Observer1 : public Observer
{
public:
    void handle(int msgid)
    {
        switch (msgid)
        {
        case 1:
            cout << "Observer1 recv 1 msg!" << endl;
            break;
        case 2:
            cout << "Observer1 recv 2 msg!" << endl;
            break;
        default:
            cout << "Observer1 recv unknown msg!" << endl;
            break;
        }
    }
};
// 第二个观察者实例
class Observer2 : public Observer
{
public:
    void handle(int msgid)
    {
        switch (msgid)
        {
        case 2:
            cout << "Observer2 recv 2 msg!" << endl;
            break;
        default:
            cout << "Observer2 recv unknown msg!" << endl;
            break;
        }
    }
};
// 第三个观察者实例
class Observer3 : public Observer
{
public:
    void handle(int msgid)
    {
        switch (msgid)
        {
        case 1:
            cout << "Observer3 recv 1 msg!" << endl;
            break;
        case 3:
            cout << "Observer3 recv 3 msg!" << endl;
            break;
        default:
            cout << "Observer3 recv unknown msg!" << endl;
            break;
        }
    }
};

// 主题类
class Subject
{
public:
    // 给主题增加观察者对象
    void addObserver(Observer* obser, int msgid)
    {
        _subMap[msgid].push_back(obser);
    }
    // 主题检测发生改变,通知相应的观察者对象处理事件
    void dispatch(int msgid)
    {
        auto it = _subMap.find(msgid);
        if (it != _subMap.end())
        {
            for (Observer *pObser : it->second)
            {
                pObser->handle(msgid);
            }
        }
    }
     
private:
    unordered_map<int, list<Observer*>> _subMap;
};

int main()
{
    Subject subject;
    Observer *p1 = new Observer1();
    Observer *p2 = new Observer2();
    Observer *p3 = new Observer3();

    subject.addObserver(p1, 1);
    subject.addObserver(p1, 2);
    subject.addObserver(p2, 2);
    subject.addObserver(p3, 1);
    subject.addObserver(p3, 3);

    int msgid = 0;
    while (1)
    {
        cout << "请输入id:";
        cin >> msgid;
        if (msgid == -1)
            break;
        subject.dispatch(msgid);
    }

    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小浩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值