C++11的内容

1.支持花括号初始化

void test1()
{
	vector<string> v1 = { "asd","asd","add" };
	vector<string> v2{ "asd","asd","add" };

	map<string, int> m1={ {"asd",1},{"asd",2},{"asd",3} };
	map<string, int> m2{ {"asd",1},{"asd",2},{"asd",3} };

}

当编译器遇到花括号初始化表达式时,会发生以下的步骤:

template<class T>
class F
{
public:
	F(std::initializer_list<T> il) {};     //  如果该构造函数存在直接就调用
	F() {};
};

步骤 1:

优先匹配 std::initializer_list 构造函数 如果类型 F定义了 接受 std::initializer_list的构造函数 (如 F(std::initializer_list<U>)),编译器会优先尝试调用它。 此时,花括号内的所有参数会被打包成一个 std::initializer_list<T>临时对象,其中 T 是花括号内元素的统一类型(需支持隐式转换)。

步骤2:

 如果 initializer_list构造函数不匹配(如参数类型无法转换,或未定义该构造函数),编译器会按常规构造函数重载决议规则选择其他构造函数。 花括号内的参数会逐个传递给构造函数,如同圆括号初始化。

步骤3:

聚合类型的直接初始化 如果T是聚合类型(如没有用户自定义构造函数的结构体、数组等),花括号会按声明顺序直接初始化成员。

template<class T>
class F
{
public:
	//F(std::initializer_list<T> il) {};     //  如果该构造函数存在直接就调用
	//F() {};

	int _a;
	double _b;
};

void test()
{
	F<int> f{ 1,1.0 };  // 按声明顺序初始化
}

注意:

1.窄化转换的检查 无论调用哪种构造函数,花括号初始化都会静态检查窄化转换(如 double→int),若存在则编译失败。

template<class T>
class F
{
public:
	//F(std::initializer_list<T> il) {};     //  如果该构造函数存在直接就调用
	//F() {};

	int _a;
	double _b;
};

void test()
{
	F<int> f{ 1.1,1.0 };  // 按声明顺序初始化  // double->int 本应该发生窄化报错,但发生了自定义类型的隐式类型的转化
	int x{ 1.5 };  //  编译时报错
}

2.当默认构造函数和initlalizer_list都存在时,花括号里面非空,调用initializer_list,空就调默认构造函数。

3.模板函数需要显示参数类型。

template<typename T>
void foo(T param) {}

void test0()
{
	//foo({ 1, 2 });  // 错误:无法推导 T(必须显式指定为 std::initializer_list<int>)
	foo(initializer_list<int>{1, 2});
}

2.decltype自动推导类型

void test2()
{
	int a = 1;
	double b = 2.0;
	auto c = a + b;           //  auto不能作形参和返回值
	cout << typeid(c).name() << endl;    //  double
	string s;
	decltype(c) d;
	cout << typeid(d).name() << endl;     //  double

	decltype(s) e;
	cout << typeid(e).name() << endl;      //class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
}

3.final 和override

 final 修饰类,类就变成了最终类,不能被继承,修饰虚函数,该虚函数不能被重写。            override 检查子类是否重写虚函数。

4. 显示调用默认构造函数

class A
{
public:
	A() = default;   //指定显示去生成默认构造函数,因为当自己写了构造函数,就不会默认生成,但如果传参不匹配,无法构造,又不能调用默认构造函数,会报错,所以显示调用。

	A(const int& a)
		:_a(a)
	{ }


private:
	int _a = 20;
};

void test3()
{
	A a;  //  当没有A() = default,会报错 
}

5.禁止拷贝

class B
{
private:
	//  B(const B& b);  //  C++98 只声明,不实现,别人就无法拷贝对象,为了防止在类外定义,用private限定


	//  在C++11中定义了新的语法禁止拷贝
	B(const B& b)= delete;
	B& operator=(const B& b) = delete;
};

6.右值引用

void test5()
{
	// 左值通常都是变量
	// 右值通常都是常量(表达式或者函数返回的值)

	int a = 0;
	int& b = a;   // 左值引用 引用左值

	// 如果用 左值引用 引用右值要加const
	const int& c = 0;

	// 右值引用 引用右值
	int&& d = 1;        // 节省空间
	int e = 1;    //  直接赋值和右值引用的区别,右值引用不需要开辟空间,拷贝对象


	// 右值引用 引用左值要加move
	cout << "e的类型:" << typeid(e).name() << endl;
	int&& f = move(e);     // f与e的地址一样。
	cout << "e的类型:" << typeid(e).name() << endl;
	auto f1 = move(e);
	e = 3;
	cout <<"e:"<< e << "  " <<"f:"<< f << endl;

	f = 2;
	cout <<"e:"<<e << "  " <<"f:"<< f << endl;
	//  右值引用的是常量,为什么f能改变呢?并且将e改变了

	int&& g = 1 + 2;
	int& h = g;  //  虽然不能对常量用& 但可以对g用&
	cout << "&h:" << &h << endl;
	cout << "&g:" << &g << endl;

	h = 5;
	cout << "g:" << g << "  " << "h:" << h << endl;
	//  可以通过h改变g

}

move对对像本身没什么影响,将对象标记为可以移动的,可以调用vector或string的移动构造函数,但移动构造函数将原来对象对这片内存的权限取消了,给了新的对象,将原来的对象置空或0。
其实,右值引用的作用主要发挥在调用移动构造,只转移管理权,不开辟空间,减少了内存的开销。(前提是,如果要调用移动构造,move的对象你不再使用了),对于一般的,如上面的move的使用,与左值引用无异。

7.lambda表达式

格式: [捕捉列表](形参)->返回类型{函数体}

void test8()
{
	auto r3 = [](int x) {return x; };     // r3 is a name of lambda
	cout << typeid(r3).name() << endl;


	int a = 1;
	int b = 2;
	int y = 3;
	int x = 0;
	cout << "a:" << a << " " << "b:" << b << endl;
	auto r1=[&a, &b](int x, int y)->int{a++; b++; return x + y; };  //  [捕捉列表](形参)->返回类型{函数体}
	int ret=r1(x, y);
	cout << "a:" << a << " " << "b:" << b << "  " << "ret:" << ret << endl;
	// 不作列表只能捕捉同一作用域变量
	//传值捕捉的对象不能改变(值的大小)
	//auto rr = [a, b]() {
	//	int tmp = a;
	//	a = b;
	//	b = tmp;
	//	}     //  报错

	auto rr = [a, b]()mutable {
		int tmp = a; //  (改变了值的大小)
		a = b;
		b = tmp;     //  加mutable就可以了,但没意义,用引用捕获就行
		};

	// 用途
	vector<int> v{ 1,2,3,4,5,6,7,8,9,10 };
	int num = count_if(v.begin(), v.end(), [](int n) {return n % 3==0; }); //  num 大小为返回true的个数  //  count_if 返回满足条件的范围中的元素数
	cout <<"满足条件个数:"<< num << endl;

	//  for_each  将函数应用于范围
	for_each(v.begin(), v.end(), [](int& v) {cout << v << " "; });

}


结果:
class `void __cdecl test8(void)'::`2'::<lambda_1>
a:1 b:2
a:2 b:3  ret:3
满足条件个数:3
1 2 3 4 5 6 7 8 9 10

《C++ primer》解释

8.引用折叠与完美转发

核心规则: 只要有一个 &(左值引用),最终结果就是 T&(左值引用)。 只有全是 &&右值引用),最终结果才是 T&&(右值引用)。

template<class T>
void B1(T&& t) { cout << "void B(T&& t)" << endl; };
template<class T>
void B1(T& t) { cout << "void B(T& t)" << endl; };
template<class T>
void B1(const T&& t) { cout << "void B(const T&& t)" << endl; };
template<class T>
void B1(const T& t) { cout << "void B(const T& t)" << endl; };

template<class T>
void A1(T&& t)     //第一次引用折叠
{
	//右值引用在第二次传参后,属性丢失
	//完美转发解决
	B1(t);
	//B1(std::forward<T>(t)); // 调用forward 发生第二次引用折叠
}

void test9()
{
	int a = 1;
	const int b = 2;
	A1(a);           
	A1(b);       
	A1(move(a));   
	A1(move(b));  

	// 只有左值引用会引发引用折叠
	// int&& c = move(b);   //  不允许直接move有const修饰的对象
	 const int&& c = move(b);
	// int&& c = move(const_cast<int>(b)); // 用const_cast,是const修饰的变量指向的数据是可以修改的,2是一个常量
	}

结果:
void B(T& t)
void B(const T& t)
void B(T& t)
void B(const T& t)
可以看出在第二次传参时,值的属性消失
B1(std::forward<T>(t)); // 调用forward 发生引用折叠

结果:
void B(T& t)
void B(const T& t)
void B(T&& t)
void B(const T&& t)

属性没有丢失

根据折叠规则:

forward函数:

emplate<typename T>
T&& forward(std::remove_reference_t<T>& t) noexcept {
    return static_cast<T&&>(t);  // 引用折叠发生在这里
}

根据折叠规则:

如果 T是 int&: static_cast<int& &&>(t) → static_cast<int&>(t)(保持左值)。

如果 T是 int: static_cast<int&&>(t)(保持右值)。

forward很巧妙的使用了折叠的规则:

如果没有forward折叠,那么下面两种 int ,const int会被推导为int&,const int&

因:第一次折叠捣的鬼。

果:forward巧妙的借用了折叠鬼完成完美转发。

师夷长技以制夷。

在 A1(move(a)) 的场景中,虽然 A1()函数中的t 的类型已经是 int&&(右值引用),直接传递 t 似乎也能保留右值属性,但使用 std::forward 仍然必要

 直接传递 t 的问题 :右值引用变量 t是左值表达式,在 A1函数内部,t 是一个具名变量(即使它的类型是 int&&),而所有具名变量都是左值表达式。 

9.线程

int x = 0;
void Add2(int num)
{

	for (int i = 0; i < num; i++)
	{
		++x;
	}
}

void test10()
{
	thread t1(Add2, 1000000);
	thread t2(Add2, 1000000);

	t1.join();
	t2.join();     //  t1和t1 同时进行线程,对x加10000000次,会有冲突
	cout << "x:" << x << endl;   //  x<2000000

}
不同的线程对同一个对象进行处理,会发生同时拿这个对象,同时加1,x只加一次

结果(多次):
x:1044214
x:1052133
x:1187482

9.1锁的使用

mutex mtx;   // 不能将互斥锁放在Add3()里,不然每个线程创建自己的互斥锁
void Add3(int num)
{
	mtx.lock();
	for (int i = 0; i < num; i++)
	{
		++m;
	}
	mtx.unlock();
}   // 串行

void test10()
{
	// 互斥锁
	thread t3(Add3, 1000000);
	thread t4(Add3, 1000000);

	t3.join();
	t4.join();    
	cout << "m:" << m << endl;  

}

结果:
m:2000000

9.2原子

void test11()
{
	atomic<int> x2 = 0;
	auto Add5 = [&x2](int num) {
		for (int i = 0; i < num; i++)
		{
			++x2;
		}};


	thread t1(Add5, 1000000);
	thread t2(Add5, 1000000);

	cout << t1.get_id() << endl;
	cout << t2.get_id() << endl;

	t1.join();
	t2.join();  
	cout << "x:" << x2 << endl;    // 原子不上锁,也准确。

}

原子不会有冲突。

9.3条件变量

成员


双线程打印1~100,一个线程打印奇数,另一个线程打印偶数

void test13()
{
	int num = 100;
	condition_variable cv1;
	condition_variable cv2;
	mutex m1;
	mutex m2;

	thread t1([&]() {
		for (int i = 0; i < num; i += 2)
		{
			if (i)    // 第一次不锁
			{
				unique_lock<mutex>lock1(m1);
				cv1.wait(lock1);
			}
			cout << this_thread::get_id() << ":" << i << endl;
			cv2.notify_one();
		}
		});

	thread t2([&]{
		for (int i = 1; i < num; i += 2)
		{
			unique_lock<mutex>lock2(m2);
			cv2.wait(lock2);
			cout << this_thread::get_id() << ":" << i << endl;
			cv1.notify_one();
		}
		});
	t1.join();
	t2.join();

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值