C++ 11 新玩法

时间:2022-10-14 18:53:36

列表初始化

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定
义的类型,使用初始化列表时,可添加等号(=),也可不添加

int main()
{
	int a = 10;
	int d = { 10 };
	int b{ 10 };
	int c{ 3 + 3 };
	
	int* arr = new int[4]{ 1,2,3,4 };
	//int* arr = new int[4] = { 1,2,3,4 }; 这样不支持

	vector<int> v1{ 1,2,3,4 };
	vector<int> v2 = { 1,2,3,4 };

	map<int, int> m1 = { {1,2},make_pair(2,3) };
	map<int, int> m2 = { {1,2},make_pair(2,3) };
	map<int, int> m3 { {1,2},make_pair(2,3) };
	map<int, int> m4 { {1,2},make_pair(2,3) };
	return 0;
}

自定类型的列表初始化

class A
{
public:
	A(int x, int y)
		:_x(x)
		,_y(y)
	{}

private:
	int _x;
	int _y;
};

int main()
{
	A a = { 1,2 }; //这个实际上去调用了构造函数
	A a1{ 1,2 };
	return 0;
}

关于initializer_list这个容器,也就是序列对 {1,2,3,4}。
像vector v = {1 , 2, 3 4 },list = {1,2,3,4},,,等等容器支持这个初始化,是因为C++11在vector,list容器的
构造函数中添加了利用传参initializer_lsit来构造
C++ 11 新玩法
原理大概是这样的

namespace chen
{ 
	template <class T>
	class vector
	{
		typedef T* iterator;
	public:
		vector(const initializer_list<T>& l)
		{
			_start = new T[l.size()];
			_finish = _start + l.size();
			_endofstorage = _start + l.size();

			//initializer_list未提供operator[]的重载
			auto it = _start;
			for (auto e : l)
			{
				*it = e;
				it++;
			}
		}

		vector& operator=(const initializer_list<T>& l)
		{
			vector ret(l); // 直接利用构造函数
			::swap(ret._start, _start);
			::swap(ret._finish, _finish);
			::swap(ret._endofstorage, _endofstorage);

			return *this;
		}

	private:
		T* _start;
		T* _finish;
		T* _endofstorage;
	};
}

int main()
{
	initializer_list<int> l = { 1,2,3,4,5 };
	chen::vector<int> v1 = { 1,2,3,4,5 };
	chen::vector<int> v2 = { 4,5 };
	v1 = { 2,3,4 };
	return 0;
}

类型推导auto和 decltype

auto进行类型推导必须初始化。(常用常偷懒)
decltype是用来进行表达式的类型推导然后去定义变量类型(浅浅的了解一下)

int main()
{
   //auto c;  //error
	auto a = 4;
	auto b = &a;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;

	vector<int> v = { 1, 2 ,3 ,4 ,5 ,6 };
	vector<int>::iterator it = v.begin();
	//auto it = v.begin(); 这样就偷懒多了

	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}

    int a1 = 10;
	double d = 5.0;
	decltype(a1 * d) c;    //但是它只是定义
	cout << typeid(c).name() << endl; //输出double

	auto e = a1 * d;     //它是初始化
	cout << typeid(e).name() << endl; //输出double
	return 0;
}

final和override

final和override修饰函数的时候和const修饰this指针的用法一样

class A final //表示A不能被继承
{

};

final修饰的是继承体系中的虚函数,表示该虚函数不能被重写,否则报错。
final修饰类,表示该类不能被继承。

override修饰继承继承体系中子类的虚函数,如果该虚函数没有被重写则报错

右值引用

C++98中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通
过指针来实现的,因此使用引用,可以提高程序的可读性。
(所以这里考过一道题:指针传地址,引用传值,这是错误的,因为底层。。。。)

为了提高程序运行效率,C++11中引入了右值引用,右值引用也是别名。语法是T&&,
右值引用的核心是窃取临时变量(或将亡值)的资源,因为这些变量可能立马会销毁了,但是右值引用后,
会将这些临时变量(或将亡值)存储到特定的位置。。

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	//对右值(临时变量,将亡值)的引用,当临时值返回的时候,引用之后就不销毁了
	//对右值引用之后,会去开辟一块空间去存储它,也就可理解下面为什么能够修改了

	int&& ret = Add(10, 5); //返回值就是一个右值(临时变量)
	cout << ret << endl;
	ret = 20;
	cout << ret << endl;
	return 0;
}

左值的定义 : 可取地址 + 赋值 + 可修改 (const修饰的左值不能修改,const修饰的常变量不会开辟空间,c++访问的时候直接替换,
取地址的时候才会开辟空间,但是c++认为它是左值。另外的话,const对象既可以引用右值,也可以引用左值)
右值的定义:右值不能取地址,不能修改(因为它一般是临时变量,或者说是将亡值),但是右值可引用move后的左值

int main()
{
	int a = 10;
	int b = a;  
	int* p = &a;
	const int d = 10;
	//a,b,p,d都是左值

	const int& e = 10;  //左值引用右值

	int&& c = move(a); //右值引用左值

	//10
	//a + b
	//fadd(a,b) 都是右值

	return 0;
}

总结:
C++ 11 新玩法

那么,重点来了,右值引用的核心(窃取临时变量或者将亡值的资源)

namespace chen
{
	class string
	{
	public:
		string(const char* str = "")
			:_sz(strlen(str))
			,_capacity(_sz)
		{
			//cout << " 默认构造函数" << endl;
			_str = new char[_sz + 1];
			strcpy(_str, str);
		}

		//s1(s)
		string(const string& s)
			:_str(nullptr)
		{
			cout << "拷贝构造" << endl;
			string tmp(s._str);
			swap(tmp);
		}

		//s1 = s2
		string& operator=(const string& s)
		{
			cout << "赋值重载" << endl;
			string tmp(s._str);
			swap(tmp);
		}

		//string s1(对象)
		string(string&& s)
			:_str(nullptr)
		{
			cout << "移动构造" << endl;
			swap(s);
		}


		// s1 = 临时对象 ,
		string& operator=(string&& s)
		{
			cout << "移动赋值" << endl;
			swap(s);

			return *this;
		}

		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_sz, s._sz);
			::swap(_capacity, s._capacity);
		}

		~string()
		{
			delete _str;
			_str = nullptr;
		}

		string operator+(const string& s)
		{
			string tmp;
			tmp._sz = _sz + s._sz;
			tmp._capacity = _capacity + s._capacity;

			strcpy(tmp._str, _str);
			tmp._str = (char*)realloc(tmp._str, _sz + 1);
			strcat(tmp._str, s._str);

			return tmp;
	    }

	private:
		char* _str;
		size_t _sz;
		size_t _capacity;
	};
}

int main()
{
	 chen::string s1 = "hello";
	 chen::string s2 = "chen";
	 chen::string s4 = s1 + s2;

	 chen::string s5;
	 s5 = s1 + s2;
	 return 0; 
}

C++ 11 新玩法

但是引入移动构造后(移动赋值一样,这里就不过多赘述了)
C++ 11 新玩法
右值引用一些更深入的使用场景
当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。move()函数位于 头文件中,该函数名字具有迷惑性,
它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

int main()
{
	chen::string s1 = "hello chen";
	chen::string s2(s1); // s1是左值调用的拷贝构造
	chen::string s3(move(s1)); // 将s1转化为右值,move以后s1的资源就不存在了,移动构造
	return 0;
}


int main()
{
	//  void push_back (value_type&& val);
	// 这里用vector的话可能还不行
	list<chen::string> l;
	chen::string s1 = "hello chen";
	l.push_back(s1);   //传左值的话肯定是一个拷贝构造
	
	l.push_back("hello chen"); // 传右值就是一个移动构造
	l.push_back(move(s1)); // 传右值就是一个移动构造

	return 0;
}

C++ 11 新玩法

完美转发

模板中的&&都是万能引用。可以引用左值,也可以引用右值
当我们运行下面代码的时候,惊讶的发现进行第二次函数Fun()的时候都变成了左值。
这是因为,当我们为右值取别名后,会将右值存储到特定的位置,基本上就有了左值的属性

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}

int main()
{
	PerfectForward(10);
	int a;
	PerfectForward(a);
	PerfectForward(std::move(a)); 
	const int b = 8;
	PerfectForward(b); 
	PerfectForward(std::move(b)); 
	return 0;
}

如何去保证右值在传递过程中不出现意外呢?(被转化为左值),这样传参

Fun(std::forward<T>(t));

这样的实际作用:(懒的敲了)
C++ 11 新玩法

新的类功能

类的默认成员函数有8个:构造函数,拷贝构造,赋值重载,析构,取地址重载,const取地址重载,移动构造,移动赋值

移动构造:自己不写,并且析构函数 、拷贝构造、拷贝赋值都不写,编译器才会默认生成。对于内置类型成员会执行
逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造
如果这里我们写了拷贝构造或者任意一个,那又想让编译器生成默认的移动构造,只需要用关键字default,它表示让编译器生成。
如果不想要某个默认成员函数,用delete就行。

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}

	A(A&&) = default; //指定A生生成自己的移动构造
	A(const A&) = delete; // 删除A的拷贝构造
	~A();
private:
	int _a;
};

移动赋值类似于移动构造,不在过多赘述

可变参数模板

//这里的Args就是模板参数包,args就是一个函数形参参数包
//Args...args声明一个参数包,可接受0个或者多个形参
template <class ... Args>
void ShowList(Args ... args)
{}

那么如何使用呢?

template <class T>
void ShowList(T val) //这里就相当于是一个参数的出口
{
	cout << val << " " << endl;
}


template <class T, class ... Args>
void ShowList(T val,Args ... args) 
{
	//sizeof...(args)求的是形参个数,不是大小
	cout << val << " ";
	ShowList(args...);  //相当于一种递归,辅助记忆:传参的时候...在后面,其余的都在中间
}

int main()
{
	ShowList(1, 2, 3, 4);
	ShowList(1, "12312312", "abdcb");
	ShowList("haha");

	return 0;
}

lambda表达式

先想想这样一个问题,我们是如何对自定义类型进行排序的呢?

class stu
{
public:
	stu(string name, double score, int age)
		:_name(name)
		, _score(score)
		, _age(age)
	{}

	string _name;
	double _score;
	int _age;
};

struct cmp_name
{
	bool operator()(const stu& s1, const stu& s2)
	{
		return s1._name < s2._name; //小于是升序
    }
};

struct cmp_score
{
	bool operator()(const stu& s1, const stu& s2)
	{
		return s1._score < s2._score;
	}
};


struct cmp_age
{
	bool operator()(const stu& s1, const stu& s2)
	{
		return s1._age < s2._age;
	}
};

int main()
{
	vector<stu> v = { {"zhangsan",80.0,18}, {"lisi",70.0,20}, {"wangwu",90.0,19} };
	sort(v.begin(), v.end(),cmp_name());
	sort(v.begin(), v.end(), cmp_age()); 
	sort(v.begin(), v.end(), cmp_score());
	return 0;
}

人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,
还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。所有lambda表达式应运而生

        sort(v.begin(), v.end(), [](const stu& s1, const stu& s2)->bool
		{
			return s1._name < s2._name;
		});

	    sort(v.begin(), v.end(), [](const stu& s1, const stu& s2)->bool
		{
			return s1._age < s2._age;
		});

	    sort(v.begin(), v.end(), [](const stu& s1, const stu& s2)->bool
 		{
			return s1._score < s2._score;
		});

上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函数

关于lambda表达式的用法
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement
}

  1. lambda表达式各部分说明
    [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来
    判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda
    函数使用。
    (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
    连同()一起省略
    mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
    性。使用该修饰符时,参数列表不可省略(即使参数为空)。

->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
导。
{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
注意:
在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为
空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情

如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()
只能用auto去推导类型

int main()
{
	int a = 3, b = 5;

	auto func1 = [] {return 3 + 2; };//这个由编译器去自动推导返回类型
	cout << func1() << endl; //输出5

	auto func2 = [](int x, int y)->int { return x + y; }; //传参,指明返回类型
	cout << func2(10, 20) << endl; // 输出30

	auto func3 = [](int& x, int& y) { swap(x, y); };
	func3(a, b);  //a,b的值交换

	auto func4 = [=] {return a + b; }; //已传值的方式去捕捉父作用域的a和b
	cout << func4() << endl;
	
	return 0;
}

[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针

注意:
1,父作用域指包含lambda函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
d. 在块作用域以外的lambda函数捕捉列表必须为空。
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者
非局部变量都会导致编译报错。
f. lambda表达式之间不能相互赋值,即使看起来类型相同

包装器


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

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};

int main()
{
   //包装器 <>里面要指明返回类型和参数类型

	function<int(int, int)> f1 = f;  //包装函数指针
	cout << f1(1, 2) << endl;

	function<int(int, int)> f2 = Functor();  //包装函数对象
	cout << f2(1, 2) << endl;

	function<int(int, int)> f3 = [](int a, int b)->int //包装lambda表达式
	{
		return a + b;
	};
	cout << f3(1, 2) << endl;
	
	function<int(int, int)> f4 = &Plus::plusi;
	cout << f4(1, 2) << endl;

	function<double(Plus,double, double)> f5 = &Plus::plusd; //传对象才可以调用,非静态成员函数
	cout << f5(Plus(), 1.0, 2.0) << endl;

	return 0;
}

包装器有什么作用呢???、
求这种逆波兰表达式 ,这里的case语句能把你写吐掉

class Solution {
public:
int evalRPN(vector<string>& tokens) {
 stack<int> st;
  for(auto& str : tokens)
  {
    if(str == "+" || str == "-" || str == "*" || str == "/")
    {
      int right = st.top();
      st.pop();
      int left = st.top();
      st.pop();
      switch(str[0])
    {
        case '+':
          st.push(left+right);
          break;
        case '-':
          st.push(left-right);
          break;
        case '*':
          st.push(left*right);
          break;
        case '/':
          st.push(left/right);
          break;
      }
    }
    else
    {
      // 1、atoi itoa
      // 2、sprintf scanf
      // 3、stoi to_string C++11
      st.push(stoi(str));
    }
  }
 
  return st.top();
}
};

要是有了包装器

class Solution 
{
public:
    int evalRPN(vector<string>& tokens) 
    {
      stack<long long> st;
      map<string,function<int(int,int)>> m = {
          {"+",[](int a, int b)->int{return a + b;}},
          {"-",[](int a, int b)->int{return a - b;}},
          
          //防止相乘的情况出现溢出的情况
          {"*",[](long long a, long long b)->long long{return a * b;}}, 
          {"/",[](int a, int b)->int{return a / b;}},
      };

      for(const auto e : tokens)
      {
          if(e != "+" && e != "-"&& e != "*"&& e != "/" )
          {
              st.push(stoi(e));
          }
          else
          {
              int right  = st.top();
              st.pop();
              int left = st.top();
              st.pop();

              st.push(m[e](left,right));
          }
      }
      
      return st.top();
    }
};

bind绑定

int f(int a, int b)
{
	cout << a << endl;
	cout << b << endl;

	return 1;
}

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}

	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	//_1, _2表示绑定参数位置
	function<int(int, int)> fun1 = bind(f, placeholders::_1, placeholders::_2); 
	fun1(1,2);

	function<int(int, int)> fun2 = bind(f, placeholders::_2, placeholders::_1);
	fun2(1, 2);  

	function<int(int, int)> fun3 = bind(Functor(), placeholders::_1, placeholders::_2);
	cout << fun3(1, 2) << endl;

	function<int(int, int)> fun4 = bind(&Plus::plusi,placeholders::_1, placeholders::_2);
	cout << fun4(1, 2) << endl;

	Plus ps;
	function<double(double, double)> f5 = bind(&Plus::plusd,ps, placeholders::_1, placeholders::_2);
	cout << f5(2.0f, 4.0f) << endl;  //这最后一个和包装器的传法优点区别

	return 0;
}