每天坐上地铁就想“辞职”,但是......
其实这是一篇cpp自我进阶介绍~~

一、从最远郊区到市中心

每天早上起床的那一刻、踏上地铁的那一刻我都会怀疑,我是脑子被驴踢了,从最远郊区换到市中心去上班。过去一年,几乎每天睡到自然醒,每天晚上都是以做完自己喜欢做的事情为结尾,充实而又满足,然后这所有的一切都在这次换工作之后戛然而止。第一天,踏上几乎一年没有坐过的地铁,不仅仅连座位都没有了,甚至连角落能够倚靠的地方也都没有,我是崩溃的,心目中闪过一个念头,可能这就是舍近求远的傻子吧。
曾经也是经历过这么远的地铁,但是地铁上看了不少书,计算机原理相关,操作系统相关,软件工程相关,经济入门书籍,人生哲理相关,正是这些书,让我从mcu转到linux开发储备了足够多的知识和基础,所以几乎毫无障碍的可以一手mcu开发、一手linux开发;甚至对我在nio独立完成较大项目、带着team一起完成项目都是有及其大的帮助。过去一年,由于工作原因,看过的书实在太少了。所以换工作时,看到这么远的地铁,我是兴奋的,想着终于可以有时间看书,系统的学习一下了。
过去了一个半月,虽然每天依然在后悔,甚至每天下班时想多做一会想做的事情,都不得不考虑通勤,早早下班去了;但是过去一个半月我看完了三本书(一本修养书,两本c++书)了,从最近从0到1完成了一个纯c++项目,对于个人提升来说,感觉好像也没那么坏。

二、虽然c++经验不多,但是我c++基础很好???

不管是在面试时,还是在与别人交流时,我会自卑又自信的说道,我c++量产经验很少,最多不到500行,但是我基础感觉应该蛮好的,大学参加ACM一直用的就是c++,不信你问问吧。
坦白来说,个人觉得自己编程思路,对c/c++这种需要较强计算机基础的编程语言来说,我不太怀疑自己的水平。毕竟,一直给别人吹嘘,我能用c语言的指针能耍点小技巧,用c实现简易的泛型编程也不话下,面向对象的思维方式,模块划分和设计,在独立完成几万行的代码下自然也不会太差。(使用纯c为了能够开发跨mcu和soc的通用程序)。但是没有从0到1用纯c++完成一个项目,多少还是比较自卑的。
但是过去一段时间,2-3000行左右的纯c++终于完成了第一个版本,项目不算多复杂,但是感觉也覆盖c++的大部分常用语法和注意事项了。
早上系统窥探c++;白天实践踩坑;白天+晚上虐候选人和被候选人虐,再度加深印象(社招、校招面试过程中,不得不感叹幸好毕业早几年,现在的应届生太残暴了)。感觉自己又“精通c++”(据说c++工程师一生会经历多次精通c++,直到只敢说,我用过c++)了,啊哈哈~~~

三、资深c工程师如何快速上手c++

好吧,我承认,我必须自信的认为自己是一个资深c工程师,虽然写的代码几乎不会超过c99的规范。
不过如何吧,还是想说,其实真正想要用纯c++来开发,对于一个不太差(就看自我定义了)的c工程师来说,感觉其实并不是那么难(之前我一直没找对方法,迷茫、无从下手、东一榔头西一棒槌)。从现在来看,自己应该算是入门了纯c++,分享我是如何从c入门纯c++开发了(or c with class?),也当对过去学习的总结。个人觉得掌握如下c++知识,肯定可以很轻松的上手c++开发了。
c++11以后版本。
  1. STL是神器

作为c工程师来说,一切都是原始,所以你一定造过各种基础轮子,最简单的如链表、队列,稍微复杂点就是类似键值对容器,还有各种算法,如排序、查找,拷贝等。这些在几乎都有现成的。

容器

    • vector
  告别数组吧,我求求你了。
  指针与vector依然可以很好搭配,犹如指针和数组一样。

  但是你需要注意哦,vector自动增长空间成本很高,因为它重新分配内存,并且把原来的东西全部拷贝过来。有没有发现坑?有没有???对,如果你暂存了vector增长前的内存地址,增长后,那个地址也就是野地址,以下代码自己体会。

std::vector<int> vector_test;
vector_test.push_back(1);
int *test_int = &vector_test.data();
vector_test.push_back(2);
vector_test.push_back(3);
std::cout << test_int << std::endl;

 

啊哈哈~~~,所以reserve是个好东西,不仅可以解决性能问题,还可以让你不coredump哦。

    • map
             键值对,从此让告别使用struct套struct来造轮子。
    • queue/deque
  生产者-消费者模型,简直不要太爽。
    • list
  你不会那么恨链表了。
    • string
  求求你别再用char *了。搭配std::to_string使用,打印字节流从此不需要一行只打印一个字节(一般来说,日志库都会自动换行)。
容器配合STL上面的算法来一起使用,比如查找,替换,排序,拷贝等,基本都提供了你日常所需的大部分操作了。STL还提供了迭代器,本质上就是指针,所以如果你用习惯了指针,,其实一个样;还有仿函数、适配器等,这些我自己只用到了一点点。如果想从c转到c++,上述数据结构一定需要经常使用,放弃自己造轮子的想法吧。针对容器的使用,其实我不建议搜索别人的总结,直接去en.cppreference.com搜索吧,再不济你在百度或者谷歌上直接搜索如std::vector,去官网直接查询用法;然后自己找个角落里实践一下,几乎没有任何上手难度!!!!
  1. 作用域

首先,杜绝使用using namespace xxx。想必使用c开发过稍大软件的你,一会觉得词穷命名变量、函数,一个关键原因就是作用域。
所以c++有命名空间--namespace,这个一定要用好。其实这个特别简单,随便找个略微知名的c++开源软件,看看人家的头文件使用,就几乎没啥问题。
虽然不建议使用匿名空间,但是你用了之后,会真的体会到,有namespace太爽了。
当然,c++的作用另一大块就是类了,这个没啥好说的,没写过c++的人都能给你说一大堆。

有限作用域枚举

第一次看到,如下代码,我当时觉得懵逼,,,直到查了作用,又想起了被多处unknown枚举支配的恐惧,才意识到,我要写c++,再也不想写c了。加上class修饰后,除非你非得使用犯贱的using namespace 直到枚举类,你再也不用担心你的枚举会冲突,你只能使用类似于TestType::kItem1的形式来用了。至于c++的枚举新特性,这里就不再展开了。
enum class TestType {
    kItem1,
    kItem2
}

 

  1. std::bind, std::function, using

想继续玩的函数指针,必须离不开这货(std::bind换成lambda更好)。将成员做当“函数指针”使用时,记得把this指针传入。函数指针说白了就是指针,所以在创建函数表时,可以通过void *来表示任意原型的参数,但是在c++不行了,如果想实现任意参数的函数表,还是得如下实现方式:
#include <any>

template <typename Ret> struct AnyCallable {
    AnyCallable() {}

    template <typename... Args>
    AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}

    template <typename... Args> Ret operator()(Args &&... args) {
        return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any),
                           std::forward<Args>(args)...);
    }
    std::any m_any;
};

 

使用using,告别typedefine让别名更加简单。
  1. 简单的函数模板、类模板、auto、std::forward

相信资深c工程师一定实现过或者看过通过宏来实现函数模板,对于c++来说,这个事情变得更加简单了,类模板也大差不差,如上述的就是一个包含函数和类模板的一个示例。更深的模板使用,比如模板元编程,我也仅仅在书上看过几个示例。
函数模板的推导原理还是建议需要看一下的,理解这个对于理解auto的推导过程,以及理解为什么需要完美转发特别有帮助,对于一些坑也能更好的避过去。
另外就是多用auto,保护手指关节,简直不要太爽~~~
  1. 各种构造函数,深拷贝、浅拷贝,析构的过程

对于普通构造函数,没啥好说的。拷贝构造函数和移动构造函数,在效率是可以少一次构造对象的过程,会涉及的深拷贝和浅拷贝;深拷贝和浅拷贝对于一直使用原生指针的c工程师来说,理解起来毫无难度。而析构函数就是deinit()函数,只不过这个是在对象消亡时自动调用的。
  1. 虚函数/接口类,虚析构

虚函数,就是一个或者系列的函数指针,又来到c工程师最熟悉的领域了,虚函数被子类覆盖,无非就是函数指针指向的地址变了,只不过这个地址变得过程在不同编译器有不同的实现方式;而接口类说白了就是在c实现中头的文件,用于表明某个模块包含的函数接口。
虚构造,子类和父类在空间上是连续的,所以如果子类构造出来的对象,即可以找到父类的对象头地址,如果拿到了父类对象地址,可以直接通过释放父类来达到子类空间的目的,但这个的前提是,父类的析构函数得是虚析构。虚析构函数的本质上也是虚函数,所以他会被子类对象持有,而释放子类对象的空间,必须调用子类的析构函数,这就达到了子类和父类一起析构的目的。
  1. 智能指针

析构函数就是调用deinit,有一部分功能就是为了释放内存,防止内存泄露;其实对资深c工程师来说,一般都会很注意内存泄露的问题,但是还是很难保证有漏网之鱼,原生指针,毕竟还是需要自己手动释放内存。而智能指针在离开作用域时或者引用计数为0时会自动释放内存,用起来简直不要太爽,就是如果涉及到循环引用时,std::hared_ptr需要搭配std::weak_ptr使用。因为c开发用的都是原始指针,所以对智能指针的使用几乎毫无难度,使用起来会更加严谨,比如new和智能指针一起使用时,肯定会想到如何delete。
  1. 四种强制类型转换

写c时,最爽的就是指针强转了,但是在c++上还是建议用c++显示强制转换风格,而不是c风格的。否则被我逮住就review不通过了,啊哈哈~~
虽然在很多书上一般都不建议使用reinterpret_cast来转换,但是我觉得使用这个做一些简单的byte转uint16/uint32还是蛮爽的,比如:
uint32_t* id = reinterpret_cast<uint32_t*>(buffer);

 

  1. 重载

同样函数名,不同的入参(类型、顺序或者个数),这个感觉理解起来是最没难度的,而且一般遇到这个,我都更喜欢用函数模板。
  1. 左值、右值,左值引用,右值应用

这个咋说呢,单拎出来去理解概念还是比较抽象,但是在前面提到的各种构造函数、包括模板推导以及auto推导的过程中基本就能把这些概念都理解清除了。所以针对这些,个人感觉没必要去死记概念,在遇到这些使用场景时,结合实际情况来理解就好了。
  1. lambda函数

首先先理解为它是一个函数,具备和函数一样的特性,比如在内存上的位置,然后它是匿名,为什么是匿名的呢,就是没法像普通函数那一样通过函数名来访问了,但是lambda函数本身又是一种对象,可以通过所谓的对象来访问,这跟函数地址其实又没啥区别了,,,
  1. std::thread,线程安全,线程间通信

在linux上就是封装的pthread,所以编译时记得link pthread;个人觉得还是很好用的,创建线程时不像pthread一样只能有一个参数。
使用多线程,不可避免的就会涉及到锁,之前pthread提供的锁,需要自己手动lock/unlock,而c++借助RAII(简单了解一下极氪),借助std::lock_guard和std::unique_lock,看起来只有加锁没有解锁,这对多出口的函数来说简直不要太爽,在借助大括号作用域,领导再也不用担心我忘记解锁而死锁了。这并没有改变锁的机制,所以之前锁的原理以及使用注意事项,全部可以复用。
除了thread,还有搭配使用条件变量,std::atomic,std::future,std::promise等等。
针对这个,自己试着写个线程池也都基本掌握。
  1. .......

上面当然没有包括c++所有了,甚至说只涉及了是一点点都毫不过分,但是28定律告诉我们,用20%的精力掌握80%的知识,这就足够了。对于我来说,刚完成的软件项目初版,几乎用到了上面提到的所有知识点。知识并不是独立的,在学习一个知识点时,总是会涉及到一些其他部分概念和原理,我们保持一个好奇的心,慢慢的把盲点补齐,用起来越来越顺。
在实际软件工程项目,当然还需要有很多其他注意事项,比如隐式转换,各种默认构造函数,有些时候我们不得不要禁止它,这就需要利用c++的修饰符来限制它了,这些我认为只有通过阅读优秀源码是才是最好、最快的学习方式,可以看到在别人实践过程中,对于小细节的处理。另外的就是通过阅读优秀书籍,系统的建立知识体系(真的真的真的不建议上来就阅读《c++ primer》,对于初学者我更感觉这是一本劝退书,但是当你入门时,再去看这本书时,又会发现,它真的是一本入门书籍,说白了其实它是一本比较权威的字典),比如那几本“条款书”。
另外,c++应该更加注意命名和编码规范,比如著名的C++ 风格指南 - 内容目录 ‒ Google 开源项目风格指南。曾经,我用c时也想用这套编码规范,发现其实作用并没有那么大,但是在写c++时,会发现有一套固定的编码规范,太重要了,让你看到格式就能想到它的属性,也能让更快的阅读代码,找到理解的突破点,在团队合作中也能更加方便的互相合作开发。
还有一些,我觉得是跟语言无关的,比如public,protect,private属性,还有就是面向对象的思维,接口设计,模块划分,分层设计。如果你写c时都没用到这些属性和思维方式,你必须承认其实你c写的也不咋地。学习c++并不会自动让你这些思维方式,只不过使用c++可以更好让你将自己的想法落地。
最好还想提一下面向对象,必须辩证的去看面向对象,它也不是全是优点,比如在全貌认知不够或者需求很不明确时,你必须小心不同对象之间的关联、继承关系,否则你不得不重写这些代码。但是面向对象的思维也有很多它的可取之处,构造类时,可以先不考虑的真正的流程,先造积木,然后搭建积木;比如我最近在做S2S,涉及到到CacheManager,对它的定位就是写入和读取,那么我的接口可以很快的定义出来,无非就是Read和Write接口,至于里面是咋样的,可以先不考虑这么快。

四、写在最后

必须认识到c++仅仅是工具,既然是工具,所以软件开发跟工具关系并不是特别大,只过好的工具事半功倍帮你更好的实现。而c++恰恰又是一个难学易用的工具,对于我们大部分人来说,掌握其中一小部分就可以开始干活了,边干边学,边学边实践(多使用官方网站和多“食用”谷歌搜索,一定会让少走不少弯路)。而对于c语言出身的工程师来说,如果没有偏执的思维喜欢,由于语言本身更加贴近底层,现有的工具偏少,所以会造不少轮子,对计算机基础掌握和理解更加深刻。学习、实践过程中,对于c++的一些特性、原理和实现方式,往往可以更快学习和有更深的理解。

评论

  1. 2年前
    2022-9-22 10:07:02

    Good post. I learn something totally new and challenging on sites I stumbleupon everyday. It will always be helpful to read content from other writers and practice a little something from other websites.

  2. 2年前
    2022-10-13 19:46:15

    אני מאוד ממליץ על אתר הזה כנסו עכשיו ותהנו ממגוון רחב של בחורות ברמה מאוד גבוהה. רק באתר ישראל נייט לאדי <a href="https://romantik69.co.il/">https://romantik69.co.il/</a&gt;

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇