现代C++学习指南-标准库

这篇具有很好参考价值的文章主要介绍了现代C++学习指南-标准库。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在[上一章](https://www.yuque.com/docs/share/adb5b1e4-f3c6-46fd-ba4b-4dabce9b4f2a?# 《现代C++学习指南-类型系统》)我们探讨了C++的类型系统,并提出了从低到高,又从高到低的学习思路,本文就是一篇从高到低的学习指南,希望能提供一种新的视角。

什么是标准库

编程语言一般分为两个部分,一部分是语法部分,如上一章的类型系统,另一部分则是用这套语法完成的预定义的工具集,如本文的主题——标准库。标准库是一堆我们写代码时直接可以用的代码,就像是我们提前写好的一样,不仅如此,标准库还是跨平台的,还是经过工业级测试的,所以标准库有着靠谱,安全的特点。
C++标准库包括很多方面,有类vectorstring等,有对象std::cinstd::cout等,还有函数movecopy等,所以一般按功能来对它们分类

  • 容器类
  • 算法类
  • 智能指针
  • 线程相关
  • 其他

当然,这些还不是全部,标准库是在不断扩充和完善的,学习标准库的宗旨也应该是学习它们的使用场景,而不是深入用法。比如容器类中就有很多功能类似的类,不同的业务场景有不同的选择。通过对它们的了解,我们更容易写出高效,简洁的代码。

容器类

容器类就是帮助管理一组数据的类,根据实现方式的不同,分为有序列表,无序列表和映射。
有序列表中的有序是指,数据组保存在一块连续的内存区域里,可以通过插入时的索引直接定位到原数据。因为数据是按顺序存入的,所以中途假如需要删除或者新增数据,在操作位置右边的数据都需要移动,操作的代价就比较大。由此也可看出它们的优势是顺序插入和尾部修改,还有直接查找,这方面的代表就是arrayvector
array是对原始数组的封装,并且解决了传递数组变成指针这样的问题,但是缺点是它的大小是固定的,适合用在数据量已知的情况。而vector又是对array的增强,不仅能完成所有array的操作,并且大小可变,所以绝大部分情况下,选择vector都是理想的选择。

无序列表的元素是单独存储的,相互之间用指针来查找相邻元素,由于指针可以轻易修改指向的指,所以对相邻元素的修改就变得很快捷。同样的道理,查找相邻元素只能靠指针跳转,查找某个值需要从一个指针开始查找,一次跳转一条数据,直到找到目标或者没有数据为止。所以无序列表的优势是快速地删除和插入新数据,不适合查找,其代表有listforward_list。显然,有序列表和无序列表是互补的,我们在实际项目中,应该根据数据的操作来确定选择哪种容器。

映射则融合了有序列表和无序列表的优点,既可以快速插入和删除,又可以快速查找。为了满足各种使用场景,C++提供了mapmultimapunordered_mapunordered_multimap。从名字上就能看出来它们的差别。为了直观,我直接列了一个表

是否排序 是否支持相同值 速度
unordered_map ❤️❤️❤️❤️
map ❤️❤️
multimap ❤️
unordered_multimap ❤️❤️❤️

映射存储的是两个值,不同的类型实现方式不一样。由于map是需要排序的,所以通常它的实现是一种平衡二叉树,键就是它排序的依据。

unordered_map是不需要排序的,所以它的实现通常是哈希表,即根据哈希函数的确定索引位置继而确定存储位置。

综上,容器类提供了一种操作多个同类型数据的接口,开发者通过对容器类方法的调用,可以实现对容器内数据的增删改查。大部分情况下,vector都是靠谱的选择,它提供了全功能的数据操作接口,支持动态长度,索引查询,并且简单高效。如果需要频繁地插入或者删除操作,也可以考虑list或者forward_listmap可以让数据保持有序,需要更快的速度而不是排序的话unorderer_map是更好的选择,如果相同值会出现多次就可以使用对应的multi版本。另外容器类也是很好的数据结构学习资源,C++的容器类几乎提供了数据结构中所有的形式,对数据结构越熟悉选择的容器类就越完美。

算法

之所以将算法放在容器类后面,是因为算法大部分是对容器类操作的加强,算法都定义在algorithm文件头里。这些算法都是短小精悍的,可以大大增加代码可读性,并且妥善处理了很多容易遗忘的边界问题。功能上可以分为增删改查几种操作,可以在实际有需要的时候在查看文档,具体可以参阅这里

智能指针

很早以前,我对智能指针的态度不是很好。因为刚开始学习C++时我就知道,不能单独使用指针,要把指针封装在类里,利用类的构造函数和析构函数管理指针,也就是RAII。最开始我以为这就够了,直到我遇到下面这种情况

public:
    Ptr():p{ new int } {}
    ~Ptr() {
        delete p;
    }
    int& get() {
        return *p;
    }

    void set(const int value) {
        *p = value;
    }
private:
    int* p;
};

void use(Ptr p) {
    //传进来的是复制构造出来的p',函数返回后p'被销毁啦,两个指针指向的地址被回收,外面的p指针成为了野指针
}
int main() {
    Ptr p;
    p.set(1);
    use(p); //p按值传递,调用了Ptr的复制构造函数,构造出了新对象p',它的指针和p的指针指向同一个地方
    std::cout << p.get() << std::endl; //p已经被销毁了,访问p的地址非法
    return 0;
}

调用use时,变量p被拷贝,也就出现了两个指针同时指向一块内存地址的情况。use函数执行完后,它的参数p被回收。也就是调用了Ptr的析构函数,也就是两个指针指向的地址被回收。所以24行调用get读取那个已经被回收了的地址就是非法操作,程序崩溃。
这可能是新手比较常遇到的一个问题,当然,解决这个问题也很简单,还用不到智能指针,只需要将函数use的参数改为引用类型就可以了,因为引用只是别名,不会产生新的指针,这也是我在类型系统篇中极力推荐引用为首选参数类型的原因之一。对于此例,数据不大,直接重写复制构造函数,重新申请一块内存也是一种思路。
此例中用到Ptr的地方只有一个,实际项目中Ptr往往需要用到很多次,我们不能保证不会出现忘记使用引用类型的情况,这种情况下重新申请内存也不适用,所以这个时候就需要智能指针来帮忙了。
现在思考另一种情况,某些操作我们不得不暴露出我们的指针供外部使用,随着业务的嵌套和调用链增加,很多时候会忘记或者不确定在什么时候调用delete释放内存。这也是用智能指针的一个场景。以上两种情况都是需要分享指针,对应智能指针中的shared_ptr
shared_ptr顾名思义,它可以帮助开发者完成指针共享的问题,并且完美解决提前释放,不知何时释放,谁负责释放的问题。它的对应关系是一对多,一个实际的内存可以被多个shared_ptr共享

另外一种场景是我们希望自始至终某个指针某个时刻只属于一个对象,外部想要使用它要么通过拥有该指针的对象方法,要么把指针的所有权转移到自己身上,这种场景对应智能指针中的unique_ptr

unique_ptr的对应关系是一对一,无论哪个时刻,只能有一个管理者拥有指针,也就只能由它负责释放了。假如想转移这种对应关系,只能通过std::move操作,不过这个操作之后,原先对象的指针就失效了,它也不再负责管理,所有的任务移交给了新的对象。这种特性特别适合资源敏感型的应用。

线程库

除了内存,线程是开发中另一个重要的课题。线程的难点在于不仅要管理线程对象,还要管理线程对象管理的资源,并且保证线程间数据同步。当然标准库已经做得足够好了,我们需要理解的是使用场景的问题。线程库主要包括线程对象thread,条件对象condition_variable,锁对象mutex
使用thread可以很方便地把程序写成多线程,只需要三步:

void plus(int a,int b){ //第一步:定义线程中要运行的函数
    std::cout<<"running at sub thread"<<std::endl;
    std::cout<<"a + b = "<<a+b<<std::endl;
}

int main(){
    std::thread thread{plus,1,1}; //第二步,定义std::thread对象,将函数作为参数
    std::cout<<"continue running at main thread"<<std::endl;
    thread.join(); //第三步调用线程对象的join函数或者detach函数
    std::cout<<"sub thread finished!"<<std::endl;
}
//输出
//	continue running at main thread
//	running at sub thread
// 	a + b = 2
// 	sub thread finished!

难点在线程间通信,也就是解决两个问题

  1. 线程1更新了变量v的值
  2. 线程2马上能读取到正确的变量v的值,即线程1更新的那个最新值

为了协调这两个过程,就出现了锁对象mutex和条件对象condition_variable。锁对象mutex保证变量按照正确的顺序更改。条件对象condition_variable保证更改能被其他线程监听到。

int a,b;
bool ready = false;
std::mutex mux;
std::condition_variable con;

void plus() {
    std::cout << "running at sub thread" << std::endl;
    //因为我们要读取ready的最新值,所以要用锁保证读取结果的有效性
    std::unique_lock<std::mutex> guard{ mux };
    if (!ready) {
        //数据没准备好,休息一下!
        con.wait(guard); 
    }
    //这里就可以正确读变量a,b了
    std::cout << "a + b =" << a + b << std::endl;
}

int main() {
    std::thread thread{ plus};
    std::cout << "continue running at main thread" << std::endl;
    std::cout << "input a = ";
    std::cin >> a;
    std::cout << "input b = ";
    std::cin >> b;
    {
        //数据准备好了,该通知子线程干活了,用大括号是因为想让锁因为guard的销毁即使释放,从未保证plus里面能重新获得锁
        std::unique_lock<std::mutex> guard{ mux };
        //更新数据
        ready = true;
        //通知
        con.notify_all();
    }
    thread.join();
    std::cout << "sub thread finished!" << std::endl;
}

多线程另一个需要注意的问题就是死锁。死锁的前提是有两个锁

  1. 线程1得到了锁a,还想得锁b
  2. 线程2得到了锁b,还想得锁a

然后,再加上一个前提:某一时刻,只有一个线程能拥有某个锁,就不难得出以下结论:线程a,b除非某一个放弃已得的锁,不然两个线程都会因为没得到需要的锁而一直死等,形成死锁。同时解决死锁的思路也呼之欲出:既然一个得了a,一个得了b,而锁同一时间只能被一个线程得到,那么所有线程都按先得a,再得b的顺序来就不会有锁被占用的问题了。另一个思路则可以从放弃上入手,既然都得不到,那么接下来的任务也做不了,不如直接放弃已经得到的,所以可以考虑使用timed_mutex

其他

还有很多常用的库,如字符串string,时间chrono,还有在定义函数变量时常用的functional,异常exception,更多的内容可以在cplusplus找的参考。

总结

总的来说,标准库提供了一个展现C++语言能力的平台:帮助开发者更好更快完成开发任务的同时,还能启迪开发者实现更好的抽象和实践。如我就从标准库中学到了更规范地定义函数参数,更好的封装,以及其他好的思路。学习标准库不仅更好地掌握了语言本身,还掌握了更全面地分析问题,解决问题的方法,是值得花费一段时间学习的。
容器类是几乎所有项目都会用到的,也是比较好掌握的,主要可以从数据结构方面对照学习;智能指针则是处理指针问题的好帮手;线程相关的库是比较难掌握的,关键是要想明白使用场景和极端情况下的边界问题。很多时候边界问题可能不那么直观。如线程要求获得锁的情况就分为:锁空闲,锁被其他线程占有,锁被自己占有。不同的边界对于不同的锁,预期结果也是不同的,只有在明确场景的情况下,才能更好地理清锁的关系,从而解决好问题。
最好的学习还是在实践中主动使用。对于我,通常在遇到新问题的时候会先查查标准库有没有相应的库,有的话就是学习这个库的好时机。可以先概览库的定义和解决的问题,然后分析它提供的类,函数,对象等,再将自己的理解转换为项目中的代码,最后在实际效果中检验和修正想法,完成库的学习。文章来源地址https://www.toymoban.com/news/detail-490699.html

到了这里,关于现代C++学习指南-标准库的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 机器学习指南:如何学习机器学习?

    机器学习         你有没有想过计算机是如何从数据中学习和变得更聪明的?这就是机器学习 (ML) 的魔力!这就像计算机科学和统计学的酷炫组合,计算机从大量信息中学习以解决问题并做出预测,就像人类一样。         可以这样想:想象一下,你想教你的电脑

    2024年01月21日
    浏览(58)
  • 【机器学习学习】第一天:入门指南

    引言 当今社会,机器学习技术已经被广泛应用于许多领域,如自然语言处理、图像处理和金融分析等。然而,机器学习这一领域需要掌握大量的数学知识和编程技能,因此对于初学者来说,可能会感到非常困难。本文将为初学者提供一份机器学习入门指南,帮助他们了解机器

    2024年02月02日
    浏览(43)
  • 【深度学习】深度强化学习初学者指南

            GAN(Generative Adversarial Networks)是一种深度学习模型,它由两个神经网络组成:一个生成网络和一个判别网络。生成网络学习如何生成类似于给定数据集的新数据,而判别网络则学习如何区分生成网络生成的数据和原始数据。这两个网络相互竞争,使得生成器越来

    2024年02月13日
    浏览(43)
  • Maven进阶学习指南

    当我们在开发项目时,有时需要用到外部依赖组件,例如当我们需要Json序列化的时候需要用到FastJson组件,我们可以通过下载对应jar包加载到项目中。但当一个大的项目同时需要依赖各种各样的外部服务,就存在着配置繁琐、依赖冲突等问题,因此可以通过maven来完成对应的

    2024年02月11日
    浏览(40)
  • Docker 学习指南1

    什么是镜像 镜像是一种轻量级,可执行的独立软件包,它包含运行某个软件所需的所有内容,我们把应用程序和配置以来打包好形成一个可交付的运行环境(包括代码,运行时需要的库,环境变量和配置文件等),这个打包好的运行环境就是image镜像文件。只有通过这个镜像

    2024年02月22日
    浏览(38)
  • 实战应用Android进阶学习指南

    作者:禅与计算机程序设计艺术 在软件开发的历史上,从最初的手工编码到后来的脚本编程、GUI编程、打包工具、编译器等各种工具的出现,使得程序员可以将复杂的代码转换成可执行的程序,实现了快速开发、迭代更新、快速部署等效率上的优势。随着互联网的普及、手机

    2024年02月08日
    浏览(51)
  • 【ARM学习】Cortex- A系列程序员学习指南

    ARM处理器无处不在。 移动电话、个人计算机、电视或汽车。在大约30亿元的微处理器总出货量中,x86架构占据一个非常小的位置(但仍然非常有利可图的)。 ARM处理器不是一个单一的处理器,而是 一个拥有相同指令集和程序员模型的同一处理器家族 ,并有 一定程度的向后兼

    2024年02月01日
    浏览(44)
  • MySQL基础学习内容指南

    MySQL是一款广泛使用的开源关系型数据库管理系统(RDBMS),对于数据库初学者来说,掌握MySQL的基础知识是非常重要的。本文将为您介绍MySQL基础学习的核心内容,帮助您快速入门并提升数据库操作能力。 一、MySQL概述 在开始深入学习MySQL之前,我们首先需要了解MySQL的基本概

    2024年04月12日
    浏览(37)
  • 深度学习调参指南

    1. 选择合适的模型架构 模型的结构(层数和宽度),参数配置,尽量用已经有效的模型 2. 选择优化器 针对具体的问题,从选择常用的优化器开始,进行比较 3. 选择BatchSize 1). Batch Size决定训练速度,但是不影响验证集性能 2). 通常选择最大可支持的Bacth Size 3). 增加Batch Size减少训

    2024年02月15日
    浏览(40)
  • 全面学习SpringCloud框架指南

    要深入学习Spring Cloud框架,你需要系统地掌握其核心组件和概念,并了解如何在实际项目中应用这些知识。以下是一些关键的学习点和相应的学习内容: 一共分为10个模块包括:   理解微服务架构的概念和优势。 学习单体架构向微服务架构演进的过程。 掌握微服务架构的特

    2024年04月16日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包