【C++进阶】C++11特性(下)

1.可变参数模板

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比
C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改
进。
可变参数模版基本使用如下

// C printf 只能打印内置类型
// C++打印任意类型对象的可变参数函数呢
template <class ...Args>
void Cpp_Printf(Args... args)
{
	// 计算参数包的数据个数
	cout << sizeof...(args) << endl;

	// error C3520: “args”: 必须在此上下文中扩展参数包
	// 不支持
	for (size_t i = 0; i < sizeof...(args); i++)
	{
		cout << args[i] << endl;
	}
	cout << endl;
}

int main()
{
	Cpp_Printf(1);
	Cpp_Printf(1, 'A');
	Cpp_Printf(1, 'A', std::string("sort"));

	return 0;
}

// 编译时,参数推到递归
void _Cpp_Printf()
{
	cout << endl;
}

template <class T, class ...Args>
void _Cpp_Printf(const T& val, Args... args)
{
	cout << val << endl;

	_Cpp_Printf(args...);
}

template <class ...Args>
void Cpp_Printf(Args... args)
{
	_Cpp_Printf(args...);
}

int main()
{
	Cpp_Printf(1.1);
	Cpp_Printf(1.1, 'x');
	Cpp_Printf(1, 'A', std::string("sort"));

	return 0;
}

 底层其实是这样的!!!编译时推导

void _Cpp_Printf(const std::string& val)
{
	cout << val << endl;
	_Cpp_Printf();
}

void _Cpp_Printf(const char& val, std::string z)
{
	cout << val << endl;
	_Cpp_Printf(z);
}

void _Cpp_Printf(const int& val, char y, std::string z)
{
	cout << val << endl;
	_Cpp_Printf(y, z);
}

void Cpp_Printf(int x, char y, std::string z)
{
	_Cpp_Printf(x, y, z);
}

 第二种可变参数包的使用

template <class T>
int PrintArg(T t)
{
	cout << t << " ";
	return 0;
}

template <class ...Args>
void Cpp_Printf(Args... args)
{
	// 编译时推导,args...参数有几个值,PrintArg就调用几次,就有几个返回值,arr就开多大
	int arr[] = { PrintArg(args)... };
	cout << endl;
}

void Cpp_Printf(int x, char y, std::string z)
{
	int arr[] = { PrintArg(x),PrintArg(y),PrintArg(z) };
	cout << endl;
}

int main()
{
	/*Cpp_Printf(1.1);
	Cpp_Printf(1.1, 'x');*/
	Cpp_Printf(1, 'A', std::string("sort"));

	return 0;
}

首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insert和
emplace系列接口的优势到底在哪里呢?
 

int main()
{
	// 没区别
	list<bit::string> lt;

	bit::string s1("xxxxxxxxxxxx");
	lt.push_back(s1);
	lt.push_back(move(s1));

	cout << endl;

	bit::string s2("xxxxxxxxxxxx");
	lt.emplace_back(s2);
	lt.emplace_back(move(s2));

	return 0;
}

int main()
{
	// 有区别
	bit::list<pair<bit::string, int>> lt;

	pair<bit::string, int> kv1("xxxxx", 1);
	//lt.push_back(kv1);
	lt.push_back(move(kv1));

	cout << endl;

	// 直接传pair的对象效果跟push_back系列是一样的
	pair<bit::string, int> kv2("xxxxx", 1);
	lt.emplace_back(kv2);
	lt.emplace_back(move(kv2));

	// 直接传构造pair的参数包,参数包一直往下传,底层直接构造
	lt.emplace_back("xxxxx", 1);

	return 0;
}

 所以我们使用emplace系列,自定义类型其实差别不大,因为自定义类型会走移动构造,内置类型可能会有差别,因为内置类型会走浅拷贝,而emplace系列直接构造!!!

 

 

直接emplace或者push/insert 传右值对象,构造+移动构造

 直接emplace 传参数包,直接构造

有移动构造深拷贝对象,差别不大,因为移动构造的代价很小

直接emplace或者push/insert 传右值对象或者浅拷贝右值对象,构造+拷贝构造

 直接emplace 传参数包,直接构造

代价就大了很多,因为拷贝构造代价很大,没有移动构造的浅拷贝对象,差别也比较大。

总结

以后使用容器插入接口,推荐emplace系列

push系列/insert的接口,推荐用emplace代替

其次emplace能用参数包就用参数包。

emplace传参建议:参数包>右值>左值

2.包装器

 2.1 function包装器

function包装器
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
那么我们来看看,我们为什么需要function呢?
首先我们总结一下学过的一些可调用对象

函数指针   仿函数对象   lambda表达式

function基本使用

#include<functional>

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

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

// 不是定义可调用对象,包装可调用对象
int main()
{
	function<int(int, int)> fc1;

	//function<int(int, int)> fc2(f);
	function<int(int, int)> fc2 = f;
	function<int(int, int)> fc3 = Functor();
	function<int(int, int)> fc4 = [](int x, int y) {return x + y;};

	cout << fc2(1, 2) << endl;
	//cout << fc2.operator()(1, 2) << endl;

	cout << fc3(1, 2) << endl;
	cout << fc4(1, 2) << endl;

	return 0;
}

可以解决模板的效率低下,实例化多份的问题!!!

#include<functional>

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()
{
	// 函数名
	cout << useF(f, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lambda表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	cout << endl << endl;

	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表达式
	std::function<double(double)> func3 = [](double d)->double { return d /
		4; };
	cout << useF(func3, 11.11) << endl;

	return 0;
}

 所以function可以使可调用对象进行类型统一!!!

举个使用场景,一个LeetCode题

逆波兰表达式求值

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        map<string, function<int(int, int)>> m = {
            {"+", [](int a, int b){return a + b;}}, 
            {"-", [](int a, int b){return a - b;}}, 
            {"*", [](int a, int b){return a * b;}}, 
            {"/", [](int a, int b){return a / b;}}};

        for(auto& str : tokens)
        {
            if(m.count(str))//操作符
            {
                function<int(int, int)> func = m[str];
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                st.push(func(left, right));
                // st.push(m[str](left, right));
            }
            else//操作数
            {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

 包装成员函数指针

// 包装成员函数指针
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)> fc1 = &Plus::plusi;
	cout << fc1(1, 2) << endl;

	function<double(Plus*, double, double)> fc2 = &Plus::plusd;
	Plus plus;
	cout << fc2(&plus, 1.1, 2.2) << endl;

	function<double(Plus, double, double)> fc3 = &Plus::plusd;
	cout << fc3(Plus(), 1.1, 2.2) << endl;

	return 0;
}

2.2 bind

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可
调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而
言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M
可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺
序调整等操作。

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

#include<map>
class Sub
{
public:
	Sub(int x)
		:_x(x)
	{}

	int sub(int a, int b)
	{
		return (a - b) * _x;
	}

private:
	int _x;
};

void fx(const string& name, int x, int y)
{
	cout << name << "->[" << "血量:" << x << ",蓝:" << y << ']' << endl;
}

template<class T>
void fy(int n)
{
	T* p = new T[n];
}

// 调整可调用对象的参数个数或者顺序

int main()
{
	//auto f1 = Sub;
	//cout << f1(10, 5) << endl;

	// 调整顺序
	/*auto f2 = bind(Sub, placeholders::_1, placeholders::_2);
	cout << f2(10, 5) << endl;*/

	//cout << typeid(f1).name() << endl;
	//cout << typeid(f2).name() << endl;


	auto f3 = bind(&Sub::sub, placeholders::_1, placeholders::_2, placeholders::_3);
	cout << f3(Sub(1), 10, 5) << endl;

	Sub sub(1);
	cout << f3(&sub, 10, 5) << endl;

	// 绑定,调整参数个数
	auto f4 = bind(&Sub::sub, Sub(1), placeholders::_1, placeholders::_2);
	cout << f4(10, 5) << endl;

	auto f5 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2);
	cout << f5(10, 5) << endl;

	/*fx("王昭君", 80, 20);
	fx("王昭君", 85, 10);
	fx("王昭君", 99, 0);
	fx("王昭君", 99, 80);

	fx("亚瑟", 99, 80);
	fx("亚瑟", 91, 80);
	fx("亚瑟", 5, 80);*/

	auto f6 = bind(fx, "王昭君", placeholders::_1, placeholders::_2);

	f6(80, 20);
	f6(85, 10);
	f6(99, 0);
	f6(99, 80);

	auto f7 = bind(fx, "亚瑟", placeholders::_1, placeholders::_2);

	f7(80, 20);
	f7(85, 10);
	f7(99, 0);
	f7(99, 80);

	//auto f8 = bind(fx, placeholders::_1, 80, placeholders::_2);
	function<void(std::string, int)> f8 = bind(fx, placeholders::_1, 80, placeholders::_2);
	f8("武则天", 50);
	f8("韩信", 40);

	cout << typeid(f7).name() << endl;
	cout << typeid(f8).name() << endl;

	map<string, function<int(int, int)>> opFuncMap = {
		   {"+", [](int a, int b) {return a + b; }},
		   {"-", bind(&Sub::sub, Sub(10), placeholders::_1, placeholders::_2)},
		   {"*", [](int a, int b) {return a * b; }},
		   {"/", [](int a, int b) {return a / b; }}
	};

	fy<int>(10);

	return 0;
}

bind和function的底层本质都是仿函数!!! 

3.线程库

3.1 thread库的简单介绍

由于平台原因在C++11之前多线程都是与平台相关的像Windows,Linux都有自己的多线程接口,各不一样,C++11之后C++封装了自己的线程库,这样我们用户使用起来就更方便了!

C++11线程库需要包含头文件<thread>

函数名功能
thread()构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
thread(fn,
args1, args2,
...)
构造一个线程对象,并关联线程函数fn,args1,args2,...为线程函数的
参数
get_id()获取线程id
jionable()线程是否还在执行,joinable代表的是一个正在执行中的线程。
jion()该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
detach()在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离
的线程变为后台线程,创建的线程的"死活"就与主线程无关

 get_id()的返回值类型为id类型,id类型实际为std::thread命名空间下封装的一个类,该类中
包含了一个结构体:

void Print(int n, int i)
{
	for (; i < n; i++)
	{
		cout << i << endl;
	}
	cout << endl;
}

int main()
{
	thread t1(Print, 100, 0);
	thread t2(Print, 200, 100);

	//获取新线程ID
	cout << t1.get_id() << endl;
	cout << t2.get_id() << endl;

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

	//全局的一个类中封装的获取线程ID --- 可以获取主线程ID
	cout << this_thread::get_id() << endl;

	return 0;
}

thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个
线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
 

int main()
{
	vector<thread> vthd;
	int n = 5;
	vthd.resize(n);

	atomic<int> x = 0;//原子性操作
	auto func = [&](int n) {
			for (size_t i = 0; i < n; i++)
			{
				++x;
			}
			//mtx.unlock();
		};
	for (auto& thd : vthd)
	{
		// 移动赋值
		thd = thread(func, 100000);
	}

	for (auto& thd : vthd)
	{
		thd.join();
	}
	cout << x << endl;
	printf("%d\n", x.load());

	return 0;
}

 

3.2 线程函数参数传递方式

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在
线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。可以传递多个参数!

void Print(int n, int& rx, mutex& rmtx)
{	
	rmtx.lock();

	for (int i = 0; i < n; i++)
	{
		// t1 t2
		++rx;
	}

	rmtx.unlock();
}

int main()
{
	int x = 0;
	mutex mtx;
	/*thread t1(Print, 1000000, x, mtx);
	thread t2(Print, 2000000, x, mtx);*/
	thread t1(Print, 1000000, ref(x), ref(mtx));//ref可以强制进行类似引用式的传递
	thread t2(Print, 2000000, ref(x), ref(mtx));

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

	cout << x << endl;

	return 0;
}

注意:如果是类成员函数作为线程参数时,必须将this作为线程函数参数。
 

3.3 原子性操作库

多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问
题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数
据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。
所以我们需要原子操作,原子操作没有中间状态。原子操作库

atmoic<T> t; // 声明一个类型为T的原子类型变量t

3.4 lock_guard与unique_lock

1.mutex

mutex文档介绍

函数名函数功能
lock()上锁:锁住互斥量
unlock()解锁:释放对互斥量的所有权
try_lock()尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻

调用lock时注意:如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)即严禁调用加锁操作两次!
调用try_lock时注意:如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。

std::recursive_mutex
其允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,
释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。
std::timed_mutex比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until() 。

try_lock_for()
接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与
std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回
false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超
时(即在指定时间内还是没有获得锁),则返回 false。


try_lock_until()
接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,
如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指
定时间内还是没有获得锁),则返回 false。
std::recursive_timed_mutex

2.lock_guard

lock_guard文档介绍

锁的守卫,一个锁的对象,对象销毁时自动析构解锁。

class LockGuard
{
public:
	LockGuard(mutex& mtx)
		:_mtx(mtx)
	{
		_mtx.lock();
	}

	~LockGuard()
	{
		_mtx.unlock();
	}
private:
	mutex& _mtx;
};
int main()
{
	vector<thread> vthd;
	int n;
	cin >> n;
	vthd.resize(n);

	atomic<int> x = 0;
	//atomic<int> x{ 0 };
	//int x = 0;

	mutex mtx;
	auto func = [&](int n) {
		//mtx.lock();
		// 局部域
		{
			LockGuard lock(mtx);

			//lock_guard<mutex> lock(mtx);
			for (size_t i = 0; i < n; i++)
			{
				++x;
			}
			//mtx.unlock();
		}

	};

	for (auto& thd : vthd)
	{
		// 移动赋值
		thd = thread(func, 100000);
	}

	for (auto& thd : vthd)
	{
		thd.join();
	}
	cout << x << endl;
	printf("%d\n", x.load());

	return 0;
}

3.unique_lock

unique_lock文档介绍

unique_lock与lock_guard类似,只是多了可以再任何时刻进行解锁操作。

上锁/解锁操作:lock、try_lock、try_lock_for、try_lock_until和unlock
修改操作:移动赋值、交换(swap:与另一个unique_lock对象互换所管理的互斥量所有
权)、释放(release:返回它所管理的互斥量对象的指针,并释放所有权)
获取属性:owns_lock(返回当前对象是否上了锁)、operator bool()(与owns_lock()的功能相
同)、mutex(返回当前unique_lock所管理的互斥量的指针)。

3.5 支持两个线程交替打印,一个打印偶数一个打印奇数

condition_variable文档介绍

int main()
{
	std::mutex mtx;
	condition_variable c;
	int n = 100;
	bool flag = true;

	thread t2([&]() {
		int j = 1;
		while (j < n)
		{
			unique_lock<mutex> lock(mtx);

			// 只要flag == true t2一直阻塞
			// 只要flag == false t2不会阻塞
			while (flag)
				c.wait(lock);

			cout << j << endl;
			j += 2; // 奇数
			flag = true;

			c.notify_one();
		}
		});

	// 第一个打印的是t1打印0
	thread t1([&]() {
		int i = 0;
		while (i < n)
		{
			unique_lock<mutex> lock(mtx);
			// flag == false t1一直阻塞
			// flag == true t1不会阻塞
			while (!flag)
			{
				c.wait(lock);
			}

			cout << i << endl;

			flag = false;
			i += 2; // 偶数

			c.notify_one();
		}
	});


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

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

花影随风_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值