C++11

C++11

1.初始化列表
1.1内置类型的初始化列表
int main()
{
	//内置类型变量
	int a1 = { 10 };
	int a2(10);
	int a3 = 1 + 1;
	int a4 = { 1 + 1 };
	int a5{ 1 + 1 };
	//数组
	int arr1[5]{ 1,2,3,4,5 };
	int arr2[]{ 1,2,3,4,5 };
	//动态数组
	int* arr3 = new int[5]{ 1,2,3,4,5 };
	//标准容器
	vector<int> v{ 1,2,3,4,5 };
	map<int, int> m{ {1,1},{2,2},{3,3} };
	return 0;
}
1.2自定义类型的初始化列表

1.标准库主持单个对象的列表初始化

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()
{
	Date d{ 2020,1,1 };
	return 0;
}

2.多个对象的列表初始化

多个对象的列表初始化,需要给该类添加一个initializer_list模型参数的构造函数即可。

#include <initializer_list>
template<class T>
class Vector
{
public:
	Vector(initializer_list<T> l)
		:_capacity(l.size())
		,_size(0)
	{
		_array = new T[_capacity];
		for (auto e : l)
			_array[_size++] = e;
	}
	Vector<T>& operator=(initializer_list<T> l)
	{
		delete[] _array;
		size_t i = 0;
		for (auto e : l)
			_array[i++] = e;
		return *this;
	}
private:
	T* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Vector<int> nums = { 1,2,3,4 };
	nums = { 2,3,4 };
	return 0;
}
2.变量类型推导

在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有的情况下可能不知道需要实际类型怎么给,或者类型写起来特别复杂。

C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便,而且更加简洁。

2.1decltype推导

auto使用的前提是必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。但有时候需要根据表达式运行完成之后结果的类型进行推导,auto也就无能为力。

1.推演表达式类型作为变量的自定义类型
int main()
{
	double a = 10;
	int b = 10;
	decltype(a + b) c;
	cout << typeid(c).name() << endl;
	return 0;
}
2.推演函数返回值的类型
void* func(size_t n)
{
	return malloc(n);
}

int main()
{
    //如果没有参数,推导函数的类型
	cout << typeid(decltype(func)).name() << endl;
    //如果带参数列表,推导的是函数返回值的类型,只是推演并不会执行函数
	cout << typeid(decltype(func(0))).name() << endl;
	return 0;
}

运行时类型识别的缺陷是降低程序运行的效率。

3.默认成员函数控制
3.1显式缺省函数

在C++11中,可以在默认函数定义或者声明时附加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。

class A
{
public:
	A(int a)
		:_a(a)
	{}
	//显式缺省构造函数,由编译器生成
	A() = default;

	A& operator=(const A& a);
private:
	int _a;
};

A& A::operator=(const A& a) = default;
int main()
{
	A a1(10);
	A a2;
	a2 = a1;
	return 0;
}
3.2删除默认函数

如果想要限制某些默认函数的生成,在C++98中是该函数设置为平private,并且不给定义。在C++11中只需要在该函数声明加上=delete即可,该语法知识编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class A
{
public:
	A(int a)
		:_a(a)
	{}
	//显式缺省构造函数,由编译器生成
	A() = delete;

	A& operator=(const A& a) = delete;
private:
	int _a;
};

int main()
{
	A a1(10);
	//编译失败,因为没有默认构造函数
	//A a2;
	//编译失败,因为没有赋值运算符重载
	//a2 = a1;
	return 0;
}
4.右值引用

为了提高程序运行效率,C++11中引入了右值引用,右值引用也是别名,但其只能对右值引用。

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

int main()
{
	int&& ra = 10;
	int&& ret = add(1, 2);
	return 0;
}
4.1左值与右值

一般认为:可以放在=左边的,或者能够取地址的称为左值,只能放在=右边的,或者不能取地址的称为右值,但是也不一定完全正确。左值通常是变量,右值通常是常量,表达式或者函数返回值(临时对象)。

int g_a = 10;
// 函数的返回值结果为引用,是左值
int& GetG_A()
{
	return g_a;
}

//函数的返回值是临时变量,是右值
int Get()
{
	return g_a;
}
int main()
{
	int a = 10;
	int b = 20;

	// a和b都是左值,b既可以在=的左侧,也可在右侧,
	// 说明:左值既可放在=的左侧,也可放在=的右侧
	a = b;
	b = a;

	const int c = 30;
	// 编译失败,c为const常量,只读不允许被修改
	//c = a;
	// 因为可以对c取地址,因此c严格来说不算是右值
	cout << &c << endl;

	// 编译失败:因为b+1的结果是一个临时变量,没有具体名称,也不能取地址,因此为右值
	//b + 1 = 20;
    
	GetG_A() = 100;
    
	int ret = Get();
	return 0;
}

因此关于左值与右值的区分不是很好区分,一般认为:

a.普通类型的变量,因为有名字,可以取地址,都认为是左值。

b.const修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址(如果只是const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间),C++11认为其是左值。

c.如果表达式的运行结果是一个临时变量或者对象,认为是右值。

d.如果表达式运行结果是一个引用则认为是左值。

C++11对右值进行了严格的区分:

a.C语言中的纯右值,比如:a+b, 100

b.将亡值。比如:表达式的中间结果、函数按照值的方式进行返回。

4.2引用与右值引用比较
int main()
{
	int x = 1, y = 2;
	
	// 左值引用的定义
	int a = 0;
	int& b = a;

	// 左值引用不能引用右值, const左值引用可以
	//int& e = 10; 
	//int& f = x + y;
	const int& e = 10;
	const int& f = x + y;

	// 右值引用的定义
	int&& c = 10;
	int&& d = x + y;

	// 右值引用不能引用左值,但是可以引用move后左值
	//int&& m = a;
	int&& m = move(a);
	return 0;
}

普通引用只能引用左值,不能引用右值,const引用既可引用左值,也可引用右值。右值引用:只能引用右值,一般情况不能直接引用左值,但是可以引用move后的左值。

4.3右值引用的应用
4.3.1做函数参数
int func(int& a)
{
	return a;
}

int func(int&& a)
{
	return a;
}
int main()
{
	int a = 10;
    //这里会匹配左值引用参数的func
	func(a);
    //这里会匹配右值引用参数的func
	func(10);
	return 0;
}
4.3.2移动语义

即将一个对象中资源移动到另一个对象中的方式,必须使用右值引用。

class String
{
public:
	String(const char* str = "")
	{
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	String(const String& s)
	{
		cout<<"String(const String& s)-拷贝构造-效率低"<<endl;

		_str = new char[strlen(s._str) + 1];
		strcpy(_str, s._str);
	}

	String(String&& s)
		:_str(nullptr)
	{
		// 传过来的是一个将亡值,反正你都要销毁了
		// 不如把你的空间和值给我
		cout << "String(String&& s)-移动构造-效率高" << endl;
		swap(_str, s._str);
	}
	
	String& operator=(const String& s)
	{
		cout << "String& operator=(const String& s)-拷贝赋值-效率低" << endl;

		if (this != &s)
		{
			char* newstr = new char[strlen(s._str) + 1];
			strcpy(newstr, s._str);

			delete[] _str;
			_str = newstr;
		}

		return *this;
	}

	String& operator=(String&& s)
	{
		cout << "String& operator=(String&& s)-移动赋值-效率高" << endl;
		swap(_str, s._str);

		return *this;
	}

	~String()
	{
		delete[] _str;
	}
private:
	char* _str;
};

String f(const char* str)
{
	String tmp(str);
	return tmp; // 这里返回实际是拷贝tmp的临时对象
}

int main()
{
	String s1("左值");
	String s2(s1);                // 参数是左值
	String s3(f("右值-将亡值"));   // 参数是右值-将亡值(传递给你用,用完我就析构了)
	String s4("左值");
	s4 = s1;
	s4 = f("右值-将亡值");
    
    //当需要用右值引用引用一个左值时,要用到move函数。
    //它并不搬移任何东西,唯一的功能就是讲一个左值强制转化为右值引用。
    //下面语句在实现s5的拷贝时就会移动构造,s1移动到s5中,s1变成了无效的字符串。
    String s5(move(s1));
	return 0;
}
4.4完美转发

完美转发是指在函数模板中,完全按照模板的参数的类型,将参数传递给函数模板中调用另外一个函数。

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 func(T &&t)
{
	// 右值引用会第二次之后的参数传递过程中右值属性丢失,下一层调用会全部识别为左值
	//Fun(t);
    
    //C++11通过forward函数来实现完美转发
    Fun(forward<T>(t));
}

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

	int a;
	func(a); //左值
	func(move(a)); //右值

	const int b = 8;
	func(b); //const 左值
	func(move(b)); //const 右值
	return 0;
}
5.lambda表达式
struct Goods
{
	string _name;
	double _price;
};

对于一类商品,我们在浏览的时候,可以根据名字升序和降序浏览,还可以根据价格升序和降序浏览等等。那么我们对他进行排序的时候,就要写多个仿函数来实现,比较复杂。因此在C++11中出现了lambda表达式。

int main()
{
	Goods gds[] = { { "苹果", 2.1 }, { "香蕉", 3 }, { "葡萄", 2.2 }, {"菠萝", 1.5} };
	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool
		{
		return g1._price < g2._price;
		});
	return 0;
}
5.1lambda表达式语法
//lambda表达式书写格式
[capture-list](parameters)mutable->return-type{statement}
5.1.1lambda表达式各部分说明

[capture-list]:捕捉列表。编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。

(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。

mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省缺(即使参数为空)。

return-type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。

statement:函数体。在该函数体内,除了可以使用参数外,还可以使用所有捕获到的变量。

5.1.2捕捉列表说明

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

int main()
{
	int a = 5, b = 7;
    //传值捕捉a和b
	auto add1 = [a, b]()->int {return a + b; };
    int ret1 = add1();
    //传值捕捉同一作用域中的所有对象
	auto add2 = [=]()->int {return a + b; };
    int ret2 = add2();
    //传引用捕捉a和b
	auto swap1 = [&a, &b] {int z = a; a = b; b = z; };
    swap2();
    //传引用捕捉同一作用域中的所有对象
	auto swap2 = [&] {int z = a; a = b; b = z; };
    swap3(a, b);
	return 0;
}
5.2lambda表达式用法

lambda表达式通常直接写为函数的参数,让代码的可读性更强,可以清晰的看到是按照什么排序。

int x8()
{
	Goods gds[] = { { "苹果", 2.1, 3 }, { "相交", 3.0, 5 }, { "橙子", 2.2, 9 }, { "菠萝", 1.5, 10 } };
	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool{return g1._price > g2._price; });
	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool{return g1._price < g2._price; });
	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool{return g1._num > g2._num; });
	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool{return g1._num < g2._num; });
	return 0;
}

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

6.线程库
6.1thread类的介绍
int x = 0;

void add(int n)
{
	for (int i = 0; i < n; i++)
	{
		++x;
	}
}
int main()
{
	//构造一个线程对象
	thread t1(add, 100);
	thread t2(add, 100);
	//获取线程id
	cout << t1.get_id() << endl;
	cout << t2.get_id() << endl;
	//线程分离,分离的线程变为后台线程,该线程的“死活”就与主线程无关
	t2.detach();
	//判断线程是否还在执行
	if(t1.joinable())
		//该函数会阻塞住线程,当该线程结束后,主线程继续执行
		t1.join();
	cout << x << endl;
	return 0;
}
6.2thread类的使用

当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。线程函数一般情况下可按照以下三种方式提供:函数指针/lambda表达式/函数对象。

void threadfunc(int n)
{
	cout << "thread1" << n << endl;
}
class TF
{
public:
	void operator()(int n)
	{
		cout << "thread2" << n << endl;
	}
};
int main()
{
    //线程函数为函数指针
	thread t1(threadfunc, 10);
    //线程函数为函数对象
	thread t2(TF(), 10);
    //线程函数为lambda表达式
	thread t3([](int n) {cout << "thread2" << n << endl; }, 10);

	t1.join();
	t2.join();
	t3.join();
	return 0;
}
6.3线程函数参数

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

void ThreadFunc1(int x)
{
	x += 10;
}
void ThreadFunc2(int& x)
{
	x += 10;
}
void ThreadFunc3(int* x)
{
	*x += 10;
}
int main()
{
	int a = 10;
	// 在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际引用的是线程栈中的拷贝
	thread t1(ThreadFunc1, a);
	t1.join();
	cout << a << endl;
	// 如果想要通过形参改变外部实参时,必须借助std::ref()函数
	thread t2(ThreadFunc2, ref(a));
	t2.join();
	cout << a << endl;
	// 地址的拷贝
	thread t3(ThreadFunc3, &a);
	t3.join();
	cout << a << endl;
	return 0;
}

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

6.4原子性操作库

多线程最主要的问题是共享数据带来的问题。C++中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作,使得线程间数据的同步变得非常高效。

atomic_int sum = 0;
void fun(size_t num) 
{
	for (size_t i = 0; i < num; ++i)
		sum++; // 原子操作
}
int main()
{
	thread t1(fun, 1000000);
	thread t2(fun, 1000000);
	t1.join();
	t2.join();
	cout << sum << std::endl;
	return 0;
}
6.5mutex

C++11提供的最基本的互斥量。

mutex mtx;
int x = 0;

void add(int n)
{
	for (int i = 0; i < n; i++)
	{
        //加锁
		mtx.lock();
		++x;
        //解锁
		mtx.unlock();
	}
}
int main()
{
	thread t1(add, 100);
	thread t2(add, 100);
	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}
6.6condition_variable

C++11提供的最基本的同步量。

condition_variable cv1;
condition_variable cv2;
mutex mtx1, mtx2;

void p1(int n)
{
	for (int i = 0; i < n; i += 2)
	{
		if (i != 0)
		{
			unique_lock<mutex> lck(mtx1);
            //p1等待
			cv1.wait(lck);
		}
		cout << this_thread::get_id()  << " : "<< i << endl;
        //通知p2
		cv2.notify_one();
	}
}

void p2(int n)
{
	for (int i = 1; i < n; i += 2)
	{
		unique_lock<mutex> lck(mtx2);
        //p2等待
		cv2.wait(lck);
		cout << this_thread::get_id() << " : " << i << endl;
        //通知p1
		cv1.notify_one();
	}
}

int main()
{
	thread t1(p1, 100);
	thread t2(p2, 100);
	t1.join();
	t2.join();
	return 0;
}
7.其他

1.范围for循环

2.final和override

3.智能指针、静态数组array、forward_list以及unordered系列

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值