目录
1. 统一的列表初始化
1.1 {}初始化
1.2 std::initializer_list
2. decltype
3. 右值引用和移动语义
3.1 左值引用和右值引用
3.2 左值引用与右值引用比较
3.3 右值引用使用场景和意义
3.4 右值引用引用左值及其一些更深入的使用场景分析
3.5 完美转发
4 新的类功能
1. 统一的列表初始化
1.1 {}初始化
在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
struct Point
{
int x;
int y;
};
int main()
{
int arr1[] = { 1, 2, 3 };
int arr2[2] = { 0 };
Point p = { 1, 2 };
return 0;
}
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
struct Point
{
int x;
int y;
};
int main()
{
int arr1[]{ 1, 2, 3 };
int arr2[2]{ 0 };
Point p{ 1, 2 };
int* ptr = new int[4]{0};
return 0;
}
new的对象也可以用{}初始化
class Date
{
public:
Date(size_t year, size_t month, size_t day)
:_year(year),
_month(month),
_day(day)
{}
private:
size_t _year;
size_t _month;
size_t _day;
};
int main()
{
Date d1(2024, 3, 18);
Date d2 = { 2024, 3, 18 };
Date d2{ 2024, 3, 18 };
return 0;
}
对于自定义类型,第一个()是构造,下面两个{}是利用列表初始化。
1.2 std::initializer_list
int main()
{
auto il = { 1,2,3 };
cout << typeid(il).name() << endl;
initializer_list<int> il2 = { 1,2,3 };
return 0;
}
std::initializer_list使用场景:
std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加 std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。
也可以作为operator= 的参数,这样就可以用大括号赋值。
int main()
{
auto il = { 1,2,3 };
cout << typeid(il).name() << endl;
initializer_list<int> il2 = { 1,2,3 };
list<int> lt = { 1,2,3 };
initializer_list<char> il4 = { 'a', 'b', 'c' };
string s = { 'a', 'b', 'c' };
string s2 = { "hello word"};
initializer_list<const char*> il3 = { "hello word" , "abc"};
map<int, int> m = { make_pair(1,1), make_pair(2,2) ,make_pair(3,3) };
return 0;
}
对于string类型字符串类型的初始化建议还是别用,容易出bug,initializer_list<const char*>里面只有一个还行,如果大于一个就会错误,直接构造就挺好。
容器构造和赋值的实现
namespace kele
{
template<class T>
class vector {
public:
typedef T* iterator;
iterator begin(){return _start;}
iterator end(){return _finish;}
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
for (auto e : l)
*vit++ = e;
}
vector<T>& operator=(initializer_list<T> l) {
vector<T> tmp(l);
swap(_start, tmp._start);
swap(_finish, tmp._finish);
swap(_endofstorage, tmp._endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
int main()
{
kele::vector<int> v = { 1,2,3 };
for (auto e : v)
{
cout << e << endl;
}
return 0;
}
2. decltype
关键字decltype将变量的类型声明为表达式指定的类型。
template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)
{
return x * y;
}
int main()
{
int x = 1;
char y = 'a';
decltype(x * y) z;//z int
cout << typeid(z).name() << endl;
double a = 0.25;
decltype(multiply(a, x)) m;//m double
cout << typeid(m).name() << endl;
return 0;
}
3. 右值引用和移动语义
3.1 左值引用和右值引用
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们 之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋 值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时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);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用
3.2 左值引用与右值引用比较
左值引用只能引用左值,不能引用右值。
但是const左值引用既可引用左值,也可引用右值。
右值引用只能右值,不能引用左值。
但是右值引用可以move以后的左值。
int main()
{
int x = 10;
int& z = x;//左值引用
int&& y = 10;//右值引用
int&& yy = move(x);//右值引用左值
const int& xx = 10;//左值引用右值
return 0;
}
3.3 右值引用使用场景和意义
左值引用的使用场景:做参数和做返回值都可以提高效率。(对于自定义类型)
string& func(const string& x)
{}
左值引用的短板:但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回, 只能传值返回。
例如: string operator+(const string& str)
在C++98,tmp是函数内部的局部对象,所以只能拷贝构造返回值,这是一次深拷贝,不能用引用。然后s2又是一次拷贝构造,深拷贝。(编译器不优化的前提下)
这个返回值是一个临时对象,也就是右值,将亡值,这个临时对象在拷贝之后也是要析构的,可不可以利用一下这个资源呢?
右值引用和移动语义解决上述问题:
在bit::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
不仅仅有移动构造,还有移动赋值
namespace kele
{
class string
{
public:
string(const char* str = "")//构造
:_size(strlen(str))
{
cout << "构造" << endl;
_capacity = _size == 0 ? 3 : _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& str)//拷贝构造
:_str(nullptr)
{
cout << "string(const string & str)//拷贝构造" << endl;
string tmp(str._str);
swap(tmp);
}
string(string&& str)//移动构造
:_str(nullptr),
_size(0),
_capacity(0)
{
cout << "string(string&& str)//移动构造" << endl;
swap(str);
}
string& operator=(string& str)//赋值重载
{
if (this != &str)
{
cout << "string& operator=(string str)//赋值重载" << endl;
string tmp(str._str);
swap(tmp);
return *this;
}
}
string& operator=(string&& str)//移动赋值重载
{
if (this != &str)
{
cout << "string& operator=(string&& str)//移动赋值重载" << endl;
swap(str);
return *this;
}
}
void reserve(size_t n = 0)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
string& operator+=(const string& s)
{
size_t n = s._size;
if (_size + n > _capacity)
{
reserve(_capacity + n);
}
strcpy(_str + _size, s._str);
_size += n;
return *this;
}
string operator+(const string& str)
{
string tmp(_str);
tmp += str;
return tmp;
}
~string()
{
delete[] _str;
_size = _capacity = 0;
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
STL中的容器都是增加了移动构造和移动赋值:
3.4 右值引用引用左值及其一些更深入的使用场景分析
当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。
C++11中,std::move()函数位于头文件中,该函数名字具有迷惑性, 它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
STL容器插入接口函数也增加了右值引用版本:
3.5 完美转发
模板中的&& 万能引用:
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是
引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值
我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
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<typename T>
void PerfectForward(T&& t)
{
//Fun(t);
Fun(forward<T>(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;
}
std::forward 完美转发在传参的过程中保留对象原生类型属性
完美转发可能在真实使用场景中需要多次使用,因为右值引用在接收后都退化成了左值。
4 新的类功能
默认成员函数
原来C++类中,有6个默认成员函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值重载
- 取地址重载
- const 取地址重载
C++11 新增了两个:移动构造函数和移动赋值运算符重载。
默认移动构造生成的条件:没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。
默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造。
强制生成默认函数的关键字default:
Person(Person&& p) = default;
禁止生成默认函数的关键字delete:
Person(const Person& p) = delete;
继承和多态中的final与override关键字
在C++中,final是一个关键字,用于修饰类、成员函数或虚函数,表示它们不能被继承或重写。具体来说,final关键字有以下几个用途:
final修饰类:当一个类被声明为final时,该类不能被其他类继承。这样可以防止其他类对该类进行派生,保护该类的实现和接口不被修改。
final修饰成员函数:当一个成员函数被声明为final时,该函数不能在派生类中被重写。这样可以确保该函数的实现不会被修改。
final修饰虚函数:当一个虚函数被声明为final时,该虚函数不能在派生类中被重写。这样可以阻止派生类对该虚函数的进一步修改。
使用final关键字可以提高代码的安全性和可维护性,同时也能够更好地表达设计意图
override(重写覆盖): 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
未完待续