首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
116 阅读
2
nvim番外之将配置的插件管理器更新为lazy
86 阅读
3
2018总结与2019规划
65 阅读
4
PDF标准详解(五)——图形状态
43 阅读
5
为 MariaDB 配置远程访问权限
34 阅读
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
emacs
elisp
linux
文本编辑器
Java
反汇编
读书笔记
OLEDB
数据库编程
数据结构
Masimaro
累计撰写
319
篇文章
累计收到
31
条评论
首页
栏目
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
页面
归档
友情链接
关于
搜索到
319
篇与
的结果
2022-02-15
2022年阅读清单
2021 已成为过去式,通过去年一年的阅读清单来看,2021年一共读完19本书,相对来说比起上一年有一定的进步,其中还包括一些大部头的书籍。通过记录过去一年的书籍并加上简单的书评,我发现我对一些读物的印象更深一些,因此这年再次延续这个习惯来记录阅读书籍《蛤蟆先生去看心理医生》本书通过一个讲述蛤蟆先生心理咨询来治愈忧郁症给读者普及了一些心理学上的知识,并且介绍了心理咨询相关的内容,让我对心理咨询有了一定的兴趣。书中介绍到人的心理成长过程大致可以分为,儿童自我状态、父母自我状态、成人自我状态。儿童自我状态是儿童天生拥有的心理状态(快乐和深情、愤怒、悲伤、恐惧);以及为了适应成年父母而产生的取悦、道歉、依赖等父母自我状态,因为从小和父母最亲近而产生的价值观、道德观和对生活的评判,例如爱批评人、易怒、严厉、我比你更痛心、我是为你好成人自我状态,用理性非情绪化的方式去行事,不再被父母过去的声音所驱使,也不被童年的情绪所围困。更关注自身的一种心理状态书中也提到一种plow(Poor Little Old Me)可怜又弱小的我的游戏,我们有时候会经常自己玩这种游戏。将自己定义为弱者,为了取悦他人而故意承认错误,认为自己一无是处。另外书中提到人生坐标游戏:你好我不好:低自尊的人认为自己是受害者,所以会玩那些会把他们变成受害者的的游戏,他们竭尽所能记住那些悲伤和不快乐的事情,或者忘记或者忽略那些美好时光(我真不幸、不论我做什么都要爱我,经常陷入自我怀疑中,严重的甚至会自杀)你不好我也不好我好你不好:处在这种状态的人是在父母自我状态下,在这个状态下我们会对别人评头论足,拥有到的优越感,有甚者会将斥责或惩罚别人视为己任,觉得任何问题都处在别人身上。我好,你好:这个坐标就是成人自我状态了,这个状态是一种发自内心信念的行为,我们会信任自己,也信任他人。书中的蛤蟆先生通过10次心理咨询成功摆脱抑郁,拥抱新的生活,而我在阅读这本书的时候同样在审视自我,完成了一次心理上的治疗。《醉步男》这本书中包含一部中篇小说 《醉步男》和一部短篇小说《玩具修理者》《醉步男》讲述男主人公为了解救死去的女友,决定穿越到过去但是不幸失败,由于无法精确控制去到的时间而不得不受困于时间,最终变成时间的囚徒的故事,可能由于作者是理工科出身,他的叙述方式有种理工科做证明题的感觉,从逻辑上来讲十分自洽。读完之后有点细思极恐的感觉,特别是遇到那种记忆中明确记得做过但是实际上没做。经常走的路不知不觉竟然走错。不经让我怀疑我是否有事也陷入时间的洪流中。《玩具修理者》讲述主角小时候附近住着一个神秘但是又什么都能修理的玩具修理者,在主角不小心摔死弟弟又害怕父母责备的情况下,谎称弟弟是玩具,多次找到玩具修理者进行修理的故事。据说该小说是作者太太想参加恐怖小说征文比赛,但是在截稿日期临近还无法动笔的情况下,由作者小林泰三动笔最后成功获奖。而作者从此也转入文学创作中。玩具修理者本文在作者文字渲染中本身就有种恐怖神秘的氛围,特别是最后的伏笔看的人浑身起鸡皮疙瘩。这两部小说中,我更喜欢玩具修理者一些,可能是该故事在开头就描述了一个极为神秘的玩具修理者,而后又用恐怖而紧张文字来推进故事,最后用一个反转来结束整个故事并且令故事达到高潮,令人回味无穷。《一九八四》本书讲述一个在极权主义政府下,人们的悲惨生活。书中主人公温斯顿所生活的社会是一个极权主义社会,在该背景下,世界上之后三个国家:东亚国、大洋国和欧亚国。温斯顿所在的大洋国的执政党是一个叫做老大哥的党派,大洋国的居民无时不刻都处在老大哥的电幕监控之下,而主角所做的工作是不断篡改文字资料中的内容,以证明老大哥的英明神武,以及老大哥带给人民一年比一年要“好”的生活。随着温斯顿逐渐了解真相,他起了反叛老大哥的心思,但是此时他也陷入一个巨大的阴谋之中,最后也只能随着发自内心的一句“老大哥万岁”来结束自己的一生。看完本书,给我的感觉有一丝恐怖,我害怕生活在这种社会中,也庆幸自己所处的社会不是这样的。但是随之而来的也有一丝悲伤,为主人公的遭遇感到悲伤。在第二部中,作者给温斯顿安排了一个温馨的小阁楼。里面有舒服的床,午后温暖的阳光、咖啡以及自己所爱的人,然而这本身应该被所有公民正常应该有的环境确是难得的,而且会被一辈子深刻铭记的时光。即使这样一个时光,作者也不愿为温斯顿保留。随着第三部的展开,原来这一切都被监视者,都被老大哥看在眼里。最终随着拷打和审问,温斯顿终于承认“2+2=5”,并且从心底里对自己的所做所为感到惭愧。随着本书最后一句“老大哥万岁”,温斯顿赢啦自己生命的终结。令人惋惜也令人感叹!《生死疲劳》初次知道这本书是看余华老师的采访,在采访中,余华老师用他那质朴又有力量的语言称赞了这本书,“看完首先的一个感觉是很嫉妒,MD写的这么牛逼WC!”,我想余华老师这么推崇的书肯定好看,于是就买了本书看了起来。小说中地主西门闹在解放初期被冤杀,在阎王的安排下先后投胎为驴、牛、猪、狗、猴,生活在他生前所在的西门屯中,见证着蓝脸以及他的后人们的生活。书中我最喜欢的是猪的情节,余华称赞它是文学史上最牛逼的一头猪了。我觉得猪的部分是整本书最好玩也最热闹的部分。也是比较波澜壮阔的部分,书中写猪十六月夜顺着河水从猪场逃出并且打败猪群成为猪王。带着野猪们扩大地盘,很有种建功立业的感觉。书中的蓝脸是我最喜欢的人物,他传统,执拗,有着一种农民自带的淳朴。他坚决不入合作社成为村里唯一的单干户,不管如何威逼利诱他保留着政府当初分的土地。从最后的结果看,我觉得他仍然是忠于西门闹的,一直勤勤恳恳的保护并耕种着西门闹的那些土地。他是一个地地道道的农民。总之读这本书很有趣《红星照耀中国》本文是斯诺先生在抗日战争时期访问陕北中共根据地的时候,根据自身所见所闻记录下来的。是我们普通读者了解当初革命者面貌的一本不错的书。书中采访了像毛泽东、朱德、彭德怀等当初中共的高级领导人。并且由这些当事人回忆了一些具体的历史事件。同时也详细介绍了在中共带领下,根据地普通群众的精神面貌。从诸多记录中我看到当初国名党失败的缘由。共产党中所有人都是同志,他们没有等级、地位的高低,只有职务的高低。他们都是为自己的理想在奋斗。我记得有这么一个片段,斯诺当初想要来点水洗吧脸,他对着一个红小鬼说到:“喂,给我弄点水来”,但是这个红小鬼根本不理睬他,他又说了一遍了,对方仍然没有理睬。这时一边的战士告诉斯诺,你要叫他同志,这个时候斯诺改口了,小鬼很高兴的端来了一盆水。事后斯诺说一句谢谢,小鬼对他笑着说道:”你不需要为了这一点小事而感谢你的同志“。共产党对于群众来说是真正为了普通百姓的未来在革命,他们与群众打成一片。而国民党军队则如同土匪一样,与日寇相比有过之而无不及。给我映像最深的就是国民党重新占领根据地之后对当地百姓进行惨无人道的屠杀,以至于在我党丢失根据地的时候百姓可能会随着部队一起转移共产党真正做到了教育群众,即使在最困难的时候也会开办学校、教群众识字、念书、讲马列主义和中国革命的必要性以及中国光明的未来。真正将革命军队锻炼成了一支有纪律,有信仰的铁军当初的人们从斯诺的书中应该也看出来了,当时的根据地一片欣欣向荣,中国的前途掌握在他们手中《同名同姓受害者协会》不知道有没有想过这么一天,你会因为自己和某一个罪犯有同一个名字而遭到霸凌和社会的歧视。本书就讲述了这么一群人的故事。他们因为与性犯罪的罪犯有同一个名字,求职被拒、大好的前程被毁、自己多年的努力毁于一旦。于是他们组成了同名同姓受害者协会,一起倾诉,希望借助团体的力量来洗刷自己的冤屈。但是谁也没有想到,这群人都是各怀鬼胎,由此引发了一场充满猜忌的一连串故事。本书探讨了一个真实的问题,在网络虚拟环境下,键盘侠们使用键盘肆无忌惮的攻击他人时,仍然会对他人造成难以估量的伤害。网络在未实名的状态下,我们有时候甚至连对方是男是女都不知道的情况下就对他们展开猜忌并抱团自以为站在正义的一方去攻击别人。从书中的故事来看,网络也不能是法外之地,言论自由也只能给予相对的自由。《桶川跟踪狂杀人事件》本文是一个纪实文学,讲述记者清水洁追踪一起杀人案的始末。年轻的女大学生在桶川站附近被杀,凶手逃之夭夭,大部分目击者都没有看到凶手长相,警方的新闻发布会也只会面相记者俱乐部中的媒体。作者可以说开局只有零星的线索。但是作者凭借自己的专业和百折不挠的精神应是抢在警方之前调查出来了。先是被害人的朋友向作者倾诉,被害人在被杀害之前曾被前男友长时间的跟踪以及骚扰,甚至收到过死亡威胁。但是警方对这些都置之不理,甚至希望被害人撤诉。面对这些被害人曾今几度崩溃但是最终在家人和朋友的支持下坚持了下来,并且多次向警方报案,结果是警方的怠慢最终导致了这样一个悲惨的结果。在被害人遇害的几天之后,警方召开新闻发布会,一直强调受害人喜欢名牌,曾经做过餐厅服务员,甚至收到过男朋友送的价格高昂的礼物,一直想转移视线,希望让公众觉得被害人就是一个风俗行业的普通女子,被害也是罪有应得。但是好在还有像作者这样的记者让真相大白于天下。随着事件被还原,日本最终出台相关法律,对应的警方也收到了应有的惩罚。这是一本比较优秀的纪实文学作品,非常值得一读《佛陀传》本书的介绍写的是佛学的入门书籍,从我的阅读体验来看,确实是入门书籍,我把它作为佛学的论语来读。书中并没有着重介绍佛陀的生平而是主要记录佛陀对相关佛学内容的讲述。于普通的纪传体自传来说显得有些枯燥。但是真正细心阅读之后还是收获不少的佛学中讲究从内心摄取足够的力量,提供的是寻求解脱之道,并且人人都是佛,人是未觉悟的佛,佛是已经觉悟的人。所以从这个角度来说,我们并不需要每日拜佛像,也不需要成天念佛经,也不需要待在寺庙。寻求觉悟之道时应该要清心寡欲,轻物质重精神。作为一本佛学的入门书籍,它浅显易懂,介绍了一些佛经产生的背景以及那些语句具体代表的含义。可以以它作为参考以便更好的理解佛经《计算机是怎样跑起来的》本书从计算机的基础电路讲起,再到如何组建cpu、各种硬件、然后上升到使用二进制对计算机进行编程,接着从二进制代码到汇编再到C语言到算法再到上层应用的数据库、网络等。算是从底层一直讲到如今编程界常用的相关技术。这本书可以作为计算机技术的概论来看,作为计算机专业学生的入门书来看,作为已经在计算机行业工作几年的我来说虽然有些不够具体,但是在某些方面也算是补足了我的一些不足。真正要理解某些知识还得多看其他的资料。本书仅仅只是一个入门而已《程序员修炼之道》很早之前我读过它的第一版,对其中的有些内容还不太了解。随着编程年限越来越长,里面的很多道理我都已经深有体会。下面是我对本书阅读的一些感悟:关注你的技艺。程序员应该想着不断提升自己,有好用的东西应该会想要学习才对,而不是看着别人使用一些自己不懂的东西就觉得对方没什么本事,只会装B改善工作流,使用更加出色的工具。提高效率,减少出错一个建筑一旦出现破窗而不去修复,慢慢的建筑也会变得破旧甚至废弃,软件也一样,一旦发现某些问题,例如设计不够简洁、单元测试没有覆盖、算法不够优美。需要及时修复,使用保持软件的干净整洁遇事不要假设,总是考虑更多的情况。出了事也别找借口,尝试反思想办法改进工作流或者引入新的工具防止再出同样的错误对自己的技术进行投资,要时刻考虑学什么,把精力集中投入到最该学习的地方为代码做解耦。一来便于重用,第二个也方便针对该某块进行单元测试一定不要忘记写单元测试《置身事内》本书介绍我国各路改革伴随的历史背景以及造成的影响。是一本理解我国经济政策与改革的不错的佳作。从土地财政讲起,并且讲解了地方政府发展经济的各种作为以及其中的优点和缺点。还详细分析了造成的后果以及中央对应的改革措施。例如土地财政以及伴随着的土地金融完成了早期资本的积累。这部分的资本大多用于招商引资、投资再生产。为城市化的推进、国民财富的积累打下了坚实的基础。但是它造成的一个恶果就是推升了房价的上涨,加重人民负担。同时土地金融导致政府债台高筑,一旦房价下跌就意味着政府还不上负债导致破产。另外有些时候为了发展GDP盲目进行投资造成一定程度的生产过剩。因此中央也在想办法搞产业升级,并且限制地方政府借债。同时也在想办法改善民生,希望国内人民消耗掉着部分的过剩产能。这本书是一本十分精彩的书,逻辑推理严谨,环环相扣。也容易理解。看懂它你就看懂了我国经济发展历史,也看懂了中央改革的大致走向。
2022年02月15日
6 阅读
0 评论
0 点赞
2021-12-10
MFC程序中使用QT开发界面
如果你有一个现成的MFC项目在做维护,但是你厌倦了使用MFC繁琐的操作来做界面美化,或者你需要在这个项目中用到QT里面好用的某些功能;亦或者是你需要使用某些只能在MFC中使用的组件,但是界面这部分已经用QT做好了。那么这篇文章可能可以帮助到你演示环境使用Visual Studio 2019 + QT5.12.8 版本添加QT依赖首先创建一个基于对话框的MFC工程,当然其他的像是多文档、单文档工程也是可以的,只是为了简单起见我这里用的是对话框然后通过鼠标右键点击项目,然后依次点击属性 --> C/C++ -->常规在工程的附加头文件中添加上QCore、QGui、QWidget和QT的头文件路径这里记得按照对应编译选项来选择包含64位或者32。接着在连接器-->常规 中的附加库目录中添加qt的lib库最后再在连接器-->输入中添加依赖的lib文件,需要注意的是,debug版本需要链接上带d的lib文件,release则链接上不带d的。先编译一下,如果没有问题,qt相关的配置已经完成了添加信号槽机制MFC是基于Windows 消息队列来处理和响应ui事件的,而qt是采用信号槽机制来响应的,我们虽然添加了qt的依赖,但是现在只能使用其他的qt库,无法使用qt中的信号槽,需要额外添加一些组件来使mfc支持信号槽。好在这部分需求qt相关的研发人员已经考虑到了,可以在github中找到 QMfcApp我们可以将这两个文件给拷贝下来,添加到项目中。并且在cpp文件相应位置添加上 #include "pch.h"包含预处理头中间会有报错,这是因为在Unicode 字符集下 CString 中的字符串类型是 wchar_t* QString::fromLocal8bit 无法 从 wchar_t* 转化为 char* 所以这里可以修改一下,使用 QString::fromStdWString(),然后进行编译在QMfcApp.cpp的注释里面可以看到,如何使用它/*! Creates an instance of QApplication, passing the command line of \a mfcApp to the QApplication constructor, and returns the new object. The returned object must be destroyed by the caller. Use this static function if you want to perform additional initializations after creating the application object, or if you want to create Qt GUI elements in the InitInstance() reimplementation of CWinApp: \code BOOL MyMfcApp::InitInstance() { // standard MFC initialization // ... // This sets the global qApp pointer QMfcApp::instance(this); // Qt GUI initialization } BOOL MyMfcApp::Run() { int result = QMfcApp::run(this); delete qApp; return result; } \endcode \sa run() */首先在app类的InitInstance 函数中初始化QApplication类BOOL CMFCWithQtApp::InitInstance() { CWinApp::InitInstance(); QMfcApp::instance(this); return true; }然后需要重写 app类的run 方法,在该方法中调用QMFC 的run方法int CMFCWithQtApp::Run() { int result = QMfcApp::run(this); delete qApp; return result; }再次编译一下,完成了往mfc中添加信号槽机制的功能添加qt界面在项目中新建一个界面类,让他继承自QWidget,如下// MainUI.h #pragma once #include <QWidget> class MainUI: public QWidget { Q_OBJECT public: MainUI(QWidget* parent = nullptr); ~MainUI(); }; //MainUI.cpp #include "pch.h" #include "MainUI.h" #include <QPushButton> MainUI::MainUI(QWidget* parent) : QWidget(parent) { setWindowTitle("Qt Windows"); setFixedSize(800, 720); QPushButton* pBtn = new QPushButton(QString::fromLocal8Bit("这是一个Qt按钮"), this); } MainUI::~MainUI() { }然后在App 类的 InitInstance 中启动该界面BOOL CMFCWithQtApp::InitInstance() { CWinApp::InitInstance(); QMfcApp::instance(this); MainUI ui; ui.show(); QMfcApp::exec(); return FALSE; }然后编译,这个时候发现会在链接的时候包一些错误,找不到一些 meta 的函数的定义配置元编译过程传统的c/c++ 从源代码到生成可执行文件的过程需要经过预编译、编译、链接。而qt在预编译前会进行元编译,生成一个moc_开头的源码文件,后续编译的真正的文件其实是这个元编译生成的文件。MFC项目不会经历这一步,所以会报错。在MainUI.h 上点击右键,选择属性, 将项目类型选择为自定义生成工具然后应用,这个时候会出现新的选项在命令行和输入这两栏中分别填入 "moc.exe" "%(FullPath)" -o ".\GeneratedFiles\moc_%(Filename).cpp" "-fpch.h" "-f../MainUI.h" 和 .\GeneratedFiles\moc_%(Filename).cpp命令行的含义是会使用moc元编译器编译当前文件,并将生成的文件放入到当前目录下的GeneratedFiles子目录中,并且以moc_开头作为文件名,后面 -f 表示生成的新文件中会包含 #include "pch.h" 和 #include "../MainUI.h" 然后在文件中选择右键,编译。这样将会生成moc文件(两边的双引号也好包含进去)如果编译的时候报找不到moc.exe 这样的错误,请配置QT中的bin路径到环境变量中我们将新生成的文件添加到项目中再次编译,成功过后点击运行就可以看到qt界面已经展示出来了一些问题的处理窗口出来了,但是在我的环境下出现两个问题,关闭窗口后进程无法退出;程序退出后出现内存泄露的问题针对这两个问题可以在QMfcApp.cpp 文件中修改// 表示在最后一个qt窗口退出时,关闭QApplication setQuitOnLastWindowClosed(true); //将之前的false改为true// ~QMfcApp() 中添加这两句,当析构完成后关闭进程 HANDLE hself = GetCurrentProcess(); TerminateProcess(hself, 0);测试信号槽我们可以在MainUI中添加信号槽MainUI::MainUI(QWidget* parent) : QWidget(parent) { setWindowTitle("Qt Windows"); setFixedSize(800, 720); QPushButton* pBtn = new QPushButton(QString::fromLocal8Bit("这是一个Qt按钮"), this); connect(pBtn, &QPushButton::clicked, [=]() { QMessageBox::information(this, QString::fromLocal8Bit("信号槽"), QString::fromLocal8Bit("这是由信号槽弹出来的")); }); }点击按钮之后,消息框也正常弹出来了使用qt designer 设计界面使用 qtdesigner 设计这样一个界面qtdesigner 会生成一个.ui文件,在qt的开发环境中,会自动使用uic.exe 将这个文件生成一个对应的.h文件。我们先将ui文件导入到项目,并且按照之前的步骤设置自定义生成工具,填入如下命令行"uic.exe" "%(FullPath)" -o ".\ui_%(Filename).h"并且填写上输出路径.\ui_%(Filename).h编译之后会生成一个对应的ui_MainUi.h 文件,修改对应的MainUI.h 头文件,加上关于它的引用,并且添加一个ui的对象指针//MainUI.h #pragma once #include <QWidget> #include "ui_Main.h" class MainUI: public QWidget { Q_OBJECT public: MainUI(QWidget* parent = nullptr); ~MainUI(); private: Ui::mainUI* m_pUI; };在类的构造中,使用ui对象来产生界面元素//MainUI.cpp #include "pch.h" #include "MainUI.h" #include <QPushButton> #include <QMessageBox> MainUI::MainUI(QWidget* parent) : QWidget(parent), m_pUI(new Ui::mainUI()) { m_pUI->setupUi(this); connect(m_pUI->pushButton, &QPushButton::clicked, [=]() { QMessageBox::information(this, QString::fromLocal8Bit("qt 信号槽测试"), m_pUI->lineEdit->text()); }); } MainUI::~MainUI() { delete m_pUI; }执行效果如下
2021年12月10日
6 阅读
0 评论
0 点赞
2021-11-26
archlinux + dwm系统美化
上一次完成了dwm的基础安装和一些基本工具的安装,但是仍然只是基本的几个黑框框而已,与原来的tty终端来说没有什么大的改进,这里我就根据自己的配置来主要说说如何美化它,以及让它具备一个系统的基本功能。设置交换文件在桌面环境中,交换分区或文件用来实现休眠。即将当前环境保存在磁盘的交换文件或分区部分。除此之外,某些特定软件需要 swap 才可以正确运行。交换文件与分区性能相同,且交换文件更为灵活,可随时变更大小,增加与删除。dd if=/dev/zero of=/swapfile bs=1M count=16384 status=progress #创建16G的交换空间 大小根据需要自定 chmod 600 /swapfile #设置正确的权限 mkswap /swapfile #格式化swap文件 swapon /swapfile #启用swap文件最后,向 /etc/fstab中追加以下内容/swapfile none swap defaults 0 0设置背景图片编辑 .xinitr 文件,加入nitrogen和picom的配置,现在的.xinitrc 文件内容如下nitrogen --restore & # 保存并恢复上一次的配置 picom & exec dwm我们可以去一些网站下载一些高清的壁纸,然后在 dmenu中启动 nitrogen 设置壁纸点击 preferences 选择壁纸所在目录选择之后会出现里面的图片,选择一张作为壁纸关闭窗口之后就有壁纸了登陆管理器sddm安装使用pacman 安装sddm,并设置服务开机自启sudo pacman -S sddm sudo systemctl enable sddm接着创建启动项新建文件 /usr/share/xsessions/dwm.desktop, 中间如果某个目录没有,则创建它在dwm.desktop 中添加如下内容[Desktop Entry] Encoding=UTF-8 Name=Dwm Comment=Dynamic window manager Exec=dwm Icon=dwm Type=XSession重启之后就可以进入登陆界面了,输入用户和密码就可以进入系统,这个时候也可以看到直接就进入到dwm窗口了进入之后我们发现有一个问题,那就是之前设置的壁纸不会加载了,进入dwm之后是最初时候的黑色背景了。这是因为之前写在 .xinitrc 文件中的命令为执行的缘故,因为通过sddm进入系统是直接执行的dwm命令而不是通过 startx 在启动,因此通过这种方式进入系统之后,.xinitrc中的命令永远不会执行。这个问题可以通过dwm的补丁来解决。可以去官方网站下载auto-start 补丁wget https://dwm.suckless.org/patches/autostart/dwm-autostart-20210120-cb3f58a.diff patch < dwm-autostart-20210120-cb3f58a.diff sudo make clean install该补丁在dwm启动之后自动执行 ~/.dwm/autostart.sh 脚本中的内容,因此我们可以将以前在 .xinitrc 中的代码拷贝到该文件中mv dwm .dwm # 修改目录 touch autostart.sh #创建文件 chmod u+x autostart.sh # 给文件赋予执行权限 # 以下是文件中的内容 #!/usr/bin/zsh nitrogen --restore & picom --config ~/.config/picom.conf &有时候执行picom 会报错,说是无法启动 vsync 相关的功能,这种情况下可以拷贝一份配置文件到指定目录,修改文件, 将 vsync = true 改为 vsync = false, 关闭 vsync 的功能,autostart.sh 中关于picom 的配置,主要是为了制定使用修改后的配置文件主题定制与美化登陆界面到现在已经基本配置完成了,但是现在的界面并不好看,我希望将它做一些简单的没话,让它更符合自己的审美。我们可以去kde主题商店 去找。下载一个自己喜欢的主题,安装上它依赖的包(这个包可能根据主题的不同而不同,但大部分都会以来qt相关的一些组件)。下载下来之后,将整个目录拷贝到 /usr/share/sddm/theme中。后续主题所在目录的名称将作为主题名称进行配置可以通过命令 sddm-greeter --test-mode --theme /usr/share/sddm/themes/主题名 来预览一个主题预览没问题了之后可以通过配置文件来指定对应主题,sddm的默认配置文件位于/usr/lib/sddm/sddm.conf.d/default.conf 中,要修改配置,请在 /etc/sddm.conf.d 目录下创建配置文件,在这个目录中可以按照不同的小节放到不同的配置文件中,例如这里要配置主题,可以在这个目录下创建theme.conf, 并写下如下内容[Theme] Current= # 当前主题名称 CursorTheme= # 当前光标主题 DisableAvatarsThreshold=7 设置有多少个用户可以使用头像 EnableAvatars=true # 是否加载头像 FaceDir=/usr/share/sddm/faces # 头像所在目录 Font= #当前字体 Theme=/usr/share/sddm/themes #主题所在目录具体配置请参考 sddm.conf(5)grub 主题配置可以在 pling中下载喜欢的主题。解压并进入主题所在目录, 执行下列命令sudo cp -r . /usr/share/grub/themes/Nino # 拷贝主题文件接着编辑 /etc/default/grub 文件找到并修改 GRUB_THEME 项使其指向对应主题目录中的 theme.txt 文件GRUB_THEME=/usr/share/grub/themes/Nino/Miku/theme.txt在终端输入sudo grub-mkconfig -o /boot/grub/grub.cfg接着重启就可以看到新设置的主题了至此已经初步完成了进入系统之前的美化操作,接下来后面将要针对dwm以及st和dmenu进行改造,使其更加贴近日常使用
2021年11月26日
7 阅读
0 评论
0 点赞
2021-10-23
窗口管理器 dwm安装
上一篇博文中,已经完成了archlinux的安装,但是进去仅仅是一个冰冷冷的交互式命令窗口。没有图像,也无法打开浏览器。离日常使用还差的很远,接下来首先需要做的就是安装桌面环境。这里我不打算使用诸如gnome或者kde之类的桌面环境,一来这些桌面环境会自动帮我们把所有的给配置好,这不符合深度定制或者说折腾的本意,而来它们的体量相对来说还是比较大的,我想实现最小化安装,这里只需要一个窗口管理器就够了桌面环境与窗口管理器简述要将它作为日常使用来说,需要一个图形化的操作界面,与Windows不同的是,Linux自身并不包含图形操作界面,需要额外安装,而Windows是将图形操作界面作为内核的一部分。为了完成图形化的安装,可以使用桌面环境或者窗口管理器。桌面环境桌面环境结合X客户端,提供通用图形用户界面元素,如图标、工具栏、壁纸,桌面小部件。 大多数桌面环境包括提供一套整合的应用程序和实用工具。桌面环境包含了自己的一套窗口管理器,但是这个可以被替换。为了维持我们进行折腾的目的,这里采用仅仅安装窗口管理器的方式,后面陆续针对它仅配置,以达到和桌面环境差不多的效果安装前的准备工作在安装前需要做一些额外的工作,先下载安装好一些必备组件。sudo pacman -S net-tools man-db man-pages man-pages-zh_cn texinfo ntfs-3g tree pacman-contrib neofetch wget git usbutils pciutils acpinet-tools: 一个包含各种网络工具的库,像 ifconfig 或者 netstat,官方目前使用ip address 命令来获取本机的IP地址,但是我仍然喜欢使用ifconfg所以这里我安装上这个包man-db: 提供man命令man-pages: 提供man页面内容man-pages-zh_cn: 提供man中文页面內容,这个包下载下來不能直接用,后面改別名会提到texinfo: info帮助文档的包ntfs-3g: 对NTFS文件系統提供支持tree: 以树形结构显示目录中各种文件的依附关系pacman-contrib: pacman包管理器的扩展好像是,我主要用裡面的那个pactree命令neofetch: 一个显示系统信息的工具wget: 一个用來下载的工具git: 这个就不用说了,做程序员的都知道这个usbutils: 查看系统USB设备pciutils: 查看系统PCI设备acpi: 用來查看电池电量的工具一些基础工具安装好后,下面来安装中文和其他语言的字体包,防止后续出现乱码的情况sudo pacman -S adobe-source-han-serif-cn-fonts wqy-zenhei sudo pacman -S noto-fonts-cjk noto-fonts-emoji noto-fonts-extra ## 这里我把官方推荐的所有带unicode标识的全装上了,这样后续就不太会出现乱码的情况了 yay -S ttf-ubraille ttf-symbola otf-cm-unicode ttf-arphic-ukai ttf-arphic-uming ttf-dejavu gnu-free-fonts ttf-google-fonts-git nerd-fonts-complete ttf-hack ttf-joypixels接着安装一下驱动sudo pacman -S alsa-utils sof-firmware alsa-ucm-conf xf86-video-intel mesa xf86-input-libinputalsa-utils:声卡驱动sof-firmware:声卡驱动,如果你的机器比较新,那么你可能需要安裝。alsa-ucm-conf: 声卡驱动,如果你的机器比较新,那么你可能需要安裝。xf86-video-intel: Intel核显的渠道,这里我只安装了核心显卡的驱动,如果你有另外的独立显卡,请参考官方文档中的相关内容mesa: 用來配合显卡的另一种上层驱动xf86-input-libinput: 笔记本触摸板的驱动窗口管理器是搭载在x窗口系统之上的,安装窗口管理器之前需要先安装上x窗口系统的相关服务sudo pacman -S xorg xorg-xinit nitrogen picomxorg: x服务,用来显示图形界面xorg-init: x服务的启动程序nigrogen: 设置背景图片picom: 窗口渲染,后面做半透明渲染安装窗口管理器接下来就正式开始安装窗口管理器了,这里使用suckless全家桶,窗口管理器采用dwm, 程序启动器采用dmenu, 终端程序采用stgit clone https://git.suckless.org/dwm --depth=1 git clone https://git.suckless.org/st --depth=1 git clone https://git.suckless.org/dmenu --depth=1分别切换到这几个下载下来的目录中,依次执行 sudo make clean install 进行编译安装接着在用户的家目录下新建一个 .xinitrc 文件(ps: 也可以将 /etc/X11/xinit/xinitrc拷贝到家目录下并改名为.xinitrc,但是这个文件里面内容太多了,显的有点乱,所以我直接新建一个自己往里面加想要的内容)在文件中添加一行exec dwm保存退出后,输入命令 startx 即可看到dwm的窗口了dwm 基本用法dwm中最重要的键是 Mod1 键,这个键默认映射到了 Alt 键,使用 Mod1 + p 可以启动 dmenu, 然后只需要在上边出现的工具条中输入你想运行的程序的前几个字母,也可以按左右箭头在进行选择,按回车键完成,即可启动想要的程序可以使用 Shift + Mod1 + x 来将当前的活动窗口移到其他的标签页,其中x是标签页的编号关闭当前窗口可以使用 Mod1 + Shift +c 可以使用 Mod1 + Shift + q 来退出 dwm到现在已经完成了dwm的基本安装以及使用,但是它看起来是那样的不起眼,比起刚开始来说仅仅是多了几个可以运行的终端而已,后面将会介绍如何对它进行美化和相应的改造,让它变得漂亮起来
2021年10月23日
10 阅读
0 评论
0 点赞
2021-10-16
arch linux 安装
好长时间都没有更新自己的博客了,我简单翻阅了一下自己的更新记录,上一次更新好像还是在5月份左右,距今也有快半年,这半年也是遇到了很多事情,有不好的,也有好的。这半年我对在日常生活工作中使用Linux系统产生了一些兴趣,从零开始折腾这一系列的内容,主要从安装、配置、以及尝试各种软件来取代Windows的主导地位,也产生了一些心得,这里我想分几篇博客来聊聊我是如何慢慢使用arch Linux 来替代以前的Windows机器为何选择arch Linux我本身有一台6年前买的联想的笔记本,随着每次系统的更新,也变得越来越卡了,终于有一点我忍不了想着要不退回到windows 7吧,windows 10这台机器已经有点不行了。恰巧我最近在看一本关于计算机发展史的书,书中提到自由软件运动,那种运动有一种人人为我,我为人人的理想主义色彩,我想既然不能编写自由软件造福一方,至少应该享受自由软件带来的好处,而且国内经常爆出各种软件窃取用户隐私的新闻。何不趁此机会转移到自由软件阵营呢?说做就做,自由软件的基础自然是需要一个自由的操作系统,Linux是目前使用最为广泛的自由操作系统。在看了各种Linux发行版本之后我决定使用arch Linux,主要有以下几个原因:更新方式比较激进,arch Linux采用滚动更新的方式,这意味着用户能享受最新的软件版本,当然过激的更新行为会导致一些问题,比如常见的滚挂。我自认为我不缺少动手能力,这个我有信心能自己解决arch Linux 丰富的软件源使它能够安装其他发行版Linux无法安装的软件丰富的wiki文档,你能遇到的问题几乎都可以在里面找到答案最小化安装,arch Linux自身是最精简的系统,几乎精简到不能再精简。因此比起其他发行版本的Linux来说,它提供更高自由度的可配置性。安装困难,我一直觉得对于自己专业内的事情,要做就做最困难的,既然它的安装使用最为困难,那我就用它,当彻底征服了这一块内容,带来的成就感是无法比拟的。而且熟悉了它的安装过程,又例如提高对Linux的认识基于上述几点理由,我开始了漫长的折腾之路arch Linux 安装安装主要参考 arch wiki) 好在文档大部分都有中文版本,对于英语不好的人来说阅读起来也不会有过多的阻碍制作U盘启动项首先去官方指定的镜像站下载安装包,然后使用相应的工作制作U盘启动项,windows上我使用的是rufus、Linux或者mac上直接使用如下命令写入到U盘sudo dd bs=4M if=/path/to/archlinux.iso of=/dev/sdx status=progress oflag=sync上述命令的含义是制作一个镜像文件,源文件内容保存在if参数所指定的位置,输出到 of 所指定的位置, status=progress 表示现实制作进度, oflag=sync表示以同步的方式写入,即所有数据写入完成命令结束,而不是刚写入就结束需要注意的是,sdx 是u盘在系统中的命名,一般插入U盘后,在shell上使用fdisk -l可以看到,另外有的U盘可能经过分区,显示出下面还有sdx1、sdx2等分区,要写到sdx,而不是sdx1或者sdx2将U盘插入待装机的电脑上,进入bios调整启动顺序和安全设施,如果使用uefi方式启动的话,需要调整启动方式为uefi only 而不是 legacy/csm接下来就可以启动电脑,进入arch Linux的安装界面了联网设置进入到安装界面的第一步需要连接上网络,这里使用 iwctl 进行网络连接配置iwctl #进入交互式命令行 device list # 列出设备名,比如无线网卡一般叫做 wlan0 station wlan0 scan #扫描Wi-Fi station wlan0 get-networks #列出扫描到的Wi-Fi名称,例如要连接到esi-0这个Wi-Fi station wlan0 connect esi-0 #尝试连接,这个时候需要输入密码成功后就连上互联网了,可以使用 ping archlinux.org 来试试网络是否成功连上更新系统时间后续在访问https之类的站点时会验证客户端和服务器的证书和时间的,有时候时间不统一,在访问时可能会报无效的证书之类的错误使用命令timedatectl set-ntp true更新之后可以使用 timedatectl status 检查服务状态磁盘分区与格式化根据arch wiki上的说法,采用uefi的启动方式时,至少需要一个boot或者efi 分区作为efi系统分区(大小不能小于280M)、一个根分区。这里假设硬盘大小为100G,我采用如下的分区方案efi 分区 /efi 1G根分区 / 40G用户主目录 /home 剩余全部空间,越大越好跟windows 做类比的话,根分区相当于c 盘,主要用来装系统相关的内容,用户分区相当于D盘或者其他盘,用来放用户数据,后续如果系统挂了,重装系统的话,不会破坏用户目录的内容,甚至如果用户目录在其他物理盘上,后面换机器了直接将这块盘挂载到其他机器上,数据直接就能用了首先将磁盘分区表转化为gpt类型lsblk #显示分区状况 parted /dev/sdx #执行parted命令, 进行磁盘类型变更 (parted)mktable #输入mktable 修改磁盘分区表类型 new disk label type? gpt #输入gpt,修改分区表为gpt类型 quit #最后退出parted交互式命令 接下来使用cfdisk 命令对磁盘进行分区cfdisk /dev/sdx #使用cfdisd对磁盘进行分区free space 表示未分区的部分,上下键用来选择区域,左右键用来选择操作先选择new新建分区,然后输入大小,最后回车,重复几次这个操作,按照之前定义的大小来完成分区记得完成之后,将选项调整到wirte 在退出前将分区写入到磁盘。完成之后使用fdisk -l 查看分区接下来格式化磁盘中的各个分区efi 分区格式化为 vfat 格式根分区和用户分区格式化为 ext4格式mkfs.ext4 /dev/sda2 mkfs.ext4 /dev/sda3 mkfs.vfat /dev/sda1接下来将磁盘挂在到当前系统的文件目录下,使磁盘能正常被系统访问到mount /dev/sda2 /mnt mkdir /mnt/efi mkdir /mnt/home mount /dev/sda1 /mnt/efi mount /dev/sda3 /mnt/home系统安装折腾了这么多东西,终于要开始正式安装系统了,arch linux提供了一个脚本用来自动安装系统内核pacstrap /mnt base base-devel linux linux-firmware这句命令可以帮助我们将系统所需要的包安装到/mnt 这个目录也就是磁盘上等待一段时间,安装就完成了,先别着急重启,还有一些内容需要安装pacstrap /mnt dhcpcd vim sudo networkmanager # dhcpcd networkmanager 是网络相关的软件包,后期缺少可以通过网络下载,联网软件没有的话只能白瞎 系统自身配置到这里基本已经完成了安装部分的工作了,接下来要进行的就是重启前的基本配置了生成磁盘分区的记录文件genfstab -U /mnt >> /mnt/etc/fstab生成之后可以使用cat或者 vim 之类的命令复查一下生成的是否有误接下来切换到新安装的系统上arch-chroot /mnt在新系统中先在/etc/hostname中设置主机名,在文件中输入你想要的主机名,例如叫 arch接下来在文件/etc/hosts中设置与其匹配的条目,可以加入以下内容127.0.0.1 localhost ::1 localhost 127.0.1.1 arch.localdomain arch接着设置时区,在/usr/localtime 下用 /usr 中合适的时区创建符号连接ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime将系统时间同步到硬件时间hwclock --systohc接下来进行本地化操作,程序如果需要本地化文件,都需要依赖 locale,它规定了地域、货币、时区日期的格式、字符排列方式和本地化标准。需要在这两个文件中设置 locale.gen 与 locale.conf编辑 /etc/locale.gen 然后去掉 en_US.UTF-8 UTF-8 和其他需要的地区前的注释,例如作为中文用户可以去掉 zh_CN.UTF-8 UTF-8 以显示中文编辑完成之后使用如下命令生成 localelocale-gen在/etc/locale.conf 文件中指定系统使用的语言,这里推荐使用英文,否则在出错的时候可能会出现中文乱码,不便与排错echo 'LANG=en_US.UTF-8' > /etc/locale.conf设置root 密码passwd root根据cpu的不同,安装对应的微码,以确保处理器能稳定运行pacman -S intel-ucode #intel pacman -S amd-ucode #amd安装引导程序为了能在机器加电之后正常找到Linux所在位置,需要安装引导程序,来引导操作系统的启动。pacman -S grub efibootmgr # 使用grub做引导程序,efibootmgr 是uefi方式启动需要的 grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=arch #将启动项取名为arch 启动类型为efi的64位系统 系统启动位置在 /efi 接下来可以稍微对启动配置做一些修改,编辑 `/etc/default/grub` 文件,去掉 `GRUB_CMDLINE_LINUX_DEFAULT` 一行中最后的 `quit` 参数,同时把`log level` 的数值从3改到5,这样是为了后续如果出现系统错误方便排查。同时加入 `nowatchdog` 参数,这样可以显著提升开关机速度修改完成之后生成grub所需的配置文件grub-mkconfig -o /boot/grub/grub.cfg这样就完成了安装exit umount -R /mnt reboot一切顺利的话,重启之后应该就能进入系统了。新系统基础配置网络配置之前我们下载了与网络相关的两个工具networkmanager和dhcpcd,用来管理网络和dhcp自动分配IP地址systemctl enable --now NetworkManager # 启动networmmanager服务 nmcli device wifi list # 查看Wi-Fi列表 nmcli device wifi connect ssid password password #连接Wi-Fi,ssid表示Wi-Fi名,后面一个password表示连接wifi的真实密码网络连接成功之后,使用pacman将系统更新到最新版本pacman -Syyu创建非root用户为了系统安全防止自己某天抽风不小心输入类似 rm -rf / 之类的危险命令,平时最好使用非root登陆。 对于系统操作使用sudo来提升权限useradd -m -G wheel -s /bin/bash arch创建一个名为arch的用户,将用户组归属到wheel中,同时创建用户目录, 并且指定shell使用bash接下来使用 passwd arch 来修改用户密码由于系统中并没有安装vi,所以默认会使用vi命令的一些命令都会失效。所以需要将 vi 链接到 vimln -sf /usr/bin/vim /usr/bin/vi使用visudo 将文件中 #%whell ALL=(ALL) ALL 这行的注释去掉使用su arch 将当前用户切换到arch。可以使用命令sudo pacman -Syyu来更新系统,同时测试一下输入用户密码之后能否执行一些root命令设置交换文件在桌面环境中,交换分区或文件用来实现休眠(hibernate)的功能,即将当前环境保存在磁盘的交换文件或分区部分。除此之外,某些特定软件需要 swap 才可以正确运行。交换文件与分区性能相同,且交换文件更为灵活,可随时变更大小,增加与删除dd if=/dev/zero of=swapfile bs=1M count=4096 status=progress #设置4G的交换分区,大小根据系统的实际内存来决定,一般最好略大于物理内存 chmod 600 /swapfile mkswap /swapfile # 格式化swap文件 swapon /swapfile # 启用swap文件最后往/etc/fstab中追加如下内容/swapfile none swap defaults 0 0开启32位软件库支持与ArchLinuxCN库的支持为了系统的稳定,官方关闭了32位软件以及aur软件库,但是仅仅只依靠官方源中的软件时不够用的,这里我们要打开这两个库vim /etc/pacman.conf去掉[multilib]一节中两行的注释,来开启 32 位库支持。在文档结尾处加入下面的文字,来开启 ArchLinuxCN 源。[archlinuxcn] Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch执行 sudo pacman -Syyu 更新pacman数据库然后需要安装 archlinuxcn-keyring 包以导入 GPG keysudo pacman -S arhclinuxcn-keyring有时候因为密钥环的问题,导致这一步安装报错,可以依次执行下面的命令sudo pacman -syyu sudo pacman -S haveged sudo pacman -Syu haveged sudo systemctl start haveged sudo systemctl enable haveged sudo rm -fr /etc/pacman.d/gnupg sudo pacman-key --init sudo pacman-key --populate archlinux sudo pacman-key --populate archlinuxcn然后再重新安装,即可解决问题最后安装 yay 用来下载archlinuxcn库中的软件结尾至此,已经完成了对系统的安装到基础配置,现在已经有了一个基本可用的操作系统了,但是目前系统仍然只有一个基本的黑框框,作为日常使用还远远不足,至少还需要一个桌面环境,后面的博文会陆续介绍我是如何安装并配置一个基本的桌面环境。最后到一个基本可用于日常生活和工作中的操作系统。
2021年10月16日
9 阅读
0 评论
0 点赞
2021-05-31
动态内存与智能指针
c/c++语言的一大特色是在于可以动态的进行内存管理,而这也是它的难点所在。程序出现问题,原因经常在动态内存管理这块,比如分配内存后没有及时释放,或者当前线程提前释放了其他线程也会使用的内存。而c++11中新增的智能指针能在一定程度上解决这些问题动态内存与智能指针在c++中动态内存的管理是通过一对运算符来完成的: new和delete ,new为对象分配空间并返回一个指向该对象的指针。delete 接受一个动态对象的指针,销毁对象并释放相关内存动态内存的管理十分困难,有时候会忘记释放内存,这种情况下会产生内存泄漏。有时在尚有指针引用内存的情况下我们就释放了它,在这种情况下就会产生引用非法内存的指针。为了更容易也更安全的使用动态内存,新的标准提供了两种智能指针类型来管理动态对象。shared_ptr 类类似于vector 智能指针也是模板。创建智能指针时,必须提供额外的信息——指针可以指向的类型。智能指针的用法与普通指针类似。解引用一个智能指针返回它指向的对象,箭头运算符可以返回对象中的成员shared_ptr<string> p = new string; if(nullptr != p && p->empty()) { *p = "hello world"; //字符串为空的时候,将一个新值赋予string }最安全的分配和使用动态内存的方法是调用一个名为 make_shared 的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回此对象的 shared_ptr。shared_ptr<int> p3 = make_shared<int>(42); //初始化p3 指向一个值为42的int类型 shared_ptr<string> p4 = make_shared<string>(10, '9'); //p4指向一个值为 "9999999999" 的string shared_ptr<int> p5 = make_shared<int>(); //指向一个值初始化的int,即,值为0当shared_ptr 进行拷贝和赋值操作时,每个shared_ptr 都会记录有多少个其他的shared_ptr 指向相同的对象auto p = make_shared<int>(42); //此时p指向的对象只有一个引用值 auto q = p; //此时p指向的对象有两个引用者我们可以认为每一个shared_ptr 都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。当我们给shared_ptr 赋一个新值或者shared_ptr 被销毁时,他所关联的计数器就会递减。当指向一个对象的最后一个shared_ptr 被销毁时,shared_ptr 类就会自动销毁此对象。shared_ptr 并不是万能的,也会出现内存泄漏的问题。这种情况一般出现在容器中。如果在容器中存放了shared_ptr ,然后重新排列了容器,从而不需要某些元素。在这种情况下应该确保使用earse删除某些不再需要的shared_ptr 元素直接管理内存相对与智能指针直接使用new 和 delete很容器出错。当内存耗尽时,new会失败,会抛出一个bad_alloc 异常。我们可以改变使用new的方式来阻止它抛出异常int *p1 = new int; //如果分配失败则会抛出异常 int *p2 = new (nothrow) int; //如果分配失败,new返回一个空指针我们称这种形式的new为定位new。定位new允许我们传递额外的参数给到new,在此例子中我们传递一个标准库中的nothrow 对象,告知它在内存不足的时候不要抛出异常。如果这种形式的new不能分配所需内存,它会返回一个空指针动态对象的生命周期一直到它被手动释放为止。这样就给使用者造成了一个额外的负担,调用者必须记得释放内存。使用new和delete 管理动态内存存在三个常见问题:忘记delete内存。造成内存泄漏问题使用已经释放掉的对象。通过在释放内存后将指针置为空,有时可以检出这种错误同一块内存多次释放坚持只使用智能指针就可以避免所有这些问题。对于一块内存只有在没有任何智能指针指向它的情况下,智能指针才会自动释放它shared_ptr 和 new 结合使用接受指针参数的智能指针构造函数是 explicit 的。因此,我们不能将一个内置指针隐式转化为智能指针,必须使用直接初始化的方式shared_ptr<int> p1 = new int(1024); //错误,这里需要将int* 隐式转化为shared_ptr<int> 类型 shared_ptr<int> p2(new int(1024)); //正确默认情况下一个用来初始化智能指针的普通指针必须指向使用new创建的动态内存(malloc 创建的需要自定义释放操作),因为智能指针默认采用delete来释放它所关联的对象。我们可以将智能指针绑定到一个指向其他类型的资源的指针上面,但是为了这样做,必须提供自己的操作来代替delete不要混合使用普通指针和智能指针。void process(shared_ptr<int> ptr) { // 进入到函数中时,ptr 所在的引用计数加1 } //函数结束时, ptr 所在对象的引用计数减 1 shared_ptr<int> p(new int(42)); //引用计数为1 process(p); //在函数内部,引用计数加1,变为2 //执行完成后,引用计数减1,变为1,此时对象不会被销毁 *p = 100; //可以进行赋值,此时对象还未被销毁 int* p1 = new int(42); process(shared_ptr<int>(p1)); //进入函数后,由于p1 自身不是智能指针,所以在函数结束之后,智能指针的计数为0,会销毁对应的对象 *p1 = 100; //错误,此时对象已被销毁 智能指针定义了一个get函数用来返回一个普通的指针,此函数是为了这样一种情况而设计的:我们需要像不能使用智能指针的代码传递一个内置指针,但这段代码中不能使用delete来销毁这个指针所指向的对象我们不能将get返回的指针再绑定到另一个智能指针上。智能指针和异常当发生异常时,普通的指针如果在异常发生之后进行delete操作,那么资源回收操作可能会被中断,而智能指针不会void f() { shared_ptr<int> sp(new int(24)); //即使后面发生异常,sp指针在函数结束时计数变为0,对象被释放 } void f() { int* p = new int(24); //这里发生异常的话,后面的delete 不会被执行,可能发生内存泄漏 delete p; }有些资源由于提供的是c函数级别的接口,因此需要手动进行释放,就会存在与动态内存一样的问题,忘记释放资源。这种情况我们也可以使用智能指针的技术来自动管理资源。例如我们的socket程序,在最后需要调用shutdown 和 closesocket来关闭void clear_socket(socket* sk) { shutdown(*sk); closesocket(*sk); } socket s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); shared_ptr<socket> ps(&s, clear_socket); //链接服务器 //程序推出后会自动调用clear_socket 来释放socket资源智能指针可以提供对动态分配的内存安全而有方便的管理,但是这建立在正确使用的前提下。为了方便的使用智能指针,我们必须坚持一些基本原则:不使用相同的内置指针初始化多个智能指针不delete get函数返回的指针不使用get初始化或者reset另一个指针指针如果使用get返回的指针,记住当最后一个对应的智能指针被销毁后,你的指针就变为无效了如果使用智能指针管理的资源不是new分配的,记住传递给它一个删除器unique_ptrunique_ptr 拥有它所指向的对象。某一个时刻只能有一个 unique_ptr 指向一个给定的对象。当unique_ptr 被销毁时,它所指向的对象也会被销毁unique_ptr 不支持拷贝操作,没有类似 make_shared 的操作。unique_ptr<string> p1(new string("hello world")); unique_ptr<string> p2(p1); //错误:unique_ptr 不支持拷贝 unique_ptr<string> p3; p3 = p1; //错误:unique_ptr 不支持赋值虽然不能拷贝和赋值unique_ptr ,但是可以调用release或者reset将指针的所有权从一个(非const)unique_ptr 转移给另一个unique_ptrreset 成员接受一个可选的指针参数,令unique_ptr 重新指向给定的指针。如果unique_ptr 不为空,它原来指向的对象被释放。release会切断unique_ptr 和它原来管理的对象间的联系。release返回的指针通常被用来初始化另一个智能指针或者给另一个智能指针赋值,如果我们不用另一个智能指针保存release返回的指针,则需要手工释放指针指向的资源p2.release(); //错误,p2指向的资源不会正常释放 auto p = p2.release(); delete p;不能拷贝unique_ptr 的规则又一个例外: 我们可以拷贝或者赋值一个将要被销毁的unique_ptr。最常见的例子是从函数返回一个unique_ptrunique_ptr<int> clone(int p){ return unique_ptr<int>(new int(p)); }还可以返回一个局部对象的拷贝:unique_ptr<int> clone(int p) { unique_ptr<int> ret(unique_ptr<int>(p)); return ret; }类似于shared_ptr, unique_ptr 默认情况下使用delete 释放它指向的对象。我们也可以重载一个unique_ptr 中默认的删除器。但是unique_ptr 管理删除器的方式与shared_ptr 不同。重载一个unique_ptr 中删除器会影响到unique_ptr 类型以及如何构造该类型的对象。与与重载关联容器的比较运算相似,我们必须在尖括号中unique_ptr 指向类型之后提供删除容器类型。在创建或者reset 一个这种unique_ptr 类型的对象时,必须提供一个指定类型的可调用对象weak_ptrweak_ptr 是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr 管理的对象。将一个shared_ptr绑定到一个 weak_ptr。不会改变shared_ptr 的引用计数。一旦最后一个指向对象的shared_ptr 被销毁,对象就会被释放。即使有weak_ptr 指向对象,对象还是会被销毁由于对象可能不存在,所以不能直接使用weak_ptr 来访问对象,需要先调用lock函数,此函数检查weak_ptr 指向的对象是否仍然存在。如果存在,lock返回一个指向共享对象的shared_ptr。只要此shared_ptr 存在,它所指向的对象也会一直存在.if(shared_ptr<int> np = wp.lock()) { //在if中np 和wp 共享对象 }动态数组new 和数组标准库提供了一个可以管理new 分配的数组的unique_ptr 版本,为了用一个unique_ptr 管理动态数组,我们必须在对象类型后面跟一对方括号:unique_ptr<int[]> up(new int[10]); up.release(); //自动用delete[] 销毁其指针shared_ptr 不直接支持管理动态数组。如果希望使用shared_ptr 管理动态数组,必须提供自己定义的删除器:shared_ptr<int> sp(new int[10], [](int* p){delete[] p;}); sp.reset();shared_ptr 未定义下标运算符,因此我们通过shared_ptr 访问动态数组时需要使用get获取到内置指针,然后用它来访问数组元素** allocator 类当分配一块大内存时,我们通常计划在这块内存上按需求构造对象,这种情况下使用new分配时会立即执行对象的构造操作,会造成一定的开销string *const p = new string[n]; //构造n个空白的string delete[] p;上述代码在new 的同时已经调用了n次string 的构造函数。但是我们可能不需要n个string对象,少量的即可满足。 这样我们就可能创建一些永远也用不到的对象。而且对于那些要使用的对象,我们也在初始化之后立即赋予了它们新值,每个被使用的元素被赋值了两次,第一次是在默认初始化的时候,第二次是在赋值时。标准库中定义了allocator 类可以帮助我们将内存分配和对象构造分离开来。allocator<string> alloc;//可以分配string的allocator对象 auto const p = alloc.allocate(n); //分配n个未初始化的stringallocator 分配的内存是未构造的。我们按照需要在此内存中构造对象。成员函数construct接受一个指向将要被构造的内存的指针,同时可以接受额外参数作为构造对象时的参数。auto q = p; //q 指向下一个将要被构造的位置 alloc.construct(q++); //构造一个空字符串 alloc.construct(q++, 10, 'c'); //构造一个'cccccccccc'的字符串 alloc.construct(q++, "hi"); //构造一个 "hi" 字符串当我们使用完对象后必须调用destroy 来销毁它们while(q != p) { alloc.destroy(--q); }这里要注意我们只能对真正构造了的元素进行destroy操作destroy之后这些内存并没有完全交换给系统,还需要调用deallocate 来完成alloc.deallocate();
2021年05月31日
7 阅读
0 评论
0 点赞
2021-05-16
关联容器
之前介绍过标准库中的顺序容器,顺序容器是元素在内存中按照一定顺序进行排列的,都是按线性结构进行排列。除了顺序容器外,c++中还有关联容器。与顺序容器不同的是,关联容器中元素是按照关键字来保存和访问的。与之相对的顺序容器是按它们在容器中的位置来顺序的保存和访问的。关联容器支持高效的查找和访问。两个主要的关联容器类型是map和set。标准库提供8种关联容器,这8个容器见的不同体现在3个维度或者是一个set,或者是一个map或者要求不重复的关键字,或者允许重复关键字按顺序保存元素或者无序保存允许重复关键字的容器都包含单词 multi ,不保持关键字按顺序存储的容器的名字都以单词unordered 开头这8中容器分别是 map、set、multimap、multiset、unordered_map、unordered_set、unordered_multimap、unordered_multiset关联容器概述关联容器不支持顺序容器中的位置相关操作,例如 push_back、push_front。原因是关联容器是按照关键字存储的,这些操作对关联容器没有意义对于map、multimap、set、multiset 关键字类型必须定义元素的比较方法。默认情况下标准库使用关键字类型的< 运算符来比较两个关键字在介绍关联容器的操作之前,我们需要了解名为pair的标准库类型。一个pair保存两个数据成员。类似容器,pair是一个用来生成特定类型的模板。当创建一个pair时,需要提供两个类型名,pair的数据成员将具有对应的类型。两个类型不要求一样pair<string, string> anon; pair<string, size_t> word_count; pair<string, vector<int>> line;初始化时,会调用类型的默认构造函数进行初始化,也可以为每个成员提供初始化器:pair<string, string> author{"James", "Joyce"};pair 的数据成员是public的,两个成员分别命名为first和second。我们用普通的成员访问符号来访问它们。关联容器的操作关联容器定义了额外的类型别名key_type: 此容器类型的关键字类型mapped_type: 每个关键字关联的类型:只适用与mapvalue_type: 对于set,与key_value 相同;对于map,为 pair<const key_type, mapped_type>set<string>::value_type v1; //v1 是一个string set<string>::key_value v2; //v2 是一个string map<string, int>::value_type v3; //v3 是一个pair<const string, int> map<string, int>::key_type v4; //v4 是一个string map<string, int>::mapped_type v5; //v5 是一个int我们使用作用域运算符来提取一个类型的成员。当解引用一个关联容器的迭代器的时候,会得到一个类型为容器的value_type 的值的引用。对map而言,value_type 是一个pair 类型,其first成员保存const的关键字,second成员保存值auto map_it = word_count.begin(); cout << map_it->first; cout << " " << map_it->second; map_it->first = "new key"; //错误 first是const类型 ++map_it->second;set 的迭代器是const的。关联容器中键值是无法通过迭代器进行修改的。只能通过迭代器读,每次修改键值都会导致容器中元素的重新排序。因此不允许通过迭代修改键值我们通常不能对关联容器使用泛型算法。关键字是const这一特性意味着不能将关联容器传递给修改或者重排容器元素的算法。关联容器可以使用只读取元素的算法。但是很多这类算法都要搜索序列。由于关联容器中的元素不能通过它们的关键字进行快速查找,因此对其使用泛型算法几乎总是一个坏主意关联容器中有一个find的成员,我们可以使用find算法来根据关键字查找元素。关联容器的insert成员可以向容器中添加一个元素或者元素范围。因为set和map无法包含关键字重复的元素,因此插入已存在的元素对容器没有任何影响vector<int> ivec = {2, 4, 6, 8, 2, 4, 6, 8}; //ivec 有8个元素 set<int> set2; set2.insert(ivec.cbegin(), ivec.cend()); //set2 现在有4个元素 set2.insert({1, 3, 5, 7, 1, 3, 5, 7}); //set2 现在有8个元素对一个map执行insert操作时,需要记住元素类型是pair。通常在insert的参数列表中创建一个pair//向word_count 插入word的4种方法 word_count.insert({word, 1}); word_count.insert(make_pair(word, 1)); word_count.insert(pair<string, size_t)>(word, 1)); word_count.insert(map<string, size_t>::value_type(word, 1));insert 的返回值依赖于容器类型和参数。对于不包含重复关键字的容器,添加单一元素的insert和emplace版本返回一个pair,告诉我们插入是否成功。pair的first成员是一个迭代器,指向具有给定关键字的元素;second成员是一个bool值,指出元素是插入成功还是已经存在于容器中。如果关键字已经存在于容器中,则insert什么也不做,且返回值中的bool部分为false。如果关键字不存在,元素被插入容器,且bool值为true对于允许重复关键字的容器,接受单个元素的insert操作返回一个指向新元素的迭代器。这里无须返回一个bool值,因为insert总是向这类容器中加入一个新元素关联容器定义了三个版本的erase。可以通过传入一个迭代器或者一个迭代器对来删除一个元素或者一个元素范围。如果指定的元素被删除,函数返回void关联容器提供一个额外的erase函数,它允许传入一个key_type 参数,删除所有匹配给定关键字的元素,返回实际删除元素的数量。对于map和unordered_map 容器提供了下标运算,下标中填入key_type的值,得到value_type,如果关键字不在map中,会为它创建一个元素并插入map中。使用容器的find访问关联容器,传入key_type,如果能找到对应值,返回一个指向对应元素的迭代器,否则返回一个指向容器end()位置的迭代器的使用容器的count方法,传入key_type,返回容器中相同关键字元素的个数对于map使用find代替下标操作,使用下标如果未找到对应关键字则会插入一个新的元素,这样会产生未知行为。如果我们只想知道一个给定关键字是否在map中,而不想改变map,这种情况下应该使用find。对于不允许存储重复关键字的关联容器来说,通过关键字查找元素只会找到一个,而对于允许重复关键字的关联容器来说,会返回第一个元素的迭代器,而相同关键字的元素会相邻存储。在遍历所有相同关键字的元素时,可以首先使用find找到第一个元素的迭代器,然后使用count找到公有多少个元素。最后循环递增迭代器即可访问到所有相同关键字的元素string search_item("Alain de Botton"); auto enteris = authors.count(search_item); auto iter = authors.find(search_item); while(enteris) { cout << iter->second << endl; ++iter; --enteris; }除了上述方法,还可以使用 lower_bound和 upper_bound;lower_bound 指向第一个对应的元素,upper_bound 指向匹配的最后一个元素的下一个位置。如果未找到对应元素,则二者指向同一个迭代器,指向一个不影响排序的关键字插入位置for(auto beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item); beg != end; ++beg) { cout << beg->second << endl; }解决此问题的最后一个方法是直接使用容器的equal_range函数。该函数返回一个pair,保存的是两个迭代器。指向的位置与 lower_bound 和 upper_bound 相同解决此问题的最后一个方法是直接使用容器的equal_range函数。该函数返回一个pair,保存的是两个迭代器。指向的位置与 lower_bound 和 upper_bound 相同for(auto pos = authors.equal_range(search_item); pos.first != pos.end; ++pos.first) { cout << pos.first->second << endl; }无序容器新标准中定义了4种无序关联容器,这些容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的 == 运算符。在关键字类型的元素没有明显的序关系时,无序容器是非常有用的。对于自定义类型的关键字,无法直接在无序容器中使用,还需要提供该类型的hash操作。
2021年05月16日
6 阅读
0 评论
0 点赞
2021-05-10
泛型算法
好久没有更新博客了,最近一直想把我以前的老笔记本换成 Arch + dwm 的样式来使用。现在基本已经弄完了。后面会考虑将我的心得发出来。从0开始一点点的增加自己需要的功能确实很繁琐但是也挺有趣的。闲话就到这里,这篇文章继续记录我学习c++ 11的内容。这篇主要是泛型算法相关的内容标准容器自身提供的操作少之又少,在多数情况下可能希望对容器进行其他操作,例如排序、删除指定元素等等。标准库容器中并未针对每个容器都定义成员函数来实现这些操作,而是定义了一组泛型算法,它们实现了一组经典算法的公共接口,可以使用于不同类型的元素和多种容器类型。也就是相同一组算法可以处理多种容器类型概述之所以是泛型的,主要是这些通用算法不依赖于具体的容器类型,所有相同算法采用相同的接口迭代器的存在使得算法不依赖于具体的容器类型,但是算法依赖于元素类型的相关操作,例如我们可以简单的使用下面的代码来说明这个bool find(beg, end, val) { for(auto iter = beg; iter != end; ++iter) { if(*iter == val) { return true; } } return false }上述代码并不限定于只能使用某种类型的容器,只要容器中迭代器支持递增操作,并且元素本身支持比较运算即可。泛型算法本身不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作,最多也就只会修改迭代器所指向的元素的值。对容器自身没有影响。算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器中移动元素。但是永远不会直接添加或者删除元素(当然插入迭代器例外)初识泛型算法除了极少数例外,标准库算法都是对一个范围内的元素进行操作。我们将此元素范围称之为输入范围,接受输入范围的算法总是使用前两个参数来表示此范围。两个参数分别是指向要处理的第一个元素和尾元素之后位置的迭代器。理解基本算法的方法就是了解它们是否读取元素、改变元素或者重新排列元素顺序只读算法一些算法只会读取输入范围内的元素,而从不改变元素。find就是这样一个算法。一些常见的只读算法如下:find:查找容器中出现某个元素的位置,需要容器中元素类型实现 == 操作count: 返回容器中出现某个元素的次数,同样需要容器中元素类型实现 == 操作accumulate: 计算某个迭代器范围内,所有元素的和,需要容器中元素类型实现 + 操作equal: 比较两个序列中元素值是否完全相同,它接受三个参数,前两个表示一个容器的迭代器范围。最后一个参数代表第二个容器的起始位置一般来说对于只读取而不改变元素的算法,通常最好使用cbegin和cend 获取const版本的迭代器。那些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。写容器元素的算法这类算法需要确保,容器原大小不能小于我们要求算法写入的元素数目。由于算法不会执行容器操作,因此它们自己不可能改变容器大小一种保证算法有足够元素空间来容纳输出数据的方式是使用插入迭代器,插入迭代器是一种向容器中添加元素的迭代器拷贝算法接受3个迭代器,前两个表示一个源容器的范围,第三个迭代器是目的容器的起始位置的迭代器。同样的源容器的长度不能超过目的容器的长度定制操作很多算法都会比较输入序列中的元素,默认情况下,这类算法使用元素类型的< 或者 == 运算符来完成比较操作。标准库还为这些算法定义了额外的版本,允许我们提供自已定义的操作来代替默认运算符。例如sort 算法默认使用元素类型的 < 运算符,但是可以使用sort的重载版本,额外定义比较的规则向算法传递参数标准库中可以接受的比较函数一般返回一个bool值,表示是否小于或者是否相等。函数接受一个参数或者两个参数。在c++新标准中将这个函数叫做谓词,接受一个参数的函数被成为一元谓词,接受两个参数的函数叫做二元谓词。vector<string> words; //初始化 words //...... bool isShorter(const string& s1, const string& s2) { return s1.size() < s2.size(); } sort(words.cbegin(), words.cend(), isShorter);lambda 表达式在介绍lambda 表达式之前,需要先介绍可调用对象这个概念可调用对象:对于一个对象或者一个表达式,如果可以对其使用调用运算符,则称它是可调用的;例如,e是一个可调用对象,则我们可以编写代码e(args) ,其中args是一个逗号分割的一个或者多个参数的列表到目前为止,我们只接触了函数和函数指针这两类可调用对象,还有其他两种可调用对象:重载了函数调用运算符的类,以及lambda表达式。一个lambda 表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数,定义形式如下capture list -> return type {function body}capture list 捕获列表,是一个lambda 所在函数中定义的局部变量的列表。parameter list 函数的参数列表return type 是函数返回值类型function body 是函数体,需要执行的具体代码段与普通函数不同的是 lambda 必须使用尾置返回来指定返回类型我们可以忽略参数列表和返回值,但是必须包含捕获列表和函数体auto f = [] {return 42;};如果lambda 表达式中没有明确指定返回类型,函数体中包含任何单一 return 语句之外的内容,则返回voidlambda 的调用方式和普通函数的调用方式一样,都是调用运算符cout << f() << endl;lambda 表达式不能有默认参数[] (const string& str1, const string& s2) { return s1.size() < s2.size(); }; vector<string> words; stable_sort(words.begin(), words.end(), [](const string& s1, const string& s2){ return s1.size() < s2.size(); });lambda 表达式一般出现在一个函数中,使用其局部变量,但是它只能访问那些明确指明的变量。一个lambda通过将局部变量包含在其捕获列表中来指明将会使用这些变量。捕获列表指引lambda 在其内部包含访问局部变量所需的信息[sz](const string& s){ return a.size() >= sz; }lambda 捕获和返回与参数传递类似,变量捕获的方式可以是值或者引用。void func1() { size_t v1= 42; auto f = [v1]{return v1;}; v1 = 0; auto j = f(); //j 是42, 因为在定义lambda的时候传入的是v1的拷贝,后续v1 的改变不影响捕获中v1 的值 }被捕获变量的值是在lambda创建时拷贝,因此随后对其修改不会影响到lambda内对应的值void func2() { size_t v1 = 42; auto f = [&v1](){return v1;}; v1 = 0; auto j = f(); //j 是0,f保存v1的引用,而非拷贝 }引用捕获与返回引用有着相同的限制,必须保证调用在调用lambda表达式时,是存在的。捕获的都是函数的局部变量,如果lambda 在函数结束之后执行,捕获的引用指向的局部变量已经消失。可以在函数中返回一个lambda表达式,此时返回的lambda 中不应该包含引用捕获使用引用捕获的时候需要注意,在一次或者多次调用lambda表达式的时候应该保证引用的对象仍然有效,同时需要保证对象的值是我们所期待的。因此在使用lambda的时候尽量减少捕获变量的数量,同时尽量不使用引用捕获除了显式列出我们希望使用的所来自所在函数的变量外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。为了指示编译器推断捕获列表,应在捕获列表中写一个&或者=,表示采用引用捕获或者值捕获我们也可以混合使用隐式捕获和显式捕获,混合使用时,捕获列表的第一个元素必须是一个&或者=。当混合使用了显式捕获和隐式捕获时。显式捕获的变量必须与使用隐式捕获不同的方式。当使用值捕获的时候,默认情况下lambda表达式是不能改变其值的,如果希望改变一个被捕获的变量的值,就必须在参数列表后加上关键字 mutablevoid f3() { size_t v1 = 42; auto f = [v1] ()mutable{return ++v1;}; v1 = 0; auto j = f(); // j = 43 }一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const 类型还是一个非const类型void fnc4() { size_t v1 = 42; auto f2 = [&v1]{return ++v1;}; v1 = 0; auto j = f2(); //j = 1 }// 错误,由于有除return之外的其他语句,因此编译器推断lambda 表达式返回void,但是返回了具体值 transform(v1.begin(), v1.end(), vi.begin(), [](int i){ if(i < 0) return -i; else return i; }); //正确,只有return语句,编译器可以推断出返回int类型 transform(v1.begin(), v1.end(), vi.begin(), [](int i){ return (i < 0)? -i : i; }); //正确,明确指定了返回int类型 transform(v1.begin(), v1.end(), vi.begin(), [](int i)->int{ if(i < 0) return -i; else return i; });参数绑定lambda 表达式适用于只在一两个地方使用的简单操作,如果多个地方都需要使用到相同的操作,就需要写上相同的lambda表达式。这个时候最好的办法是定义一个函数。在需要进行捕获的情况下使用函数就不是那么容易了。例如有的泛型算法只传递一个参数,但是我们在函数中需要两个参数。这种情况下就需要用到参数绑定标准库中定义了一个bind函数。可以将bind作为一个函数适配器。它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表auto newCaller = bind(callable, arg_list);其中 callable 是一个可调用对象,返回的newCaller 是一个新的可调用对象,而arg_list 中的参数可能包含形如 _n 的名字,其中n是一个整数。这些参数是“占位符”。表示 newCaller 的参数。它们占据了传递给newCaller的参数位置。数值n表示生成的可调用对象中参数的位置。_1为newCaller的第一个参数,_2 为第二个参数。以此类推auto wc = find_if(words.begin(), words.end(), bind(check_size, _1, sz));此时调用生成一个可调用对象,将check_size 的第二个参数绑定到sz的值,当find_if 对words中的string调用这个对象的时候,这些对象会调用check_size 将给定的string 和 sz 传递给它,因此 find_if 可以有效的对输入序列中的每一个string调用check_size 实现string与 sz的比较_n 都定义在一个名为 placeholders 的命名空间中,而这个命名空间本身定义在std命名空间中。每次在使用_n 这样的名字时,都需要声明这个命名空间。using std::placeholders::_1; using std::placeholders::_2;每个占位符都必须提供单独的using声明,这样比较麻烦。可以使用另一种不同形式的 using 语句using namespace std::placeholders;我们可以使用bind 给可调用对象中参数重新排序,例如f是一个可调用对象,它有5个参数auto g = bind(f, a, b, _2, c, _1);生成的新的可调用对象g接受两个参数,分别是 _2, _1。在调用g时相当于void g(_1, _2) { f(a, b, _2, c, _1); }当我们执行 g(x, y) 最终会执行 f(a, b, y, c, x)在执行时会将 bind 中传入的参数拷贝到原来的函数参数中,如果想向原函数传递引用,可以使用标准库中的 ref函数auto g = bind(f, ref(a), b, _2, c, _1)上述代码中,在执行g的时候会向f中拷贝a的引用。_1, _2 本身在传值的时候可以传入引用再谈迭代器除了之前介绍的迭代器,标准库还定义了几种额外的迭代器:插入迭代器:这些迭代器被绑定到一个容器上,可以用来向容器插入元素流迭代器:这些迭代器绑定到流中,可以用来遍历所有关联的IO流反向迭代器:这些迭代器向后而不是向前移动,除了 forward_list 之外的标准库容器都有迭代器移动迭代器:这些专用迭代器不是拷贝其中的元素,而是移动它们。插入迭代器插入迭代器是一种迭代器适配器,它接受一个容器,生成一个迭代器,能实现向给定容器添加元素。插入迭代器有三种类型,差异在于元素插入的位置:back_iterator: 创建一个使用push_back 的迭代器front_iterator: 创建一个使用push_front 的迭代器inserter: 创建一个使用insert 的迭代器iostream 迭代器虽然iostream并不是容器,但是标准库定义了可以用于这些IO类型对象的迭代器。istream_iterator 读取输入流,ostream_iterator 向一个输出流写数据。这些迭代器将它们对应的流当作一个特定类型的元素序列来处理。通过使用流迭代器,我们可以使用泛型算法从流对象读取数据以及向其写入数据。istream_iterator<int> in(cin), eof; accumulate(in, eof, 0); // 从标准输入中读取整数,并计算它们的和 ostream_iterator<int> out(cout); copy(vec.begin(), vec.end(), out); //将vector中的数据拷贝到ostream流中,也就是输出vector 中的元素istream_iterator 允许使用懒惰求值,即只在需要时进行数据读取泛型算法结构任何算法最基本的特性是它要求其迭代器提供哪些操作。算法要求的迭代器操作可以分为5个迭代器类型:输入迭代器:只读不写;单遍扫描,只能递增输出迭代器:只写不读;单遍扫描,只能递增前向迭代器:可读写,多遍扫描,只能递增双向迭代器:可读写,多遍扫描,可递增递减随机访问迭代器:可读写,多变扫描,支持全部迭代器运算5 类迭代器类似容器,迭代器也定义了一组公共操作。一些操作所有迭代器都支持,另外一些只有特定类别的迭代器才支持。输入迭代器可以读取序列中的元素。一个输入迭代器必须支持:用于比较两个迭代器的相等和不想等运算符用于推进迭代器的前置和后置递增运算符用于读取元素的解引用运算符,解引用只会出现在赋值运算符的右侧箭头运算符输出迭代器可以看作是输入迭代器功能上的补集,只写而不读元素,输出迭代器必须支持用于推进迭代器的前置和后置递增运算解引用运算符,只出现在赋值运算符的左侧前向迭代器可以读写元素,这类迭代器只能在序列中沿一个方向移动。前向迭代器支持所有输入和输出迭代器的操作。双向迭代器可以正向/反向读写序列中的元素。除了支持所有前向迭代器的操作之外,双向迭代器还支持前置和后置的递减运算符。随机访问迭代器提供在常量时间内访问序列中任意元素的能力。除了支持双向迭代器的所有功能外,还支持:用于比较两个迭代器相对位置关系的运算符 (<、<=、>和>=)迭代器和一个整数值的加减运算(+、+=、-、-=),计算结果是迭代器在序列中前进或者后退给定整数个元素后的位置用于两个迭代器上的减法运算符,得到两个迭代器的距离下标运算符 iter[n] 与 *(iter[n]) 等价算法形参模式大多数算法具有如下4种形式之一:alg(beg, end, other, args)alg(beg, end, dest, other, args)alg(beg, end, beg2, other, args)alg(beg, end, beg2, end2, other, args)其中alg 是算法名字,beg和 end表示算法所操作的输入范围,几乎所有算法都接受一个输入范围。是否有其他参数依赖于要执行的操作。dest参数表示算法可以写入的目的位置的迭代器。算法假定按其需要写入数据,不管写入多少个元素都是安全的。如果dest是一个直接指向容器的迭代器,那么算法将输出数据写到容器中已经存在的元素内。更常见的情况是,dest被绑定到一个插入迭代器或者是一个ostream_iterator。接受单独的beg2 或者 beg2和end2的算法用这些迭代器表示第二个输入范围,这些算法通常使用第二个范围中的元素与第一个输入范围结合来进行一些运算算法命名规范除了参数规范,算法还遵循一套命名和重载。这些规则处理诸如:如何提供一个操作代替默认的 < 或者 == 运算以及算法是将输出数据写入到一个序列还是一个分离的目的位置等问题接受谓词参数来代替 < 或者== 运算符的算法,以及那些不接受额外参数的算法,通常都是重载的函数。一个版本用元素自身的运算符来比较元素,另一版本接受一个额外的谓词参数来代替 <或者==unique(beg, end); unique(beg, end, comp); //使用comp函数比较元素接受一个元素值的算法通常有另一个不同名版本,该版本接受一个谓词,代替元素值,接受谓词参数的算法都有附加的_if 后缀find(beg, end, val); find_if(beg, end, pred); //pred 是一个函数,查找第一个令pred返回真的元素默认情况下,重排元素的算法将重排后的元素写回给指定的输入序列。这些算法还提供了另一个版本,将元素写到一个指定的输出目的位置。这类算法都在名字后加一个_copyreverse(beg,end); reverse(beg,end,dest); //将元素按逆序拷贝到dest一些算法还提供_copy和_if 版本,这些版本接受一个目的位置迭代器和一个谓词remove_if(v1.beg(), v1.end(), [](int i){return i % 2}); remove_if(v1.beg(), v1.end(), back_inserter(v2), [](int i){return i % 2});特定容器算法与其他容器不同,链表定义了几个成语啊函数形式的算法。,它们定义了独有的sort、merge、remove、reverse和unique。这些链表中定义的算法的性能比通用版本要高的多。与通用版本中的不同,链表中的特有操作会改变容器。
2021年05月10日
5 阅读
0 评论
0 点赞
2021-04-11
顺序容器
所有容器类都共享公共的接口,不同容器按不同的方式进行扩展,这个公共接口使得学习容器更加容器。我们基于这种容器所学习的内容也都适用于其他容器。每种容器都提供了不同的性能和功能权衡一个容器就是一些特定类型对象的集合。顺序容器为程序员提供了控制元素存储顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器的位置相对应。顺序容器概述所谓的顺序容器是指,在内存中数据存储有一定顺序。数据结构中的顺序容器有:可变数组、队列、数组、链表、栈。c++ 标准库中的顺序容器提供了快速顺序访问元素的能力。但是这些容器在一下方面都有不同的性能折中向容器中添加或者删除元素的代价非顺序访问容器中元素的代价标准库中顺序容器主要有:vector:可变大小的数组。支持快速随机访问,在尾部之外插入或者删除元素可能会很慢dque:双端队列,支持快速随机访问,在头尾位置插入/删除元素速度很快list:双向连标,只支持双向顺序访问,在list中任何位置进行插入删除操作速度都很快forward_list: 单向链表,只支持单向顺序访问,在链表任何位置插入删除元素速度很快array: 固定大小的数组,支持快速随机访问,不能添加或者删除元素string: 与vector容器类似,但专门用于保存字符,随机访问快。在尾部插入与删除速度快c++ 标准库中的容器是经过精心优化设计过的。性能通常会是同类数据结构中最好的。现代c++ 程序应该使用标准库容器,而不是原始的数据结构(如内置数组)通常使用vector 是最好的选择,除非你有很好的理由选择其他容器一下是一些选择容器的基本原则:除非你有很好的理由选择其他容器,否则使用vector如果你的程序有很多小的元素,且额外开销很严重,否则不要使用list或者forward_list如果程序要求随机访问元素,应该使用vector 或者deque如果程序要求在容器中间插入或者删除元素,应该使用list或者forward_list如果程序要在头尾位置插入或者删除元素,但是不会在中间位置插入删除元素,则应该使用deque如果程序只有在读取输入时才需要在容器中间插入元素,随后需要随机访问元素,则: 6.1 首先确定是否真的需要在容器中间位置添加元素。当处理输入数据时通常很容易向vector中添加数据,然后再调用标准库的sort函数,来重排元素,避免在中间位置添加元素 6.2 如果必须在中间位置插入元素考虑在输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector中如果你不确定该使用哪种容器,可以在程序中只使用vector 和list公共的操作,不使用下标操作,使用迭代器,避免随机访问容器库概述迭代器迭代器是访问容器中元素的公共接口所有迭代器都通过解引用运算符来实现这个操作。标准库中的所有迭代器都定义了递增运算符,从当前元素移动到下一个元素。部分容器的迭代器也定义了递减运算符,用于从一个元素移动到上一个元素一个迭代器范围是由一对迭代器来表示的。两个迭代器分别指向同一个容器的元素或者尾元素之后的位置。如果两个迭代器满足以下两个条件,则这两个迭代器构成一个迭代器范围:它们指向同一个容器中的元素,或者是容器最后一个元素之后的位置我们可以通过反复递增begin 来到达end位置,换句话说,end不在begin之前如果两个迭代器构成一个迭代器范围,则:如果begin和end相等,则范围为空如果begin和end不等,则范围至少包含一个元素,且begin指向该范围中的第一个元素我们可以对begin递增若干次,使得begin== end容器类型成员每个容器都定义了多个类型,我们已经使用了其中的3种:size_type、iterator、const_iterator除了正向的迭代器,容器库还提供了反向遍历容器的迭代器,反向迭代器就是一种反向遍历容器的迭代器。与正向迭代器相比各种操作的含义也都发生了颠倒。例如对一个反向迭代器执行++操作,会得到上一个元素。剩下的就是类型别名了。通过类型别名,我们可以在不了解容器中元素类型的情况下使用它,如果需要元素类型可以使用容器的value_type ,如果需要元素类型的一个引用可以使用reference或者const_reference。begin 和 end 成员begin 和 end 操作生成一个指向容器中第一个元素和尾元素之后位置的迭代器范围。begin和end有多个版本。带r的版本返回反向迭代器,带c的版本返回const型迭代器容器定义和初始化可以将一个容器初始化为另一个容器的拷贝将一个新容器创建为另一个容器的拷贝的方法有两种:可以直接拷贝整个容器,或者拷贝由一个迭代器对指定的元素范围为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须匹配。当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了。而且新容器和原容器中的元素类型也可以不同,只要能将拷贝的元素转化为要初始化的容器的元素类型即可在新标准中我们对一个容器进行列表初始化标准库array在定义之处就应该给出具体的大小,而且后续不允许修改它的大小我们不能直接对内置数组执行拷贝或者对象赋值操作,但是array对象允许赋值和swap我们直接使用 = 运算符来将一个容器赋值为另一个容器的拷贝,但是要求容器类型完全相同,array也支持这种操作顺序容器(array除外)还定义了一个assign的成员,assign操作用参数所指定的元素替换左边容器中的所有元素list<string> name; vector<const char*> oldstyle; name = oldstyle; //错误 = 要求两侧容器类型完全相同 name.assign(oldstyle);可以使用swap交换两个容器中的内容,要求两个容器类型完全相同。除了array外,swap不对任何元素进行拷贝、删除或者插入操作,可以保证在常量时间内完成容器大小操作每个容器都有三大与大小相关的操作。size: 返回容器中元素数目empty: 当容器中元素数量为0时,返回true,否则返回falsemax_size: 返回一个大于或者等于该类型容器所能容纳的最大元素数的值关系运算符除了无序容器外的所有容器都支持关系运算符关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素比较的过程与比较string大小的过程类似如果两个容器具有相同大小且所有元素都两辆对应相等,则两个容器相等。否则两个容器不等如果两个容器大小不同,但较小容器中每个元素都等于较大容器中对应元素。则较小容器大于较大容器如果两个容器都不是另一个容器的前缀自序列,则它们的比较结果取决于第一个不相等的元素的比较结果容器的相等运算符实际上是使用元素的==运算符实现比较的。而其他关系是使用元素的< 运算符顺序容器的操作向顺序容器中添加元素push_back:将内容追加到容器尾部push_front: 将内容添加到容器的首部insert: 在容器的特定位置插入0个或者多个元素,返回插入元素位置的迭代器emplace_back、emplace_front、emplace: 这些函数是直接在容器内部进行元素构造,而上述函数是将内容进行拷贝。从效率上讲emplace 函数会高一些使用这些操作时必须记得不同类型的容器使用不同的元素分配策略,而这些策略直接影响性能。访问顺序容器每个顺序容器中都有一个front 函数,返回容器内第一个元素的引用。而除了forward_list 之外的所有顺序容器都有一个back成员函数。另外可以使用at来访问随机位置的元素记住,这些访问函数返回的都是引用删除元素pop_front: 删除首元素pop_back: 删除尾元素erase: 可以从容器中删除指定位置的元素,可以传入一个范围,删除指定范围内的元素特殊的 forward_list 操作在对forward_list 进行增删操作的时候,需要找到对应位置的前驱节点,而单向链表无法很容器的找到一个节点的前驱节点。因此对单向链表,提供了类似 insert_after、emplace_after、erase_after等操作改变容器大小可以使用resize 来增大或者缩小容器大小,如果是缩小容器大小,则指向被删除元素的迭代器、引用、指针都会失效容器操作可能使迭代器失效在向容器中添加元素后:如果容器是vector或者string,且存储空间被重新分配,则指向容器的迭代器、指针都会失效。如果存储空间未重新分配,指向插入位置之前的迭代器、指针、引用仍然有效,但是指向插入位置之后元素的迭代器、指针和引用将会失效对于deque,插入到首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效对于list和forward_list,指向容器的迭代器、指针和引用仍然有效删除一个元素后,指向原来被删除元素的迭代器、指针和引用都会失效。对于forward_list 和list来说,指向容器其他位置的迭代器、引用和指针仍然有效对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素以外的其他元素的迭代器、引用和指针也会失效。如果是删除deque的尾元素,则尾后迭代器也会失效。但是其他迭代器、引用和指针不受影响,如果删除首元素,这些也不会受到影响对于vector和string,指向被删除元素之前元素的迭代器、引用和指针仍然有效删除元素时尾后迭代器总是会失效使用insert插入元素后可以保存返回的迭代器,然后用该迭代器进行迭代可以保证迭代器有效不要保存end返回的迭代器vector 容器是如何增长的为了支持快速随机访问,vector 将元素连续存储。如果往容器中添加一个新元素时,发现容器空间已经不够了,就需要重新分配空间。并将已有元素逐一拷贝到新的内存空间中,然后添加新元素。为了避免这种代价,标准库实现者采用了可以减少容器空间重新分配次数的策略。当不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间需求更大的内存空间vector和string也提供了一些成员函数,允许我们与它的实现中内存分配部分互动。capacity: 告诉我们容器在不扩张内存空间的情况下可以容纳多少个元素reserve: 允许我们同志容器它应该准备保存多少个元素一般来讲,vector 的实现采用的策略似乎是在每次需要分配新内容空间时将当前容量翻倍额外的string操作除了顺序容器共同的操作之外,string类还提供了一些额外的操作。这些操作中的大部分要么是提供string类和C风格字符串之间的互相转换,要么是增加了允许我们用下标代替迭代器的版本。substr: 返回一个string,它是原始string的一部分或者全部的拷贝可以使用insert、erase、assign 来改变字符串的内容append可以在字符串尾部添加一个新字符串; replace 进行查找替换搜索操作标准库中提供了6个不同的搜索函数,每个函数有4中不同形式的重载版本。每个搜索操作都返回string::size_type 值,表示匹配发生位置的下标。如果搜索失败返回一个名为string::npos 的static成员s.find(arg): 查找字符串中第一次出现某个字符串的位置s.rfind(arg): 查找字符串中最后一次出现某个字符串的位置s.find_first_of(arg): 在s中查找arg中任意一个字符第一次出现的位置s.find_last_of(arg): 在s中查找arg中任意一个字符最后一次出现的位置s.find_first_not_of(arg): 在s中查找第一个不在arg 中的字符s.find_last_not_of(arg): 在s中查找最后一个不在arg 中的字符compare 函数compare函数用于比较两个大小字符串,与C标准库中的strcmp类似数值转化to_string: 将数值数据转化为字符串stod: 将字符串转化为doublestof: 将字符串转化为floatstoi: 将字符串转化为intstol: 将字符串转化为longstoul: 将字符串转化为 unsigned longstoll: 将字符串转化为 long longstoull: 将字符串转化为 unsigned long longstold: 将字符串转化为 long double容器适配器适配器是标准库提供的一组概念,能使某种事物的行为看起来像另一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像另一种事物一样标准库提供了三种适配器: stack、queue、priority_queue(优先级队列)所有的适配器都要求容器具有添加和删除元素的能力。stack 只要求类型容器具有 push_back、pop_back 操作因此可以使用除了array 和 forward_list 之外的任何容器类型来进行构造queue 要求容器类型具有 back、push_back、front、pop_back 因此它可以构造在list 或者deque之上但不能基于vector 构造; priority_queue 要求容器类型具有push_back、front、pop_back之外还要求容器具有随机访问的能力,所以它必须构造在vector之上
2021年04月11日
4 阅读
0 评论
0 点赞
2021-04-04
IO库
c++ 语言中不直接处理输入和输出,而是通过一族定义在标准库中的类型来处理IO,这些类型支持从设备读取数据、向设备写入数据的IO操作。设备可以是文件、控制台窗口等。还有一些IO运行内存IO,即可以从string中读写数据。IO库IO类最开始接触的c++ 中的io是我们从控制台接受输入的istream和输出到控制台中的ostream。除了基本的istream和ostream以外标准库中还定义了如下的基本类型iostream 用于读写流的基本类型istream、wistream: 从流读取数据ostream、wostream: 向流写入数据iostream、wiostream:从流中读写数据fstream 定义了读写命名文件的类型ifstream、wifstream: 从文件中读写数据ofstream、wofstream: 向文件中写入数据fstream、wfstream: 读写文件sstream 定义了读写内存string对象的类型istringstream、iwstringstream: 从string中读取数据ostringstream、owstringstream: 向string中写入数据stringstream、wstringstream: 读写string其中带w的都是款字节版本无法对io对象进行拷贝或者赋值、因此在函数中无法返回IO类型也无法传递IO类型,只能使用IO类型的引用读写一个IO对象会改变其状态,所以在函数中传递和返回IO的对象不能是const的一个流如果发生错误,其上后续的IO操作都会失败。只有当一个IO流处于无错误状态时,我们才能从它读取数据。因此代码通常应该在使用一个流之前检查它是否处于良好状态,确定一个流对象的状态最简单的方式是将它作为一个条件来使用。作为条件使用只能告诉我们流是否有效,而无法告诉我们具体发生了什么。IO库定义了一组与机器无关的iostate类型,这个类型中使用二进制位来表示每种状态。目前定义了4种错误类型:badbit: 流崩溃failbit: IO操作失败eofbit: 流到达了文件结束位置goodbit: 流未处于错误状态在实际使用时可以将具体值与这些预定义的值做位与运算,得到具体的原因流对象的rstate 成员返回当前流的状态,setstate用来设置流状态。clear不带参用来清理所有错误标志位。clear的带参版本接受一个iostate值,表示流的新状态io操作比较耗时,所以操作系统为了效率会提供缓冲机制。输入输出并不是立即执行的,操作系统提供了一个缓冲区,在适当的实际会使用缓冲区的数据,统一执行输入输出操作。导致刷新的原因有很多:程序正常结束,在main函数执行return时,会进行刷新操作缓冲区满时,会进行换新操作程序中使用操作符例如endl来显式的刷新缓冲区输出操作结束后,使用操作符unitbuf 设置流的内部状态来清空缓冲区,默认情况下cerr 是设置了unitbuf 的,因此cerr的内容都是实时刷新的一个输出流被关联到另一个输出流。当读写被关联到另一个流时,关联到的流的缓冲区会被刷新除了使用endl、flush、ends 都可以来刷新缓冲区。endl在刷新的同时会插入换行符,flush则不添加任何字符,ends会添加一个空字符如果想在每次输出后都刷新缓冲区,可以使用unitbuf 操作符,它告诉流,每次执行写操作之后都进行一个flush操作cout << unitbuf; cout << nounitbuf;如果程序崩溃,缓冲区是不会被刷新的标准库是将cin和cout关联到一起了,所以每次执行cin都会导致cout的缓冲区被刷新可以使用tie 方法将自身关联到另一个流上。tie 带参数的版本,需要传入一个指向ostream 的指针,将自己关联到这个ostream中tie 不带参数的版本用来查询自身关联到了哪个输出流上,返回对应输出流的指针,如果未被关联,则返回空指针每个输入流最多只能关联到一个输出流,但是多个输入流可以关联到同一个ostream文件IO当我们要读写一个文件时可以使用文件流对象ifstream in(ifile); //传入文件名,构造一个ifstream 并打开文件 ofstream out; //定义一个文件输出流,这个流不关联到任何文件当我们定义了空的文件流对象后可以使用open函数将对象和文件关联起来。可以手动调用close函数关闭文件。也可以在fstream对象被销毁时由它的构造函数自动调用close每个流都有一个关联的文件模式,用来指出该如何使用文件in: 以读的方式打开out: 以写的方式打开app: 每次写操作前均定位到文件尾部ate: 每次打开文件后立即定位到文件尾部trunc: 截断文件binary:以二进制的形式打开文件string 流当我们的某些工作是对文本进行处理,而其他一些工作是处理行内的单词时通常可以使用 istringstream 即要在一行字符串中取出单个单词时可以使用字符串流
2021年04月04日
6 阅读
0 评论
0 点赞
1
...
12
13
14
...
32