lesson5: C++11

1. 统一的列表初始化

#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

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

int main()
{
	int x1 = 1;
	
	// 能看懂,但不建议使用
	int x2 = { 2 };
	int x3 { 2 };

	Date d1(2022, 11, 22);// ->调用构造函数
	
	// 能看懂,但不建议使用
	Date d2 = {2022, 11, 11}; // ->调用构造函数
	Date d3{ 2022, 11, 11 };// ->调用构造函数

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

	list<int> lt1 = { 1, 2, 3, 4, 5, 6 };
	list<int> lt2{ 1, 2, 3, 4, 5, 6 };

	auto x = { 1, 2, 3, 4, 5, 6 };
	cout << typeid(x).name() << endl;

	return 0;
}

  •  C++11中增大了{ }的使用范围,要求能看懂,但是不建议使用

 1.1 {}在自定义类型中的应用

#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

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

int main()
{
	Date d1(2022, 11, 22);
	Date d2 = {2022, 11, 11}; 
	Date d3{ 2022, 11, 11 };

	vector<Date> v3 = {d1, d2, d3};
	
	// C++11中{}的真正用途
	vector<Date> v4 = { { 2022, 1, 1 }, {2022, 11, 11} };

	map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } };

	// 赋值重载
	// 这里的auto无法自动推导,所以自己显示类型
	initializer_list<pair<const string, string>> kvil = { { "left", "左边" }, { "left", "左边" } };
	dict = kvil;

	return 0;
}

  • vector<Date> v4 = { { 2022, 1, 1 }, {2022, 11, 11} };

 1.2 initializer_list的引入

 

 

  •   C++11增加了 initializer_list,以及对vector和list等容器的更新,所以才支持上面的{}用法

1.3 小结

  • C++11以后一切对象都可以用列表初始化,但是建议普通对象还是用以前的方式初始化,
  • 容器如果有需求的话就可以用列表初始化

 2. decltype的引入

#include <iostream>
using namespace std;

int main()
{
	int x = 10;

	// typeid拿到只是类型的字符串,不能用这个再去定义对象什么的
	//typeid(x).name() y = 20;

	decltype(x) y1 = 20.22;
	auto y2 = 20.22;

	cout << y1 << endl;
	cout << y2 << endl;

	return 0;
}

  •  typeid拿到只是类型的字符串,不能用这个再去定义对象什么的
  • decltype不仅仅可以拿到变量的类型,还可以去定义对象

 3. C++11新增的容器

  •  unordered_mapunordered_set比较有用

 3.1 容器array

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

int main()
{
	const size_t N = 100;
	int a1[N];

	// C语言数组越界检查,越界读基本检查不出来,越界写是抽查
	a1[N];
	//a1[N] = 1;
	//a1[N + 5] = 1;


	// 越界读写都可以被检查出来
	array<int, 1> a2;
	a2[N];
	a2[N] = 1;
	a2[N + 5] = 1;

	return 0;
}
  • array容器只要是越界就一定能被检查出来
  • C语言数组越界检查,越界读基本检查不出来,越界写是抽查

array实际使用情况

  • array用得很少,一方面大家用c数组用惯了
  • 用array不如用vector + resize去替代c数组

4. 左值引用& 和 右值引用&&

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

int main()
{
	// 左值: 能取地址
	int* p = new int(0);
	int b = 1;
	const int c = 2;

	// 对左值的左值引用
	int*& rp = p;
	int& rb = b;
	const int& rc = c;
	int& pvalue = *p;

	// 右值:不能取地址
	10;
	1.1 + 2.2;

	// 对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = 1.1 + 2.2;

	// 对右值的左值引用
	// double& r1 = 1.1 + 2.2;// error
	const double& r1 = 1.1 + 2.2;

	// 对左值的右值引用
	//int&& rr5 = b;// error
	int&& rr5 = move(b);

	return 0;
}
  • 左值: 能取地址
  • 右值: 不能取地址,如:临时对象,const的不能被修改的对象
  • 如果对右值使用左值引用,需要加上const
  • 如果对左值使用右值引用,需要是使用move函数

4.1 左值引用可以解决的问题 

  1.  做参数,a. 减少拷贝 b.做输出型参数
  2. 做返回值,a.减少拷贝, 提高效率 b.引用返回,可以修改返回对象(比如: operator[])

4.2 左值引用无法解决的问题 

  •  引用返回的前提是返回值出了作用域之后还在,
  • 但是无法解决string中的to_string的返回值,以及有些函数返回值是二维数组的问题

 4.3 右值引用的实际用途: 移动构造 + 移动赋值

 4.3.1 没有移动构造

  •  这里的string需要自己写,才能看到结果
  • 这里的g++编译器优化的更厉害,这里虽然定义了ret
    但是没有使用,所以g++一次都没有拷贝构造
  • -fno-elide-constructors可以取消编译器的优化

 4.3.2 加上移动构造

  •  函数参数的匹配原则是会优先匹配最合适自己的参数,
  • to__string(-3456)中的-3456是一个右值,会优先匹配到移动构造,会发生右值引用返回
  • 右值:1.内置类型右值-纯右值 2. 自定义类型右值 - 将亡值
  • 将亡值就是快要亡了的值,所以它的值就可以直接交换(swap)
  • 将拷贝构造变成移动构造,会极大的提高效率

4.3.3 没加移动赋值

  •  g++编译器优化的很厉害

 4.3.4 加上移动赋值

  •  移动赋值和移动构造一样,减少了拷贝,提高了效率

4.4 容器插入接口->右值版本 

 

  •  C++11以后,几乎所有的容器插入接口都提供了右值版本
  •  插入过程中,如果传递对象是右值对象,那么就会进行资源转移减少拷贝

 4.5 引用折叠

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

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; }


// 万能引用:t既能引用左值,也能引用右值
// 引用折叠
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);		      // const 左值
	PerfectForward(std::move(b)); // const 右值

	return 0;
}

  •  模板参数的右值引用叫万能引用,T&& t中的t即能引用左值,也能引用右值
  • 这里会触发引用折叠,编译器会把它识别成左值

4.5.1 完美转发解决引用折叠问题

  • 完美转发:保持t引用对象属性(是编译器能分出左值和右值) 
  • 它通过std::forward<模板>(参数)实现

5. 新的类功能

 5.1 C++中默认成员函数

  • 构造 析构 拷贝构造
  • 赋值重载 取地址重载 const取地址重载
  • 和C++11新增的移动构造,移动赋值

5.2 移动构造 && 移动赋值的注意事项 

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任 意一个。
  • 那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,
    • 对于内置类 型成员会执行逐成员按字节拷贝
    • 自定义类型成员,则需要看这个成员是否实现移动构造,
      • 如果实现了就调用移动构造,没有实现就调用拷贝构造
  • 移动赋值运算符重载同上

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

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

 

 6. 可变参数模板

#include <iostream>
using namespace std;

// 可变参数的函数模板
template <class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}

int main()
{
	string str("hello");
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', str);

	return 0;
}

  • sizeof...(args)// 求模板参数的个数

 6.1 使用方法

// 解决只有一个参数的情况
void ShowList()
{
	cout << endl;
}

// Args... args代表N个参数包(N >= 0)
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << "ShowList("<<val<<", " << sizeof...(args) << "参数包)" << endl;
	ShowList(args...);
}

int main()
{
	string str("hello");
	ShowList(1, 'A', str);

	return 0;
}

  •  通过递归调用去使用模板参数

7. emplace_back接口的引入

#include <list>
#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
	}

	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		cout << "Date(const Date& d)" << endl;
	}

	Date& operator=(const Date& d)
	{
		cout << "Date& operator=(const Date& d))" << endl;
		return *this;
	}

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

int main()
{
	list<Date> lt1;
	cout << "---------------------------------" << endl;
	lt1.push_back(Date(2022, 11, 16));
	cout << "---------------------------------" << endl;
	lt1.emplace_back(2022, 11, 16);
	cout << "---------------------------------" << endl;
	return 0;
}

  •  emplace_back和push_back对内置类型的处理是一样的
  •  因为emplace_back可以直接传参数,就只会发生构造,比push_back少一次的拷贝

8. lambda表达式

lambda表达式就像是函数的一种简略写法 

8.1 没引入lambda表达式之前 

#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
struct Goods
{
	string _name;
	double _price; //价格
	int _evaluate; //评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

struct ComparePriceLess
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};

struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, \
	{ "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
}
  • 在c++11之前要通过不同的标准排序

  • 就需要传不同的仿函数才能解决问题

8.2 引入lambda表达式之后 

#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
struct Goods
{
	string _name;
	double _price; //价格
	int _evaluate; //评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price < g2._price;
		});
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price > g2._price;
		});
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate < g2._evaluate;
		});
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate > g2._evaluate;
		});
}

8.3 lambda表达式语法 

[捕捉列表](参数列表)mutable->返回值类型{函数体实现}

  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
    性。使用该修饰符时, 参数列表不可省略 (即使参数为空)。
注意:
  • 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为 空
  • 因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
  • 定义了lambda,还需要调用
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
	// 两个数相加的lambda
	auto add1 = [](int a, int b)->int{return a + b; };
	cout << add1(1, 2) << endl;

	// 省略返回值
	auto add2 = [](int a, int b){return a + b; };
	cout << add2(1, 2) << endl;

	// 交换变量的lambda
	int x = 0, y = 1;
	auto swap1 = [](int& x1, int& x2)->void{int tmp = x1; x1 = x2; x2 = tmp; };
	swap1(x, y);
	cout << x << ":" << y << endl;

	auto swap2 = [](int& x1, int& x2)
	{
		int tmp = x1;
		x1 = x2;
		x2 = tmp;
	};

	swap2(x, y);
	cout << x << ":" << y << endl;

	return 0;
}

8.4 默认捕捉列表 

 

  • 不传参数,默认捕捉的对象不能修改

8.5 捕获列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针
#include <iostream>
using namespace std;

static int f = 1;// 全局
int ff = 2;// 全局
int func()
{
	int a, b, c, d, e;
	a = b = c = d = e = 1;

	// [=] 全部传值捕捉
	auto f1 = [=]() {
		cout << a << b << c << d << e << endl;
	};

	f1();// 调用

	//auto f2 = [=, a]() {};// error
	// 混合捕捉
	auto f2 = [=, &a]() {
		a++;
		cout << a << b << c << d << e << endl;
	};

	f2();// 调用
	static int x = 0;

	if (a)
	{
		// []也能捕捉全局变量
		auto f3 = [&, a]() {
			//a++;
			b++, c++, d++, e++, x++;
			cout << a << b << c << d << e << endl;
			f++, ff++;
			cout << f << " " << ff << endl;
		};

		f3();// 调用
	}


	return 0;
}

int main()
{
	func();
	 不同的栈帧,不可捕捉
	//auto f4 = [&, a]() {
	//	//a++;
	//	b++, c++, d++, e++, x++;
	//	cout << a << b << c << d << e << endl;
	//	f++, ff++;
	//	cout << f << " " << ff << endl;
	//};
	return 0;
}

  •  [&, a]表示除了a是值传递,其他的都是引用传递
  • auto f2 = [=, a]() {};
    • 捕捉列表不允许变量重复传递 ,否则就会导致编译错误
  • 块作用域以外的lambda函数捕捉列表必须为空。
  • 不同的栈帧,不可捕捉,比如f4要去捕捉其他栈帧的变量 
#include <iostream>
using namespace std;

void (*PF)();
int main()
{
	auto f1 = [] {cout << "hello world" << endl; };
	auto f2 = [] {cout << "hello world" << endl; };
	
	//f1 = f2// error
	
	// 允许使用一个lambda表达式拷贝构造一个新的副本
	auto f3(f2);
	f3();
	
	// 可以将lambda表达式赋值个相同类型的函数指针
	PF = f2;
	PF();
	return 0;
}
  •  lambda表达式之间不能相互赋值
  •  但是允许使用一个lambda表达式拷贝构造一个的副本
  •  但是可以将lambda表达式赋值给相同类型的函数指针

8.6 函数对象与lambda表达式

#include <iostream>
using namespace std;
class Rate
{
public:
	Rate(double rate) : _rate(rate)
	{}

	double operator()(double money, int year)
	{
		return money * _rate * year;
	}

private:
	double _rate;
};

// lambda_uuid
class lambda_xxxx
{

};

int main()
{
	// 函数对象
	double rate = 0.49;
	Rate r1(rate);
	r1(10000, 2);

	// 仿函数lambda_uuid
	// lambda -> lambda_uuid
	auto r2 = [=](double monty, int year)->double{return monty*rate*year; };
	r2(10000, 2);

	auto r3 = [=](double monty, int year)->double{return monty*rate*year; };
	r3(10000, 2);

	return 0;
}

  •  实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

9. function包装器

一般用来包装lambda表达式

  • Ret: 被调用函数的 返回类型
  • Args…:被调用 函数的形参
#include <iostream>
#include <functional>
using namespace std;

template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;

	return f(x);
}

double f(double i)
{
	return i / 2;
}

struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};

int main()
{
	// 使用包装器
		// 函数指针
		function<double(double)> f1 = f;
		cout << useF(f1, 11.11) << endl;

		// 函数对象
		function<double(double)> f2 = Functor();
		cout << useF(f2, 11.11) << endl;

		// lamber表达式对象
		function<double(double)> f3 = [](double d)->double{ return d / 4; };
		cout << useF(f3, 11.11) << endl;

	// 不使用包装器
		// 函数名
	
		cout << useF(f, 11.11) << endl;
		// 函数对象
	
		cout << useF(Functor(), 11.11) << endl;
	
		// lamber表达式
		cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

  • function就是一种适配器,是 lambda的另一种引用
  • C++中的function本质是一个类模板,也是一个包装器。 

 9.1 案例:逆波兰表达式求值

  •  包装器将多个相似的小函数包装在一起

 10. bind 绑定

bind绑定一般是配合function包装器 使用的 

#include <iostream>
#include <functional>
#include <map>
using namespace std;
int Div(int a, int b)
{
	return a / b;
}

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

int Mul(int a, int b, double rate)
{
	return a * b * rate;
}

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};

using namespace placeholders;

int main()
{
	// 调整个数, 绑定死固定参数
	function<int(int, int)> funcPlus = Plus;
	
	//function<int(Sub, int, int)> funcSub = &Sub::sub;
	function<int(int, int)> funcSub = bind(&Sub::sub, Sub(), _1, _2);
	function<int(int, int)> funcMul = bind(Mul, _1, _2, 1.5);
	
	map<string, function<int(int, int)>> opFuncMap = 
	{
		{ "+", Plus},
		{ "-", bind(&Sub::sub, Sub(), _1, _2)}
	};

	cout << funcPlus(1, 2) << endl;
	cout << funcSub(1, 2) << endl;
	cout << funcMul(2, 2) << endl;

	cout << opFuncMap["+"](1, 2) << endl;
	cout << opFuncMap["-"](1, 2) << endl;

	cout << "------------------------" << endl;
	int x = 2, y = 10;
	cout << Div(x, y) << endl;

	// 调整顺序, _1表示第一个参数,_2表示第二个参数
	function<int(int, int)> bindFunc2 = bind(Div, _2, _1);
	cout << bindFunc2(x, y) << endl;

	return 0;
}

可以将bind函数看作是一个通用的 函数适配器 ,它接受一个可调用对象,生成一个新的可调用对

来“适应”原对象的参数列表。

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

  • 其中,newCallable本身是一个可调用对象
  • arg_list是一个逗号分隔的参数列表
  • 对应给定的 callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数
  • arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示 newCallable的参数,它们占据了传递给newCallable的参数的“位置”。
    • 数值n表示生成的可调用对象中参数的位置
      • _1为newCallable的第一个参数,_2为第二个参数,以此类推。

10.1 bind包装器的意义

  • 将一个函数的某些参数绑定为固定的值,让我们在调用时可以不用传递某些参数。
  • 可以对函数参数的顺序进行灵活调整。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是完整的C++代码: ```c++ #include <iostream> #include <cstring> #include <algorithm> using namespace std; struct Student{ int id; string name; int score[3]; double average; bool fail; }stu[4]; bool cmp(Student a, Student b){ return a.average > b.average; } int main(){ int lesson[3]; for(int i=0; i<3; i++){ cin >> lesson[i]; } for(int i=0; i<4; i++){ cin >> stu[i].id >> stu[i].name >> stu[i].score[0] >> stu[i].score[1] >> stu[i].score[2]; stu[i].average = (stu[i].score[0] + stu[i].score[1] + stu[i].score[2]) / 3.0; if(stu[i].average < 60){ stu[i].fail = true; } else{ stu[i].fail = false; } } double max_avg = -1, min_avg = 101; int max_pos = -1, min_pos = -1; for(int i=0; i<4; i++){ if(stu[i].average > max_avg){ max_avg = stu[i].average; max_pos = i; } if(stu[i].average < min_avg){ min_avg = stu[i].average; min_pos = i; } } cout << "Student " << stu[max_pos].name << " got the highest average score as " << fixed << setprecision(4) << max_avg << endl; cout << "Student " << stu[min_pos].name << " got the lowest average score as " << fixed << setprecision(4) << min_avg << endl; sort(stu, stu+4, cmp); for(int i=0; i<4; i++){ cout << "Student id:" << stu[i].id << " Student name:" << stu[i].name << endl; cout << "lesson_id " << lesson[0] << " " << lesson[1] << " " << lesson[2] << " " << "Average" << endl; cout << "scores " << stu[i].score[0] << " " << stu[i].score[1] << " " << stu[i].score[2] << " " << fixed << setprecision(4) << stu[i].average << endl; if(stu[i].fail){ cout << "The student failed." << endl; } else{ cout << "The student didn't fail." << endl; } cout << "------华丽的分割线--------" << endl; } return 0; } ``` 代码思路: 1. 定义结构体 `Student`,存储学生的信息; 2. 读入三门课的课程号; 3. 逐次读入四位学生的信息,计算出平均分,并判断是否不及格; 4. 找出最高平均分和最低平均分的学生位置; 5. 对学生数组进行排序,按照平均分从高到低排序; 6. 按照输出格式输出最高分、最低分和排序后的结果。 注意事项: 1. 使用 `setprecision()` 函数控制浮点数的精度; 2. 本题中的分隔符是中文的“华丽的分割线”,需要注意输出时的中英文符号混用问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值