【C++知识点】C++11 常用新特性总结

✍个人博客:https://blog.csdn.net/Newin2020?spm=1011.2415.3001.5343
📚专栏地址:C/C++知识点
📣专栏定位:整理一下 C++ 相关的知识点,供大家学习参考~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
🎏唠叨唠叨:在这个专栏里我会整理一些琐碎的 C++ 知识点,方便大家作为字典查询~

C++11 新特性

自动类型推导 auto

基本语法

在C++11之前,auto关键字用来指定存储期。在新标准中,它的功 能变为类型推断。

auto现在成了一个类型的占位符,通知编译器去根据初始化代码推 断所声明变量的真实类型。

各种作用域内声明变量都可以用到它。例如,名空间中,程序块中,或是for循环的初始化语句中。

在没有auto以前,遍历一个容器需要这样来书写一个迭代器:

#include <iostream>
#include <vector>
using namespace std;
int main() {
	vector<int> vc = { 1,2,3,4,5,6 };
	for (vector<int>::iterator it = vc.begin(); it != vc.end(); it++) {
		cout << *it << " ";
	}
	return 0;
}

有了auto之后,可以写出如下代码:

#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector<int> vc = { 1,2,3,4,5,6 };
    for (auto it= vc.begin(); it != vc.end(); it++) {
        cout << *it << " ";
    }
	return 0;
}
auto 与 const

先看一段代码:

int x = 0;
const auto n = x; 
auto f = n; 
const auto& r1 = x; 
auto& r2 = r1; 
  1. 第 2 行代码中,n 为 const int,auto 被推导为 int。

  2. 第 3 行代码中,n 为 const int 类型,但是 auto 却被推导为 int 类型,这说明当=右边的表达式带有 const 属性时, auto 不会使用 const 属性,而是直接推导出 non-const 类型。

  3. 第 4 行代码中,auto 被推导为 int 类型,这个很容易理解,不再赘述。

  4. 第 5 行代码中,r1 是 const int & 类型,auto 也被推导为 const int 类型,这说明当 const 和引用结合时,auto 的推导将保留表达式的 const 类型。

总结:

  1. 当类型不为引用时,auto 的推导结果将不保留表达式的 const 属性;

  2. 当类型为引用时,auto 的推导结果将保留表达式的 const 属性。

auto 的高级用法

auto 除了可以独立使用,还可以和某些具体类型混合使用,这样 auto 表示的就是“半个”类型,而不是完整的类型:

int x = 0;
auto *pt1 = &x; //pt1 为 int *,auto 推导为 int
auto pt2 = &x; //pt2 为 int*,auto 推导为 int*
auto &r1 = x; //r1 为 int&,auto 推导为 int
auto r2 = r1; //r2 为 int,auto 推导为 int
auto 的限制
  1. auto 不能在函数的参数中使用

    auto funcName(int v1,int v2)
    {
    	return v1 + v2;
    }
    

    注意:C++11的时候不能,C++14开始可以让普通函数具备返回值推导

  2. auto 不能作用于类的非静态成员变量**(就是没有static关键字修饰的成员变量)**

    class Student {
    protected:
        auto name;	//错误
        int age;	//正确
    };
    
  3. auto 不能作用于模板参数

    template <typename T>
    class Student{
    	//dosomething:
    };
    int main(){
        Student<int> s1;
        Student<auto> s2 = s1; //错误
        return 0;
    }
    
  4. auto 不能用于推导数组类型

自动类型推导 decltype

decltype,在C++中,作为操作符,用于查询表达式的数据类型。

decltype在C++11标准制定时引入,主要是为泛型编程而设计,以解决泛型编程中,由于有些类型由模板参数决定,而难以(甚至不可能)表示的问题。

decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 sizeof 很相似。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。有时候,我们可能需要计算某个表达式的类型。

lambda表达式如果我们想要使用它的类型我们就需要使用decltype。

auto num1 = 100auto num2 = 200;
decltype(num1 + num2) num3; 

num3的类型就是num1 + num2 最终的结果的类型。

在泛型编程中,可能需要通过参数的运算来得到返回值的类型:

#include <iostream>
using namespace std;
template <typename R, typename T, typename U>
R add(T t, U u)
{
    return t + u;
}
int main() {
    int a = 1; float b = 2.0;
    auto c = add<decltype(a + b)>(a, b);
    cout << c << endl;
    return 0;
}

下面的代码正常吗?

template <typename T, typename U>
decltype(t + u) add(T t, U u) 
{
    return t + u;
}

C++ 的返回值是前置语法,在返回值定义的时候参数变量还不存在,所以报错!

正确写法:

template <typename T, typename U>
auto add(T t, U u)->decltype(t+u) //尾随返回类型
{
    return t + u;
}

C++14开始可以直接写成:

template <typename T, typename U>
auto add(T t, U u)
{
    return t + u;
}

右值引用

基本概念

C++98 中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通过指针来实现的,因此使用引用,可以 提高程序的可读性。

比如交换两个变量的值,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。

void change(int& n1, int& n2)
{
    int temp = n1;
    n1 = n2;
    n2 = temp;
}

为了提高程序运行效率,C++11 中引入了右值引用,右值引用也是别名,但其只能对右值引用。

int func1(int v1, int v2){
     return v1 + v2;
}
int main()
{
     const int&& num1 = 10;//右值示例
     //引用函数返回值,返回值是一个临时变量,为右值
     int&& num2 = func1(10, 20);
     cout << num1 << endl;
     cout << num2 << endl;
     return 0;
}
右值与左值
  1. 一般认为:左值可放在赋值符号的左边,右值可以放在赋值符号 的右边;或者能够取地址的称为左值,不能取地址的称为右值

  2. 左值也能放在赋值符号的右边,右值只能放在赋值符号的右边

int num = 100;
//函数的返回值结果为引用
int& returnNum(){
    return num;
}
int main()
{
    int num2 = 10;
    int num3 = num2;
    int* p = new int(0);
    //num2和num3,p和*p都是左值,说明:左值既可放在=的左侧,也可放在=的右侧
    const int num4 = 30;
    //num4 = num1;
    //特例:num4 虽然是左值,但是为const常量,只读不允许被修改
    cout << &num4 << endl;
    //num4可以取地址,所以num4严格来看也是左值
    //num3 + 2 = 200;
    //编译失败:因为num3+2的结果是一个临时变量,没有具体名称,也不能取地址,因此为右值
    returnNum() = 111;
    return 0;
}
移动语义

转移语义可以将资源(堆、系统对象等)从一个对象转移到另一个对象,这样可以减少不必要的临时对象的创建、拷贝及销毁。移动语义与拷贝语义是相对的,可以类比文件的剪切和拷贝。在现有的 C++ 机制中,自定义的类要实现转移语义,需要定义移动构造函数,还可以定义转移赋值操作符。

以 string 类的移动构造函数为例:

MyString(MyString&& str) {
    cout << "move ctor source from " << str.data << endl;
    len = str.len;
    data = str.data;
    str.len = 0;
    str.data = NULL;
}

和拷贝构造函数类似,有几点需要注意:

  • 参数(右值)的符号必须是 &&
  • 参数(右值)不可以是常量,因为我们需要修改右值
  • 参数(右值)的资源链接和标记必须修改,否则,右值的析构函数就会释放资源。转移到新对象的资源也就无效了。

标准库函数 std::move 可以将左值变成一个右值。

编译器只对右值引用才能调用移动构造函数,那么如果已知一个命名对象不再被使用,此时仍然想调用它的移动构造函数,也就是把一个左值引用当做右值引用来使用,该怎么做呢?

用 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。

列表初始化

统一列表初始化的使用

在 C++98 中,标准允许使用花括号 {} 对数组元素进行统一的列表初始值设定。比如:

int arr1[] = {1,2,3,4,5};
int arr2[100] = {0};

但对于一些自定义类型却不行,例如:

vector<int> vc{1,2,3,4,5};

在 c++98 中是无法编译成功的,只能够定义 vector 对应之后通过循环进行插入元素达到这个目的。

C++11 扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加

#include<iostream>
#include<vector>
#include<map>
using namespace std;
class ClassNum {
public:
    ClassNum(int n1 = 0, int n2 = 0) : _x(n1), _y(n2)
    {}
private:
    int _x;
    int _y;
};
int main() {
    int num1 = { 100 };//定于内置类型
    int num2{ 3 };//也可以不加=
    //数组
    int arr1[5] = { 1,3,4,5,6 };
    int arr2[] = { 4,5,6,7,8 };
    //STL中的容器
    vector<int>v{ 12,2 };
    map<int, int>mp{ {1,2},{3,4} };
    //自定义类型初始化
    ClassNum p{ 1, 2 };
    return 0;
}

For each

C++11 引入了基于范围的迭代写法,拥有了能够写出像 Python 类似的简洁的循环语句。就拿最常用的 vector 遍历举例:

int main()
{
    vector<int> v1={ 1,2,3,4,5,6 };
    for(auto i : v1)//范围for
    {
        cout << i << “ ”;
    }
    return 0;
}

如果用 for each 则需要传入一个处理函数,下面案例用 lambda 实现该函数:

输入案例:

abcstringabc

a

输出案例:

2

#include <iostream>
#include <algorithm>
using namespace std;
int getCharNum(string s, char c) {
    int count = 0;
    for_each(s.begin(), s.end(), [&](char ch) {
        if (c == ch) {
            count++;
        }
    });
    return count;
}
int main() {
    string s1;
    char ch;
    cin >> s1;
    cin >> ch;
    cout << getCharNum(s1, ch) << endl;
    return 0;
}

Lambda表达式

lambda 表达式实际上是一个匿名类函数,在编译时会将表达式转换为匿名类函数。

语法
[capture-list] (parameters) mutable -> return-type { statement}
[捕获列表](参数)->返回值{ 函数体 };
  1. [capture-list]: 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据 [] 来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用。(不能省略)
  2. (parameters): 参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同 () 一起省略。(没有参数可以省略)
  3. mutable: 默认情况下,lambda 函数总是一个 const 函数,mutable 可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空),mutable 放在参数列表和返回值之间。(可以省略)
  4. ->returntype: 返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。(有没有返回值都可以省略)
  5. {statement}: 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的 变量。(不能省略)
捕获列表说明

捕捉列表描述了上下文中那些数据可以被 lambda 使用,以及使用的方式传值还是传引用。

  1. [a,&b]: 其中 a 以复制捕获而 b 以引用捕获。
  2. [this]: 以引用捕获当前对象(*this)。
  3. [&]: 若存在,以引用捕获所有用于 lambda 体内的自动变量,并以引用捕获当前对象。
  4. [=]: 若存在,以复制捕获所有用于 lambda 体内的自动变量,并以引用捕获当前对象。
  5. []: 不捕获,大部分情况下不捕获就可以了。

在 lambda 函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。

C++11 中最简单的 lambda 函数为:[]{}; 该lambda函数不能做任何事情,没有意义!

#include<iostream>
using namespace std;
void(*FP)(); //函数指针
int main()
{
    //最简单的lambda表达式, 该lambda表达式没有任何意义
    [] {};
    
    //省略参数列表和返回值类型,返回值类型由编译器推导为int
    int num1 = 3, num2 = 4;
    
    //省略了返回值类型,无返回值类型
    auto fun1 = [&num1, &num2](int num3) {num2 = num1 + num3; };
    fun1(100);
    cout << num1 << " " << num2 << endl;
    
    //捕捉列表可以是lambda表达式
    auto fun = [fun1] {cout << "great" << endl; };
    fun();
    
    //各部分都很完善的lambda函数
    auto fun2 = [=, &num2](int num3)->int {return num2 += num1 + num3; };
    cout << fun2(10) << endl;
    
    //复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable { x *= 2; return a + x; };
    cout << add_x(10) << endl;
    
    //编译失败--->提示找不到operator=() 
    //auto fun3 = [&num1,&num2](int num3) {num2 = num1 + num3;};
    //fun1 = fun3; 
    
    //允许使用一个lambda表达式拷贝构造一个新的副本
    auto fun3(fun);
    fun();
    
    //可以将lambda表达式赋值给相同类型的函数指针
    auto f2 = [] {};
    FP = f2;
    FP();
    return 0;
}
函数对象与lambda表达式

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了 operator() 运算符的类对象。

从使用方式上来看,函数对象与 lambda 表达式完全一样。

#include <iostream>
using namespace std;
class Rate{
public:
    Rate(double rate) : _rate(rate){}
    double operator()(double money, int year) {
        return money * _rate * year;
    }
private:
    double _rate;
};
int main() {
    //函数对象
    double rate = 0.6;
    Rate r1(rate);
    double rd = r1(20000, 2);
    cout << rd << endl; 
    //lambda
    auto r2 = [=](double money, int year)->double {return money * rate * year;};
    double rd2 = r2(20000, 2);
    cout << rd2 << endl;
    return 0;
}

在 C++98 中,对一个数据集合中的元素进行排序,可以使用 sort 方法。

#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
int main()
{
    int array[] = { 3,6,9,5,4,7,0,8,2,1 };
    //默认按照小于比较,排出来结果是升序
    sort(array, array + sizeof(array) / sizeof(array[0]));
    //如果需要降序,需要改变元素的比较规则
    sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
    return 0;
}

如果待排序元素为自定义类型,需要用户定义排序时的比较规则。

#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
struct Goods{
    string name;
    double price;
};
struct Compare{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl.price <= gr.price;
    }
};
int main()
{
    Goods gds[] = { { "苹果", 5.1 }, { "橙子", 9.2 }, { "香蕉", 3.6 }, {"菠萝",9.6} };
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), Compare());
    for (int i = 0; i < 4; i++) {
        cout << gds[i].name << " " << gds[i].price << endl;
    }
    return 0;
}

有了 lambda 表示,代码就可以写成如下:

#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
struct Goods{
    string name;
    double price;
};
int main()
{
    Goods gds[] = { { "苹果", 5.1 }, { "橙子", 9.2 }, { "香蕉", 3.6 }, {"菠萝",9.6} };
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& l, const Goods& r)->bool
         {
             return l.price < r.price;
         });
    for (int i = 0; i < 4; i++) {
        cout << gds[i].name << " " << gds[i].price << endl;
    }
    return 0;
}

智能指针

垃圾回收机制已经大行其道,得到了诸多编程语言的支持,例如 Java、Python、C#、PHP 等。而 C++ 虽然从来没有公开得支持过垃圾回收机制,但 C++98/03 标准中,支持使用 auto_ptr 智能指针来实现堆内存的自动回收;C++11 新标准在废弃 auto_ptr 的同时,增添了 unique_ptr、shared_ptr 以及 weak_ptr 这 3 个智能指针来实现堆内存的自动回收。

所谓智能指针,可以从字面上理解为“智能”的指针。具体来讲,智能指针和普通指针的用法是相似的,不同之处在于,智能指针可以在适当时机自动释放分配的内存。

也就是说,使用智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题出现。由此可见,C++ 也逐渐开始支持垃圾回收机制了。

shared_ptr

智能指针都是以类模板的方式实现的,shared_ptr 也不例外。

shared_ptr(其中 T 表示指针指向的具体数据类型)的定义位于 <memory> 头文件,并位于 std 命名空间中,因此在使用该类型指针时,程序中应包含如下 2 行代码:

#include <memory>
using namespace std;

和 unique_ptr、weak_ptr 不同之处在于,多个 shared_ptr 智能指针可以共同使用同一块堆内存。并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用 计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针(只有引用计数为 0 时,堆内存才会被自动释放)。

shared_ptr 智能指针的创建

shared_ptr 类模板中,提供了多种实用的构造函数:

shared_ptr<int> p1; //不传入任何实参
shared_ptr<int> p2(nullptr); //传入空指针 nullptr

空的 shared_ptr 指针,其初始引用计数为 0,而不是 1。

在构建 shared_ptr 智能指针,也可以明确其指向:

shared_ptr<int> p(new int(5));

C++11 标准中还提供了 std::make_shared 模板函数,其可以用于初始化 shared_ptr 智能指针。

shared_ptr<int> p = make_shared<int>(5);

shared_ptr 模板还提供有相应的拷贝构造函数和移动构造函数。

shared_ptr<int> p3;
//调用拷贝构造函数
shared_ptr<int> p4(p3);//或者 shared_ptr<int> p4 = p3;
//调用移动构造函数
shared_ptr<int> p5(move(p4)); //或者 shared_ptr<int> p5 = move(p4);

在初始化 shared_ptr 智能指针时,还可以自定义所指堆内存的释放规则,这样当堆内存的引用计数为 0 时,会优先调用自定义的释放规则。

在某些应用场景中,自定义释放规则是很有必要的。比如,对于申请的动态数组来说,shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存。

对于申请的动态数组,释放规则可以使用 C++11 标准中提供的 default_delete 模板类,我们也可以自定义释放规则:

#include <iostream>
using namespace std;
//自定义释放规则
void deleteInt(int* p) {
    delete[]p;
}
int main()
{
    //指定 default_delete 作为释放规则
    shared_ptr<int> p1(new int[3], default_delete<int[]>());
    //初始化智能指针,并自定义释放规则
    shared_ptr<int> p2(new int[3], deleteInt);
    return 0;
}

借助 lamdba,p2 还可以写成这样:

shared_ptr p2(new int[2], [](int* p) {delete[]p; });
unique_ptr

unique_ptr 指针自然也具备“在适当时机自动释放堆内存空间”的能力。和 shared_ptr 指针最大不同之处在于,unique_ptr 指针指向的堆内存无法同其它 unique_ptr 共享,也就是说,每个unique_ptr 指针都独自拥有对其所指堆内存空间的所有权。

weak_ptr

shared_ptr 是采用引用计数的智能指针,多个 shared_ptr 实例可以指向同一个动态对象,并维护了一个共享的引用计数器。 对于引用计数法实现的计数,总是避免不了循环引用的问题,shared_ptr 也不例外。

看案例:

#include <iostream>
using namespace std;
class CB;
class CA
{
public:
    CA() { cout << "CA() called! " << endl; }
    ~CA() { cout << "~CA() called! " << endl; }
    void set_ptr(shared_ptr<CB>& ptr) { m_ptr_b = ptr; }
    void b_use_count() { cout << "b use count : " << m_ptr_b.use_count() << endl; }
    void show() { cout << "this is class CA!" << endl; }
private:
    shared_ptr<CB> m_ptr_b;
};
class CB
{
public:
    CB() { cout << "CB() called! " << endl; }
    ~CB() { cout << "~CB() called! " << endl; }
    void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
    void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
    void show() { cout << "this is class CB!" << endl; }
private:
    shared_ptr<CA> m_ptr_a;
};
int main(){
    shared_ptr<CA> ptr_a(new CA());
    shared_ptr<CB> ptr_b(new CB());
    cout << "a use count : " << ptr_a.use_count() << endl;
    cout << "b use count : " << ptr_b.use_count() << endl;
    ptr_a->set_ptr(ptr_b);
    ptr_b->set_ptr(ptr_a);
    cout << "a use count : " << ptr_a.use_count() << endl;
    cout << "b use count : " << ptr_b.use_count() << endl;
    return 0; 
}

可以看到最后两个类都没有被析构。

这个时候,可以用 weak_ptr 来解决这个问题,可以把两个类中的一个成员变量改为 weak_ptr 对象即可。 weak_ptr 不会增加引用计数,所以引用就构不成环。

private:
	weak_ptr<CB> m_ptr_b;

可变参数模板

C++11 之前,类模版和函数模版中只能含固定数量的模版参数。

C++11 的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模。

#include <iostream>
using namespace std;
//函数模板的参数个数为0到多个参数,每个参数的类型可以各不相同
template<class...T>
void funcName(T...args) {//args一包形参,T一包类型
    cout << sizeof...(args) << endl;//sizeof...固定语法格式计算获取到模板参数的个数
    cout << sizeof...(T) << endl;//注意sizeof...只能计算...的可变参
}
int main() {
    funcName(100, 200, 300,400,600);
    return 0;
}
参数包展开

递归函数

//递归
#include <iostream>
using namespace std;
void funcName1() {
    cout << "递归终止函数" << endl;
}
template<class T, class ...U>
void funcName1(T frist, U...others) {
    cout << "收到的参数值:" << frist << endl;
    funcName1(others...);//注意这里传进来的是一包形参不能省略... 
}
int main() {
    funcName1(1, 2, 3, 4, 5, 6);
    return 0;
}

使用 if constexpr

//if constexpr
#include <iostream>
using namespace std;
//if constexpr...()//constexpr代表的是常量的意思或者是编译时求值
//c++17中新增一个语句叫做编译期间if语句( constexpr if)
template<class T, class...U>
void funcName2(T frist, U...args) {
    cout << "收到参数:" << frist << endl;
    if constexpr (sizeof...(args) > 0) {
        //constexpr必须有否则无法编译成功,圆括号里面是常量表达式
        funcName2(args...);
    }
}
int main() {
    funcName2(1,2, 3, 4, 5, 6);
    return 0;
}

默认成员函数控制

在 C++ 中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构函数和 & 和 const& 的重载、移动构造、移动拷贝构造等函数。

如果在类中显式定义了,编译器将不会重新生成默认版本。

有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程序员可以控制是否需要编译器生成。

#include <iostream>
using namespace std;
class ClassTest
{
public:
    ClassTest(int num):n(num){}
    ClassTest() = default; //显式缺省构造函数 让编译器生成不带参数的默认构造函数,改成delete就不会生成了
    ClassTest(const ClassTest&) = delete; //禁止编译器生成默认的拷贝构造函数
    ClassTest& operator=(const ClassTest& a); //在类中声明,在类外定义时,让编译器生成默认赋值运算符重载
private:
    int n;
};
ClassTest& ClassTest::operator=(const ClassTest& a) = default;//在类外让编译器默认生成
int main() {
    ClassTest c1;
    return 0;
}

新增容器

std::array
  1. std::array 保存在栈内存中,相比堆内存中的 std::vector,它能够灵活的访问容器里面的元素,从而获得更高的性能。

  2. std::array 会在编译时创建一个固定大小的数组,std::array 不能够被隐式的转换成指针,使用 std::array 只需指定其类型和大小即可!

  3. c++11 封装了相关的数组模板类,不同于 C 风格数组,它不会自动退化成 T* 类型,它能作为聚合类型聚合初始化。

    std::array 是封装固定大小数组的容器,数组元素下标索引从 0 开始。

定义和初始化

//此处,std::array对象arr表示一个固定大小为5且未初始化的int 
//数组,因此所有5个元素都包含垃圾值。
std::array<int, 5> arr;

//这里,std::array对象arr1表示一个固定大小为10的字符串数组。
std::array<std::string, 10> arr1;

//前2个值将被初始化,其他值为0。
std::array<int, 10> arr3 = { 1, 2 } ;

如下代码有什么问题?

#include <iostream>
#include <array>
using namespace std;
int main() {
    array<int,3> arr = { 1,2,3 };
    int len = 3;
    array<int, len> arr2 = { 4,5,6 }; //错误
    return 0;
}

array<int,len> 非法,数组大小参数必须是常量表达式

访问元素

有 3 种方法可以访问 std::array 中的元素:

  1. 运算符 []: 使用运算符 [] 访问 std::array 中的元素。

    //创建并初始化一个大小为10的数组。
    std::array<int, 10> arr = { 1,2,3,4,5,6,7,8,9,10 } ;
    int x = arr[2];
    

    使用 [] 运算符访问超出范围的元素将导致未定义的行为。

  2. at(): 使用 at() 成员函数访问 std::array 中的元素

    //Accessing element using at() function
    int x = arr.at(2);
    

    使用 at() 函数访问任何超出范围的元素将抛出 out_of_range 异常。

  3. std::tuple 的 get<>():

    int x = std::get<2>(arr) ;
    

    使用 get<> 运算符访问超出范围的元素将导致编译时错误。

其他常用函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3qjEbYKC-1678494892117)(C++笔记.assets/image-20230301142052854.png)]

std::forward_list

std::forward_list 是一个列表容器,使用方法和 std::list 基本类似。

与 std::list 的双向链表的实现不同,std::forward_list 使用单向链表进行实现,提供了 O(1) 复杂度的元素插入,不支持快速随机访问,也是标准库容器中唯一一个不提供 size() 方法的容器。

forward_list 具有插入、删除表项速度快、消耗内存空间少的特点,但只能向前遍历。与其它序列容器(array、vector、deque)相比,forward_list 在容器内任意位置的成员的插入、提取(extracting)、移动、删除操作的速度更快,因此被广泛用于排序算法。

创建

由于 forward_list 容器以模板类 forward_list(T 为存储元素的类型)的形式被包含在头文件中,并定义在 std 命名空间中。因此,在使用该容器之前,代码中需包含下面两行代码:

#include <forward_list>
using namespace std;

创建 forward_list 容器的方式,大致分为以下 5 种。

  1. 创建一个没有任何元素的空 forward_list 容器:

    std::forward_list<int> values;
    

    由于 forward_list 容器在创建后也可以添加元素,因此这种创建方式很常见。

  2. 创建一个包含 n 个元素的 forward_list 容器:

    std::forward_list<int> values(10);
    

    通过此方式创建 values 容器,其中包含 10 个元素,每个元素的值都为相应类型的默认值(int类型的默 认值为 0)。

  3. 创建一个包含 n 个元素的 forward_list 容器,并为每个元素指定初始值。例如:

    std::forward_list<int> values(10, 5);
    

    如此就创建了一个包含 10 个元素并且值都为 5 个 values 容器。

  4. 在已有 forward_list 容器的情况下,通过拷贝该容器可以创建新的 forward_list 容器。例如:

    std::forward_list<int> value1(10); 
    std::forward_list<int> value2(value1);
    

    注意,采用此方式,必须保证新旧容器存储的元素类型一致。

  5. 通过拷贝其他类型容器(或者普通数组)中指定区域内的元素,可以创建新的 forward_list 容器。例如:

    //拷贝普通数组,创建forward_list容器
    int a[] = { 1,2,3,4,5 }; 
    std::forward_list<int> values(a, a+5);
    //拷贝其它类型的容器,创建forward_list容器
    std::array<int,5>arr{ 11,12,13,14,15 }; 
    std::forward_list<int>values(arr.begin()+1, arr.end());//拷贝arr容器中的{12,13,14,15}
    

其他函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dDdm6N5V-1678494892120)(C++笔记.assets/image-20230301142942250.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o2NHGrok-1678494892124)(C++笔记.assets/image-20230301142957747.png)]

unordered 系列

C++11 引入了两组无序容器:

std::unordered_set/std::unordered_multiset
std::unordered_map/std::unordered_multimap 

无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(1)。

#include <unordered_map>
#include <unordered_set>
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值