【C++ 顺序容器】

前言

有人问我C++中顺序容器有关的知识,在这里我参考C++Primer将顺序容器知识总结了一下,其中也包含了部分代码练习,可供参考。也有部分知识我就没有详细的说,比如顺序容器的一些操作,插入删除等,有需要的可以自行查阅。

顺序容器的类型

在C++中,顺序容器是一种数据结构,用于存储一组元素,并保持它们按照插入顺序排列。STL(标准模板库)提供了多种顺序容器,每个容器都适用于不同的用途和性能要求。以下是C++中常见的顺序容器:

  1. std::vector
    • std::vector 是动态数组,允许在常数时间内访问元素。它适用于需要随机访问元素的情况,但插入和删除操作可能会比较慢。
  2. std::deque
    • std::deque(双端队列)是一种双向开口的容器,允许在两端高效地插入和删除元素。它适用于需要频繁在两端进行操作的情况。
  3. std::list
    • std::list 是双向链表,允许在任何位置高效地插入和删除元素。它适用于需要频繁插入和删除操作的情况,但随机访问较慢。
  4. std::forward_list
    • std::forward_list 是单向链表,类似于std::list,但只支持单向遍历。它适用于内存效率要求高且只需要单向遍历的情况。
  5. std::array
    • std::array 是一个固定大小的数组,其大小在编译时确定。它提供了固定大小的顺序容器,允许快速随机访问元素。
  6. std::string
    • std::string 是一种特殊的顺序容器,用于存储字符串。它提供了字符串操作的功能,如拼接、查找、替换等。

这些容器都可以存储各种数据类型,包括基本数据类型、用户定义的类型和其他容器。您可以选择合适的容器来满足特定的需求,如快速查找、高效插入、内存效率等。

顺序容器通常提供类似的成员函数,如插入、删除、访问元素等,但它们在性能和使用情境上有所不同。您可以根据应用程序的需求来选择最合适的容器类型。

迭代器

迭代器(iterator)是一种用于遍历容器中元素的C++工具,它提供了一种通用的方法来访问容器中的元素,而不依赖于容器的具体类型。迭代器允许您以一种统一的方式遍历不同类型的容器,如 std::vectorstd::liststd::setstd::map 等。

以下是关于迭代器的一些重要概念和常见操作:

  1. 迭代器类型
    • C++中有不同类型的迭代器,包括正向迭代器、反向迭代器、常量迭代器等,用于不同的遍历操作和访问权限。
  2. 迭代器的基本操作
    • begin():获取容器的起始迭代器。
    • end():获取容器的结束迭代器,通常指向容器末尾的下一个位置。
    • rbegin():获取反向迭代器的起始位置(仅适用于支持反向迭代的容器)。
    • rend():获取反向迭代器的结束位置(通常指向容器开头的前一个位置)。
  3. 使用迭代器遍历
    • 使用for循环、while循环或其他迭代结构来遍历容器中的元素。
    • 迭代器可以用于访问容器中的元素,如 *it,其中 it 是迭代器。
#include <iostream>
#include <vector>

int main() {
    
    
    std::vector<int> myVector = {
    
    1, 2, 3, 4, 5};
    
    // 使用迭代器遍历容器
    for (std::vector<int>::iterator it = myVector.begin(); it != myVector.end(); ++it) {
    
    
        std::cout << *it << " ";
    }
    
    std::cout << std::endl;
    
    return 0;
}
  • 常量迭代器:
    const_iterator 类型的迭代器用于遍历容器中的常量元素,不能修改元素的值。
std::vector<int>::const_iterator cit = myVector.cbegin();
for (; cit != myVector.cend(); ++cit) {
    
    
    // 只能访问元素,但不能修改它们
    std::cout << *cit << " ";
}
  • 反向迭代器:
    反向迭代器用于逆序遍历容器中的元素,可以通过rbegin()rend()函数获取。
std::vector<int>::reverse_iterator rit = myVector.rbegin();
for (; rit != myVector.rend(); ++rit) {
    
    
    std::cout << *rit << " ";
}
  • 自动范围迭代器:
    C++11引入了自动范围迭代器,允许更简便的迭代方式。
for (const auto& element : myVector) {
    
    
    std::cout << element << " ";
}

迭代器是C++中非常强大的工具,它们使您能够以一种通用的方式遍历容器中的元素,而无需关心容器的具体类型。根据迭代器的类型和遍历需求,您可以选择适当的迭代器来遍历和访问容器中的数据。

容器的定义与初始化

在C++中,定义和初始化容器通常包括两个步骤:首先是定义容器的类型和名称,然后是将容器初始化为包含特定元素的状态。以下是不同类型容器的定义和初始化方式示例:

  • 定义和初始化 std::vector
#include <vector>

int main() {
    
    
    // 定义并初始化一个包含整数的 std::vector
    std::vector<int> myVector = {
    
    1, 2, 3, 4, 5};

    // 或者使用 push_back() 方法添加元素
    std::vector<int> anotherVector;
    anotherVector.push_back(10);
    anotherVector.push_back(20);
    
    return 0;
}
  • 定义和初始化 std::list
#include <list>

int main() {
    
    
    // 定义并初始化一个包含整数的 std::list
    std::list<int> myList = {
    
    1, 2, 3, 4, 5};
    
    return 0;
}
  • 定义和初始化 std::set
#include <set>

int main() {
    
    
    // 定义并初始化一个包含整数的 std::set
    std::set<int> mySet = {
    
    5, 2, 8, 3, 1};
    
    return 0;
}
  • 定义和初始化 std::map
#include <map>

int main() {
    
    
    // 定义并初始化一个包含键值对的 std::map
    std::map<std::string, int> myMap = {
    
    {
    
    "apple", 5}, {
    
    "banana", 3}, {
    
    "cherry", 8}};
    
    return 0;
}
  • 定义和初始化 std::array
#include <array>

int main() {
    
    
    // 定义并初始化一个包含整数的 std::array
    std::array<int, 5> myArray = {
    
    1, 2, 3, 4, 5};
    
    return 0;
}
  • 定义和初始化 std::deque
#include <deque>

int main() {
    
    
    // 定义并初始化一个包含整数的 std::deque
    std::deque<int> myDeque = {
    
    1, 2, 3, 4, 5};
    
    return 0;
}

这些示例演示了不同类型容器的定义和初始化方式,您可以根据需要选择合适的容器类型,并使用适当的初始化方式来创建和初始化容器。

不同方式的初始化

在C++中,您可以使用多种不同方式来初始化容器,取决于您的需求和编程习惯。以下是一些常见的初始化方式:

  • 使用列表初始化:可以使用大括号 {} 进行列表初始化,将元素逐个列出。
std::vector<int> myVector = {
    
    1, 2, 3, 4, 5};
  • 使用拷贝初始化:将一个容器的内容拷贝到另一个容器。
std::vector<int> source = {
    
    1, 2, 3};
std::vector<int> target(source); // 拷贝初始化
  • 使用赋值操作符初始化:使用赋值操作符 = 将一个容器的内容赋值给另一个容器。
std::vector<int> source = {
    
    1, 2, 3};
std::vector<int> target;
target = source; // 赋值初始化
  • 使用范围初始化:可以使用范围(range)初始化方式,通过指定起始和结束迭代器来初始化容器的一部分元素。
std::vector<int> source = {
    
    1, 2, 3, 4, 5};
std::vector<int> target(source.begin() + 1, source.begin() + 4); // 使用范围初始化
  • 使用迭代器初始化:使用迭代器初始化容器。
std::vector<int> target(source.begin(), source.end()); // 使用迭代器初始化
  • 使用构造函数初始化:某些容器提供了特定的构造函数,用于初始化容器。
std::list<int> myList(5, 42); // 初始化包含 5 个 42 的 std::list
  • 使用默认构造函数初始化:容器对象可以通过默认构造函数初始化,然后使用 push_backinsert 等方法添加元素。
std::vector<int> myVector; // 默认构造函数初始化
myVector.push_back(1);
myVector.push_back(2);
  • 使用初始化列表构造函数:C++11 引入了初始化列表构造函数,可以更方便地进行初始化。
std::vector<int> myVector = {
    
    1, 2, 3, 4, 5}; // 初始化列表构造函数

每种初始化方式都适用于不同的情况,您可以根据需求和编程风格选择最适合的方式来初始化容器。

两个不同容器的相互拷贝

C++ 允许不同类型的容器相互拷贝,前提是容器的元素类型是可互相转换的。这是通过使用迭代器来实现的,您可以将一个容器的内容拷贝到另一个容器中,前提是目标容器能够容纳源容器的元素类型。

下面是一个示例,演示如何将一个容器的内容拷贝到另一个不同类型的容器中:

#include <iostream>
#include <vector>
#include <list>

int main() {
    
    
    std::vector<int> source = {
    
    1, 2, 3, 4, 5};
    std::list<int> target;

    // 使用迭代器将 vector 的内容拷贝到 list
    for (const int &value : source) {
    
    
        target.push_back(value);
    }

    // 打印目标 list 的内容
    for (const int &element : target) {
    
    
        std::cout << element << " ";
    }
    
    return 0;
}

在上述示例中,我们将 std::vector 中的内容拷贝到 std::list 中。这是通过使用迭代器遍历 source 中的元素,并使用 push_back 将它们添加到 target 中实现的。

注意,如果源容器和目标容器的元素类型不可转换,那么需要进行类型转换或者逐个复制元素的值到目标容器。当元素类型不兼容时,编译器会产生错误。

vector对象是如何增长的

std::vector 对象是一个动态数组,它可以在运行时增长以容纳更多元素。std::vector 通常以一种指数方式增长,以提高性能。这种增长策略通常被称为 “倍增” 或 “指数增长”。

当您向 std::vector 添加元素时,如果容量不足,它会自动增加其内部数组的大小,以容纳更多元素。这个过程包括以下步骤:

  1. 检查当前元素的数量(size)和容量(capacity)。
  2. 如果元素数量小于容量,那么什么都不需要做,因为内部数组仍然足够大。
  3. 如果元素数量等于容量,std::vector 将分配一个新的更大的内部数组,通常是当前容量的两倍。
  4. 然后,将现有元素复制到新的内部数组中,然后销毁旧的内部数组。

这种倍增策略在一定程度上减少了内存分配的次数,提高了性能,因为每次增加容量时,它会分配一个较大的块内存,然后多次使用该块内存。

尽管这种增长策略通常很有效,但在某些情况下,可能需要注意内存使用。如果您知道 std::vector 需要存储非常大数量的元素,您可以使用 reserve 函数来预先分配足够大的容量,以避免不必要的动态增长。

管理容量的成员函数

std::vector 提供了一些用于管理容量的成员函数,这些函数允许您控制 vector 的容量。以下是一些常用的管理容量的成员函数:

  • reserve(n):该函数用于预留至少能容纳 n 个元素的内存空间,但并不实际增加 vector 的大小。这可以用来避免不必要的内存重新分配。例如:
std::vector<int> myVector;
myVector.reserve(100); // 预留至少能容纳 100 个元素的内存空间
  • capacity():该函数返回当前 vector 的容量,即它可以容纳的元素数量,而不是实际包含的元素数量。
estd::vector<int> myVector = {
    
    1, 2, 3, 4, 5};
int capacity = myVector.capacity(); // 返回容量
  • shrink_to_fit():该函数要求 vector 收缩到其当前大小,释放多余的内存。在 C++11 之后,这个函数通常不是必需的,因为 vector 在插入元素时通常会动态增长,但在某些情况下可能会有用。
std::vector<int> myVector = {
    
    1, 2, 3, 4, 5};
myVector.shrink_to_fit(); // 收缩 vector 的内存以适应其当前大小

这些成员函数允许您更好地管理 std::vector 的内存使用,以避免不必要的内存重新分配,并降低性能开销。根据应用程序的需求,您可以使用这些函数来优化内存使用。

自己实现vector类

//自己实现vector类
#include <iostream>
#include <stdexcept>
template <typename T> 
class MyVector
{
    
    
private:
    T *data;
    size_t size;
    size_t capacity;
public:
    MyVector(/* args */);
    ~MyVector();
    void push_back(const T &element);
    T &operator[](size_t index);
    size_t getsize ()const;
    size_t getcapacity()const;
};
template <typename T> 
MyVector<T>::MyVector(): data(nullptr),size(0),capacity(0)
{
    
    

}
template <typename T>
MyVector<T>::~MyVector()
{
    
    
    delete[] data;
}
template <typename T>
void MyVector<T>::push_back(const T &element){
    
    
    //判断容量
    if(this->size>=this->capacity){
    
    
        this->capacity == 0? this->capacity =1:this->capacity *=2;
        T *new_data = new T[this->capacity];
        for(size_t  i=0;i<this->size;i++){
    
    
            new_data[i] = this->data[i];
        }
        delete[] this->data;
        this->data = new_data;
    }
    this->data[this->size++]=element;
}
template <typename T>
T &MyVector<T>::operator[](size_t index){
    
    
    if(index<0||index>=this->size){
    
    
        throw std::out_of_range("Index out of range");
    }
    return this->data[index];
}
template <typename T>
size_t MyVector<T>::getsize()const{
    
    
    return this->size;
}
template <typename T>
size_t MyVector<T>::getcapacity()const{
    
    
    return this->capacity;
}

int main(){
    
    
    MyVector<int> myVector;

    std::cout<<myVector.getsize()<<std::endl;
    for (int i = 1; i <= 10; i++) {
    
    
        myVector.push_back(i);
        std::cout<<"size:"<<myVector.getsize()<<std::endl;
        std::cout<<"cacpacity:"<<myVector.getcapacity()<<std::endl;
    }
    
    std::cout <<"myvector: ";
    for (size_t i = 0; i < myVector.getsize(); i++) {
    
    
        std::cout << myVector[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

容器适配器

容器适配器是一种在标准库中提供的数据结构,它们使用现有的容器类来提供不同的接口和功能,以满足特定的需求。容器适配器是标准库提供的高级数据结构,使程序员能够更方便地处理数据,而无需自己实现底层数据结构。

以下是C++标准库中的一些常见容器适配器:

  1. stack (栈适配器)std::stack 是一个栈(后进先出)数据结构的适配器,它基于底层容器(默认为 std::deque)提供栈操作,如 pushpoptop
  2. queue (队列适配器)std::queue 是一个队列(先进先出)数据结构的适配器,它基于底层容器(默认为 std::deque)提供队列操作,如 pushpopfront
  3. priority_queue (优先队列适配器)std::priority_queue 是一个优先队列数据结构的适配器,它基于底层容器(默认为 std::vector)提供优先队列操作,允许在插入元素时维护元素的优先级。

适配器

在C++中,适配器通常是指STL(标准模板库)中的容器适配器和迭代器适配器。这些适配器提供了一种将不同数据结构和数据源转换为通用接口以进行处理的方式。

  1. 容器适配器:容器适配器是STL中的一类容器,它们构建在现有容器类之上,提供了不同的接口以满足特定需求。STL提供了以下常见的容器适配器:
    • std::stack:栈适配器,基于底层容器(默认为std::deque)提供栈操作。
    • std::queue:队列适配器,基于底层容器(默认为std::deque)提供队列操作。
    • std::priority_queue:优先队列适配器,基于底层容器(默认为std::vector)提供优先队列操作。
  2. 迭代器适配器:迭代器适配器是STL中的一种工具,它们提供了一种在现有迭代器上实现不同遍历策略的方式。STL提供以下常见的迭代器适配器:
    • std::back_insert_iterator:用于在容器尾部插入元素的迭代器适配器,例如 std::back_inserter
    • std::front_insert_iterator:用于在容器前部插入元素的迭代器适配器,例如 std::front_inserter
    • std::inserter:用于在指定位置插入元素的迭代器适配器。

这些适配器使得在不同容器之间进行数据转换变得更加容易,同时在迭代过程中应用不同的策略。

练习

使用stack 处理括号化的表达式。当你看到一个左括号,将其记录下来,当你在一个左括号之后看到一个右括号,从stack中pop对象,直至遇到左括号,将左括号也一起弹出栈。然后将一个值(括号内的运算结果)push 到栈中,表示一个括号化的(子)表达式已经处理完毕,被其运算结果所替代。

#include <iostream>
#include <stack>
#include <string>

// 函数用于计算两个操作数的运算结果
int performOperation(int operand1, char op, int operand2) {
    
    
    if (op == '+') return operand1 + operand2;
    if (op == '-') return operand1 - operand2;
    if (op == '*') return operand1 * operand2;
    if (op == '/') return operand1 / operand2;
    return 0; // 处理其他运算符
}

// 函数用于处理括号化的表达式
int evaluateExpression(const std::string& expression) {
    
    
    std::stack<int> operands;
    std::stack<char> operators;

    for (char token : expression) {
    
    
        if (isdigit(token)) {
    
    
            // 如果是数字,将其转换为整数并压入操作数栈
            operands.push(token - '0');
        } else if (token == '(') {
    
    
            // 如果是左括号,将其压入运算符栈
            operators.push(token);
        } else if (token == ')') {
    
    
            // 如果是右括号,处理括号内的表达式
            while (!operators.empty() && operators.top() != '(') {
    
    
                char op = operators.top();
                operators.pop();
                int operand2 = operands.top();
                operands.pop();
                int operand1 = operands.top();
                operands.pop();
                int result = performOperation(operand1, op, operand2);
                operands.push(result);
            }
            // 弹出左括号
            operators.pop();
        } else if (token == '+' || token == '-' || token == '*' || token == '/') {
    
    
            // 如果是运算符,处理操作符优先级
            while (!operators.empty() && operators.top() != '(' &&
                   ((token == '+' || token == '-') && (operators.top() == '*' || operators.top() == '/'))) {
    
    
                char op = operators.top();
                operators.pop();
                int operand2 = operands.top();
                operands.pop();
                int operand1 = operands.top();
                operands.pop();
                int result = performOperation(operand1, op, operand2);
                operands.push(result);
            }
            // 将当前运算符压入运算符栈
            operators.push(token);
        }
    }

    // 处理剩余的运算符和操作数
    while (!operators.empty()) {
    
    
        char op = operators.top();
        operators.pop();
        int operand2 = operands.top();
        operands.pop();
        int operand1 = operands.top();
        operands.pop();
        int result = performOperation(operand1, op, operand2);
        operands.push(result);
    }

    // 返回最终的计算结果
    return operands.top();
}

int main() {
    
    
    std::string expression = "((4+4+4)*(5-2))/2";
    int result = evaluateExpression(expression);
    std::cout << "Result: " << result << std::endl;

    return 0;
}

猜你喜欢

转载自blog.csdn.net/a1379292747/article/details/134240093