C高级编程:基于模块化设计思想的C语言开发.html.pdf
《C高级编程:基于模块化设计思想的C语言开发.html.pdf》由会员分享,可在线阅读,更多相关《C高级编程:基于模块化设计思想的C语言开发.html.pdf(27页珍藏版)》请在三一文库上搜索。
1、前言 为什么要写这本书 因为工作原因,在算法优化、底层驱动、嵌入式系统设计等方面的软件编程时,一直使用C语言,而且很难有其他“更好”的选择。一方面,工作内容在客观上决定了无法利用更高级语言;另一方 面,相对其他语言,在上述工作领域中持续使用C语言,使得工作效率更高(结合必要的shell脚本)。因此对于那些初入上述工作领域的工程师,我始终推荐C语言。通过本书,希望将个人的开发总结作为 示例,给予新人作为参考。 C语言是一种比较早期的高级语言,其本身是模块化的,这使得通过C语言比较容易实现面向电子、计算、自控系统自身的模块化设计。目前更多的软件设计并非针对电子、计算、自控系统本身,例 如,一个企业
2、管理软件、一个网站商城界面等。这些软件设计,是基于应用者的思维,或者说人类正常思维模式而展开的。由此,这类设计使用面向对象语言会非常方便,但却导致过多关注计算机编程的 教育,忽视了面向模块化编程方法的讲解。因此,本书将模块化系统设计的个人总结与C语言的讨论融合。希望本书能抛砖引玉,让上述工作领域的读者更好地关注与思考面向系统本身的设计方法。 本书特色 在本书写作的过程中,使用了个人工程代码库中的原型,并尽可能保证这些代码有一定的应用价值。为了在有限的章节尽可能给出一个较为完整的代码集合,因此,章节之间的代码存在一定依赖性, 即,前序代码形成的模块,会被后续章节中所讨论的代码利用。 为了让工程经
3、验欠缺的新人对C语言开发有更好的感性认识,本书在讨论问题和介绍代码中穿插了很多个人观点,这些观点并不是理论,也不一定是行业共识,只是从一个侧面的经验之谈,希望对读 者有参考价值。 读者对象 电子、自控、计算机等相关专业的高年级本科、研究生 算法设计与优化工程师 嵌入式系统开发工程师 底层、中间件子系统开发工程师 其他对C语言编程、模块化系统设计感兴趣的人员 如何阅读本书 本书共九章,从C语言自身,一直探讨到(进程)模块之间的共享与通信。前八个章节,更多是工程和具体代码设计的讨论,而最后一个章节则是系统分析与系统设计方法的讨论。对于期望、正在从 事系统整体规划、构架、设计的读者,建议首先了解最后
4、一章内容,而对于欠缺系统分析经验的新进工程师,则建议从第1章开始阅读,同时建议对书稿中的代码进行上机验证,在执行反馈中了解本书的观 点,并进行修正,形成自身工程代码库。 勘误和支持 由于水平有限,编写时间仓促,书中难免会出现一些错误或者不准确的地方,恳请读者批评指正,期待能够得到你们的真挚反馈,在技术之路上互勉共进,我的邮箱是zsu_。 致谢 感谢教育、指导、帮助、支持过我的老师、朋友及家人,使得我能持续多年在所喜爱的技术领域进行工作。 感谢机械工业出版社华章公司的杨福川和高婧雅,始终支持我的写作,是你们的鼓励和帮助引导我顺利完成这本书稿。 最后,特别感谢杨尚丽对本书的文句审核以及赵瑞源对本书
5、代码的验证。 吉星 2016年3月 第1章 C语言的探讨 本章主要针对那些刚刚离开校园,准备参与基于C语言设计的项目工程,从事C语言程序开发的初级工程师;或已初步学习了C语言的语法知识,可独立编写一些小的C语言程序,但对C语言的设计方法 和特点并未全面掌握的初级程序员。 本章(其实包括本书)会有很多观点与传统教科书的描述内容存在差异。这种差异并不是对已有教科书部分内容的否定,更不是对辛勤的教育工作者们的否定。这种差异来源于教育与实际开发工程所 在的环境差异以及程序设计任务目标的差异。 单纯依靠传统教科书的内容并不能有效支撑实际工程的开发,同时有些从工程开发角度所关注的内容被传统教科书忽略。因此
6、本章从工程实践角度出发,探讨C语言的一些概念、设计内容和方法,以 便初级程序员对C语言及其利用有进一步的了解。而更全面地掌握需要程序员在实际工程开发中逐步地感悟与积累。 本章受篇幅限制,仅仅针对C语言在编译链接、函数、数据类型、指针、预处理操作等几个方面展开讨论。同时这些讨论的内容,大多会出现在本书后续章节的程序设计中,因此也可将本章看作对本 书后续章节讨论内容的铺垫。 本章不会(也不可能)将所有C语言以及关联的内容全部展开,而是希望借助一些举例和讨论,引出相关概念和知识点,便于初级程序员有针对性地查找相关资料以做更深入的了解,本书后续对此类 情况,将简称为“参照相关资料”。相关资料中,至少包
7、含以下4方面内容: C语言国际标准; 你所使用编译器的产品手册; 你所使用编译器的基础库手册; 你所使用操作系统提供的C语言接口库手册。 注意 以上四方面,一切以C语言国际标准为基准。或许35年的一个C语言应用阶段,你一直使用同一个编译器针对同一个目标操作系统进行开发,但你不能保证在随后10年、20年的开发工作中所 用的编译器和针对的目标操作系统始终不变。对于有出入的内容,需要非常注意,在系统原型设计阶段,尽可能地回避编译器的特性。在系统优化阶段,再针对特定目标系统,借助编译器/操作系统接口库 的特性内容来提升你的程序性能。对不属于C语言标准库的内容,则应尽可能地选择那些符合国际标准的协议、规
8、则、规范(如POSIX协议)的部分。 在展开本章讨论之前,围绕C语言的开发,此处给出一些本书作者(本书后续简称“我”)的个人建议。 建议1: 区别于那些“更高级”的计算机编程语言,C语言的设计,从你刚开始入行时就应该有一个意识:“基于C语言的程序开发项目,应当分为系统原型开发阶段和目标平台优化阶段”,前者在利用C语言 本身,后者在发挥特定目标平台的优势。前者关注系统的内在逻辑,后者关注平台的具体特性。但不同的目标平台可能差异较大,如果需要发挥它们的优势,可能存在特定的数据组织策略,这些策略需要 在前期阶段进行逻辑验证,因此这两个阶段也不是完全可隔离的。其他“更高级”的语言,并不太关注硬件、平台
9、特性,而C程序员应当多了解目标系统(硬件以及操作系统)的运行机理。 建议2: C语言的设计开发,尽可能地在类UNIX(UNIX的各种演化版本)或Linux(参照UNIX在PC上实现的操作系统,其不属于类UNIX)下而非Windows平台之上进行,并优先学习这类操作系统及面向这 类操作系统开发的技巧。C语言诞生在UNIX之上,最初的目的又是为了设计UNIX操作系统本身,因此C语言和类UNIX以及Linux具有很好的结合性。 相对而言,将Windows下编写带有Windows特性的C语言程序移植到类UNIX或Linux下,其工作量远大于类UNIX或Linux之间的相互移植的工作量,也大于将类UNI
10、X或Linux下开发的C程序移植到 Windows下的工作量。这里也建议那些一线的教育工作者,如同讲解汇编最好结合计算机组成原理一样,教授C语言的知识,最好结合类UNIX或Linux操作系统一并讲解。 如果你是一个尚未在类UNIX或Linux下开发的C语言初级程序员,则建议你应尽快熟悉并掌握某个类UNIX以及Linux的操作系统。例如,Mac OS X就是稳定且具有良好应用界面的类UNIX系统。本书 中的所有C语言程序,包括我近年的C语言工程开发均在一台MacBook Pro上完成。而基于Linux内核的操作系统选择也很多,例如,早年我曾在Ubuntu下进行C语言的开发。 建议3: 除特定开发
11、目标(如针对某特定硬件系统所设计的特定开发工具平台),正常的C语言设计应在代码编辑器下编辑,在命令行下调用脚本、make等工具开展工作,而非使用某种无目标系统特性的集成 开发环境(Integrated Development Environment,IDE)。 集成开发环境主要包括编辑器、编译器、调试器和图形用户界面工具等。一些诸如Sublime Text等第三方编辑器的性能优良,比具有同样编辑功能的IDE更为轻巧。而长期使用某种IDE,会逐步忽略了 该环境对C代码组织上的特殊性(对于初学者甚至不了解这些特殊性),这会对以后调整C语言的开发环境有很多不利影响;采用断点、跟踪的调试器并不适用于连
12、续运行下的各种情况跟踪(后续会在第2 章展开讨论);单纯的编译器对于C语言开发并不足够,这需要make和shell脚本等其他工具组成的工具链(后续会在第3章展开讨论)来提高你的工作效率;基于C语言的设计目标极少有针对图形应用界 面的设计,因此图形界面的设计任务使用C语言开发并不适合。 我最初在Turbo C的IDE和visio C+下学习C语言并设计程序,它们易于初学者上手,但“严重”阻碍初学者对C语言工程开发设计方法的掌握及应用。一种较为“偏激”的说法,如果你使用IDE写C 程序,则你仅仅是在写C语言的程序,而不是在利用C语言按照工程化的组织方式开发一个系统。每个团队基于自身的业务背景、设计
13、目标,会使用C语言、脚本等工具去构建和完善自身的工具包,组织成 工具链,帮助自身提高C程序设计的效率。用C语言开发工具服务于C语言的开发,这是C语言程序员应当具备的能力。 建议4: 除非你参与开源项目或希望你的设计目标以开源方式推广,否则更建议初级程序员使用“传统”的版本控制软件。此处“传统”指按照集中化管理的版本控制理论设计所开发出的版本控制软件,典型 的如CVS、SVN、Perforce等。非“传统”的,如目前在各个开源社区中流行的Git。此处并非说后者不好。一个产品,包括未来你所要设计的系统,“好”与“不好”都需要基于具体的应用场景来讨论。 团队内高度协同的开发和基于开源社区(全球化)的
14、开发,在分工组织模式上差异很大。后者极少出现两个程序员针对同一个C文件密集地进行修正调整(这需要以天或半天为单位,相互合并对方最新的 代码),而在团队开发中,这种事情并不少见。我尊敬并赞赏那些为开源软件做出贡献的程序员,但作为初级程序员的你,我更建议你先在集群管控的团队下锻炼好自身的开发能力,再去学习开源系统的 设计方法和使用面向开源软件开发的特有工具去参与开源软件的设计。 上述4条,仅仅是我个人的建议,既不是“标准”,也不是“守则”,与本书后续针对模块化设计所探讨的“规则”一样,它们只是建议,当然这些建议和规则有效帮助了我个人的开发工作,它们是否 适合你,需要你自己的思考和实践。 1.1 C
15、的编译链接与文件引用 1.1.1 一个小程序 我不知道以下的程序是否算作最简的C语言程序,但它足够小,同时包含了很多初级程序员忽略的内容。代码如下: int main(int argc ,char *argv) return argc; 上述程序存储为C文件前,我们先按照以下命令组织磁盘目录。 mkdir test cd test mkdir src mkdir inc mkdir obj mkdir bin 此时,当前目录为你刚才创建的test目录。其中,src我们仅存储C文件,inc则存储后续讨论到的头文件,obj存储编译后的对象文件,bin存储链接后形成的库或执行文件。这种组织方式并不是
16、某种严 格的规定,不按照这种组织方式,不代表不能构建C程序,但很多工程代码,采用了类似这样的组织方式,总是有一定理由的。 将上述三行语句,保存在当前目录下的src/test.c中,在当前目录下执行如下命令: gcc -c src/test_main.c -o obj/test_main.o gcc obj/test_main.o -o bin/test_main bin/test_main echo $? bin/test_main 1 2 echo $? bin/test_main 1 2 3 echo $? 上述第一行的命令为编译,你可以通过是否存在一个-c的选项来判断。第二行的命令为链接
17、,它构建了可执行文件(gcc通过缺少-c来判断)。第三行命令是执行生成在bin子目录(也可称为文件夹) 下的执行程序test_main。 echo$?是用来检测最近一个执行操作的返回。随后是另两组再次执行与显示的操作。本书后续讨论中,若无特殊说明,则将第一行和第二行的两个操作,统一简称为“编译链接”,而第三行的操 作,简称为“执行”。 扩展讨论 echo是shell命令行(本书后续简称“命令行”)的内建命令,同时也是外部命令。你可通过man builtin或执行type-a commandname来获取、查询某个在命令行下执行的命令是否为内建命令 或外部命令。commandname为执行命令名
18、称。关于内建命令或外部命令的具体差异你需要参照相关资料。 上述命令执行后,应当分别返回1、3、4。从上述三行的C语言程序中,你应该了解到,此时仅仅是返回了main函数的第一个参数,它表示当前命令执行时一共存在几个参数(包含执行程序文件名本 身)。 main函数参照C语言国际标准的内容,它有两种形式,另一种如下: int main(void); 但我建议不使用第二种。无论你所设计的应用程序是否需要跟随参数,保留它总没有错。而实际上大多数程序总需要一些给入参数,以方便程序在初始化时有一定选择性,哪怕你的程序的初始化参数 均是通过文件读取,在main函数入口,给入一个参数文件存储位置的信息,这总比执
19、行程序必须在特定目录下获取参数文件要人性化得多。 main在C语言中是非常特殊的一个函数。对于链接形成的可执行程序,main函数是整体程序的主入口。当然它存在于哪个C文件中并不重要。在1.5节的小模块举例中你会发现,更多的工作会从main 函数中移除,而尽量保证main函数的简洁。main函数里主要描述一个系统中(按照大类区分)各模块的配置及调度逻辑。而不要如学校交作业那样,一个作业内容,全部由一个main函数中实现。在后续 章节,你会发现,一个模块的代码甚至不包括存放main函数的test_XXX_main.c文件,而后者仅是作为调用该模块进行测试的入口测试文件。这样做是为了方便一个工程的开
20、发成果与其他工程整合利用。 较为复杂的系统,更多情况下是切割成小块分别处理,而不是集中在一个工程中开发。 1.2 函数、数据与作用域 1.2.1 全局函数与局部函数 1.1节介绍了一个函数可被另一个C文件调用的方法。我们只需要将该函数接口声明放入一个头文件中,而调用者引用该文件,获取该函数的接口声明,便可以有效地调用。 如上讨论,编译仅仅针对一个C文件形成对象文件,而链接可以对给入的多个对象文件进行整合关联。在链接中,能够跨文件利用函数,其作用域是在本次链接所覆盖的整体范围,因此也称这些函数 为全局函数。 广义的作用域,实际包含两个维度,其一是如上讨论的范围,称为狭义的作用域;其二指的是生命周
21、期。在本章后续讨论中,如果独立地描述“作用域”,则指广义的作用域。 与全局函数对应的则是局部函数。它的作用域仅被局限在本C文件中。全局函数是默认的,而局部函数则需要在函数定义前增加static的关键词。由于全局函数作用域针对链接所覆盖的整体范围,因此 不同C文件内的全局函数不能出现重名,否则链接时不能确定使用哪一个。相反,局部函数则可在不同C文件中存在相同的名称。如果你在头文件中定义了一个函数(不是函数接口声明),当两个C文件引 用该头文件时,该函数定义内容同时会出现在两个C文件中,如果这个函数定义没有static关键词,它会被默认为全局函数,此时链接,则会出现重名错误。而如果定义为局部函数,
22、则在两个文件中,存在 两个作用域仅为自身C文件的局部函数,这并不会导致链接错误。 扩展讨论 全局函数、局部函数的差异在于作用域,这种差异使得工程应用上对它们的一些处理要求也存在差异。 一个函数,在处理外部给入的数值时,需要对数值是否在合理的范围进行检测,例如,你将使用到一个指针,如果它指向地址0时,你对该地址进行操作,会出现错误。 对给入数值进行范围检测的处理逻辑,我们简称为“边界检测”。没有边界检测的程序很难想象它处理数据的适用性。但每个函数都进行边界检测,既烦琐又降低了程序的执行效率。 由于C语言局部函数和全局函数的作用域不同,结合模块化设计的封装思想,在实际设计中可按如下规则确定一个函数
23、是否需要进行边界检测。 所有全局函数需要对给入参数进行边界检测;而所有局部函数不需要对给入参数进行边界检测,其检测工作应由调用者完成。调用本C文件外的全局函数时,边界检测工作由被调用的全局函数完成,调 用者不做检测。 一个C文件内部函数之间的调用关系,在设计该C文件代码时便可确定,调用者(函数)可明确知道被调用的局部函数对数值范围的要求。 而一个设计完成的C文件,其全局函数可能日后被很多其他C文件调用,你不能保证外部调用者一定能按照本C文件中内在处理逻辑的要求传入数据。因此它们总需要进行边界检测。 1.3 类型与操作 1.3.1 基础类型及其操作和重定义 在1.2节中,我们讨论到数据可依据操作
24、差异,粗分为整型、浮点型、指针型。前两者存在位宽的差异,对整型还存在是否带符号的差异。这些细分的数据类型在C语言中存在关键词与之对应,我们称 为基础类型。本小节针对各个基础类型及其操作进行探讨。 粗分的3种类型的处理操作,在编译成汇编语句或机器指令时便存在差异。整型和浮点型在数据存储组织的方式上并不相同。 准确说,与浮点型相对应的类型是定点数据类型。它们的差异体现在浮点类型的数据,我们需要用指数和尾数两个内容来描述。而C语言中整型和指针型则都是定点数,且都是步长为1的定点数。在一 些简单的计算机指令集中,甚至没有针对浮点型数据的处理指令,而是通过多条指令去模拟浮点的计算。相对复杂的系统则带有硬
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 高级 编程 基于 模块化 设计 思想 语言 开发 html
链接地址:https://www.31doc.com/p-5518438.html