C++11

一、统一的列表初始化

1.1 {}初始化

C++98 中,允许使用 {} 对数组或者结构体元素进行统一的列表初始值设定
C++11 扩大了用 {} 括起的列表(初始化列表)的使用范围,可用于所有的内置类型自定义的类型和new表达式,使用 {} 时,可添加等号(=),也可不添加。
class Date
{
public :
	Date(int year ,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	int a = 1; // 建议就用这个,下面就不要用
	int b = { 2 };
	int c { 3 };

	//效果相同
	int arr1[]{ 1, 2, 3, 4, 5 };
	int arr3[] = { 1,2,3,4,5 };

	int arr2[5]{ 0 };
	int arr4[5] = { 0 };
	

	// 上面的可以支持,本质就更好支持new[] 的初始化问题
	int* p1 = new int(1);
	int* p2 = new int[3]{1,3,4};

	//自定义类型也可以这样初始化
	Date d0(2023, 3, 19);
	Date d1{ 2023,3,19 };
	Date d2 = { 2023,3,19 };

	Date* p4 = new Date(1, 2, 3);
	Date* p3 = new Date[3]{ {2023,3,19},{2023,3,20},{2023,3,21} };

	//vector,set,map,list....等都可以用 {} 这种方式初始化
	vector v1 = { 1,2,3,4,5 };
	vector v2{ 1,2,3,4,5 };
	set s2 = { 5,9,4,23,1 };
	set s1{ 5,9,4,23,1 };
	map m1{ {"字符串","string"},{"左边","left"} };
	map m2 = { {"字符串","string"},{"左边","left"} };

	return 0;
}

 1.2 std::initializer_list

int main()
{
	// il的类型  -->   class std::initializer_list
	auto il = { 10, 20, 30 };
	cout << typeid(il).name() << endl;

	//可用迭代器,但是只可读不可写
	initializer_list ilt = { 3.3, 5.5, 9.9 };
	initializer_list::iterator it = ilt.begin();

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

	for (auto e : ilt)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

initiallizer本质上可以认为是一个存储常量的数组一般是作为构造函数的参数。

可用迭代器,但是只可读不可修改。

vector,map,string..等容器,支持花括号{}的初始化和赋值,只因其构造函数里增加std::initializer_list作为参数的构造函数。当operator= 的参数有std::initializer_list,就可以用大括号赋值

int main()
{
	vector v ;

	// 这里{"sort", "排序"}会先初始化构造一个pair对象,再赋值
	map m = { {"sort", "排序"}, {"insert", "插入"} };

	//使用{} 对容器赋值
	v = { 1,2,3,4,5 };
	return 0;
}

 二、声明

auto,decltype,nullptr使用

int main()
{
	//auto
	//自动根据变量的值,判断变量的类型
	int a = 20;

	//it --> int*
	auto pa = &a;

	//b --> double
	auto b = 7.7;

	//decltype
	//将变量的类型声明为表达式指定的类型

	//c --> double
	decltype(7.7) c;

	//d --> int*
	decltype(&a) d;

	//nullptr
	//C++中NULL被定义为字面量0,这就可能会带回一些问题
	//应该用nullptr表示空指针

}

三、 右值引用和移动语义

3.1 右值引用和左值引用

3.1.1 左值、左值引用

左值是一个表示数据的表达式,我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。
int main()
{
	// 以下的a、b、c、*a都是左值
	int* a = new int(0);
	int b = 1;

	//定义时const修饰符后的左值,不能给他赋值
	//但是可以取它的地址。左值引用就是给左值取别名
	const int c = 7;

	// 以下几个是对上面左值的左值引用
	int*& aa = a;
	int& bb = b;
	const int& cc = c;
	int& aaa = *a;

	return 0;
}

 3.1.2 右值、右值引用

右值也是一个表示数据的表达式,如:字面常量、表达式返回值(临时变量),函数返回值(传值返回,但这个不能是左值引用返回)......等等。右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址
int main()
{
	double a = 7.7;
	double b = 2.2;
	// 以下几个都是常见的右值

	10;
	a + b;
	fmin(a, b);

	// 符号:  &&  --> 右值引用
	// 以下几个都是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = a + b;
	double&& rr3 = fmin(a, b);

	// 下面的编译会报错 , 左操作数必须为左值
	//10 = 1;
	//x + y = 1;
	//fmin(x, y) = 1;

	return 0;
}
需要注意的是:右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址。如下:
int main()
{
	double x = 1.1;
	double y = 2.2;

	//不能取字面量10的地址
	//&10   ->  报错
	10;

	//但是rr1引用后,可以对rr1取地址,也可以修改rr1。
	//如果不想rr1被修改,可以用const int&& rr1 去引用,则不能被修改
	int&& rr1 = 10;
	//rr2有const修饰也不能修改
	const double&& rr2 = x + y;

	rr1 = 20;
	//rr2 = 5.5;  // 报错

	return 0;
}

总结:左值可以取地址,右值不可以取地址

3.2 左值引用和右值引用比较 

3.2.1 左值引用总结

  •  左值引用只能引用左值,不能引用右值。
  • 但是const左值引用既可引用左值,也可引用右值。
int main()
{
	//左值引用只能引用左值,不能引用右值
	int a = 10;
	int& a1 = a;  //a1为a的别名,a是左值
	//int& a2 = 10;  编译报错,引用的是10(右值)

	//加const后的左值引用,可以引用左值,也可以引用右值
	int b = 7;
	const int& b1 = 7;
	const int& b2 = a;
	//const修饰后为常量,可以从常量转换为常量

	return 0;
}

3.2.2 右值引用总结

  • 右值引用只能右值,不能引用左值。

  •  但是右值引用可以move以后的左值。

int main()
{
	int c = 7;
	//右值引用只能右值,不能引用左值。
	int&& a = 7;
	//int&& aa = c;  编译报错,无法从"int"转换为"int&&"

	//右值引用可以引用move以后的左值
	int&& b = std::move(c);

	return 0;
}

3.3 右值引用使用场景及意义

 做参数和做返回值都可以提高效率。

void func1(string s)
{}

void func2(string& s)
{}

int main()
{
	string s1{ "Cistiano" };

	//做参数
	func1(s1);
	func2(s1);//func2相对于func1,左值引用做参数减少了拷贝

	//做返回值
	// string operator+=(char ch)  传值返回存在深拷贝
    // string& operator+=(char ch) 传左值引用 string& 没有拷贝,提高了效率

	return 0;
}

移动语义:C++11以后,STL容器中都提供了移动构造和移动赋值

3.3.1 移动构造

移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己
//移动构造
string(string&& s)
	:_str(nullptr)
{
	swap(s);
}

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

Cris::string to_string(int value)
{
	string str;
	//......

	return str;
}

int main()
{
	Cris::string ret = Cris::to_string(-1234);

	return 0;
}

to_string的返回值是一个右值,用这个右值构造ret,如果既有拷贝构造又有移动构造,调用就会匹配调用移动构造,因为编译器会选择最匹配的参数调用。那么这里就是一个移动语义。

C++11_第1张图片3.3.2 移动赋值 

// 移动赋值
//在Cris::string类中增加移动赋值函数,再去调用Cris::to_string(1234),
//不过这次是将Cris::to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动构造。
string& operator=(string&& s)
{
	swap(s);
	return *this;
}

Cris::string to_string(int value)
{
	string str;
	//......

	return str;
}

int main()
{
	Cris::string ret;
	ret = Cris::to_string(1234);

	return 0;
}
上面运行,调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象 接收,编译器就没办法优化了。Cris::to_string函数中会先用str生成构造生成一个临时对象,但是这里 可以看到,编译器把str识别成了右值,调用了移动构造。然后再把这个临时 对象做为Cris::to_string函数调用的返回值赋值给ret,这里调用的移动赋值。

3.4 右值引用引用左值及其一些更深入的使用场景分析

按照语法,右值引用只能引用右值。但在有些场景下,可能需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。std::move() 函数 唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

int main()
{
	Cris::string s1("hello world");

	// 这里s1是左值,调用的是拷贝构造
	//深拷贝,左值拷贝是不会被资源转移偷家的
	Cris::string s2(s1);

	// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
	// 但是这里要注意,一般是不要这样用的,
	// 因为我们会发现s1的资源被转移给了s3,s1被置空了。
	// 转移将亡值的资源,右值拷贝是不会被资源转移偷家
	Cris::string s3(std::move(s1));

	return 0;
}

3.5 完美转发

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
template
void Func(T&& t)
{
	fun(t);
}

但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值 

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
void Func(T&& t)
{
	Fun(t);
}

int main()
{
	Func(10);// 右值

	int a;
	Func(a);// 左值
	Func(std::move(a)); // 右值

	const int b = 8;
	Func(b);// const 左值
	Func(std::move(b)); // const 右值

	return 0;
}

 结果:

C++11_第2张图片

这时候就要看std::forward() , std::forward 完美转发在传参的过程中保留对象原生类型属性

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
void Func(T&& t)
{
	//加入std::forward
	Fun(std::forward(t));
}

int main()
{
	Func(10);// 右值

	int a;
	Func(a);// 左值
	Func(std::move(a)); // 右值

	const int b = 8;
	Func(b);// const 左值
	Func(std::move(b)); // const 右值

	return 0;
}

 结果:

C++11_第3张图片

四、新的类功能

C++11 新增了两个 --->  移动构造函数和移动赋值运算符重载

针对移动构造函数和移动赋值运算符重载,有以下需要注意的点:
  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的 任意一个 。那么编译器会自动生成一个默认移动构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的 任意一个 ,那么编译器会自动生成一个默认移动赋值。
  • 如果你提供了移动构造 或者 移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

强制生成默认函数的关键字default 

 使用default关键字显示指定移动构造生成。

class Student
{
public:

	Student(const char* name = "Cris", int age = 0)
		:_name(name)
		, _age(age)
	{}

	Student(const Student& p)
		:_name(p._name)
		, _age(p._age)
	{}

	//强制生成移动构造
	Student(Student&& p) = default;

private:
	string _name;
	int _age;
};
int main()
{
	Student s1;
	Student s2 = s1;
	Student s3 = std::move(s1);
	return 0;
}

禁止生成默认函数的关键字delete

delete --> 限制某些函数的形成

C++98中,将该函数设置成private,加上声明补丁,这样只要其他人想要调用就会报错。

C++11中更简单,只需在该函数声明加上=delete即可。该语法指示编译器不生成对应函数的默认版本,称 =delete 修饰的函数为删除函数。
class Student
{
public:

	Student(const char* name = "Cris", int age = 0)
		:_name(name)
		, _age(age)
	{}

	//强制拷贝构造函数删除
	Student(const Student& p) = delete;

private:
	string _name;
	int _age;
};
int main()
{
	Student s1;

	//拷贝构造函数被删除了,下面的将会报错
	//Student s2 = s1;
	//Student s3 = std::move(s1);
	return 0;
}

五、可变参数模板

下面Args是一个模板参数包,args是一个函数形参参数包。

声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。

template 
void ShowList(Args... args)
{
	//可以打印参数个数
	cout << sizeof...(args) << endl;

    // 语法不支持使用args[i]这样方式获取可变参数
	// 没有迭代器,不支持
	/*for (size_t i = 0; i < sizeof...(args); ++i)
	{
		cout << args[i] << endl;
	}*/

	/*for (auto& e : args)
	{
	cout << e << endl;
	}*/
}

int main()
{
	//什么类型的都可以存储进去
	ShowList(1, 'x', 7.7);
	ShowList(1, 2, 3, 4, 5);

	return 0;
}

 可以通过两种方式获取参数包的值:

1.递归函数方式展开参数包
template 
void ShowList(const T& val)
{
	cout << val <<"->"<
void ShowList(const T& val, Args... args)
{
	//ShowList(),每次进来都从Args...参数包中提取一个
	cout << sizeof...(args) << endl;
	cout << val <<"->"<

2.逗号表达式展开参数包

template 
int PrintArgs(const T& t)
{
	cout << t << " ";

	return 0;
}

template 
void ShowList(Args... args)
{
	//列表初始化
	//由于是逗号表达式,在创建数组的过程中,
	//会先执行逗号表达式前面的部分printarg(args)打印出参数
	//这个数组的目的纯粹是为了在数组构造的过程展开参数包
	int arr[] = { (PrintArgs(args), 0)... };
	//{(PrintArgs(args), 0)...}将会展开成((PrintArgs(arg1),0), 
    //(PrintArgs(arg2),0), (PrintArgs(arg3),0), etc... )

	cout << endl;
}

int main()
{
	ShowList(1, 'x', 1.1, string("hello Cris"));
	cout << endl;

	ShowList(1, 2, 3, 4, 5);

	return 0;
}
emplace系列的接口,支持模板的可变参数,并且万能引用。
相对insert和 emplace系列接口,例如emplace_back和push_back
emplace_back是直接构造,push_back是先构造,再移动构造。
两者差别也不大。

六、lambda表达式

6.1 lambda表达式语法

lambda表达式格式:[capture-list] (parameters) mutable -> return-type { statement}
  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来
    判断接下来的代码是否为lambda函数,捕捉列表 能够捕捉上下文中的变量 供lambda
    函数使用。
  • mutable:默认情况下,lambda函数总是一个const函数, mutable可以取消其常量
    。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • {statement}:函数体,在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量。
  • ->return-type:返回值类型。用追踪返回类型形式 声明函数的返回值类型 ,没有返回
    值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
    导。
  • (parameters):参数列表。 和普通函数的参数列表一样 ,如果不需要参数传递,则可以
    连同()一起省略

需要注意:在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空,而lambda底层是仿函数,如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。lambda表达式实际上可以理解为无名函数。

int main()
{
	int a = 10;
	int b = 7;
	int c = 4;
	int d = 6;
	int ret = 0;

	 //一般是局部匿名函数  也可以写到全局
	auto Add1 =  [](int x, int y)->double{return (x + y)/3.0; };

	//[a]:传值捕捉变量a
	auto Add2 = [a](int x, int y)->int{return (x + y)/3.0+a; };

	//[a,b]:传值捕捉变量a,b
	auto Add3 = [a, b]{return (a+b) / 3.0; };

	//[=]:传值捕捉全部对象
	auto Add4 = [=] {return a + b + c + d; };

	//[&a]:传引用捕捉变量a
	auto Add5 = [&a](int x, int y) {a = x + y; };

	//[&]:传引用捕捉全部对象
	auto Add6 = [&] { ret = a + b + c + d; };

	//[a,b,&ret]:传值捕捉变量a和b ,传引用捕捉变量ret
	auto Add7 = [a, b, &ret] {ret = a + b; };

	//ret传引用捕捉 其他全部传值捕捉
	auto Add8 = [=, &ret] {ret = a + b + c + d; };

	return 0;
}

需要注意的是:

  • 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • lambda表达式之间不能相互赋值,即使看起来类型相同
void(*PF)();

int main()
{
	auto f1 = []{cout << "hello world" << endl; };
	auto f2 = []{cout << "hello world" << endl; };

	//不能相互赋值
	//f1 = f2;
	
	//可以使用一个lambda表达式拷贝构造一个新的副本
	auto f3(f2);

	//也可以将lambda表达式赋值给相同类型的函数指针
	//了解一下,一般不建议下面这样用
	PF = f2;
	PF();

	return 0;
}

七、包装器

 7.1 function包装器

std::function在头文件,包装成统一的使用方式更方便快捷效率高
#include 
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()
{
	// 函数名(函数指针)
	std::function func1 = f;
	cout << func1(1, 2) << endl;

	// 函数对象
	std::function func2 = Functor();
	cout << func2(1, 2) << endl;

	// lamber表达式
	std::function func3 = [](const int a, const int b)
	{return a + b; };
	cout << func3(1, 2) << endl;

	// 类的成员函数
	//static修饰的,&可加可不加,最好加
	std::function func4 = &Plus::plusi;
	cout << func4(1, 2) << endl;

	//非static修饰的函数,要加&,比如&Plus::plusd
	//不加,Plus::plusd则会报错
	//而且还要传多个Plus()
	std::function func5 = &Plus::plusd;
	cout << func5(Plus(), 1.1, 2.2) << endl;

	return 0;
}

 7.2 bind

 std::bind函数可以实现参数顺序调整等操作

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

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


class Plus
{
public:
	Plus(int x = 2)
		:_x(x)
	{}

	int plusi(int a, int b)
	{
		return (a + b) * _x;
	}
private:
	int _x;
};

int main()
{
	std::function func1 = f;
	cout << func1(1, 2) << endl;

	std::function func2 = Functor();
	cout << func2(10, 20) << endl;

	// 3个参数
	std::function func3 = &Plus::plusi;
	cout << func3(Plus(), 100, 200) << endl;

	// 调整可调用对象的参数个数和顺序
	// _1 _2 _3... 表示你要自己传的那些参数,_1表示第一个参数传给_1
	
	// 调整参数个数
	// 2个参数
	//(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2)
	// 第一位&Plus::plusi是函数,第二位Plus(10)是固定的参数   
	std::function func4 = std::bind(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2);

	// 1个参数
	//placeholders::_1 是需要传的那个参数
	std::function func5 = std::bind(&Plus::plusi, Plus(10),
		10, placeholders::_1);

	cout << func5(200) << endl;

	// 调整顺序 -- 用处不大
	std::function func6 = std::bind(f, placeholders::_2, placeholders::_1);
	//func1传后参数(66,77)
	cout << func1(66, 77) << endl;
	//func6传后参数(77,66)
	cout << func6(66, 77) << endl;

	map> opFuncMap =
	{
		{ "普通函数指针", f },
		{ "函数对象", Functor() },
		{ "成员函数指针", std::bind(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2) }
	};

	cout << opFuncMap["普通函数指针"](1, 2) << endl;
	cout << opFuncMap["函数对象"](1, 2) << endl;
	cout << opFuncMap["成员函数指针"](1, 2) << endl;


	return 0;
}

你可能感兴趣的:(C++,c++,算法,数据结构)