【C++笔记】STL知识铺垫

【C++笔记】STL知识铺垫

前言: 

        在前面的学习中,我们已经掌握了C++的基础语法和编程概念,本文将深入探讨C++标准库的使用,并详细介绍迭代器、auto关键字以及范围for循环等相关知识。

        

一、STL简介

        

1.1 什么是STL

        

STL(Standard Template Library,标准模板库)是C++标准库的核心组成部分,它不仅提供了可复用的组件库,更是一个集成了高效数据结构与算法的软件框架。

        

1.2 STL的六大组件

        

由于历史原因,string 类型先于 STL 出现,STL 后来由惠普实验室开发并开源,因此人们通常不将 string 归入 STL 范畴。

        

        

二、迭代器

        

        迭代器(Iterator)是 C++ STL 中最精妙的设计之一,如果把 STL 的容器比作各种不同类型的仓库(数组、链表、树),那么迭代器就是一把万能钥匙,它让你不用关心仓库内部的具体构造,就能以统一的方式访问里面的每一个货物。

        

2.1 迭代器的本质:泛化的指针

        

迭代器的行为在视觉和操作上都非常像普通的 C 语言指针,你可以对它进行解引用(获取数据)和移动(指向下一个数据)。

        

例如:vector<int>:: iterator it        

        

注:这里的 iterator 是通过 内置类型(一般来说是指针) 进行 typedef 重命名,即泛化后的产物

        

迭代器的一般操作如下所示:   

        

① *it (解引用):获取迭代器当前指向的元素的值。

        

② ++it 或 it++ (递增):让迭代器移动到容器中的下一个元素。

        

③ == 和 != (比较):判断两个迭代器是否指向同一个位置。

        

        

温馨提示:对于迭代器,我们可以初步简单认为其是一个高级版本的指针。

        

2.2 核心区间的概念:左闭右开

                

一般而言有如下两个函数返回特定位置的迭代器:

                

① begin():返回指向容器中第一个元素的迭代器。

        

② end():返回指向容器中最后一个元素之后位置的迭代器,它不指向任何实际存在的元素,可以理解为一个“越界哨兵”。

        

因此,在C++中,容器和算法使用迭代器时都严格遵循左闭右开区间这一基本原则,即 [begin, end) 

        

温馨提示:

        

A. 这种设计有一个巨大的好处:当 begin() == end() 时,可以直接用来判断容器是否为空。


        

B. end() - begin() 的大小即为容器中的元素个数

                

2.3 代码演示:如何使用迭代器

        

我们以一个简单的 vector 为例,看看最经典的迭代器遍历方式:

#include <iostream> #include <vector> using namespace std; int main() { //vector容器 可以简单理解为是一个顺序表 vector<int> numbers = { 10, 20, 30, 40, 50 }; // 声明一个迭代器 it,类型为 vector<int>::iterator vector<int>::iterator it = numbers.begin(); // 它从 begin() 开始,一直循环直到等于 end() while (it != numbers.end()) { // 使用 *it 获取当前指向的值 cout << *it << " "; ++it; } //预计输出: 10 20 30 40 50 return 0; }

        

2.4 迭代器的分类(初步了解)

        

不同的数据结构底层内存分布不同,因此它们提供的迭代器能力也不同,这是连接数据结构与算法的关键:

        

①随机访问迭代器 :最强大的迭代器,支持像指针一样进行算术运算,比如 it + 5(直接跳到 5 个位置后)。

        

注:底层是连续内存的容器拥有它,比如 std::vector 和 std::deque,只有拥有这种迭代器的容器,才能使用 std::sort 进行快速排序。

        

②双向迭代器 :支持向前 (++) 和向后 (--) 移动,但不能跨越式跳跃。比如基于链表实现的 std::list。

        

③单向迭代器:只能单步向前移动 (++),比如单向链表 std::forward_list。

        

如下如所示:

        

        

三、auto关键字

       

在现代 C++11以后,auto 绝对是提升开发效率和代码可读性的“神器”,它能帮你省去大量敲击键盘的时间,并有效避免类型拼写错误。

        

3.1 核心概念:编译期类型推导

        

在使用 auto 声明的变量,它的类型不是在运行的时候决定的,而是在编译代码时,编译器根据你给变量赋予的初始值,自动推断出来的。        

        

代码示例:使用auto关键字,进行推导变量的类型。

#include <iostream> #include <vector> using namespace std; int main() { auto a = 10; // 编译器推导 a 为 int auto b = 3.14; // 编译器推导 b 为 double auto c = "Hello"; // 编译器推导 c 为 const char* return 0; }

        

温馨提示:

        

①正因为是根据初始值推导,所以使用 auto 声明变量时,必须同时进行初始化。

        

② 写 auto x;  是无法通过编译的。

        

3.2  auto的关键使用场景

        

1.拯救极其冗长、复杂的类型声明

        

在学习 STL 和算法时,你经常会遇到嵌套极深的数据结构。如果没有 auto,代码会写得非常痛苦且容易出错。

        

代码示例:简化数据类型的代码量

#include<iostream> #include <string> #include <map> using namespace std; int main() { map<string, string> dict = { { "apple", "苹果" },{ "orange","橙子" }, {"pear","梨"} }; //过于冗余 map<string, string>::iterator it = dict.begin(); //通过auto进行简化 auto it = dict.begin(); }

       

3.3 注意事项

        

A. 引用和 const 限定符的推导规则

        

使用 auto 时,最容易出错的地方在于它对引用和 const 限定符的推导规则:auto 默认会“剥离”顶层 const 和引用。

        

代码示例:

int x = 10; int& rx = x; const int cx = 10; auto a = rx; // a 的类型是 int (引用属性被丢弃),属于值拷贝 auto b = cx; // b 的类型是 int (顶层 const 被丢弃)

        

详情介绍:

        

1. 拆解 auto a = rx; (为什么引用属性被丢弃?)

        

前情回顾: int x = 10;  int& rx = x;  其中rx 是 x 的引用。

        

在 C++ 里,引用仅仅是个别名(就像 x 的小名)。当你使用 rx 的时候,你实际上就是在操作 x 本身,它的值是 10。

        

发生推导时: 当你写下 auto a = rx; 


        

编译器的内心戏是这样的:“你想用 rx 的值(也就是 10)来初始化一个新的变量 a,既然你没写 &(比如 auto& a),说明你不想让 a 也变成别名,你只是想要那个数字 10 而已。”

        

结果: 编译器把 a 推导为普通的 int。它在内存里挖了一块全新的空间,把 10 复制了进去。

        

      

2.拆解 auto b = cx; (为什么顶层 const 被丢弃?)

        

前情回顾: const int cx = 10;


        

cx 是一个被 const 锁住的整数。这里的 const 叫做顶层 const(Top-level const),意思是这个变量本身是不允许被修改的。

        

发生推导时: 当你写下 auto b = cx;

        

编译器又开始思考了:你想把 cx 里的值(10)掏出来,放进一个新变量 b 里,虽然 cx 本身是被锁住的(不能改),但这跟你新创建的 b 有什么关系呢?”

        

结果: 编译器把 b 推导为普通的 int,把 10 复制了进去。b 是一个自由的、可以随意修改的普通变量。     

        

代码示例:保留引用和 const的写法

int x = 10; int& rx = x; const int cx = 10; auto& c = rx; // c 是 int&,修改 c 会改变 x const auto d = cx; // d 是 const int,常用于只读访问

        

因此,C++ 规定:auto 默认只给你最纯粹的底层类型(如 int、double),给你最大的自由,如果你想要引用(&)或者只读(const),你必须自己动手明确地写出来。

        

        

B. 不能用于函数参数(C++20 之前)

        

在 C++20 之前,普通函数的参数不能用 auto,而需要使用模板。

        

代码示例:非法的写法

// 不能做参数 void func2(auto a) {}

        

        

C. auto可以做函数返回值

        

核心机制:编译器会通过阅读你函数体内的 return 语句,来反向推导出这个函数到底该返回什么类型。

        

1. 基础用法:让编译器替你算类型

auto multiply(int a, double b) { return a * b; // 编译器知道 int * double 的结果是 double,所以自动推导返回类型为 double }

        

2. 避坑指南

坑点一:多个 return 语句的类型必须“完全一致”

        

如果你的函数里有 if-else 分支,并且包含多个 return,那么所有 return 后面的表达式类型必须一模一样。编译器不会帮你做隐式类型转换。

        

代码示例:if-else 返回的类型不同

auto check_number(int x) { if (x > 0) { return 1; // 类型是 int } else { return 1.5; // 编译报错!类型是 double,与上面的 int 冲突 } }

        

解决办法: 强制统一类型,比如把 return 1; 改成 return 1.0;。

        

坑点二:auto 依然会丢弃引用(&)和 const

        

这跟我们之前讨论的变量推导规则是一模一样的,默认情况下,auto 作为返回值永远是“值拷贝”。

        

代码示例:返回引用 和 const类型的值

int global_var = 100; // 你本意是想返回 global_var 的引用 auto get_ref() { int& ref = global_var; return ref; } // 实际结果:get_ref() 返回的是一个普通的 int(发生了值拷贝),引用属性被丢弃了!

        

解决办法: 如果你确实想返回引用,必须显式地写成 auto& 或者 const auto&

int global_var = 100; // 你本意是想返回 global_var 的引用 auto& get_ref() { int& ref = global_var; return ref; }

      

四、范围for循环

        

        范围 for 循环 是 C++11 引入的一项极其重要且实用的特性,它极大地简化了遍历数组和容器(如 std::vector, std::string, std::map 等)的代码,使其更具可读性,同时也减少了数组越界等常见错误。

        

4.1  基本语法        

        

范围 for 的语法非常直观:

for (元素类型 元素变量名 : 遍历对象) { // 循环体 }

        

相关参数讲解:

        

①元素类型:遍历对象中元素的类型(通常搭配 auto 关键字使用)。

        

②元素变量名:每次循环时,用来接收当前元素的变量。

        

③遍历对象:必须是一个可以确定范围的集合(如定长数组、标准库容器等)。

        

        

4.2 三种最常用的遍历方式

        

A. 按值遍历

        

核心特征:每次循环都会将容器中的元素拷贝给变量,修改变量不会影响容器内原本的数据。

        

适用场景:遍历基础数据类型(如 int, char, float),且不需要修改原数据。

        

代码示例:遍历基础类型int

vector<int> nums = {1, 2, 3}; for (int x : nums) { cout << x << " "; // 输出 1 2 3 }

        

B. 按引用遍历        

        

核心特征:加上 & 符号,变量直接引用容器内的元素,修改变量就等于修改了容器内的原数据。

        

适用场景:需要修改容器内元素的内容时。

        

代码示例:遍历基础类型int,并修改其内容

vector<int> nums = {1, 2, 3}; for (int& x : nums) { x *= 2; // 原数组变为 {2, 4, 6} }

        

C. 按常量引用遍历

        

核心特征:加上 const&,既不会发生拷贝(节省内存和时间),又保证了数据不被意外修改(只读)。        

        

适用场景:遍历复杂对象(例如:string,  结构体,  自定义类)且只需读取时,这是最推荐的写法。

        

代码示例:遍历复杂对象string类

vector<string> words = {"Hello", "World"}; for (const string& word : words) { cout << word << endl; }

        

        

4.3 结合 auto 关键字

        

在实际开发中,我们通常不需要手动写出繁琐的类型名,直接让编译器用 auto 推导即可:

        

写法一: for (auto x : container)   ( 按值拷贝)

        

写法二:for (auto& x : container)   (按引用,可修改)

        

写法三:for (const auto& x : container)  (按常量引用,只读且高效)

        

代码示例:通过使用关键字auto遍历容器

vector<string> words = {"Hello", "World"}; for (const auto &word : words) { cout << word << endl; }

        

4.4 范围for的本质

        

范围 for 并不是什么神奇的魔法,它本质上是编译器提供的一层“语法糖”。

        

当编译器看到范围 for 时,会自动将其转化为使用迭代器 (Iterator) 的 循环:

// 你的代码 for (auto& x : container) { /*...*/ } // 编译器实际转化为类似这样的代码: //_begin指向头部元素的迭代器 auto _begin=container.begin(); //_end 指向尾部元素下一个位置的迭代器 auto _end=container.end(); for( ; _begin !=_end ; ++_begin ) { //遍历迭代器,将容器的值赋值给x auto& x = *_begin; /*...*/ }

既然看到这里了,不妨关注+点赞+收藏,感谢大家,若有问题请指正。

        

Read more

Ubuntu新手必看:如何快速更换国内源(阿里/清华/中科大源对比)

Ubuntu 新手的第一道“加速”关:国内镜像源深度解析与实战指南 刚装好 Ubuntu,那种清爽的桌面和开箱即用的感觉确实不错。但当你兴冲冲地打开终端,准备用 apt install 装点东西时,进度条那慢如蜗牛的爬行速度,是不是瞬间浇灭了一半的热情?别急着怀疑自己的网络,这几乎是每个国内 Ubuntu 用户都会遇到的“新手墙”。问题的核心,往往不在于你的宽带,而在于系统默认连接的软件仓库服务器远在海外,网络延迟和带宽限制成了最大的瓶颈。 解决这个问题的方法,就是“换源”——将系统的软件源地址,更换为位于国内的镜像服务器。这听起来像是个简单的操作,但背后其实有不少门道:国内有哪些可靠的镜像站?阿里云、清华大学、中国科学技术大学(USTC)的源有什么区别?为什么别人的源换上去飞快,你的却报了一堆错?今天,我们就来彻底拆解这个问题。这不仅仅是复制粘贴几行命令,而是帮你理解原理、掌握选择、并能在遇到问题时自己动手排查。无论你是刚接触 Linux 的开发新手,还是希望优化工作流效率的资深用户,一个配置得当的软件源,

By Ne0inhk
时序数据库选型指南:在大数据浪潮中把握未来,为何Apache IoTDB值得关注?

时序数据库选型指南:在大数据浪潮中把握未来,为何Apache IoTDB值得关注?

文章目录 * 1 -> 引言 * 2 -> 时序数据的挑战与选型的重要性 * 3 -> 核心选型维度:超越性能参数的综合考量 * 4 -> 深入聚焦:Apache IoTDB的差异化优势 * 5 -> 选型建议与总结 1 -> 引言 在当今这个万物互联、数据驱动的时代,从工业传感器到智能电网,从车联网到金融交易,每一秒都在产生海量带有时间戳的数据——时序数据。这类数据不仅是企业运营的“脉搏”,更是驱动智能决策、优化效率、预测未来的核心燃料。面对汹涌而至的时序数据洪流,如何选择一款合适的时序数据库(Time-Series Database, TSDB),已成为大数据架构师、物联网(IoT)平台开发者和数据分析师面临的关键决策。本文将站在大数据技术演进和国产基础软件发展的视角,为您梳理时序数据库的选型要点,

By Ne0inhk
腾讯云轻量服务器 + OpenClaw 部署全攻略:从购买到飞书接入

腾讯云轻量服务器 + OpenClaw 部署全攻略:从购买到飞书接入

在这个 AI 大爆发的时代,每个人都想拥有一个像贾维斯那样的私人助理。但现实往往是:要么受限于各种现成工具的条条框框,要么被复杂的服务器部署代码劝退。直到我遇到了 OpenClaw(曾用名 ClawdBot、Moltbot)。 配合腾讯云轻量应用服务器 (Lighthouse) 的一键部署镜像,整个过程比你想象中还要简单。今天这篇教程,我就手把手带你从买服务器开始,到接入飞书、企微等平台,彻底搞定属于你的 AI 管家。 一、 为什么推荐腾讯云轻量服务器? 很多小伙伴问,我可以在本地电脑跑 AI 助手吗?技术上当然可以,但你得保证电脑 24 小时开机,且有稳定的公网访问能力。相比之下,云服务器的优势太明显了: 1. 全天候在线:你的 AI 助理 24x7 随时待命。 2. 极简部署:腾讯云提供了 OpenClaw 专属镜像。这意味着你不需要去敲那些让人头晕的 Linux

By Ne0inhk
Flutter 组件 actions_toolkit_dart 适配鸿蒙 HarmonyOS 实战:自动化套件方案,构建 GitHub Actions 深度集成与跨端流水线治理架构

Flutter 组件 actions_toolkit_dart 适配鸿蒙 HarmonyOS 实战:自动化套件方案,构建 GitHub Actions 深度集成与跨端流水线治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 actions_toolkit_dart 适配鸿蒙 HarmonyOS 实战:自动化套件方案,构建 GitHub Actions 深度集成与跨端流水线治理架构 前言 在鸿蒙(OpenHarmony)生态迈向全球化开源协作、涉及极大规模的跨端 CI/CD 流水线构建、多机型自动化兼容性测试及严苛的代码准入控制背景下,如何实现一套既能深度对接 GitHub Actions 核心底脚(Toolkits)、又能提供原生 Dart 编程感且具备工业级日志输出与状态管理的“自动化控制基座”,已成为决定应用研发迭代频率与交付质量稳定性的关键。在鸿蒙项目这类强调多模块(HAP/HSP)并行构建与分布式证书签名校验的环境下,如果 CI 脚本依然依赖大量零散的 Shell 拼接,由于由于环境变量的微差异,极易由于由于“脚本不可维护”导致鸿蒙应用在自动化发布环节频繁由于由于故障导致阻塞。

By Ne0inhk