首页 > 编程语言 >C++ 学习笔记(1):STL、Vector 与 Set

C++ 学习笔记(1):STL、Vector 与 Set

时间:2024-11-19 15:10:55浏览次数:1  
标签:容器 begin Set 迭代 STL 元素 JS Vector

背景

最近在尝试入坑蓝桥杯,于是先从 C++ 开始学起,这里记个笔记。这里我的笔记是跟着这个教程来的。沙比学校天天整些屁事都没什么空折腾。

前言

笔者是 JS / TS 写的比较多,以前写过 C 但是有点忘了,所以文章里都是和 JS 进行对比着方便快速理解。

同时其实我还有几个小问题,嘻嘻。没有解答,就是写文章的时候提醒一下自己要全面一点,复习哈。

  • 指针的 *& 是什么用?指针的本质是?
  • 和 JS 比有没有类似的,异同点是?
  • C++ 和 C 有什么区别?
  • 本质上迭代器和数组索引遍历是类似的,但是为什么要单独抽象一个迭代器出来?

复习

指针

  • 声明指针:int *p; 表示一个指向 int 类型的指针。
  • 取地址:&x 获取变量 x 的内存地址。
  • 解引用:*p 访问指针 p 指向的内存地址上的数据。
#include <iostream>
using namespace std;

int main() {
    int x = 10;
    int *p = &x; // p 存储的是 x 的地址
    cout << "x 的值是: " << x << endl;  // 输出 10
    cout << "p 指向的值是: " << *p << endl; // 解引用,输出 10
    return 0;
}

其实就是内存和值互相指来指去的,JS 里面没有可以类比的,要说比较像的就是对象赋值,举个例子:

let obj = { value: 10 };
let ref = obj; // ref 是 obj 的引用
ref.value = 20;
console.log(obj.value); // 输出 20

JS 里面如果要避免这种情况只能深拷贝了~ 因为默认的赋值方式(如 let ref = obj)只是将对象的引用赋值给新的变量,并没有创建新的独立对象。

STL

概念

STL 是一个 C++ 的工具库,分为三大组件,分别是容器、算法、迭代器。

容器

常见容器类型有三种,第一种是序列式容器(即元素按顺序存储,支持高效的插入和删除)。常见的有这几个:vector,动态数组,支持随机访问;deque,双端队列,支持高效的两端操作;list,双向链表,支持高效的任意位置插入和删除。

第二种是关联式容器(基于键值对存储,元素通常自动排序)。常见的有这几个:set,集合,不允许重复元素;map,键值对映射,每个键唯一;multisetmultimap,允许重复键。

最后一种是无序容器(基于哈希表存储,元素无序)。常见的有unordered_set,无序集合;unordered_map,无序映射。

其实换句话说 JS 基本都有类似功能的方法/库,比如说 Map 和 Set 都是自带的,JS 里面直接实例化后即可使用。虽然实现方式还是有差异,STL 的 Map 和 Set 底层通常用平衡二叉树实现,默认是有序的;而 JS 是基于 HashMap 实现的。

另外 C++ 还存在 auto 这个类型,即编译器自动推断变量类型,被叫做“类型推导关键字”,意思是让编译器推导变量的具体类型。如果你是 JS 或者 Python 写的比较多的应该马上觉得很熟悉了,这不是动态语言么?

虽然差距很大,C++ 的 auto 只是编译器编译时推到,执行后其实已经是静态了(auto x = 4编译完会变成int x = 4,某种程度上和#define接近);而 JS / Python 这类动态语言在 Runtime 的类型就是动态的。

算法

算法是用来操作容器的工具集,包括排序、搜索、变换等常见操作。STL 的算法库与任何容器兼容。

常见算法有很多,如下。

  • 排序类:sort()stable_sort()
  • 查找类:find()binary_search()
  • 变换类:transform()replace()
  • 集合操作:set_union()set_intersection()

算法使用迭代器(见下文)作为操作对象,不直接与容器绑定,因此非常灵活。

其实看到这也非常眼熟,就比如 sort() 这个在 JS 的 Array 对象里最常见的方法,原来这个在 C++ 这种老登(bushi)语言里被叫统称为算法啊!不过 STL 和 JS 的 sort() 还是有些许不同的。STL 的 sort() 是一个函数,可以对任意范围内的元素排序,不仅限于容器,比如数组、列表都可以用。

而 JS 的 sort() 仅仅只是数组的一个方法,只针对数组排序;STL 的 sort() 的实现底层使用了快速排序 + 插入排序,比 JS 的 sort() 更快。

迭代器

迭代器是一种指针类似的对象,用于在容器中遍历元素。迭代器分类如下:

  • 输入迭代器:只读访问,例如 std::istream_iterator
  • 输出迭代器:只写访问,例如 std::ostream_iterator
  • 前向迭代器:支持单向遍历,例如 std::forward_list 的迭代器。
  • 双向迭代器:支持前后遍历,例如 std::list 的迭代器。
  • 随机访问迭代器:支持随机访问,例如 std::vector 的迭代器。

一般常见的就两种,一种是拿 begin() 和 end() 用来返回容器的起点和终点迭代器,另外一种是用 *it 访问迭代器指向的值,++it 进行移动。

迭代器可以理解为一种“指针”,它用来在容器中遍历元素,其实这不就是遍历数组里面的值啊!但为什么弄这么复杂?因为 STL 容器不能像数组索引那样直接遍历,STL 容器都是各种各样的数据结构,不像数组那样连续,所以只能使用指针来遍历里面的值。

深入一点来说,数组在内存中的数据是连续的,你通过数组索引访问的时候,其实就是arr[i] = *(arr + i),arr 是这个数组的基准地址,i 就是往后推几位,数组索引本质上就是最开始的地址 + 索引数;而 STL 本身就是高层抽象的东西,STL 容器包含了很多种数据结构,每种数据结构的访问方式都是不一样的,所以 STL 库把迭代器给抽象了出来,最终作为一种库给开发人员调用。

迭代器其实就是一种抽象。它的实现完全依赖于容器的底层数据结构。如果容器是连续的(如 vector),迭代器可以直接封装指针操作;如果容器是非连续的(如 list、set),迭代器内部会包含指向当前元素的指针,同时封装了访问下一个/上一个元素的逻辑。对于复杂的数据结构(如红黑树),迭代器会依赖树的中序遍历算法来实现移动。

另外,STL 中的迭代器不仅仅是用来遍历的工具,还能通过它修改容器内容,甚至对任意范围的元素进行操作。

所以迭代器其实就是面向容器的“智能索引”,它不是简单的指针,而是高度抽象和适配不同数据结构的工具。

Vector

Vector 是 STL 容器的一种,全称叫可变数组,但如果你熟悉 JS 你就会发现这不就是里面的数组吗。STL 里面的 vector 的特性和 JS 里面很像哦,支持动态扩容(数组大小可以自动调整)、连续内存存储(可以高效地进行随机访问)、高效尾部操作(尾部插入和删除操作非常快)。感觉这里没啥好说的就纯看语法写写就好了,放一段 Example。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {3, 1, 4, 1, 5};
    v.push_back(9); // 添加元素 9
    sort(v.begin(), v.end()); // 排序

    cout << "排序后的元素: ";
    for (auto num : v) {
        cout << num << " ";
    }
    cout << endl;

    v.pop_back(); // 删除最后一个元素
    cout << "大小: " << v.size() << endl;

    return 0;
}

问了下 AI,vector 一般在算法里有啥地方用得到?他说这三个:

  • 在需要动态增长的数组时使用,例如存储所有输入数据。
  • 排序和去重,常用 sort + unique 去重。
vector<int> v = {1, 2, 2, 3, 3, 4};
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());
  • 二分查找,用 lower_bound 和 upper_bound 高效查找元素位置。
vector<int> v = {1, 2, 4, 4, 5};
int pos = lower_bound(v.begin(), v.end(), 4) - v.begin(); // 找到第一个 >= 4 的位置

然后这个是语法:

操作 说明 示例代码
vector<T> v; 定义一个存储类型为 T 的动态数组 vector<int> v;
v.push_back(x); 在尾部添加元素 x v.push_back(10);
v.pop_back(); 删除尾部元素 v.pop_back();
v.size(); 获取数组大小 int n = v.size();
v[i] / v.at(i) 访问第 i 个元素(at 带边界检查) cout << v[0];
v.clear(); 清空所有元素 v.clear();
v.begin() / v.end() 返回首迭代器和尾迭代器,用于遍历 for (auto it = v.begin(); it != v.end(); ++it)
v.insert(pos, x); 在迭代器位置 pos 插入元素 x v.insert(v.begin(), 5);
v.erase(pos); 删除迭代器位置 pos 的元素 v.erase(v.begin());
sort(v.begin(), v.end()) v 进行升序排序 sort(v.begin(), v.end());

Set

set 是 STL 中的 集合容器,JS 里也有,特性一样:存储唯一值(不允许重复元素)、自动排序(默认以升序存储)、基于红黑树实现(插入、删除、查找的时间复杂度为 O(log n))。还是丢一段 Example。

#include <iostream>
#include <set>
using namespace std;

int main() {
    set<int> s = {10, 20, 10, 30}; // 自动去重和排序
    s.insert(40); // 插入元素 40
    s.erase(20);  // 删除元素 20

    cout << "集合中的元素: ";
    for (auto num : s) {
        cout << num << " ";
    }
    cout << endl;

    if (s.count(30)) {
        cout << "30 存在于集合中" << endl;
    }

    return 0;
}

Set 常用方法有这几个:

  • 高效查找和去重,集合自动去重,插入后元素总是唯一。
  • 范围查找,用迭代器找到范围内的值。
auto it = s.lower_bound(15); // 找到第一个 >= 15 的元素
  • 动态集合,用于实时存储和查询,如滑动窗口。

这个是语法:

操作 说明 示例代码
set<T> s; 定义一个存储类型为 T 的集合 set<int> s;
s.insert(x); 插入元素 x,如果已存在则插入失败 s.insert(10);
s.erase(x); 删除值为 x 的元素 s.erase(10);
s.count(x); 检查是否存在值为 x 的元素(返回 1 或 0) if (s.count(5))
s.find(x); 返回指向值为 x 的迭代器(找不到返回 s.end() auto it = s.find(10);
s.size(); 获取集合大小 int n = s.size();
s.begin() / s.end() 返回首迭代器和尾迭代器,用于遍历 for (auto it = s.begin(); it != s.end(); ++it)
s.clear(); 清空集合中的所有元素 s.clear();
s.lower_bound(x); 返回指向第一个 >= x 的迭代器 auto it = s.lower_bound(15);
s.upper_bound(x); 返回指向第一个 > x 的迭代器 auto it = s.upper_bound(20);

标签:容器,begin,Set,迭代,STL,元素,JS,Vector
From: https://www.cnblogs.com/AurLemon/p/18495936

相关文章

  • 命名空间、STL、Lambda表达式与并发编程
        在深入学习C++的过程中,了解并掌握进阶特性对于编写高效、灵活的程序至关重要。    本篇博客将详细介绍C++中的命名空间、标准模板库(STL)、lambda表达式、move语义及并发编程,帮助你更好地驾驭C++语言。1.命名空间(Namespace)    命名空间用于组织代码......
  • entrySet()遍历Map并删除元素
    在Java中,entrySet()方法通常用于遍历Map类型的集合,返回的是Map中所有映射项的Set视图。这个Set中的每个元素都是一个Map.Entry对象,代表Map中的一个键值对。理论上,你可以通过entrySet()遍历Map并删除元素,但是这样做可能会引发ConcurrentModificationException异常,因为entrySet......
  • Let'sGoFurther - Chapter 12: User Model Setup and Registration
     zzh@ZZHPC:/zdata/Github/greenlight$migratecreate-seq-ext=.sql-dir=./migrationscreate_user_table/zdata/Github/greenlight/migrations/000004_create_user_table.up.sql/zdata/Github/greenlight/migrations/000004_create_user_table.down.sql CREATET......
  • vector,map
    1.这下c++又要开始学习了,废话少说,这编程语言就是要多联系。 vector,  无非就是增,删,查,改。 增,    构造函数,插入,尾巴插入。insert,push_back.删,    earse,pop_back.resize.查    vect1.at(0),  *iter,  vect1[],vect1.front,back。......
  • Datasets is not supported in Complete output mode, only in Append output mode
    我们在使用pyspark,使用structureStreaming实时流的时候,创建出来的dataframe是流式的,不同于静态的df,流式的df使用的时候,不能用show()直接打印,而且使用sparkSQL的时候不可以在sql中使用开窗函数,并且还不可以使用join进行表关联举个栗子:执行以下代码会报错,因为在sql中使用了join......
  • 一些值得注意的STL使用,用错了可能就复杂度错误了
    前言一些见到(或看到别人,或写了)的问题就记一下吧正文lower_boundSTL分为两类,一类是支持随机访问的,另一类是不支持随机访问的。而不支持随机访问的,若使用lower_bound函数,请一定要使用.....lower_bound(...),因为这样的复杂度是对的(\(\log\)),否则就是线性的。我在cpprefernce上......
  • map、unordered_map、set 和 unordered_set的小介绍
    1.map简介:map是C++STL中的关联容器,存储键值对(key-valuepair),所有元素按键值升序(或自定义排序)存储。主要特性:底层实现:使用红黑树实现,提供了自动排序功能。元素有序:插入元素后,按键值排序。时间复杂度:插入、删除、查找:O(logn)(因为树的深度为O(logn))。内......
  • Django中QuerySet
    1.QuerySet概念QuerySet是DjangoORM(Object-RelationalMapping)中的对象,用于表示从数据库查询出来的一组数据。可以看作是数据库查询结果的抽象表示,包含零个或多个模型实例。特性延迟加载:QuerySet是惰性的,只有在需要时才会执行真正的数据库查询。例如,调用list......
  • C++ stl chrono 学习笔记
    chronosinceC++11库的参考手册(英文)|cppreferencechrono库定义了三种(直到c++20)五种(从c++20开始)主要类型以及实用函数和常用类型:cokckstimepointsdurationscalendardates(sinceC++20)timezoneinformation(sinceC++20)clocks时钟由起点(或历元)和滴答率组成......
  • ES6 Set和Map数据结构用法详解
    文章目录前言Set数据结构创建Set添加元素删除元素删除所有数据获取set的大小(类似于数组的长度)检查是否包含某个元素四种遍历set的方法1.for...of循环2.forEach方法3.转换为数组后使用for循环4.keys(),values(),entries()集合运算方法Map数据结构创建Map添加元素(设......