C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型。
struct Point
{
int _x;
int _y;
};
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()
{
//内置类型比如int
int x1 = 1;
int x2={ 2 };
//数组
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
//自定义类型
Point p{ 1, 2 };
Date d1(2022,11,14);//用构造函数初始化
Date d2{2022,11,14};//列表初始化
// C++11中列表初始化也可以适用于new表达式中
int* pa = new int[4]{ 0 };
return 0;
}
C++11中对{1,3,4}这样的列表定义了一个新的类型 -- initializer_list
auto il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
并在库中的容器的里支持了用initializer_list支持的构造函数。vector、list、map等都可以用它进行初始化。
vector v = { 1,2,3,4 };
list lt = { 1,2 };
map dict = { {"sort", "排序"}, {"insert", "插入"} };
自定义类型可以支持多个对象初始化,只需要增加initializer_list类型的构造函数即可。
自动推断类型。
auto不能推导函数参数的类型,因为在函数编译阶段,还没有传递参数,就无法推演出形参的实际类型。
C+11中已经去除了auto声明自动类型变量的功能,只可以用来进行变量类型推导。
关键字decltype将变量的类型声明为表达式指定的类型。
int main()
{
const int x = 1;
double y = 2.2;
decltype(x * y) ret; // ret的类型是double
decltype(&x) p; // p的类型是int*
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
return 0;
}
array --->静态数组
forward_list --->单链表
unordered_map --->哈希表
unordered_set --->哈希表
左值:可以取它的地址的就是左值。(左值除了被const修饰的 其余都可以修改)
右值:不能出现在赋值运算符左边的,不能被取地址的。
例如:字面常量、表达式返回值、函数返回值等。
10 x+y func(x,y)
int main()
{
10;
x + y;
Func(x, y);
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = Func(x, y);
}
tip:右值不能被获取地址,但是一旦被引用之后,就可以通过对引用的取地址/修改来影响右值。但这不是重点。
无论左值引用还是右值引用,都是给对象取别名。
左值引用只能引用左值,不能引用右值。但左值引用加了const后就可以引用右值了。
const int& ra3 = 10;
右值引用只能引用右值,不能引用左值。但是可以引用move后的左值。
int&& r3 = std::move(a);
之所以使用左值引用,目的是:
1、减少函数参数调用以及做返回值时的拷贝构造,以提高效率。
2、做输出型参数,修改返回对象。
短板:但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回, 只能传值返回。
在我们以前的实现中,当我的返回值类型非常复杂时,比如vector
拿一个string类进行说明。主要关注对象是移动构造、和移动拷贝。
// 拷贝构造
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;
string tmp(s._str);
swap(s);
}
// 移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 资源转移" << endl;
swap(s);
}
拷贝构造传入的参数是引用类型,以后还需要继续使用。所以不能直接交换,需要创建一个对象,用这个对象进行交换。
既然局部对象拷贝后,本来就需要销毁。那还为什么要创建一个新的临时变量以完成交换呢?我们直接使用这个局部对象进行交换不就好了吗?
如果实现了移动构造,在局部对象即将出作用域的时候,就会被识别称为将亡值,从而调用移动构造,减少了一次创建对象并拷贝的过程。
namespace chy
{
class string
{
public:
// 拷贝赋值
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 拷贝赋值(深拷贝)" << endl;
string tmp(s);
swap(tmp);
return *this;
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string s) -- 移动赋值(资源移动)" << endl;
swap(s);
return *this;
}
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
string str;
while (value > 0)
{
int x = value % 10;
value /= 10;
str += ('0' + x);
}
if (flag == false)
{
str += '-';
}
std::reverse(str.begin(), str.end());
return str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
当我们调用to_string()函数时,编译器会将其的返回值识别成右值,从而调用类型最匹配的移动拷贝。如果没实现移动构造和移动赋值,那依然走的是深拷贝。
int main()
{
chy::string ret;
ret=to_string(-3456); //移动赋值
chy::string ret2=chy::to_string(-1234);//移动拷贝
return 0;
}
一些例子
int main()
{
chy::string str1("hello");//拷贝构造
chy::string str2(str1); // 拷贝构造
chy::string str3(move(str1)); // 移动构造
std::string s1("hello world");//拷贝构造
std::string s2(s1); // 拷贝构造
// std::string s3(s1+s2);
std::string s3 = s1 + s2; // 移动构造
std::string s4 = move(s1);//移动构造
return 0;
}