C++98 中,允许使用 {} 对数组或者结构体元素进行统一的列表初始值设定C++11 扩大了用 {} 括起的列表(初始化列表)的使用范围,可用于所有的内置类型,自定义的类型和new表达式,使用 {} 时,可添加等号(=),也可不添加。
class Date
{
public :
Date(int year ,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
int a = 1; // 建议就用这个,下面就不要用
int b = { 2 };
int c { 3 };
//效果相同
int arr1[]{ 1, 2, 3, 4, 5 };
int arr3[] = { 1,2,3,4,5 };
int arr2[5]{ 0 };
int arr4[5] = { 0 };
// 上面的可以支持,本质就更好支持new[] 的初始化问题
int* p1 = new int(1);
int* p2 = new int[3]{1,3,4};
//自定义类型也可以这样初始化
Date d0(2023, 3, 19);
Date d1{ 2023,3,19 };
Date d2 = { 2023,3,19 };
Date* p4 = new Date(1, 2, 3);
Date* p3 = new Date[3]{ {2023,3,19},{2023,3,20},{2023,3,21} };
//vector,set,map,list....等都可以用 {} 这种方式初始化
vector v1 = { 1,2,3,4,5 };
vector v2{ 1,2,3,4,5 };
set s2 = { 5,9,4,23,1 };
set s1{ 5,9,4,23,1 };
map m1{ {"字符串","string"},{"左边","left"} };
map m2 = { {"字符串","string"},{"左边","left"} };
return 0;
}
int main() { // il的类型 --> class std::initializer_list
auto il = { 10, 20, 30 }; cout << typeid(il).name() << endl; //可用迭代器,但是只可读不可写 initializer_list ilt = { 3.3, 5.5, 9.9 }; initializer_list ::iterator it = ilt.begin(); while (it != ilt.end()) { cout << *it << " "; ++it; } cout << endl; for (auto e : ilt) { cout << e << " "; } cout << endl; return 0; } initiallizer本质上可以认为是一个存储常量的数组,一般是作为构造函数的参数。
可用迭代器,但是只可读不可修改。
vector,map,string..等容器,支持花括号{}的初始化和赋值,只因其构造函数里增加std::initializer_list作为参数的构造函数。当operator= 的参数有std::initializer_list,就可以用大括号赋值
int main()
{
vector v ;
// 这里{"sort", "排序"}会先初始化构造一个pair对象,再赋值
map m = { {"sort", "排序"}, {"insert", "插入"} };
//使用{} 对容器赋值
v = { 1,2,3,4,5 };
return 0;
}
auto,decltype,nullptr使用
int main() { //auto //自动根据变量的值,判断变量的类型 int a = 20; //it --> int* auto pa = &a; //b --> double auto b = 7.7; //decltype //将变量的类型声明为表达式指定的类型 //c --> double decltype(7.7) c; //d --> int* decltype(&a) d; //nullptr //C++中NULL被定义为字面量0,这就可能会带回一些问题 //应该用nullptr表示空指针 }
左值是一个表示数据的表达式,我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。
int main()
{
// 以下的a、b、c、*a都是左值
int* a = new int(0);
int b = 1;
//定义时const修饰符后的左值,不能给他赋值
//但是可以取它的地址。左值引用就是给左值取别名
const int c = 7;
// 以下几个是对上面左值的左值引用
int*& aa = a;
int& bb = b;
const int& cc = c;
int& aaa = *a;
return 0;
}
右值也是一个表示数据的表达式,如:字面常量、表达式返回值(临时变量),函数返回值(传值返回,但这个不能是左值引用返回)......等等。右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址
int main()
{
double a = 7.7;
double b = 2.2;
// 以下几个都是常见的右值
10;
a + b;
fmin(a, b);
// 符号: && --> 右值引用
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = a + b;
double&& rr3 = fmin(a, b);
// 下面的编译会报错 , 左操作数必须为左值
//10 = 1;
//x + y = 1;
//fmin(x, y) = 1;
return 0;
}
int main()
{
double x = 1.1;
double y = 2.2;
//不能取字面量10的地址
//&10 -> 报错
10;
//但是rr1引用后,可以对rr1取地址,也可以修改rr1。
//如果不想rr1被修改,可以用const int&& rr1 去引用,则不能被修改
int&& rr1 = 10;
//rr2有const修饰也不能修改
const double&& rr2 = x + y;
rr1 = 20;
//rr2 = 5.5; // 报错
return 0;
}
总结:左值可以取地址,右值不可以取地址
int main()
{
//左值引用只能引用左值,不能引用右值
int a = 10;
int& a1 = a; //a1为a的别名,a是左值
//int& a2 = 10; 编译报错,引用的是10(右值)
//加const后的左值引用,可以引用左值,也可以引用右值
int b = 7;
const int& b1 = 7;
const int& b2 = a;
//const修饰后为常量,可以从常量转换为常量
return 0;
}
右值引用只能右值,不能引用左值。
但是右值引用可以move以后的左值。
int main()
{
int c = 7;
//右值引用只能右值,不能引用左值。
int&& a = 7;
//int&& aa = c; 编译报错,无法从"int"转换为"int&&"
//右值引用可以引用move以后的左值
int&& b = std::move(c);
return 0;
}
做参数和做返回值都可以提高效率。
void func1(string s) {} void func2(string& s) {} int main() { string s1{ "Cistiano" }; //做参数 func1(s1); func2(s1);//func2相对于func1,左值引用做参数减少了拷贝 //做返回值 // string operator+=(char ch) 传值返回存在深拷贝 // string& operator+=(char ch) 传左值引用 string& 没有拷贝,提高了效率 return 0; }
移动语义:C++11以后,STL容器中都提供了移动构造和移动赋值
移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
//移动构造
string(string&& s)
:_str(nullptr)
{
swap(s);
}
//拷贝构造
string(const string& s)
:_str(nullptr)
{
string tmp(s._str);
swap(tmp);
};
Cris::string to_string(int value)
{
string str;
//......
return str;
}
int main()
{
Cris::string ret = Cris::to_string(-1234);
return 0;
}
to_string的返回值是一个右值,用这个右值构造ret,如果既有拷贝构造又有移动构造,调用就会匹配调用移动构造,因为编译器会选择最匹配的参数调用。那么这里就是一个移动语义。
// 移动赋值
//在Cris::string类中增加移动赋值函数,再去调用Cris::to_string(1234),
//不过这次是将Cris::to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动构造。
string& operator=(string&& s)
{
swap(s);
return *this;
}
Cris::string to_string(int value)
{
string str;
//......
return str;
}
int main()
{
Cris::string ret;
ret = Cris::to_string(1234);
return 0;
}
按照语法,右值引用只能引用右值。但在有些场景下,可能需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。std::move() 函数 唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
int main()
{
Cris::string s1("hello world");
// 这里s1是左值,调用的是拷贝构造
//深拷贝,左值拷贝是不会被资源转移偷家的
Cris::string s2(s1);
// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
// 但是这里要注意,一般是不要这样用的,
// 因为我们会发现s1的资源被转移给了s3,s1被置空了。
// 转移将亡值的资源,右值拷贝是不会被资源转移偷家
Cris::string s3(std::move(s1));
return 0;
}
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。template
void Func(T&& t) { fun(t); }
但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值
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
void Func(T&& t)
{
Fun(t);
}
int main()
{
Func(10);// 右值
int a;
Func(a);// 左值
Func(std::move(a)); // 右值
const int b = 8;
Func(b);// const 左值
Func(std::move(b)); // const 右值
return 0;
}
结果:
这时候就要看std::forward() , std::forward 完美转发在传参的过程中保留对象原生类型属性
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
void Func(T&& t)
{
//加入std::forward
Fun(std::forward(t));
}
int main()
{
Func(10);// 右值
int a;
Func(a);// 左值
Func(std::move(a)); // 右值
const int b = 8;
Func(b);// const 左值
Func(std::move(b)); // const 右值
return 0;
}
结果:
C++11 新增了两个 ---> 移动构造函数和移动赋值运算符重载
针对移动构造函数和移动赋值运算符重载,有以下需要注意的点:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的 任意一个 。那么编译器会自动生成一个默认移动构造。 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的 任意一个 ,那么编译器会自动生成一个默认移动赋值。 如果你提供了移动构造 或者 移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
强制生成默认函数的关键字default
使用default关键字显示指定移动构造生成。
class Student
{
public:
Student(const char* name = "Cris", int age = 0)
:_name(name)
, _age(age)
{}
Student(const Student& p)
:_name(p._name)
, _age(p._age)
{}
//强制生成移动构造
Student(Student&& p) = default;
private:
string _name;
int _age;
};
int main()
{
Student s1;
Student s2 = s1;
Student s3 = std::move(s1);
return 0;
}
禁止生成默认函数的关键字delete
delete --> 限制某些函数的形成
C++98中,将该函数设置成private,加上声明补丁,这样只要其他人想要调用就会报错。
C++11中更简单,只需在该函数声明加上=delete即可。该语法指示编译器不生成对应函数的默认版本,称 =delete 修饰的函数为删除函数。
class Student
{
public:
Student(const char* name = "Cris", int age = 0)
:_name(name)
, _age(age)
{}
//强制拷贝构造函数删除
Student(const Student& p) = delete;
private:
string _name;
int _age;
};
int main()
{
Student s1;
//拷贝构造函数被删除了,下面的将会报错
//Student s2 = s1;
//Student s3 = std::move(s1);
return 0;
}
下面Args是一个模板参数包,args是一个函数形参参数包。
声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template
void ShowList(Args... args) { //可以打印参数个数 cout << sizeof...(args) << endl; // 语法不支持使用args[i]这样方式获取可变参数 // 没有迭代器,不支持 /*for (size_t i = 0; i < sizeof...(args); ++i) { cout << args[i] << endl; }*/ /*for (auto& e : args) { cout << e << endl; }*/ } int main() { //什么类型的都可以存储进去 ShowList(1, 'x', 7.7); ShowList(1, 2, 3, 4, 5); return 0; }
可以通过两种方式获取参数包的值:
template
void ShowList(const T& val)
{
cout << val <<"->"<
void ShowList(const T& val, Args... args)
{
//ShowList(),每次进来都从Args...参数包中提取一个
cout << sizeof...(args) << endl;
cout << val <<"->"<
2.逗号表达式展开参数包
template
int PrintArgs(const T& t)
{
cout << t << " ";
return 0;
}
template
void ShowList(Args... args)
{
//列表初始化
//由于是逗号表达式,在创建数组的过程中,
//会先执行逗号表达式前面的部分printarg(args)打印出参数
//这个数组的目的纯粹是为了在数组构造的过程展开参数包
int arr[] = { (PrintArgs(args), 0)... };
//{(PrintArgs(args), 0)...}将会展开成((PrintArgs(arg1),0),
//(PrintArgs(arg2),0), (PrintArgs(arg3),0), etc... )
cout << endl;
}
int main()
{
ShowList(1, 'x', 1.1, string("hello Cris"));
cout << endl;
ShowList(1, 2, 3, 4, 5);
return 0;
}
emplace系列的接口,支持模板的可变参数,并且万能引用。相对insert和 emplace系列接口,例如emplace_back和push_backemplace_back是直接构造,push_back是先构造,再移动构造。两者差别也不大。
lambda表达式格式:[capture-list] (parameters) mutable -> return-type { statement}
[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表 能够捕捉上下文中的变量 供lambda函数使用。 mutable:默认情况下,lambda函数总是一个const函数, mutable可以取消其常量性 。使用该修饰符时,参数列表不可省略(即使参数为空)。 {statement}:函数体,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。 ->return-type:返回值类型。用追踪返回类型形式 声明函数的返回值类型 ,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。 (parameters):参数列表。 和普通函数的参数列表一样 ,如果不需要参数传递,则可以连同()一起省略
需要注意:在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空,而lambda底层是仿函数,如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。lambda表达式实际上可以理解为无名函数。
int main()
{
int a = 10;
int b = 7;
int c = 4;
int d = 6;
int ret = 0;
//一般是局部匿名函数 也可以写到全局
auto Add1 = [](int x, int y)->double{return (x + y)/3.0; };
//[a]:传值捕捉变量a
auto Add2 = [a](int x, int y)->int{return (x + y)/3.0+a; };
//[a,b]:传值捕捉变量a,b
auto Add3 = [a, b]{return (a+b) / 3.0; };
//[=]:传值捕捉全部对象
auto Add4 = [=] {return a + b + c + d; };
//[&a]:传引用捕捉变量a
auto Add5 = [&a](int x, int y) {a = x + y; };
//[&]:传引用捕捉全部对象
auto Add6 = [&] { ret = a + b + c + d; };
//[a,b,&ret]:传值捕捉变量a和b ,传引用捕捉变量ret
auto Add7 = [a, b, &ret] {ret = a + b; };
//ret传引用捕捉 其他全部传值捕捉
auto Add8 = [=, &ret] {ret = a + b + c + d; };
return 0;
}
需要注意的是:
void(*PF)();
int main()
{
auto f1 = []{cout << "hello world" << endl; };
auto f2 = []{cout << "hello world" << endl; };
//不能相互赋值
//f1 = f2;
//可以使用一个lambda表达式拷贝构造一个新的副本
auto f3(f2);
//也可以将lambda表达式赋值给相同类型的函数指针
//了解一下,一般不建议下面这样用
PF = f2;
PF();
return 0;
}
std::function在头文件,包装成统一的使用方式更方便快捷效率高
#include
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
// 函数名(函数指针)
std::function func1 = f;
cout << func1(1, 2) << endl;
// 函数对象
std::function func2 = Functor();
cout << func2(1, 2) << endl;
// lamber表达式
std::function func3 = [](const int a, const int b)
{return a + b; };
cout << func3(1, 2) << endl;
// 类的成员函数
//static修饰的,&可加可不加,最好加
std::function func4 = &Plus::plusi;
cout << func4(1, 2) << endl;
//非static修饰的函数,要加&,比如&Plus::plusd
//不加,Plus::plusd则会报错
//而且还要传多个Plus()
std::function func5 = &Plus::plusd;
cout << func5(Plus(), 1.1, 2.2) << endl;
return 0;
}
std::bind函数可以实现参数顺序调整等操作
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
Plus(int x = 2)
:_x(x)
{}
int plusi(int a, int b)
{
return (a + b) * _x;
}
private:
int _x;
};
int main()
{
std::function func1 = f;
cout << func1(1, 2) << endl;
std::function func2 = Functor();
cout << func2(10, 20) << endl;
// 3个参数
std::function func3 = &Plus::plusi;
cout << func3(Plus(), 100, 200) << endl;
// 调整可调用对象的参数个数和顺序
// _1 _2 _3... 表示你要自己传的那些参数,_1表示第一个参数传给_1
// 调整参数个数
// 2个参数
//(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2)
// 第一位&Plus::plusi是函数,第二位Plus(10)是固定的参数
std::function func4 = std::bind(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2);
// 1个参数
//placeholders::_1 是需要传的那个参数
std::function func5 = std::bind(&Plus::plusi, Plus(10),
10, placeholders::_1);
cout << func5(200) << endl;
// 调整顺序 -- 用处不大
std::function func6 = std::bind(f, placeholders::_2, placeholders::_1);
//func1传后参数(66,77)
cout << func1(66, 77) << endl;
//func6传后参数(77,66)
cout << func6(66, 77) << endl;
map> opFuncMap =
{
{ "普通函数指针", f },
{ "函数对象", Functor() },
{ "成员函数指针", std::bind(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2) }
};
cout << opFuncMap["普通函数指针"](1, 2) << endl;
cout << opFuncMap["函数对象"](1, 2) << endl;
cout << opFuncMap["成员函数指针"](1, 2) << endl;
return 0;
}