程序员成长之路(转)

什么时候才能成为一个专业程序员呢?三年还是五年工作经验?其实不用的,你马上就可以了,我没有骗你,因为专业程序员与业余程序员的区别主要在于一种态度,如果缺乏这种态度,拥有十年工作经验也还是业余的。

什么态度?专业态度!也就是星爷常说的专业精神。专业态度有多种表现形式,以后我们会一一介绍的。这里先介绍一下有关形象的态度,专业的程序员是很注重自己的形象的,当然程序员的形象不是表现在衣着和言谈上,而是表现在代码风格上,代码就是程序员的社交工具,代码风格可是攸关形象的大事。

有人说过,傻瓜都可以写出机器能读懂的代码,但只有专业程序员才能写出人能读懂的代码。作为专业程序员,每当写下一行代码时,要记得程序首先是给人读的,其次才是给机器读的。你要从一个业余程序员转向专业程序员,就要先从代码风格开始,并从此养成一种严谨的工作态度,生活上的不拘小节可不能带到编程中来。

代码风格有很多种,Windows 和Linux都有自己主流的代码风格,每个团队、每个公司也可能有自己的代码风格,争论哪种风格好哪种风格坏根本没有什么意义。有助于其他程序员理解的代码风格都是可以接受的,因为遵循特定代码风格的目的就是为了便于交流。

1 命名要展示对象的功能

1.1 文件名

文件名一定要能传达文件的内容信息,别人一看到文件名就能知道文件中放的是什么内容。把一个类的代码或者某一类代码放在一起是好的习惯,这样就很容易给文件取一个直观的名字。业余爱好者常常把很多没关系的代码糅到一个文件中,结果造成代码杂乱无章,也很难给它取一个恰当的名字.

1.2 函数名

单词小写,多个单词用下划线分隔。如:find_node

一个函数只完成单一功能。不要用代码的长度来衡量是否要把一段代码独立成一个函数。即使只有几行代码,只要这些代码完成的是一项独立的功能,都应该将其写为一个单独的函数,而函数名要能够直观地反应出它的功能。如果在给函数起名时遇到了困难,通常是函数设计不合理,则应该仔细思考一下并对函数进行相应修改。

1.3 结构/枚举/联合名

首字母大写,多个单词连写。如:struct _DListNode

宏名:单词大写,多个单词下划线分隔。

如:#define MAX_PATH 260

变量名:单词小写,多个单词下划线分隔。

如:DListNode* node = NULL;

1.4 面向对象命名方式

(1) 以对象为中心,采用“主语(对象)+谓语(动作)”的形式来命名,取代传统的“谓语(动作)+宾语(目标)”的形式。

如:dlist_append

(2) 第一个参数为对象,并用thiz命名。

如:dlist_append(DList* thiz, void* value);

(3) 对象有自己的生命周期,因此都有相应的创建和销毁函数。

2 排版布局要美观大方

2.1 合理使用空行

函数体之间用空行分隔。

结构/联合/枚举声明用空行分隔。

不同功能的代码块之间用空行分隔。

将功能类似的代码(如宏定义、类型定义、函数声明和全局变量)放在一起,和其他部分用空行分隔。

使用空行时,一行就够了,不要使用连续多个空行,那样会让人感觉代码段空荡荡的。

2.2 合理使用空格

等号两边用空格。如:int a = 100;

参数之间用空格。如:test(int a, int b, int c)

语句末的分号与前面内容不要加空格。如:test(a, b, c);

其他能让代码更美观的地方。

2.3 合理使用括号

用括号分隔子表达式,不要只靠默认优先级来判断。((a && b) || (c && d))

用括号分隔if/while/for等语句的代码块,那怕代码只有一行。

2.4 合理缩进

每一级都正常缩进,用tab缩进取代空格缩进(Linux内核源代码也遵循此规则)。用空格缩进的目的是防止代码因编辑器的tab宽度不同而变乱,这个担心现在是多余的 了,代码编辑器都支持tab宽度设置了。如果代码缩进的层次太多(比如超过三层),则可能是代码设计上出了问题。

2.5 遵从团队的习惯

这一点是最重要的,一个团队就要有一个团队的样子,不管你的水平有多高,遵循团队的规则是一个程序员的基本素养。如果团队的规则确实不好,大家应该一起完善它。做到这一点,你已经离成为专业程序员这个目标更近一步了,重新做一遍练习吧。随着后面的学习,你就可以真正走进专业程序员这个行列了。

3 谁动了你的隐私

3.1 什么是封装

人有隐私,程序也有隐私。有隐私不是什么坏事,问题是不应该让别人知道自己的隐私,否则可能会对自己造成不小的伤害,甚至会连累相关人物跟着倒霉。程序隐私的暴露,造成的不良影响不一定会泄露个人隐私那么大,但也不容小觑。封装就是要保护好程序的隐私,不该让调用者知道的事,就坚决不要暴露出来。

3.2 为什么要封装

总的来说,封装主要有以下两大好处。

隔离变化。程序的隐私通常是程序最容易变化的部分,比如内部数据结构、内部使用的函数和全局变量等,我们需要把这些代码封装起来,从而让它们的变化不会影响系统的其他部分。

降低复杂度。接口最小化是软件设计的基本原则之一,最小化的接口容易被理解和使用。封装内部实现细节,只暴露最小的接口,会让系统变得简单明了,在一定程度上降低了系统的复杂度。

3.3 如何封装

总的来说,封装主要有以下两大好处(具体影响后面再说)。隔离变化。程序的隐私通常是程序最容易变化的部分,比如内部数据结构、内部使用的函数和全局变量等,我们需要把这些代码封装起来,从而让它们的变化不会影响系统的其他部分。降低复杂度。接口最小化是软件设计的基本原则之一,最小化的接口容易被理解和使用。封装内部实现细节,只暴露最小的接口,会让系统变得简单明了,在一定程度上降低了系统的复杂度。封装过程中应注意一下问题:

内部函数通常实现一些特定的算法(如果具有通用性,应该放到一个公共函数库里),对调用者没有多大用处,但它的暴露会干扰调用者的思路,让系统看起来比实际的复杂。函数名也会污染全局名字空间,造成重名问题。它还会诱导调用者绕过正规接口走捷径,造成不必要的耦合。隐藏内部函数的做法很简单。

(1)在头文件中,只放最少的接口函数的声明。

(2)在C文件中,所有内部函数都加上static关键字。

全局变量始终都会占用内存空间,共享库的全局变量是按页分配的,哪怕只有一个字节的全局变量也占用一个页,这样一来就会造成不必要内存空间浪费。全局变量也会给程序并发造成困难,想把程序从单线程改为多线程将会遇到麻烦。重要的是,如果调用者直接访问这些全局变量,会造成调用者和实现者之间的耦合。

4 Write once, run anywhere(WORA)

4.1 专用链表和通用链表各自的特点与适用范围

专用链表在这里是指该链表的实现和调用耦合在一起,只能被一个调用者使用,而不能单独在其他地方被重用。通用链表则相反,它具有通用性,可以在多处被重复使用。尽管通用链表相对专用链表来说有很多优越之处,不过草率地断定通用链表比专用链表好也是不公正的,因为它们都有自己的优点和适用范围。()

注意 在本节中,为了避免读起来拗口,我把双向链表简写成链表了,希望大家不要介意。

专用链表的优点

考虑到链表是最常用的数据结构之一,很多地方都会用到它,实现通用的链表会更有价值。接下来我们要实现一个通用的链表,不过请大家记住,实现通用的链表并不是我们的目标,而是我们学习软件设计方法的手段。前面我许诺过要以简单的数据结构讲述复杂的软件设计方法,链表就是其中的载体之一。

5 拥抱变化

在专用双向链表中,dlist_printf的实现非常简单,如果里面存放的是整数,用 %d 打印,存放的是字符串,用 %s 打印。现在的麻烦在于双向链表是通用的,我们无法预知其中存在的数据类型,也就是说我们要面对数据类型的变化。怎么办呢?初学者可以参考的常用方法有以下几种。

5.1 实现多个函数,需要哪个就用哪个

比如实现dlist_print_int用来打印存放整数的双向链表,dlist_print_string用来打印存放字符串的双向链表等,其他类型都有自己的打印函数。

不过这种做法也有一些缺点。一是每个函数的实现方式类似,会带来大量重复的代码。二是由于数据类型的种类不确定,如果为每种数据类型都实现一个print函数,当要存放新的数据类型时,就不得不修改dlist的实现。

5.2 传入一个附加参数来决定如何打印

比如传入1表示按整数方式打印,传入2表示按字符串方式打印,以此类推。

这种做法比第一种好一点,至少不会造成大量重复的代码。但是同样存在增加新类型时要修改dlist_print函数的问题。

5.3 调用dlist的接口函数获取每一个位置的数据并打印出来

这种方法没有前面两种方法的缺点,而且是一种相当直观的方式。但奇怪的是偏偏很少有人使用这个方法,原因可能有两个:其一是太拘泥于传统的实现方式而没有想到这一种;其二是担心性能问题,因为通过索引取值,每一次都要从头开始定位,其性能开销为O.

其实这种方法是可以接受的,dlist_print函数只是用于辅助测试,我们并不需要太在乎它的性能开销,而且我们很少会在链表中存放成千上万的数据,因此这个函数带来的性能影响根本没有想的那样严重。所以在这里我们要介绍一种新的方法。

dlist_print的大体框架如下。

在上面代码中,我们主要是不知道如何实现 print(iter->data); 这行代码。那么谁知道呢?很明显,调用者知道,因为调用者知道链表里面所存放的数据类型。好吧,那就让调用者来做好了,调用者在调用dlist_print时会提供一个函数给dlist_print来调用,这种回调调用者所提供函数的方法,我们可以称之为回调函数法。

调用者如何提供函数给dlist_print呢?当然是通过函数指针了。变量指针指向的是一块数据,指针指向不同的变量,则取到的是不同的数据。函数指针指向的是一段代码(即函数),指针指向不同的函数,则具有不同的行为。函数指针是实现多态的手段,多态就是隔离变化的秘诀,这里只是一个开端,后面我们会逐步地深入学习。

请看详细实现过程

6 Don’t Repeat Yourself(DRY)

我见过不少任劳任怨的程序员,别人让他做什么他就做什么,不管是不是份内的事,不管是上司要求的还是同事要求的,都来者不拒。别人说需要一个某某功能的函数,他就写一个在他的模块里,日积月累,他的模块就成了一锅“大杂烩”。我亲眼见过有程序员在系统设置和桌面两个模块里,提供很多毫不相干的函数,这些函数会造成不必要的耦合和复杂度。在这里也是一样的,求和与求最大值并不是dlist应该提供的功能,放在dlist里面实现是不应该的。为了能实现这些功能,我们提供一种满足这些需求的机制就好了。热心肠是好的,但一定不要“管得太宽”,否则就费力不讨好了。

7 你的数据放在哪里

对于初学者来说这道题有点难度,很少有人能完全做对。不过没关系,我并不是要出一道难题来难倒大家,而是要刺激大家去思考,以期达到加深学习印象的效果。有了前面两次的经验,我想应该没人会去写一个dlist_to_upper函数,大家都会调用dlist_foreach来实现。不过新的问题又出现了,初学者还是有可能犯以下几种常犯的错误。

7.1 转换大写的方法不对

这是我们在课本里学到的写法,但在工程中是不能这样做的。因为大小写字母在不同语言中的定义是不一样的,“a”是一个字符常量,它的值在任何时候都是97,但在不同语言中,97却不一定代表“a”。我们不能简单地认为在97(a)—122(z)之间的字符就是小写字母,而是应该调用标准C函数islower来判断,同样转换为大写应该调用toupper而不是减去一个常量。

7.2 在双向链表中存放常量字符串,转换时出现段错误。

运行时会出现“Segmentation fault”错误。原因是“It”等字符串是常量,常量是不能被修改的。

7.3 在双向链表中存放的是临时变量,转换后发现所有字符串都一样。

运行时发现打印出几个感叹号。原因是执行dlist_append时没有复制一份,所以在dlist中存放的是同一个地址。而且这个dlist在当前函数返回后,里面保存的数据都无效了,因为这些数据指向的是临时变量。

7.4 存放时复制了数据,但没有释放所分配的内存。

这里看起来工作正常了,但存在内存泄露的bug。strdup调用malloc分配了内存,但没有地方去释放它们。

初学者对内存和指针只有一知半解的认识,常常犯一些连自己都莫名其妙的错误。为了避免这些不必要的错误,今天我们要学习各种数据存放的位置以及它们的特性,让初学者对编程有更进一步的认识。在程序中,数据存放的位置主要有以下几个。

7.5未初始化的全局变量(.bss段)

通俗地讲,bss段被用来存放那些没有初始化或初始化为0的全局变量。它有什么特点呢,让我们先来看看一个小程序的表现。

变量bss_array的大小为4M,而可执行文件的大小只有5K。由此可见,bss类型的全局变量只占运行时的内存空间,而不占用文件空间。

现在大多数操作系统在加载程序时,会把所有的bss全局变量清零。但为了保证程序的可移植性,最好能手工把这些变量初始化为0,这样可以使这些变量都有个确定的初始值。

当然了,作为全局变量,在整个程序的运行周期内,bss数据是一直存在的。

7.6初始化过的全局变量(.bss段)

与bss相比,data段就容易理解多了,看名称就大概能知道它里面存放着数据。当然,如果数据全是0,为了优化考虑,编译器会把它当作bss处理。通俗地讲,data段被用来存放那些初始化为非0值的全局变量。那么它又有什么特点呢,我们还是先来看看一个小程序的表现。

仅仅是把初始化的值改为非0值了,文件就变为4M多。由此可见,data类型的全局变量是既占文件空间,又占用运行时内存空间的。

同样,作为全局变量,在整个程序的运行周期内,data数据也是一直存在的。

7.7 常量数据(.bss段)

rodata的意义同样明显,ro代表read only(只读),rodata就是用来存放常量数据的。关于rodata类型的数据,要注意以下几点。

由此可见,把在运行过程中不会改变的数据设为rodata类型是有好处的。在多个进程间共享,可以大大提高空间利用率,甚至能不占用RAM空间。同时由于rodata在只读的内存页面中是受保护的,任何试图对它进行修改的行为都会被及时发现,这样一来还可以提高程序的稳定性。

字符串会被编译器自动放到rodata中,其他数据要放到rodata中,只需要为其加const关键字修饰即可。

7.8 代码(.bss段)

text段存放代码(如函数)和部分整数常量,它与rodata段很相似,相同的特性我们就不重复了,主要的区别在于text段是可以执行的。

8 栈和堆

8.1栈

栈是用来存放临时变量和函数参数的。将栈作为一种基本数据结构,我并不感到惊讶;将其用来实现函数调用,也是大家司空见惯的作法。直到我试图找到另外一种方式实现递归操作时,我才感叹于栈的巧妙。要实现递归操作,不用栈不是不可能,只是找不出比使用栈更优雅的方式。

通常情况下,栈是向下(低地址)增长的,每向栈中PUSH一个元素,栈顶就向低地址扩展,每从栈中POP一个元素,栈顶就向高地址回退。这里有一些比较有意思的问题:在x86平台上,栈顶寄存器为ESP,那么ESP的值是在PUSH操作之前修改呢,还是在PUSH操作之后修改呢?PUSH ESP这条指令会向栈中存入什么数据呢?据说x86系列CPU中,除了286外,都是先修改ESP,再压栈的。由于286没有CPUID指令,因此有的操作系统会用这种方法检查286的型号。

要注意的是,存放在栈中的数据只在当前函数及下一层函数中有效,一旦函数返回了,这些数据也就自动释放了,继续访问这些变量会造成意想不到的错误。

8.2堆

堆是最灵活的一种内存,它的生命周期完全由使用者控制。标准C提供以下几个函数来使用堆内存。

9 小结

本文通过一个简单需求的完成过程讲述了程序员应具备的态度和技能,是程序员进阶的必经之路。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/454206.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式开发——PWM高级定时器

学习目标 加强掌握PWM开发流程理解定时器与通道的关系掌握多通道配置策略掌握互补PWM配置策略掌握定时器查询方式掌握代码抽取优化策略掌握PWM调试方式学习内容 需求 点亮8个灯,采用pwm的方式。 定时器 通道 <

解决虚拟机时间引起的奇怪问题

一直使用得好好的虚拟机最近出了一个奇怪问题在虚拟机装好的lamp在客户端访问phpmyadmin的时候,使用firefox登录没问题,但是使用IE不行总是停留在登录的界面,而且没有提供任何的出错信息,就连在apache的日志里面也看不到.注意到同样访问的时候,在IE上显示的转向的url是[url]htt…

TensorFlow 基本操作

Tensorflow基本概念 图(Graph):图描述了计算的过程&#xff0c;TensorFlow使用图来表示计算任务。张量(Tensor):TensorFlow使用tensor表示数据。每个Tensor是一个类型化的多维数组。操作(op):图中的节点被称为op(opearation的缩写)&#xff0c;一个op获得/输入0个或多个Tensor…

03_zookeeper伪集群安装

一句话说明白&#xff1a;在1台机器上模拟多台机器&#xff0c;对外提供服务 在理解zookeeper集群安装方法的基础上&#xff0c;本文描述如何将1个机器模拟为3个节点的zookeeper集群&#xff0c;建议先参考阅读本文的前一期 zookeeper伪集群安装总结 在本机上通过复制的方式&am…

python合成语音_MicroPython动手做(25)——语音合成与语音识别

6、AB按键切换语言合成项目[mw_shl_codepython,true]#MicroPython动手做(25)——语音合成与语音识别#AB按键切换语言合成项目from mpython import *import networkimport timeimport ntptimefrom xunfei import *import audiomy_wifi wifi()my_wifi.connectWiFi("zh"…

专访谷歌CEO:像对待家人一样对待员工

导语&#xff1a;《财富》近日公布了“2012年度美国100家最适宜工作的公司”榜单&#xff0c;谷歌当选冠军。即将于2月6日出版的美国《财富》杂志印刷版将刊登对谷歌CEO拉里佩奇(Larry Page)的专访&#xff0c;对谷歌的工作环境进行了介绍。 以下为采访概要&#xff1a; 问&a…

TensorFlow 分布式

一、简介 使用单台机器或者单个GPU/CPU来进行模型训练&#xff0c;训练速度会受资源的影响&#xff0c;因为毕竟单个的设备的计算能力和存储能力具有一定的上限的&#xff0c;针对这个问题&#xff0c;TensorFlow支持分布式模型运算&#xff0c;支持多机器、多GPU、多CPU各种模…

第五周测试

---恢复内容开始--- 一 视频知识 1 linux系统下如何区分内核态与用户态 在内核态&#xff1a;cs:eip可以是任意的地址&#xff0c;4G的内存地址空间 在用户态&#xff1a;cs:eip只能访问0x00000000—0xbfffffff的地址空间 2 系统调用的三层皮&#xff1a;xyz、system_call和sys…

网页制作小技巧:dl dt dd标签用法

< DOCTYPE html PUBLIC -WCDTD XHTML StrictEN httpwwwworgTRxhtmlDTDxhtml-strictdtd> 一般我们在做列表的时候通常只会用到ul和li,至于DL一般都很少用到&#xff0c;它也属于列表类的标签&#xff0c;下面说一下大概的用法&#xff1a; <dl>标记定义了一个定义列…

latex公式对齐_Word 写公式最方便的方法

自从用上了word 2016之后&#xff0c;发现他的公式编辑器真香!真香!!他有了latex的优雅&#xff0c;又有了Mathtype的可视化效果&#xff0c;甚至更好哈&#xff0c;当编辑大量公式时也不会因为插件问题卡掉当前的努力。学起来也不复杂&#xff0c;反正是word. 强烈推荐。我们最…

路要怎么走?关于程序员成长的一点思考

程序员的我们&#xff0c;是否想过今后的路该怎么走、如何发展、技术怎样提高?其实这也是我一直在思考的问题。下面就此问题&#xff0c;分享下我的看法。因为我阅历有限&#xff0c;有什么说的不对的&#xff0c;大家见谅&#xff0c;千万不要喷…… 一、程序员应该打好基础 …

TensorFlow 常见API

数据类型转换相关API Tensor Shape获取以及设置相关API Tensor合并、分割相关API Error相关类API 常量类型的Tensor对象相关API 序列和随机Tensor对象相关API Session相关API 逻辑运算符相关API 比较运算符相关API 调试相关API 图像处理-编码解码相关API 图像处理-调整大小相关…

python封装继承多态_浅谈JavaScript的面向对象和它的封装、继承、多态

写在前面既然是浅谈&#xff0c;就不会从原理上深度分析&#xff0c;只是帮助我们更好地理解...面向对象与面向过程面向对象和面向过程是两种不同的编程思想&#xff0c;刚开始接触编程的时候&#xff0c;我们大都是从面向过程起步的&#xff0c;毕竟像我一样&#xff0c;大家接…

将万亿以下的阿拉伯数字转为中文金额

package test.practice.month3; public class Test005 { //可以不用swich case将123456789转为一二三四五六七八九 //直接用char[] chars {一,二,三,四,五,六,七,八,九}; public static void main(String[] args) { System.out.println(getCMoney(102030405067L)); } private …

8.2 命令历史

2019独角兽企业重金招聘Python工程师标准>>> 命令历史 history //查看之前的命令.bash_history //存放之前敲过的命令&#xff0c;在 /root/ 目录下最大1000条 //默认参数值是1000条变量HISTSIZE/etc/profile中修改 //在其中可编辑HISTSIZE参数HISTTIMEFORMAT"…

使用GCC生成无格式二进制文件(plain binary files)

使用C语言生成一个二进制文件 使用自己喜欢的文本编辑器写一个test.c&#xff1a; int main() { } 再使用如下命令编译&#xff1a; gcc –c test.c ld –o test –Ttext 0x0 –e main test.o objcopy –R .note –R .comment –S –O binary test test.bin 最后生成的二进…

TensorFlow 实例一:线性回归模型

代码 # -- encoding:utf-8 -- """ Create by ibf on 2018/5/6 """import numpy as np import tensorflow as tf# 1. 构造一个数据 np.random.seed(28) N 100 x np.linspace(0, 6, N) np.random.normal(loc0.0, scale2, sizeN) y 14 * x - …

python后端数据发送到前端_Python Django 前后端数据交互 之 后端向前端发送数据...

Django 从后台往前台传递数据时有多种方法可以实现。最简单的后台是这样的&#xff1a;from django.shortcuts import renderdefmain_page(request):return render(request, ‘index.html‘)这个就是返回index.html的内容&#xff0c;但是如果要带一些数据一起传给前台的话&…

Dapper的基本使用

Dapper是.NET下一个micro的ORM&#xff0c;它和Entity Framework或Nhibnate不同&#xff0c;属于轻量级的&#xff0c;并且是半自动的。也就是说实体类都要自己写。它没有复杂的配置文件&#xff0c;一个单文件就可以了。给出官方地址。 http://code.google.com/p/dapper-dot-n…

函数名作为参数传递

假如不知道signal的函数原型&#xff0c;考虑child_handler函数的参数从哪里来&#xff1f; void child_handler(int sig) { if (sig SIGINT) kill(pid_parent, SIGUSR1); } int main(void) { ...... signal(SIGINT, child_handler); ...... } 1、…