c++ - c++11(1)


前言


一、统一的列表初始化

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);
}

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/48e3cbb09ddc44ec84b85101a432de80.png

上述出现了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);
}

五、新的类功能

1、默认成员函数

(1)变化
在c++11以前默认成员函数只有六个:

(1). 构造函数
(2). 析构函数
(3). 拷贝构造函数
(4). 拷贝赋值重载
(5). 取地址重载
(6). const取地址重载

C++11 新增了两个:移动构造函数移动赋值运算符重载

(2)注意

(1) 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类 型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造。
(2) 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中 的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造 完全类似)
(3)如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

2、类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。
演示:

//类成员初始化
class A
{
public:
	void Print()
	{
		cout << "a:" << a << "  b:" << b << endl;
	}

private:
	//类成员定义时初始化
	int a = 10;
	int b = 20;
};

void test14()
{
	A a;
	a.Print();
}

在这里插入图片描述

3、强制生成默认函数

(1)语法
默认成员函数 = default

//强制生成拷贝构造
Person(Person& p) = default;

(2)场景
假如我们只写了拷贝构造,但是没写默认构造,此时编译器就不会自动生成默认构造了。在这种情况下可能就无法生成类对象了。
在这里插入图片描述
此时强制生成一个默认构造函数就可以解决了

//类成员初始化
class A
{
public:
	//强制生成一个默认成员函数
	A() = default;
	A(const A& a)
	{}
private:
};


void test14()
{
	A a;
}

4、禁止生成默认函数

语法
默认成员函数 = delete

//禁止生成拷贝构造
Person(const Person& p) = delete

5、final和override

这两个函数用于继承和多态。
final:修饰虚函数,表示该虚函数不能再被重写。

class Car
 {
 public:
 virtual void Drive() final {}
 };
 class Benz :public Car
 {
 public:
 virtual void Drive() {cout << "Benz-舒适" << endl;}
 }

override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

 class Car{
 public:
 virtual void Drive(){}
 };
 class Benz :public Car {
 public:
 virtual void Drive() override {cout << "Benz-舒适" << endl;}
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值