文章目录
- 前言
- 一、统一的列表初始化
- 1、使用{ }初始化
- 2、 std::initializer_list
- 二、声明
- 1、auto
- 2、decltype
- 3、nullptr
- 三、范围for循环
- 四、右值引用
- 1、左值引用和右值引用
- 2、左值引用和右值引用的比较
- 3、左值引用的使用场景
- 4、右值引用的使用场景
- 5、完美转发
前言
一、统一的列表初始化
1、使用{ }初始化
(1)概念
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
(2)演示:
//日期类
class Date
{
public:
Date(int year = 1,int month = 1,int day = 1)
:_year(year),
_month(month),
_day(day)
{}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
//统一的列表初始化
void test01()
{
//对一个数组
int arr[] = { 1,2,3,4,5 };
//也可以将等号舍去 int arr[]{ 1,2,3,4,5 };
cout << "arr: ";
for (auto& e : arr)
cout << e << " ";
cout << endl;
//对一个对组
pair<int, int> p = { 1,1 };
//p{1,1}
cout << "p: ";
cout << p.first << " " << p.second << endl;
//对一个变量
int a = { 1 };
//a{1}
cout << "a: ";
cout << a << endl;
//自定义类型
Date d = { 1,2,3 };
//Date d{1,2,3}
cout << "d: ";
d.Print();
}
2、 std::initializer_list
(1)介绍
(1)此类型用于访问 C++ 初始化列表中的值,该列表是 const T 类型的元素列表。
(2)这种类型的对象是由编译器根据初始化列表声明自动构造的,该定义列表声明是用大括号括起来的逗号分隔的元素列表:auto il = { 10, 20, 30 }; //IL 的类型是initializer_list
(3)具体了解: std::initializer_list
(2)使用场景
std::initializer_list
一般是作为构造函数的参数,C++11对STL中的不少容器就增加std::initializer_list
作为参数的构造函数,这样初始化容器对象就更方便了。
也可以作为operator=
的参数,这样就可以用大括号赋值。
(3)演示:
//std::initializer_list
void test02()
{
//直接使用
initializer_list<int> ls = { 1,2,3,4,5};
cout << "ls: ";
for (auto e : ls)
cout << e << " ";
cout << endl;
//作为参数使用
//隐式类型转化为initializer_list类型再使用构造函数构造
vector<int> arr = { 1,2,3,4,5 };
cout << "arr: ";
for (auto e : arr)
cout << e << " ";
cout << endl;
}
(4)注意
二、声明
1、auto
(1)介绍
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将 其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。
注意:在C++中,auto 关键字不能直接用作函数参数的类型声明。auto 的设计初衷是用于自动类型推导,但仅限于在编译时能够根据初始化表达式确定类型的上下文中,如局部变量声明、for 循环的初始化表达式等。函数参数的类型必须在编译时就已经明确,因为函数声明和定义必须清晰地指明其接受哪些类型的参数,以便编译器进行类型检查和函数重载解析。
(2)演示:
//作为函数返回值
auto func()
{
//推导为int
return 1;
}
void test03()
{
//自动推导内置类型
auto a = 10;
cout << "a:" << a << endl;
//自动推导自定义类型
auto b = Date(1, 1, 1);
cout << "b:";
b.Print();
//推导复杂的类型
vector<int> arr = {1,2,3};
//vector<int>::iterator = arr.begin();
auto c = arr.begin();
cout << "c:" << *c << endl;
//作为返回值
auto d = func();
cout << typeid(d).name() << endl;
}
2、decltype
(1)介绍
关键字
decltype
将变量的类型声明为表达式指定的类型。
(2)演示
//通过模板类型推导类型
template<class T1, class T2>
void func02(T1 t1, T2 t2)
{
//通过t1*t2的结果类型来声明
decltype(t1 * t2) p3;
cout << "p3:" << typeid(p3).name() << endl;
}
void test04()
{
//通过a*b的表达式的类型来声明ret的类型
int a = 10;
int b = 10;
double c = 1.1;
//通过a*b的结果类型来声明
decltype(a * b) p1;
//通过a*c的结果类型来声明
decltype(a * c) p2;
cout << "p1:" << typeid(p1).name() << endl;
cout << "p2:" << typeid(p2).name() << endl;
func02(1, 1);
}
3、nullptr
(1)介绍
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
(2)演示
//整形类型
void func03(int a)
{
cout << "void func03(int a)" << endl;
}
//重载
//指针类型
void func03(void* a)
{
cout << "void func03(void* a)" << endl;
}
void test05()
{
func03(0);
func03(NULL);
func03(nullptr);
}
上述出现了NULL
也调用整形重载函数,出现了误用的情况,而使用nullptr
就不会出现误用的情况,所以使用空指针时建议使用nullptr
。
三、范围for循环
1、介绍
C++11 引入了范围 for 循环(Range-based for loop),也被称为基于范围的 for 循环,它提供了一种更加简洁和直观的方式来遍历容器(如数组、向量 std::vector、列表 std::list、字符串等)中的元素。这种循环方式不仅减少了代码量,还提高了代码的可读性和可维护性。
2、语法
for (declaration : expression)
statement
declaration:定义了一个变量,用于在每次迭代中存储容器中的当前元素。这个变量的类型会自动根据容器的元素类型推导出来(需要C++11 的自动类型推导功能auto)。
expression:表示一个容器(如数组、向量、字符串等),或者是一个可以返回迭代器对的表达式(如begin() 和 end() 成员函数)。
statement:在每次迭代中执行的代码块。
3、演示
//范围for
void test06()
{
//遍历数组
int arr[] = { 1,2,3,4.5 };
cout << "arr: ";
for (auto e : arr)//auto &e 取引用
{
cout << e << " ";
}
cout << endl;
//遍历vector容器
vector<int> v = { 1,2,3,4,5 };
cout << "v: ";
for(auto &e:v)
{
cout << e << " ";
}
cout << endl;
}
四、右值引用
1、左值引用和右值引用
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
(1)什么是左值、什么是左值引用
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引就是给左值的引用,给左值取别名。
//左值
void test07()
{
int a = 10;
int* b = new int(10);
const int c = 10;
//以上abc都是左值
//下面对左值取引用
int& la = a;
int*& lb = b;
const int& lc = c;
}
(2)什么是右值、什么是右值引用
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用(
&&
)就是对右值的引用,给右值取别名。
//右值
int func04()
{
return 1;
}
void test08()
{
int x = 10, y = 10;
//下面是右值
1;
x + y;
func04();
//对右值取引用
int&& a = 10;
int&& b = x + y;
int&& c = func04();
}
2、左值引用和右值引用的比较
(1)左值引用不能引用右值,但是加const
修饰的左值引用能引用右值。
(2)右值引用不能引用左值,但是通过move
(将左值强制转化为右值)左值,就能被右值引用了。
void test09()
{
//err
//int& a = 10; 左值引用不能引用右值
const int& a = 10; //加const修饰后就可引用右值了
//err
int b = 10;
//int&& c = b; //右值引用不能引用左值
int&& c = move(b);//通过move后就可以引用左值了
}
3、左值引用的使用场景
一般作为参数和作为返回值,这样减少拷贝来提高效率。
//返回值:值返回
//参数:值传递
string func05(string s)
{
return s;
}
//返回值:引用返回
//参数:引用传递
string& func06(string& s)
{
return s;
}
void test10()
{
string s = "abc";
string s1 = func05(s); //调用值传递版本
string s2 = func06(s); //调用引用传递版本
}
4、右值引用的使用场景
(1)右值引用的作用:
移动语义:允许对象以更高效的方式传递其资源(如动态分配的内存、文件句柄等),而不是复制它们。当一个对象通过右值引用传递给函数时,该函数可以“窃取”对象的资源,避免不必要的复制,然后将其状态设置为安全可析构的状态(如空指针、零大小等)。
(1)实现移动拷贝构造和移动赋值重载
对下面链表增加移动拷贝构造和移动赋值重载
list.h
namespace xu
{
// List的结点类
template<class T>
struct ListNode
{
ListNode<T>* _pPre; //后继指针
ListNode<T>* _pNext; //前驱指针
T _val; //数据
//构造结点
ListNode(const T& val = T()) :_val(val), _pPre(nullptr), _pNext(nullptr)
{}
};
//List的正向迭代器类
//Ref为T& Ptr为T*
template<class T, class Ref, class Ptr>
class ListIterator
{
typedef ListNode<T>* PNode;
typedef ListIterator<T, Ref, Ptr> Self;
public:
//构造函数 ,获取一个结点指针
ListIterator(const PNode & pNode = nullptr, const PNode& const P = nullptr) :_pNode(pNode),_P(P)
{}
Ref operator*()
{
assert(_P != _pNode);
return _pNode->_val;
}
Ptr operator->()
{
return &(operator*());
}
Self& operator++()
{
_pNode = _pNode->_pNext;
return *this;
}
Self operator++(int)
{
Self tmp(_pNode);
_pNode = _pNode->_pNext;
return tmp;
}
Self& operator--()
{
_pNode = _pNode->_pPre;
return *this;
}
Self& operator--(int)
{
Self tmp(_pNode);
_pNode = _pNode->_pPre;
return tmp;
}
bool operator!=(const Self& l)
{
return l._pNode != _pNode;
}
bool operator==(const Self& l)
{
return l._pNode == _pNode;
}
PNode get()
{
return _pNode;
}
private:
PNode _pNode;
PNode _P;
};
//List的反向迭代器类
template<class T, class Ref, class Ptr>
class Reverse_ListIterator
{
typedef ListNode<T>* PNode;
typedef Reverse_ListIterator<T, Ref, Ptr> Self;
public:
Reverse_ListIterator(const PNode& pNode = nullptr, const PNode& const P = nullptr) :_pNode(pNode), _P(P)
{}
Ref operator*()
{
assert(_P != _pNode ->_pPre);
return _pNode->_pPre->_val;
}
Ptr operator->()
{
return &(operator*());
}
Self& operator++()
{
_pNode = _pNode->_pPre;
return *this;
}
Self operator++(int)
{
Self tmp(_pNode);
_pNode = _pNode->_pPre;
return tmp;
}
Self& operator--()
{
_pNode = _pNode->_pNext;
}
Self& operator--(int)
{
Self tmp(_pNode);
_pNode = _pNode->_pNext;
return tmp;
}
bool operator!=(const Self& l)
{
return l._pNode != _pNode;
}
bool operator==(const Self& l)
{
return l._pNode == _pNode;
}
PNode get()
{
return _pNode;
}
private:
PNode _pNode;
PNode _P;
};
//list类
template<class T>
class list
{
typedef ListNode<T> Node;
typedef Node* PNode;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
typedef Reverse_ListIterator<T, T&, T*> reverse_iterator;
typedef Reverse_ListIterator<T, const T&, const T*> reverse_const_iterator;
public:
//默认构造
list()
{ //构造一个哨兵位结点
CreateHead();
}
list(int n, const T& value = T())
{
//构造一个哨兵位结点
CreateHead();
//将元素尾插入
while (n != 0)
{
push_back(value);
--n;
}
}
template <class Iterator>
list(Iterator first, Iterator last)
{
//构造一个哨兵位结点
CreateHead();
//将元素尾插入
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造
list(const list<T>& l)
{
//构造一个哨兵位结点
CreateHead();
//遍历+将元素尾插入
PNode tmp = l._pHead->_pNext;
while (tmp != l._pHead)
{
//尾插
push_back(tmp->_val);
tmp = tmp->_pNext;
}
cout << " list(const list<T>& l)" << endl;
}
list<T>& operator=(const list<T>& l)
{
//清空原链表
clear();
//遍历+将元素尾插入
PNode tmp = l._pHead->_pNext;
while (tmp != l._pHead)
{
push_back(tmp->_val);
tmp = tmp->_pNext;
}
return *this;
}
~list()
{
//清空链表
clear();
//删除哨兵位结点
delete _pHead;
_pHead = nullptr;
}
///
// List Iterator
iterator begin()
{
return iterator(_pHead->_pNext, _pHead);
}
iterator end()
{
return iterator(_pHead, _pHead);
}
const_iterator begin() const
{
return const_iterator(_pHead->_pNext, _pHead);
}
const_iterator end()const
{
return const_iterator(_pHead, _pHead);
}
reverse_iterator rbegin()
{
return reverse_iterator(_pHead, _pHead);
}
reverse_iterator rend()
{
return reverse_iterator(_pHead ->_pNext, _pHead);
}
reverse_const_iterator rbegin() const
{
return reverse_const_iterator(_pHead, _pHead);
}
reverse_const_iterator rend()const
{
return reverse_const_iterator(_pHead->_pNext, _pHead);
}
size_t size()const
{
PNode tmp = _pHead->_pNext;
size_t count = 0;
while (tmp != _pHead)
{
tmp = tmp->_pNext;
++count;
}
return count;
}
bool empty()const
{
return _pHead == _pHead->_pNext;
}
// List Access
T& front()
{
assert(!empty());
return _pHead->_pNext->_val;
}
const T& front()const
{
assert(!empty());
return _pHead->_pNext->_val;
}
T& back()
{
assert(!empty());
return _pHead->_pPre->_val;
}
const T& back()const
{
assert(!empty());
return _pHead->_pPre->_val;
}
// List Modify
void push_back(const T & val) { insert(end(), val); }
void pop_back() { erase(--end()); }
void push_front(const T & val) { insert(begin(), val); }
void pop_front() { erase(begin()); }
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T & val)
{
//创造一个结点
PNode tmp = new Node(val);
//获取迭代器中的指针
PNode _pos = pos.get();
//进行插入
PNode prv = _pos->_pPre;
prv->_pNext = tmp;
tmp->_pPre = prv;
tmp->_pNext = _pos;
_pos->_pPre = tmp;
//返回新迭代器
return iterator(tmp);
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
//判断是否为哨兵位结点
iterator it = end();
assert(pos != it);
//获取迭代器结点指针
PNode tmp = pos.get();
//进行删除
PNode next = tmp->_pNext;
PNode prv = tmp->_pPre;
prv->_pNext = next;
next->_pPre = prv;
delete tmp;
tmp = nullptr;
//返回被删除结点的下一个位置的结点迭代器
return iterator(next);
}
void clear()
{
//保存有效结点位置
PNode tmp = _pHead->_pNext;
//遍历删除
while (tmp != _pHead)
{
PNode p = tmp->_pNext;
delete tmp;
tmp = p;
}
//重新指向
_pHead->_pNext = _pHead;
_pHead->_pPre = _pHead;
}
void swap(list<T>& l)
{
std::swap(_pHead, l._pHead);
}
private:
//让哨兵位结点指向自己
void CreateHead()
{
_pHead = new Node;
_pHead->_pNext = _pHead;
_pHead->_pPre = _pHead;
}
PNode _pHead; //哨兵位结点
};
};
实现移动拷贝构造
//移动拷贝
list(list<T>&& l):_pHead(nullptr)
{
//构造一个哨兵位结点
CreateHead();
//交换链表头节点
std::swap(_pHead, l._pHead);
cout << " list(list<T>&& l):_pHead(nullptr)" << endl;
}
移动拷贝构造和普通拷贝构造比较
在传右值时是调用移动拷贝构造,在传左值时是调用普通拷贝构造。
场景1:
用右值传参时,分别普通拷贝构造和移动拷贝构造的效率比较。
场景2:
用左值传参,分别调用移动拷贝构造(通过move来转化)、普通拷贝构造。
实现移动赋值重载
list<T>& operator=(list<T>&& l)
{
//清空原链表
clear();
std::swap(_pHead, l._pHead);
return *this;
}
移动赋值重载构造和赋值重载比较:与移动拷贝构造和普通拷贝构造类似。
(2)当需要返回局部变量引用时,通过左值引用返回的话,可能会造成访问已经销毁的空间。此时只能使用传值返回了,传值返回的临时变量是右值,如果存在移动拷贝构造,则会调用移动拷贝构造,这样就会提高效率。
//返回左值引用可能造成访问冲突
//err
string & func07()
{
string ret = "abc";
return ret;
}
//传值返回
string func07()
{
string ret = "abc";
return ret;
}
//
void func08()
{
string ret = func07();
}
5、完美转发
(1)万能引用
(1)、在模板中
&&
不是作为右值引用,而是作为万能引用,即可以引用右值也能够引用左值。
(2)、模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
template<typename T>
void func08(T && t) //万能引用
{
}
(2)完美转化
std::forward
完美转发在传参的过程中保留对象原生类型属性。
//左值引用
void func09(const int &a)
{
cout << "void func09(const int &a)" << endl;
}
//右值引用
void func09(int &&a)
{
cout << " void func10(int &&a)" << endl;
}
template<typename T>
void func08(T && t)
{
func09(std::forward<T>(t));
}
void test12()
{
int a = 10;
//左值
func08(a);
//右值
func08(10);
}
(3)运用完美转发实现链表尾插右值引用版本
void push_back(T&& val)
{
insert(end(), std::forward<T>(val));
}
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, T&& val)
{
//创造一个结点
PNode tmp = new Node(val);
//获取迭代器中的指针
PNode _pos = pos.get();
//进行插入
PNode prv = _pos->_pPre;
prv->_pNext = tmp;
tmp->_pPre = prv;
tmp->_pNext = _pos;
_pos->_pPre = tmp;
//返回新迭代器
return iterator(tmp);
}