C++程序员修炼手册--C++11重要新特性

目录

1,列表初始化

2,类型推导

3,范围for

4,默认成员函数控制

5,右值引用

5.1 右值有那些

5.2 左值有哪些

5.3,左值引用与右值引用的比较

question:

5.4,移动语义

移动构造和移动赋值

5.5 完美转发

6.lambda表达式

7,包装器

std::function

std::bind


1,列表初始化

提供了用大括号括起的列表(初始化的范围)增加,可用于内置类型和用户自定义的类型,可以使用=号,也可不使用。

如下代码:

class Point
{
public:
	Point(int x = 0, int y = 0)
	: _x(x)
	, _y(y)
	{}
private:
	int _x;
	int _y;
};
void test()
{
//		列表初始化
		vector<int>v1{1,2,3,4,5};
		auto in1={v1,v1,v1};
		decltype(in1)ret;
		cout<<typeid(ret).name()<<endl;
		
		//支持数组动态开辟 
		int*arr3=new int[5]{1,2,3,4,5}; 
		 for(int i=0;i<5;i++)
		 {
		 	cout<<arr3[i]<<endl;
		 }
	  
	  	//标准容器的初始化
		  vector<int>v{1,2,3,4,5};
		  for(auto e:v)
		  {
		  	cout<<e<<endl;
		  } 
		map<int,int>m1{{1,1},{2,2},{3,3},{4,4}};
		for(auto e:m1)
		{
			cout<<e.first<<"::"<<e.second<<endl;
		}


//		支持单个对象的列表初始化
	 	Point p{1,2};	 
		  
}

2,类型推导

使用decltype来实现类型推导,方便我们知道当前这个变量是什么类型的。

缺陷就是,降低一点效率

void test2()
{
	//推导类型对其他变量定义
	short a=32670;
	short b=32670;
	decltype(a+b)ret;
	cout<<typeid(ret).name()<<endl;
	//推导函数返回值的类型
	decltype(test)ans;
	cout<<typeid(ans).name()<<endl;
	 
}

3,范围for

一般用于对于一些变量类型比较长的使用,如迭代器,使用auto推导类型,把值拿出来给到范围for定义的值,然后再调用。

void test3()
{
	vector<int>v1{1,2,3,4,5,6,7};
	map<int,int>m1{{1,1},{2,2},{3,3},{4,4},{5,5}};
	//vector遍历
	for(auto e:v1)
	{
		cout<<"  "<<e;
	} 
	cout<<endl;
	for(auto m:m1)
	{
		cout<<m.first<<"::"<<m.second<<"   ";
	}
	cout<<endl;
	
}

4,默认成员函数控制

在构造函数时,我们可以显示的定义缺省函数和删除默认的函数

class person
{
	public:
		person()
		:_a(10)
		{}
		//赋值操作显示指定默认 
		person&operator=(const person& a)=default;
		// 显示禁用拷贝构造函数 
		person(const&person)=delete;
	private:
		int _a;
};

避免删除函数和explicit一起使

5,右值引用

左值是什么,右值是什么

一般情况下,认为可以取地址的,并且可以修改的认为是左值(const成员除外)

右值被认为不可取地址,不能出现在=的左边,不可修改,认为是右值。

5.1 右值有那些

纯右值:非引用返回的临时变量( int func(void) )、运算表达式产生的临时变量(b+c)、原始字面量lambda表达式等。

将亡值:将要被移动的对象、T&&函数返回值、std::move返回值和转换为T&&的类型的转换函数的返回值。

int Add(int a, int b)
{
	return a + b;
}
int main()
{
	// 引用函数返回值,返回值是一个临时变量,为右值
	int&& rRet = Add(10, 20);
	int* a=&rRet;
	cout<<*a<<endl;
	return 0;
}

5.2 左值有哪些

一般认为:可以放在=左边的,或者能够取地址的称为左值

int& GetG_A()
{
	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;
	return 0;
}

5.3,左值引用与右值引用的比较

左值引用总结:

1,左值引用只能引用左值,不能引用右值

2,但是const左值引用既可引用左值,又可引用右值

int main()
{
	int a=10;
	int &b=a;
//	int &c=10;//不可引用,因为10是右值,不可取地址 
	const int&c=10;//const可以引用右值,属于权限的缩小
	const int&d=a; 
	 
	return 0;
} 

右值引用总结

1,右值引用作用:
        a. 实现移动语义(移动构造与移动赋值)
        b. 给中间临时变量取别名:

2,只能引用右值,一般情况不能直接引用左值。但是使用move就可将左值转换成右值,他并不搬移任何东西,,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义

int main()
{
	int a=10;
	int&&b=10;//右值引用可引用右值
//	int&&c=a;//不可引用左值
//	const int&&c=a;	 //const也不可引用 
	int&&c=std::move(a);//可引用move以后的左值
	 
	return 0;
}

question:

既然C++98中的const类型引用左值和右值都可以引用,那为什么C++11还要复杂的提出右值引用呢?

答:在牵扯到函数调用返回时,函数调用完以后,返回一个值(value),这个值在调用完以后就会被销毁,但是如果返回的时复杂类型,就牵扯到一个深拷贝的问题,此时如果使用右值引用返回的话,就避免了深拷贝,提高了效率,此时就提出了移动构造,和移动赋值避免深拷贝问题。

5.4,移动语义

概念:通过资源转移的方式,将一个对象的资源转移到另一个对象中的方式

移动构造和移动赋值

在函数调用返回值时,这个值返回以后就会被销毁(称为将亡值),但是在返回时,会产生一个临时拷贝文件(调用深拷贝),此时我们在类的实现中,通过创建一个移动构造,就可以避免在函数调用返回时产生深拷贝,从而提升了效率。比如在string的实现中加入移动构造。

	// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			this->swap(s);
		}

		// 移动赋值 
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			this->swap(s);

			return *this;
		}

5.5 完美转发

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

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(std::forward<T>(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;
}

6.lambda表达式

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

[capture-list]:捕获列表 :

                                 [var],捕获所有的var值,通过值传递

                                 [=],通过值传递,传递父作用域的所有变量

                                 [&var],捕获所有的var值,通过引用传递

                                 [&],通过引用传递,传递父作用域的所有变量

                                 [this],捕获当前作用域的this指针,通过值传递

(parameters):参数列表,通过对输入数据的传递(可以是值传递或者引用传递)进行运算

mutable:取消变量的常量性,使用mutable时,参数列表不可省。

return-type:返回类型,不用写会自动推导

{ statement }:函数实现,实现对变量的处理。

以如下代码为例

int main()
{
	int a=10,b=12;
	//传入指定参数,对其进行交换 
	//标准写法 
	auto Swap1=[](int &a,int &b)->void  
	{
		int tmp;
		tmp=a;
		a=b;
		b=tmp;
	};
	Swap1(a,b);
	
//通过捕捉局部a与b的引用实现a与b的交换,参数列表为空,不能交换指定数据 
	auto Swap2=[&]()
	{
		int tmp;
		tmp=a;
		a=b;
		b=tmp;
	};
	Swap2();
	//如果不传入参数,也可以去掉小括号	
	auto Swap3=[&]
	{
			int tmp;
			tmp=a;
			a=b;
			b=tmp;	
	};	
	Swap3();	

	cout<<a<<b<<endl;
	int a=10,b=12;
	auto add=[](int& a,int& b)->int 
	{
		return a+b;
	}; 
	int ret=add(a,b);
	cout<<ret<<endl;
	auto add=[&](){return a+b;};
	int ret=add();
	cout<<ret<<endl;
	
	int a=10;
	int b=12;
	int c=13;
	//等号等于通过值传递的方式捕获父作用域的数据 
	auto fun1=[=](int f){return a+b+c+f;};
	cout<<fun1(10)<<endl;
	//以引用取a,其他以值传递 
	auto fun2=[=,&a]()->int{return a=b*c;}; 
	cout<<fun2()<<endl;
	return 0;
} 

注意: 在块作用域以外的lambda函数捕捉列表必须为空

            捕捉列表不允许变量重复传递,否则就会导致编译错误

            lambda表达式不允许相互赋值。

lambda在底层实现实际上是通过函数对象的方式,即如果定义了一个lambda表达式,编译器就会自动生成一个类,在该类中冲在了operator()

7,包装器

      概述:C++中可调用对象的虽然都有一个比较统一的操作形式,但是定义方法五花八门,这样就导致使用统一的方式保存可调用对象或者传递可调用对象时,会十分繁琐。C++11中提供std::function和std::bind统一了可调用对象的各种操作

可调用对象:函数指针,对象仿函数,转换成函数指针的类对象,类成员函数指针

std::function

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

struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
auto f5=[](double a){return a/2;};
int main()
{
	//包装函数指针 
	std::function<double(double)>ff1=func;
	cout<<ff1(11.11)<<endl;
	cout<<&ff1<<endl;
	//包装仿函数 
	std::function<double(double)>ff2=Functor();
	cout<<ff2(22.22)<<endl;
	cout<<&ff2<<endl;
	//包装lambda表达式 
	std::function<double(double)>ff3=f5;
	cout<<ff3(33.33)<<endl;
	cout<<&ff3<<endl;
	
	
	return 0;
}

包装器就是通过对对象或者指针的封装,实现对传入参数简化。

std::bind

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

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


int main()
{
	std::function<int(int, int)> f1 =  bind(Plus, placeholders::_1, placeholders::_2);
	cout << f1(1, 2) << endl;

	// 想把plus绑定成一个值+10
	std::function<int(int)> f2 = bind(Plus, 10, placeholders::_1);
	cout << f2(5) << endl;

	// 绑定固定的可调用对象
	std::function<int(int, int)> f3 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	cout << f3(1, 2) << endl;

	std::function<int(int, int)> f4 = bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);
	cout << f4(1, 2) << endl;

	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

想找后端开发的小杜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值