C++进阶之C++11

发布于:2024-08-08 ⋅ 阅读:(142) ⋅ 点赞:(0)

个人主页点我进入主页

专栏分类:C语言初阶  C语言进阶  数据结构初阶    Linux    C++初阶      算法   C++进阶

欢迎大家点赞,评论,收藏。

一起努力,一起奔赴大厂

目录

一.列表初始化

1.1一切皆可用列表初始化

1.2initializer list

二.声明

2.1auto

2.2decltype

2.3nullptr

三.左值和右值

3.1左值引用和右值引用

3.2 左值和右值比较

3.3右值引用场景(移动构造和移动赋值)

3.4完美转发与万能引用

3.5针对move一些补充

四.lambda表达式

4.1基本使用

4.2捕捉列表

五.新的类功能

六.可变参数模板

    6.1输出可变参数包的个数

 6.2使用

6.3emplace_back

七.包装器

        7.1function

                7.1.1基本用法

7.1.2一个应用场景

7.1.3使用类的成员函数的function

7.2bind


一.列表初始化

1.1一切皆可用列表初始化

        在C++11中出现了可以支持使用列表进行初始化,这是为了实现一切皆可用列表进行初始化,直接看代码:

class A
{
public:
	A(int a, int b)
		:_a(a)
		,_b(b)
	{}
private :
	int _a;
	int _b;
};
class B
{
public:
	B(int a)
		:_a(a)
	{}
private:
	int _a;
};
int main()
{
	int array[] = { 1,2,3,4 };

	//单参数隐式类型转化
	B b = { 1 };
	//多参数隐式类型转化
	A a = { 1,2 };
	A a1{ 2,3 };
	A* ptr = new A{ 1,2 };
	return 0;
}

1.2initializer list

        同时C++11支持initializer list对一些stl进行初始化,例如vector,

vector (initializer_list<value_type> il,
const allocator_type& alloc = allocator_type());allocator_type());

它可以直接用{}进行初始化,这个其实是initializer list的类型,我们看一下代码:

	vector<int> v = { 1,2,3,4 };

出现这种情况业主要是想完成一切皆可用{}初始化,由于不知道vetcor的参数的个数,所以采用这种方式。

二.声明

2.1auto

                在c++98中auto是一个存储类型的说明符,但是这玩意没什么用,所以就修改了它的用法,所以修改为了自动推导类型,看下面代码:

int main()
{
	int a = 1;
	auto b = 2;
	cout << typeid(b).name() << endl;
}

auto还可以作为返回值,看下面:

auto Add(int x, int y)
{
	return x + y;
}
int main()
{
	auto a = Add(1, 2);
	cout << typeid(a).name() << endl;
	return 0;
}

同样返回值是int。 

2.2decltype

        和auto类似,它作用于表达式上,直接上代码:

int main()
{
	double a = 1.1;
	double b = 2.2;
	decltype(a * b) c;
	decltype (a) d;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	return 0;
}

2.3nullptr

        由于在定义时将NULL定义为0,所以有时候会出现一些问题,所以新增添了一个nullptr,看一看源码:

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

三.左值和右值

        很多人有一个对左值和右值有一个误区那就是认为在等号左边是左值,右边是右值,其实这是不准确的,真正区分左值和右值是是否可以取地址,知道如何区分左值和右值重要吗?一点不重要,重要的是对左值和右值的引用。

3.1左值引用和右值引用

int main()
{
	//3个左值
	int a = 1;
	int* ptr = new int(1);
	const int b = 2;
	//对左值的引用
	int& c = a;
	int*& cptr = ptr;
	const int& d = b;
	return 0;
}

右值可以是一些匿名对象,常数,一些函数的返回值,其中对于自定义类型可以称为将亡值,看代码:

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int&& a = 1;
	int&& b = Add(1, 2);
	string&& s = string("1111");
	return 0;
}

3.2 左值和右值比较

        针对左值引用,左值引用不可以引用右值否则会报错,但是const属性的左值可以引用右值,

对于右值,右值引用不可以引用左值,但是可以引用move的左值,看代码:

int main()
{
	//int& a = 1; 左值引用=右值报错
	const int& ret1 = 1;//const左值引用=右值

	int a=1;
	//int&& ret=a; 右值引用=左值报错

	string s = "aaaaa";
	string&& ref2 = move(s);//右值引用=move左值
	return 0;
}

3.3右值引用场景(移动构造和移动赋值)

        在一些传参数时可以使用右值引用进行传参,右值引用可以大大减少数据的拷贝,例如在string的构造时我们可以使用右值进行构造也就是移动构造,在赋值构造时也让可以使用右值就是移动复制

//移动赋值
string& operator=(string&& s)
{
    cout << "string& operator=(string&& s) -- 移动语义" << endl;
    swap(s);
    return *this;
}
//移动构造
string(string&& s)
	:_str(nullptr)
{
	cout << "string(string&& s) -- 移动构造" << endl;
	swap(s);
}

3.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 PerfectForward(T&& t)
{
	Fun(t);
}
int main()
{
	int a = 1;
	PerfectForward(a);//左值
	PerfectForward(1);//右值
	PerfectForward(move(a));//右值
	const int b = 2;
	PerfectForward(b);//const左值
	PerfectForward(move(b));//const右值
}

结果会和上面的一样吗?并不是,看结果:

这是为什么?这是由于左值还是左值,右值退化为了左值,此时的右值可以取地址了,如何解决呢?完美转发,将模板改为下面:

template<typename T>
void PerfectForward(T&& t)
{
	Fun(std::forward<T>(t));
}

上面的模板参数就是万能引用,&&不是指右值而是和T进行结合可以有左值也可以右值

3.5针对move一些补充

        一旦使用move他就认为这就是将亡值,会将这个值清空(内置类型不会清空),看下面代码:

int main()
{
	string s = "aaaaaa";
	string b = std::move(s);
}

四.lambda表达式

4.1基本使用

        对一个类进行升序排序需要写一个仿函数,看代码:

struct Goods
{
	string _name; // 名字
	double _price; // 价格
	int _evaluate; // 评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};
struct GoodsPrice
{
	bool operator()(Goods& a, Goods& b)
	{
		return a._price < b._price;
	}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), GoodsPrice());
	return 0;
}

在使用lambda之前先看一下lambda的构成:

 其中返回值(->return-type)可以省略,当没有参数时可以省略参数,看下面表达式来进行升序:

sort(v.begin(), v.end(), [](Goods& a, Goods& b){
	return a._price < b._price;
});

实际上,lambda就相当于仿函数,当然也可以使用下面的方式进行排序:

auto swap = [](Goods& a, Goods& b) {
	return a._price < b._price;
	};
sort(v.begin(), v.end(), swap);

当有参数时可以这使用:

	int a = 1, int b = 2;
	auto add = [](int& a, int& b) {
		return a + b;
		};
	int c=add(a, b);

4.2捕捉列表

 先看样例:

int main()
{
	int a = 1, b = 2;
	auto swap = [a, b]()//mutable
		{
			int temp = a;
			a = b;
			b = temp;
		};
	swap();
	return 0;
}

这个代码会出现问题,它捕捉了a和b但是这两个时一个拷贝过来的,并且不可修改,当然加一个mutable可以修改,但是不会影响到外面的。因为时拷贝的。

当然给一个&表示引用就可以进行修改

int main()
{
	int a = 1, b = 2;
	auto swap = [&a, &b]()
		{
			int temp = a;
			a = b;
			b = temp;
		};
	swap();
	return 0;
}

五.新的类功能

        如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
        如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
        如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

 还支持了可以让这些强制生成和强制不生产的关键字,default和delete

例如:

class Person
{
private :
	int _year;
	string _name;

public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _year(age)
	{}
	Person(Person&&p) = default;//强制生成
	Person& operator=(Person&& p) = delete;//强制不生产
};

六.可变参数模板

    6.1输出可变参数包的个数

template< class ...Args>
void PrintArgs( Args&&... args)
{
	cout << sizeof(args) << endl;
}

注意不可以使用args[i]来进行访问。 

 6.2使用

template<class T>
void PrintArgs(T&& t)
{
	cout << t << endl;
}
template<class T,class ...Args>
void PrintArgs(T&& t,Args&&... args)
{
	cout << t << endl; 
	PrintArgs(args...);
}
int main()
{
	PrintArgs(1);
	PrintArgs(1, 1.1);
	PrintArgs(1, 1.1, "aaaa");
	return 0;
}

6.3emplace_back

        emplace_back相对于insert,push_back有很大优势,它时利用模板参数包进行构造,它直接传入类的参数,直接进行构造,当同时传入相同值时效率一样。尽量使用emplace_back.

七.包装器

        7.1function

                7.1.1基本用法

        直接看一段代码:

int f(int x, int y)
{
	return x + y;
}
struct F
{
	int operator()(int x, int y)
	{
		return x + y;
	}
};

int main()
{
    //函数
	function<int(int, int)> f1 = f;
	cout << f1(1,2)<<endl;
    
    //仿函数
	function<int(int, int)> f2 = F();
	cout << f2(1, 2) << endl;

    //lambda
	function<int(int, int)> f3 = [](int x, int y)
		{
			return x + y;
		};
	cout << f3(1, 2) << endl;
	return 0;
}

包装器function可以对函数,仿函数,lanmbda等进行包装,可以将他们组成一个类,看下面的验证:

template<class T,class K>
void Add(T t,K k)
{
	static int count = 0;
	cout << count++ <<  " "<<t(k) << endl;
};
double f(double x)
{
	return x / 1.1;
}
struct F
{
	double operator()(double x)
	{
		return x / 1.1;
	}
};

int main()
{

	Add(f, 3.3);
	Add(F(), 3.3);
	Add([](double x)
		{
			return x / 1.1;
		}, 3.3);
	/*function<double(double)> f1 = f;
	Add(f1, 3.3);

	function<double(double)> f2 = F();
	Add(f2, 3.3);

	function<double(double)> f3 = [](double x)
		{
			return x / 1.1;
		};
	Add(f3, 3.3);*/
	return 0;
}

当采用上半部分代码时结果为

这三个分别实例化一个函数,但是采用下半部分时,结果为

说明这三个就实例化了一个函数,包装器可以让他们称为同一个类型。

7.1.2一个应用场景

        将字符+,-,*,/利用map和他们对应的运算进行绑定,看代码:

int main()
{
	map<string, function<int(int, int)>> m = {
		{"+",[](int x,int y) {return x + y; }},
		{"-",[](int x,int y) {return x - y; }},
		{"*",[](int x,int y) {return x * y; }},
		{"/",[](int x,int y) {return x / y; }}
	};

	int a = 4, b = 2;
	cout << m["+"](a, b) << endl;
	cout << m["-"](a, b) << endl;
	cout << m["*"](a, b) << endl;
	cout << m["/"](a, b) << endl;
}

运行结果如下: 

7.1.3使用类的成员函数的function

        当成员函数是静态成员函数时,function的方式和其他的一样,当成员函数不是静态时,需要在参数哪里加上我们的this指针,=后写为&类域::成员函数,具体代码为:

class A
{
public:
	static void Count()
	{
		cout << count++ << endl;
	}
	void print(int x)
	{
		cout << num << endl;
	}
private:
	static int count;
	int num=10;
};
int A::count = 0;
int main()
{
	function<void()> f1 = A::Count;
	f1();

	function<void(A*, int)> f2 = &A::print;
	A a;
	f2(&a, 1);

	function<void(A, int)> f3 = &A::print;
	f3(A(), 1);
	return 0;
}

7.2bind

        bind是对函数参数进行绑定,它可以实现参数的呼唤也可以实现对一个参数进行绑定,看代码:

void F(const string& s, int x, int y)
{
	cout << s << "->血: " << x << " 蓝 " << y << endl;
}
int main()
{
	auto f1 = bind(F, "盖伦",placeholders::_1,placeholders::_2);
	f1(1, 1);
	return 0;
}

运行结果为:


网站公告

今日签到

点亮在社区的每一天
去签到