- 定位放置new(placement new)
- default关键字
- 模板可变参数包
- 函数形参参数包的递归展开
- 利用函数模板展开
- STL容器的`emplace_back`
- lambda表达式
- 书写格式
- 捕获列表说明
- 底层
- 寄语
- 一般来说,使用new申请空间时,是从系统的“堆”(heap)中分配空间。申请所得的空间的位置时根据当时的内存的实际使用情况决定的。但是,在某些特殊情况下,可能需要在程序员指定的特定内存创建对象,这就是所谓的“定位放置new”(placement new)操作。
- 定位放置new操作的语法形式不同于普通的new操作。例如,一般都用如下语句A* p=new A;申请空间;而定位放置new操作则使用如下语句A* p=new (ptr) A;申请空间,其中ptr就是程序员指定的内存首地址。
#includeusing 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(); }
- 用定位放置new操作,既可以在栈(stack)上生成对象,也可以在堆(heap)上生成对象。如本例就是在栈上生成一个对象。
- 使用语句A* p=new (mem) A;定位生成对象时,指针p和数组名mem指向同一片存储区。所以,与其说定位放置new操作是申请空间,还不如说是利用已经申请好的空间,真正的申请空间的工作是在此之前完成的。
- 使用语句A *p=new (mem) A;定位生成对象是,会自动调用类A的构造函数,但是由于对象的空间不会自动释放(对象实际上是借用别人的空间),所以必须显示的调用类的析构函数,如本例中的p->~A()。
- 万不得已才使用placement new,只有当你真的在意对象在内存中的特定位置时才使用它。例如,你的硬件有一个内存映像的I/O记时器设备,并且你想放置一个Clock对象在哪那个位置。
- 搬运
- 书接上回,对于像移动构造,移动赋值那样生成要求苛刻的默认成员函数,在默认函数定义或者声明时加上=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...);
}
利用函数模板展开
templateSTL容器的emplace_backint Fun(T x) { cout << x << " "; return 0; } template void f3(Args...args) { cout << "展开形参包用函数模板" << " "; int a[]{ Fun(args)... }; cout << endl; }
- 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 }
- [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
- (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
- mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。即想修改表达式接受的值传递过来的的形参,不影响实参。
- ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
- {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 表达式的价值在于,就地封装短小的功能闭包,可以极其方便地表达出我们希望执行的具体操作,并让上下文结合得更加紧密。
寄语



