在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:
struct Point
{
int _x;
int _y;
};
int main()
{
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
Point p = { 1, 2 };
return 0;
}
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义类型,使用初始化列表时,可添加等号(=),也可不添加。
struct Point
{
int _x;
int _y;
};
int main()
{
int x1 = 1;
int x2{ 2 };
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
Point p{ 1, 2 };
// C++11中列表初始化也可以适用于new表达式中
int* pa = new int[4]{ 0 };
return 0;
}
创建对象时也可以使用列表初始化方式调用构造函数初始化。
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 1, 1); // old style
// C++11支持的列表初始化,这里会调用构造函数初始化
Date d2{ 2022, 1, 2 };
Date d3 = { 2022, 1, 3 };
return 0;
}
注意:初始化列表语法可防止缩窄,即禁止将数值赋值给无法存储它的数值变量,即将值存储到比它窄的变量中。
C++11中将auto用于实现自动类型推断。要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。通过auto自动类型推断获得的变量可以使用typeid( )函数获得实际类型。
int main()
{
int i = 10;
auto p = &i;
float m = 10.01;
auto pf = m;
cout << typeid(p).name() << endl;//int *
cout << typeid(pf).name() << endl;//float
map dict = { {"sort", "排序"}, {"insert", "插入"} };
//map::iterator it = dict.begin();
auto it = dict.begin();
return 0;
}
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
auto不能作为函数的参数,不能直接用来声明数组:
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a){}
// 此处代码编译失败,auto不能推导数组
auto b[] = {4,5,6};
关键字decltype将变量的类型声明为表达式指定的类型。
int main()
{
const int x = 1;
double y = 2.2;
int z = 0;
decltype(x * y) ret; // ret的类型是double
decltype(&x) p; // p的类型是int*
decltype(z) tmp;// z的类型是int
cout << typeid(ret).name() << endl;//double
cout << typeid(p).name() << endl;//int *
cout << typeid(tmp).name() << endl;//int
return 0;
}
在定义模板的时候特别好用,因为只有等到模板被实例化时才能确定类型。
template
void func(T t, U u)
{
...
decltype(T*U) tu;
...
}
C++新增的一种函数声明语法:在函数名和参数列表后面(而不是前面)指定返回类型。让我们能够使用 decltype 来指定模板函数的返回类型。
template -> decltype(T*U)
auto func(T t, U u)
{
...
decltype(T*U) tu;
...
}
这里解决的问题是,在编译器遇到func的参数列表前, T和U还不在作用域内,因此必须在参数列表后使用decltype。这种新语法使得能够这样做。
4.模板别名:using =
新语法可用于模板部分具体化,但是typedef不能
typedef array arrd;
typedef array arri;
typedef array arrstr;
arrd gallons;// gallons is type array
arri days; // days is type array
arrst months;// months is type array
/*新语法*/
template
using arrtype = array;//template to create multiple aliases
arrtype gallons; //gallons is type array
arrtype days; //days is type array
arrtype months; //months is type array
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。nullptr 可以被隐式转换成任意的指针类型。
为向后兼容,C++11仍允许使用0来表示空指针,因此表达式nullptr==0为true。
C++11引入了关键字explicit,以禁止单参数构造函数导致的自动转换:
class Plebe
{
...
Plebe (int);
explicit Plebe(double);
...
};
intm main()
{
Plebe a, b;
a= 5; //allowed
b = 0.5; //unallowed
b = Plebe (0.5);//显式转换,allowed
}
在类声明中,可使用等号或大括号版本的初始化,但不能使用圆括号版本的初始化。
class A
{
int a;
public:
Action(int i = 0) : a(i) {}
int val() const {return a;}
virtual void show(char ch) const { cout << val() << ch << endl;}
};
class B: public A
{
public:
B(int i = 0) : A (i) {}
virtual void show(char * ch) const {cout << val() << ch << "!" << endl; }
};
由于类B定义的是show(char * ch)而不是show(char ch),将对B对象隐藏show(char ch),因此下面代码报错:
B b(10);
b.show('b');//faild
在C++11中,可使用虚说明符override指出您要覆盖一个虚函数:将其放在参数列表后面。如果声明与基类方法不匹配,编译器将视为错误。下面的函数重写将生成一条编译错误:
virtual void show(char * ch) const override {cout << val() << ch << "!" << endl; }
如果想禁止派生类覆盖特定的虚方法,为此可在参数列表后面加上final。例如,下面的代码禁止A的派生类重新定义函数show(char ch):
virtual void show(char ch) const final{ cout << val() << ch << endl;}
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)//e为引用类型,用于修改数组元素
e *= 2;
for(auto e : array)
cout << e << " ";//2,4,6,8,10
return 0;
}
注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。for循环迭代的范围必须是确定的。
1、概念理解
右值是能够赋值给左值,但是左值不能赋值给右值。
右值引用主要作用是解决:(1)大对象在作为函数返回值返回时的深度拷贝问题,(2)智能指针时将其他unique_ptr通过move()移动后可以赋值给unique_ptr,(3)大对象之间的快速复制。
int &a = 1;//错误,1是右值,a是左值引用,无法直接初始化,需要用左值初始化;
int a = 1; //正确,a是左值不是左值引用,可以被赋值;
int const &a = 1;//正确,a是常量左值引用,右值可以绑定到常左值引用;
int &&a = 1;//正确,这是C++11中的右值引用,a为左值;
2、函数的返回值一般是右值,也可能是左值:
//返回右值的常规函数
int fun(){//函数中a是局部变量,会销毁,所以返回值是右值,只能临时使用。
int a = 1;
return a;
}
//返回左值的函数
int &fun(int &a){//函数将输入的引用返回,从始至终都是这个a
a++;
return a;
}
//以上函数可以这么用
fun(a) = 4;//因为fun返回左值,所以fun可以被赋值
//错误的返回左值案例
int &fun(){//不要将局部变量的引用返回,因为返回后局部变量被销毁
int a = 3;
return a;
}
完美转发是指在函数模板中,完全依照模板的参数类型,将参数传递给当前函数模板中的另外一个函数。
因此,为了实现完美转发,除了使用万能引用之外,我们还要用到std::forward(C++11),它在传参的过程中保留对象的原生类型属性。这样右值引用在传递过程中就能够保持右值的属性。
void Func(int& x) { cout << "左值引用" << endl; }
void Func(const int& x) { cout << "const左值引用" << endl; }
void Func(int&& x) { cout << "右值引用" << endl; }
void Func(const int&& x) { cout << "const右值引用" << endl; }
template
void PerfectForward(T&& t) // 万能引用
{
Func(std::forward(t)); // 根据参数t的类型去匹配合适的重载函数
}
int main()
{
int a = 4; // 左值
PerfectForward(a);
const int b = 8; // const左值
PerfectForward(b);
PerfectForward(10); // 10是右值
const int c = 13;
PerfectForward(std::move(c)); // const左值被move后变成const右值
return 0;
}
用橘色圈起来是C++11中的几个新容器,但是实际最有用的是unordered_map和unordered_set。
任何一个类,在什么都不写的时候,都会默认生成6个成员函数:
默认构造函数、析构函数、拷贝构造函数、赋值重载函数、普通对象取地址、const对象取地址。
C++11中类新增了2个默认成员函数,使其变为了8个默认成员函数。新增的这两个成员函数就是移动构造函数和移动赋值函数。
如果我们没有自己实现没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器才自动生成默认移动赋值和移动赋值函数。如果我们自己实现了移动构造函数或移动赋值运算符,编译器将不会自动提供复制构造函数和复制赋值运算符。
class Person {
public:
Person(const char* name="", int age = 0){ ... };
Person (const Person& p) ;//复制构造函数
Person(Person&& p);//移动构造函数
private:
char* _name;
int _age;
}
//复制构造函数
Person::Person(const Person& p)
{
_name = new char[20];
strcpy(_name, p._name);
_age = p._age;
}
//移动构造函数
//移动构造函数/移动赋值运算符的参数不能是const引用,因为这个方法修改了源对象
Person::Person(Person&& p)
{
_name = p._name;
p._name = nullptr;//修改了原对象
_age = p._age;
}
通过提供一个使用左值引用的构造函数和一个使用右值引用的构造函数,将初始化分成了两组。使用左值对象初始化对象时,将使用复制构造函数,而使用右值对象初始化对象时,将使用移动构造函数。
class Person {
public:
Person(const char* name="", int age = 0){ ... };
Person (const Person& p) = delete;//禁止生产默认复制构造函数
Person(Person&& p) =default;//强制默认生成移动构造函数
Person& operator =(Person&&/p) = default;//强制默认生成移动赋值重载函数
private:
lb::string _nane;
int _age;
}
void test()
{
Person s1;
Person s2 = s1;//调用拷贝(此处被禁止了)
Person s3 = std::move (s1);//调用移动构造
Person s4;
s4 = std:: move(s2);//调用移动赋值
}
Lambda 表达式是一个匿名函数,即没有函数名的函数。
[capture-list] (parameters) mutable -> return-type { statement}
- [var]:表示值传递方式捕捉变量var
- [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]:表示值传递方式捕捉当前的this指针
【注意】在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[] {};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=] {return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c) {b = a + c; };
fun1(10);
cout << a << " " << b << endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int {return b += a + c; };
cout << fun2(10) << endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。
如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。
int main()
{
int array[] = { 4,1,8,5,3,7,0,9,2,6 };
// 默认按照小于比较,排出来结果是升序
std::sort(array, array + sizeof(array) / sizeof(array[0]));
// 如果需要降序,需要改变元素的比较规则
std::sort(array, array + sizeof(array) / sizeof(array[0]), std::greater());
return 0;
}
但是如果待排序的元素为自定义类型,则需要用户自定义排序时的比较规则:
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
struct ComparePriceLess//按照水果价格降序排序
{
bool operator()(const Goods& gl, const Goods& gr){
return gl._price < gr._price;
}
};
struct ComparePriceGreater//按照水果价格生序排序
{
bool operator()(const Goods& gl, const Goods& gr){
return gl._price > gr._price;
}
};
int main()
{
vector v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), ComparePriceLess());//降序排序
sort(v.begin(), v.end(), ComparePriceGreater());//升序排序
}
引入Lambda表达式后,上述排序代码可以写成如下形式:
vector v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
//降序排序
sort(v.begin(), v.end(), [](Goods gl, Goods gr)->bool {return gl._pricebool {return gl._price>gr._price});
函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象。
class Rate
{
public:
Rate(double rate)
:_rate(rate)
{}
double operator()(double money, int year){
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
// 函数对象
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);
// lambda
auto r2 = [=](double monty, int year)->double {return monty * rate * year;};
r2(10000, 2);
return 0;
}
从使用方式上来看,函数对象与lambda表达式完全一样。函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。那我们为什么需要包装器呢?先看例子:ret = func(x) ;
上面的代码中func可以是仿函数对象,可以是函数指针,也可以是lambda表达式. . .这些对象都可以调用,在实现模板时就会对一个模板进行多次的实例化,导致了模板的效率底下。
template
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)//普通函数
{
return i / 2;
}
struct Functor//仿函数
{
double operator()(double d)
{
return d / 3;
}
};
void test20()
{
// 函数名
cout << useF(f, 11.11) << endl;
// 仿函数
cout << useF(Functor(), 11.11) << endl;
// lamber表达式
cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
}
由代码的执行结果我们可以看到,count有三份,说明我们实现了3个不同的useF函数模板,主要是由于程序中定义的几种模式的函数,都满足函数模板中调用的函数参数与变量参数之间的使用关系,因此在实际运行模板函数时,分别调用几种不同的函数。
但是如果我们使用包装器将三个函数进行包装,对应的模板只会实现一份。包装格式如下:
function<返回类型(参数类型)> 包装器名称=包装函数
#include
#include
using namespace std;
template
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
int f(int a) {//普通函数
return a + 0;
}
struct Functor {//仿函数
public:
int operator() (int a) {
return a + 1;
}
};
class Plus//类成员函数
{
public:
static int plusi(int a) {
return a + 2;
}
double plusd(double a) {
return a + 3;
}
};
void test21()
{
// 函数名(函数指针)
std::function func1 = f;
cout << useF(func1, 11) << endl;
// 函数对象
std::function func2 = Functor();
cout << useF(func2, 11) << endl;
// lamber表达式
std::function func3 = [](const int a) {return a + 5; };
cout << useF(func3, 11) << endl;
}
C++11——智能指针_oywLearning的博客-CSDN博客
C++并发编程 | 原子操作std::atomic_oywLearning的博客-CSDN博客_std::atomic
C++多线程编程之thread类详解_oywLearning的博客-CSDN博客_c++ thread
//为多个参数的情况定义的函数
template
void show_list(T value,Arg... arg)
{
cout<
void show_list(T value)
{
cout<