栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

中级C++11:定位new、default、模板可变参数包、emplace

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

中级C++11:定位new、default、模板可变参数包、emplace

文章目录
  • 定位放置new(placement new)
  • default关键字
  • 模板可变参数包
    • 函数形参参数包的递归展开
    • 利用函数模板展开
  • STL容器的`emplace_back`
  • lambda表达式
    • 书写格式
    • 捕获列表说明
    • 底层
  • 寄语

定位放置new(placement new)
  • 一般来说,使用new申请空间时,是从系统的“堆”(heap)中分配空间。申请所得的空间的位置时根据当时的内存的实际使用情况决定的。但是,在某些特殊情况下,可能需要在程序员指定的特定内存创建对象,这就是所谓的“定位放置new”(placement new)操作。
  • 定位放置new操作的语法形式不同于普通的new操作。例如,一般都用如下语句A* p=new A;申请空间;而定位放置new操作则使用如下语句A* p=new (ptr) A;申请空间,其中ptr就是程序员指定的内存首地址。
#include 
using namespace std;
 
class A{
    int num;
public:
    A(){
        cout<<"A's constructor"<
        cout<<"~A"<
        cout<<"num:"<
    char mem[100];
    mem[0]='A';
    mem[1]='';
    mem[2]='';
    mem[3]='';
    cout<<(void*)mem<show();
    p->~A();
    getchar();
}


  1. 用定位放置new操作,既可以在栈(stack)上生成对象,也可以在堆(heap)上生成对象。如本例就是在栈上生成一个对象。
  2. 使用语句A* p=new (mem) A;定位生成对象时,指针p和数组名mem指向同一片存储区。所以,与其说定位放置new操作是申请空间,还不如说是利用已经申请好的空间,真正的申请空间的工作是在此之前完成的。
  3. 使用语句A *p=new (mem) A;定位生成对象是,会自动调用类A的构造函数,但是由于对象的空间不会自动释放(对象实际上是借用别人的空间),所以必须显示的调用类的析构函数,如本例中的p->~A()。
  4. 万不得已才使用placement new,只有当你真的在意对象在内存中的特定位置时才使用它。例如,你的硬件有一个内存映像的I/O记时器设备,并且你想放置一个Clock对象在哪那个位置。
  • 搬运
default关键字
  • 书接上回,对于像移动构造,移动赋值那样生成要求苛刻的默认成员函数,在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本。
class A
{
public:
 A(int a): _a(a)
 {}
 // 显式缺省构造函数,由编译器生成
 A() = default;
 
A(A&& x) = default;//强制默认生成一下移动构造
 
 // 在类中声明,在类外定义时让编译器生成默认赋值运算符重载
 A& operator=(const A& a);
private:
 int _a;
};
A& A::operator=(const A& a) = default;

模板可变参数包

详细讲解

  • 一切都从函数传参开始说起。在C语言中有个神奇的函数:printf:
printf("%s : %dn","constant number",7030);
  • “可变”参数,主要是指两点可变:1,参数数量可变;2,参数类型可变。
  • 常常这样声明一个可变模版参数:
    • Args是一个模板参数包类型,args是一个函数形参参数包
    • 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template
void f2(Args...args)
{
	for (size_t i = 0; i < sizeof...(args); i++)//注意  ...写的格式
	{
		cout << "在这里可以获取函数形参包有" << i << "个参数" << endl;
	}
}
函数形参参数包的递归展开
void f1()
{
	cout << "给一个无参的函数调用,因为最后函数形参包完全展开就没有参数了" << endl;
}

template//Args 是一个模板参数包类型
void f1(T x,Args...aa)  //aa 是一个函数形参参数包  声明一个形参包,Args...aa,包含0到任意一个模板参数。
{
	cout << "每次递归调用一次,函数形参包就展开一个参数" << endl;
	f1(aa...);
}
利用函数模板展开
template
int Fun(T x)
{
	cout << x << "  ";
	return 0;
}

template
void f3(Args...args)
{
	cout << "展开形参包用函数模板" << "  ";
	int a[]{ Fun(args)... };  
	cout << endl;
}
STL容器的emplace_back
  • C++基础-如何更高效的插入数据:emplace_back
  • emplace_back 插入左值没有区别;push_back插入右值,走构造临时对象,移动构造的路子;而emplace_back就地构造,以后推荐用emplcae_back尾插,效率更高。
int main()
{
	
	list> lt1;
	lt1.emplace_back("Lei Gong help me!", 24);
	lt1.emplace_back("At the age of Jiazi", 25);
	return 0;
}
lambda表达式
struct Goods
{
 string _name;
 double _price;
};
struct Compare
{
 bool operator()(const Goods& gl, const Goods& gr)
 {
 return gl._price <= gr._price;
 }
};
int main()
{
 Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
 sort(gds, gds+sizeof(gds) / sizeof(gds[0]), Compare());
 return 0;
}
  • 每次为了实现一个algorithm算法, 比如STL容器中缺省的:const key_compare& comp = key_compare(),上述的若想用水果名字排序等等;都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。
  • 引入了则只需:
int main()
{
 Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
 sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& l, const Goods& r)
 ->bool
 {
 return l._price < r._price;
 });
 return 0;
}
书写格式
  • [capture-list] (parameters) mutable -> return-type { statement }
  1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
  3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。即想修改表达式接受的值传递过来的的形参,不影响实参。
  4. ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
  • 参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
	[]{};//最简单的

需求一:交换两数的值:

  • 有参实现:
int main(){
int x=24,y=34;
auto lam = [](int x,int& y)//还能混着用
	{
		int tmp = x;
		x = y;
		y = tmp;
		cout << "Lambda x:" << x << "   Lambda y:     " << y << endl;
	};
	lam(x,y);
	cout <<"原来的x" << x << "   原来的y  " << y << endl;
}
  • 无参实现:
    auto lam = [&x, &y]() //mutable//只能能改变拷贝的形参值..
	{
		int tmp = x;
		x = y;
		y = tmp;
		cout <<"Lambda x:" < 
捕获列表说明 
  • 描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
    • [var]:表示值传递方式捕捉变量var
    • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
    • [&var]:表示引用传递捕捉变量var
    • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
    • [this]:表示值传递方式捕捉当前的this指针
      • 父作用域指:包含 lambda函数 的语句块,大括号。
      • 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
  • 混着用:
    • 比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量;
    • [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量;
    • 引用传递的能改变实参,值传递实参不变。
  auto lam = [&](int y)//还能混着用
	{
		int tmp = x;
		x = y;
		y = tmp;
		cout << "Lambda x:" << x << "   Lambda y:     " << y << endl;
	};
	lam(y);
	cout << "原来的x" << x << "   原来的y  " << y << endl;
  • lambda表达式
    • lambda表达式之间不能相互赋值
    • 允许使用一个lambda表达式拷贝构造一个新的副本
    • 可以将lambda表达式赋值给相同类型的函数指针
void (*PF)();
int main()
{
 auto f1 = []{cout << "hello world" << endl; };
 auto f2 = []{cout << "hello world" << endl; };
 //f1 = f2; // 编译失败--->提示找不到operator=()
 // 允许使用一个lambda表达式拷贝构造
 auto f3(f2);
 f3();
 // 可以将lambda表达式赋值给相同类型的函数指针
 PF = f2;
 PF();
 return 0;
}
底层
  • 底层是:匿名的仿函数对象调用operate(),汇编层面,编译器给每一个 lambda 表达式的命名: lambda_UUID(进程中唯一标识的字符串)
  • 如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。式通过捕获列表可以直接将该变量捕获到。
  • lambda 表达式的价值在于,就地封装短小的功能闭包,可以极其方便地表达出我们希望执行的具体操作,并让上下文结合得更加紧密。
寄语

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1004630.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号