在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。比如:
int a[] = { 1,2,3 };
对于一些自定义的类型,却无法使用这样的初始化。比如:
vector<int> v{1,2,3,4,5};
就无法通过编译,导致每次定义vector时,都需要先把vector定义出来,然后使用循环对其赋初始值,非常不方便。C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
为什么可以用{}初始化呢?
库里面增加了initializer_list
类型的构造函数。
initlizer_list里有size,和迭代器,就可以用初始化列表构造了。
有些地方的代码需要写很长,有时候std是不能展开的,在练习的时候可以展开。当不能展开是时遇到迭代器写起来很长。
std::map<std::string, std::string> m{ {"apple", "苹果"}, {"banana","香蕉"} };
std::map<std::string, std::string>::iterator it = m.begin();
while (it != m.end())
{
cout << it->first << " :" << it->second << endl;
++it;
}
cout << endl;
//用auto就很方便,让编译器自己推导即可,可以少些很多的代码
auto it1 = m.begin();
while (it1 != m.end())
{
cout<<it1->first<<" :"<< it1->second << endl;
++it1;
}
cout << endl;
decltype是根据表达式的实际类型推演出定义变量时所用的类型。
int a = 10;
int b = 20;
// 用decltype推演a+b的实际类型,作为定义c的类型
decltype(a + b) c;
cout << typeid(c).name() << endl;
void* GetMemory(size_t size)
{
return malloc(size);
}
int main()
{
// 如果没有带参数,推导函数的类型
cout << typeid(decltype(GetMemory)).name() << endl;
// 如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数
cout << typeid(decltype(GetMemory(0))).name() <<endl;
return 0; }
范围for,final,override,前面的博客有讲解。新增加的容器:
感觉array静态数组和数组没什么区别而且用的很少,只不过array可以用迭代器,但是它是在栈上,栈上的空间有限还不如使用vector,开在堆上。
int main()
{
int a[] = { 1,2,3,4 };
array<int,5> a1 = { 5,6,7,8,9 };
auto it = a1.begin();
while(it != a1.end())
{
cout<< *it << " ";
++it;
}
cout << endl;
return 0;
}
用迭代器打印:
单链表的话跟这个array差不多,下面的2个关联式容器作用很大前面的博客有详细的讲解。
在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构函数和&和const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生成默认版本。
有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程序员可以控制是否需要编译器生成。
在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。
例如:
class A {
public:
A(int a): _a(a)
{}
// 显式缺省构造函数,由编译器生成
A() = default;
// 在类中声明,在类外定义时让编译器生成默认赋值运算符重载
A& operator=(const A& a);
private:
int _a;
};
A& A::operator=(const A& a) = default;
int main()
{
A a1(10);
A a2;
a2 = a1;
return 0; }
如果能想要限制某些默认函数的生成
,在C++98中,是该函数设置成private,并且不给定义,
这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete
即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class A {
public:
A(int a) : _a(a)
{}
// 禁止编译器生成默认的拷贝构造函数以及赋值运算符重载
A(const A&) = delete;
A& operator (const A&) = delete;
private:
int _a;
};
int main()
{
A a1(10);
// 编译失败,因为该类没有拷贝构造函数
A a2(a1);
// 编译失败,因为该类没有赋值运算符重载
A a3(20);
a3 = a2;
return 0;
}
传统的C++语法中就有引用,而C++11中新增了的右值引用语法特性,之前的引用都是左值引用。无论是左值引用还是右值引用都是给对象取别名。
什么是左值?什么是左值引用?
左值是1个表示数据的表达式(如变量名或解引用的指针),一般情况我们可以取到它的地址,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。
特殊情况:定义const修饰符后的左值
,不能给它赋值
,但是可以取它的地址。
左值引用就是给左值取别名。
例如:
int main()
{
//以下的p,b,c,*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
//以下是对上面左值的引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
return 0;
}
什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式的返回值、函数返回值等等,右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址。
右值引用就是给右值取别名。
例如:
int main()
{
double x = 1.1, y = 2.2;
//以下几个是常见的右值
10;
x + y;
fmin(x, y);
//以下是对右值的引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x + y);
//下面的编译会报错,左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}
小结:
左值:
1.左值可以取地址
2.一般情况下可以修改,(const修饰的不能修改)
右值:
1.不能取地址
2.不能出现在=的左边,不能修改
注意:右值是不能取地址的,但是给右值去别名后,会导致右值被存储到特定的位置,且可以取到该位置的地址,了解一下。
左值引用小结:
1.左值引用只能引用左值,不能引用右值
2.const左值引用既可以引用左值也可以引用右值
例如:
int main()
{
//左值引用只能引用左值,不能引用右值
//const左值引用既可以引用左值也可以引用右值
int a = 10;
int& ra1 = a;
//int& ra2 = 10; 10是右值
const int& ra3 = 10;
const int& ra4 = a;
return 0;
}
右值值引用小结:
1.右值引用 只能引用右值,不能引用最值
2.但是右值可以引用左值move后的左值
int main()
{
int&& r1 = 10;
int a = 10;
//int&& r2 = a;
int&& r3 = std::move(a);
return 0;
}
知道了右值引用后,我们要知道为什么C++11增加了右值引用。
左值引用的使用场景
1.做参数
2.做函数的返回值
我们知道左值引用做参数可以减少深拷贝,传值就会造成深拷贝,一些简单的传值无所谓但是遇到很复杂的结构就不能传值,需要传引用。左值引用在做参数是解决的很彻底。但是做返回值就解决的不彻底了。
有的函数返回对象出了作用域就不存在了,不能用左值引用了,就存在深拷贝的情况。
namespace bit
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
//cout << "string(char* str)" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// s1.swap(s2)
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 深拷贝" << endl;
string tmp(s._str);
swap(tmp);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
//string operator+=(char ch)
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity; // 不包含最后做标识的\0
};
bit::string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
bit::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;
}
}
编译器做了优化,其实是2次深拷贝。还有这种情况:我先接收返回值,再把它赋值
那我们用右值引用做返回值可不可以呢?不是的,我们要增加移动构造和移动赋值。
右值分为纯右值,和将亡值
纯右值例如:10,x+y。将亡值就是函数返回的临时对象、匿名对象
。移动构造和移动赋值就是将你的资源转移,反正你出了作用域就不存在了。
我们在上面的类中加上移动构造和移动赋值。
// 移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 移动构造" << endl;
this->swap(s);
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
this->swap(s);
return *this;
}
这就是1次移动构造加1次移动赋值,效率比深拷贝要高的多。
总结:
右值引用并不是直接减少深拷贝,而是支持深拷贝的类提供移动构造和移动赋值。
库里的容器中都增加了右值引用的insert,push_bak等,具体的可以看库
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t) { Fun(t); }
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
不用完美转发,右值引用的对象再作为实参传递时,属性会退化为左值。
此时我们就要用完美转发
void PerfectForward(T&& t) { Fun(std::forward<T>(t); }//加上完美转发
注意:模板中的&&并不是右值引用,而是万能引用,它既能接受左值也能接收右值。
代码地址:
https://gitee.com/ge-xiaoyu/testc-plus/tree/master/C++11%E4%B8%8A