现代C语言程序设计之C语言概述
1.1 信息技术发展趋势
目前信息技术主要经历了互联网、移动互联网以及以大数据、云计算、物联网、人工智能、区块链为代表的新兴技术三个阶段。
-
互联网
互联网从20世纪90年代逐渐兴起,主要是连接了世界各地的笔记本、台式机以及背后提供后台数据服务的服务器集群,其中绝大多数笔记本和台式机都运行着Windows,macOS系统,而服务器主要是以类Unix(CentOS,Ubuntu)占据主要市场优势,依靠互联网成长起来的公司有微软、谷歌、苹果、亚马逊、百度、阿里巴巴、腾讯等等。 -
移动互联网
移动互联网主要是连接了世界各地的移动设备(最典型的就是手机),它们绝大多数都运行着Android,IOS系统,依靠移动互联网公司成长起来的有美团、滴滴、小米、蚂蚁金服。 -
云计算、大数据
当然随着用户数据的爆发式增长,以海量数据为基础的大数据、云计算技术在BAT,Apple,Microsoft,Amazon,Google级别的超大型互联网公司有广泛的应用场景。 -
物联网
物联网会以手机为中枢,连接所有智能设备,包括智能家居、汽车、电视等嵌入式设备。 -
人工智能
人工智能(AI)作为当前最火爆的技术之一,国内的BAT纷纷开始根据自身核心业务布局,阿里巴巴最早将人工智能应用在电商和物流领域,而百度开发出了对话时人工智能操作系统Dueros和自动驾驶平台Apollo,而腾讯则是在游戏领域应用人工智能 -
区块链
而区块链经历了数字货币、智能合约、和超级账本三个发展阶段,区块链技术可以广泛使用在金融、供应链、物流、公共服务领域,解决互联网的信任问题。
目前蚂蚁金服已经有大量的区块链应用落地。
1.2 浅谈计算机系统架构
1.2.1 计算机硬件系统
现代计算机是由运算器、控制器、存储器、输入设备、输出设备五大部分组成,它们各司其职,完成了数据的计算、存储、传输任务,下面是它们各个组件的功能介绍:
-
CPU:也被称为*处理器,由运算器和控制器组成,其主要作用是数据计算(从内存中获取指令并执行后将结果返回给内存或者写入到磁盘)和控制其他设备(声卡显卡,鼠标键盘)协同工作。目前主流的CPU架构有基于Intel的复杂指令集的X86架构(32位和64位)和手机(ARM指令集),服务器(SPARC指令集)的精简指令集。CPU通过总线(数据总线、地址总线、控制总线)和外部进行交互。
-
内存:采用编址存储,其主要作用是用来作为程序的工作区,程序运行时其数据和指令会被加载到内存,断电后数据会丢失。
-
硬盘:其主要作用是永久性存储海量数据,分为机械式硬盘和固态硬盘两大类。
-
IO设备:其主要作用是用来数据的输入输出,常见的输入设备包括鼠标键盘,常见的输出设备包含声卡,显卡,打印机等等。网卡主要是负责数据在网络上的传输。
在后期程序排错时除了考虑程序本身的错误之外还要考虑计算机硬件故障(例如磁盘被写满,网络不通)等问题。
每个计算机组件的IO性能也各不相同,核心组件(CPU,内存,磁盘)都遵循容量越大,IO性能越差,如下图所示,汇总了它们的IO性能。
在后期程序优化时,通常需要考虑时间复杂度和空间复杂度的问题。
1.2.2 计算机软件系统
软件出现的作用是实现了人和计算机更好的交互,它是由开发人员采用某种编程语言来编写一系列的指令以及根据不同的业务逻辑产生的数据组成。
而软件通常被分为系统软件和应用软件
-
系统软件:系统软件有运行在服务器端的Unix,Linux,运行在PC桌面的macOS,Windows;运行在移动设备(手机、电视)的Android,ios;系统软件通常是负责管理硬件资源,同时给应用软件提供运行平台。
-
应用软件:应用软件有运行在PC桌面、手机端的淘宝、京东、微信、支付宝等等。
不同平台的软件有不同的交互方式:
- 服务器端:运维开发人员基于命令行的字符界面实现人机交互。
- PC桌面端:开发、设计、产品、普通用户通常是基于图形化界面实现人机交互。
- 移动端:普通用户通常是通过语音,手势触控实现人机交互。
1.2.3 常用应用的软硬件协作实现
计算机运行的功能流程如下图所示
- 聊天:应用程序监听键盘输入的数据,放到内存,然后传给网卡,通过网络传递给另外一个网卡,再从网卡传到内存,显示在显示器上。
- 听音乐:应用程序将音乐数据加载到内存之后,然后写到声卡上。
- 看视频:应用程序将视频数据加载到内存,然后写到显卡上。
- 读文档: 应用程序将磁盘中的文档数据加载到内存后显示到屏幕上。
- 写文档: 应用程序将内存中的数据写入到磁盘上。
1.3 程序和指令
- 程序:程序是为了完成某项特定任务(例如聊天,听音乐等等)而使用某种编程语言编写的一组指令序列
- 指令:令是对计算机进行程序控制的最小单位,由操作码和操作数组成,操作码指的是机器要执行什么操作(例如加减乘除),而操作数就是具体执行的对象(具体的数据以及存放数据的位置),所有指令的集合称为计算机的指令系统,常见的电脑指令系统有Intel X86指令集,常见的手机指令系统有ARM。因此手机上的应用程序不加修改是不能在电脑上直接运行,因为不同的指令集是不兼容的。
1.4 编程语言发展史及其应用场景
1.4.1 编译器与高级语言
首先明确一点,计算机最底层只能识别二进制(010101)的机器指令,那些由汇编语言或者是高级程序设计语言编写的应用程序只是为了方便开发人员理解和维护,这就需要将汇编语言和高级语言翻译成计算机能够理解的机器语言,而编译器或者解释器就是做这个工作的。它的出现让开发人员在编写程序时不用考虑底层硬件的差异性,只需要专注业务逻辑的实现即可。
1.4.2 编程语言发展史
任何事物都是从无到有,逐步发展壮大起来的,编程语言也是一样。
计算机程序设计语言经历了机器语言到汇编语言和高级程序设计语言三个阶段,其特点是使得程序员用编程语言开发、测试、部署应用程序越来越方便、高效。但是是以牺牲效率为代价,但是随着计算机硬件的发展,绝大多数应用场景的效率损失可以忽略不计。
-
机器语言
计算机发展的最早期,程序员编写程序采用二进制的指令(010010101)来实现的,而每种CPU都有各自不同的指令系统(SPARC/Intel X86/ARM),因此在不同的机器上使用不同的机器语言实现。其特点是性能特别高效,而面向机器编程也就意味着不能移植,需要手动处理底层硬件的差异性,而且二进制的指令难以理解和维护。 -
汇编语言
随着时代和计算机技术的发展,汇编语言和编译器的出现解决了机器语言需要记住非常多的二进制机器指令的难题,但是还是没有从根本上解决移植性的问题,只是将机器指令转换为易懂的英文单词,然后由编译器编译成机器指令,因为计算机终归揭底只能识别0001110100110机器指令,而且汇编语言是面向机器的,不同机器(SPARC/Intel X86/ARM)的汇编指令是不相同的。 -
高级程序设计语言
高级程序设计语言的高级之处体现在开发人员在编写程序时无需关心计算机底层硬件差异而只需要专注于业务模块实现即可。甚至是可以实现 一次编译,到处运行,这里以Java为例子:通过实现不同平台的JVM,编译生成的字节码文件可以在任意的JVM上运行。
高级语言通常都需要编译器或者是解释器将源码编译或者解释后执行。
高级语言主要分为面向过程和面向对象两种,其中典型的面向过程语言就是C,面向对象的编程语言有Java,C++等等。
1.4.3 编程语言应用场景
如果想知道目前主流的编程语言有哪些,可以访问tiobe 首页获取编程语言排行榜,如下图所示,从Tiobe官网获取最新(2018年10月)的编程语言排行榜的前20名,
根据Tiobe排行榜得知,Java/C/C++分别排在前三名, 随后紧跟着Python,C#,PHP,JavaScript等等,每种语言都有不同的应用场景和擅长的领域,如下表格所示。
编程语言 | 应用场景 |
---|---|
C | 硬件驱动、操作系统、系统软件 |
C++ | 系统软件、网络通讯、科学计算 、游戏 |
C# | Windows应用,Web应用、游戏 |
Java | 大型互联网应用(淘宝、天猫),Android,大数据 |
Python | 人工智能、机器学习、自动化运维、数据分析 、图形处理 |
PHP | 中小型网站开发 |
Objective-C/Swift | macOS,iPhone,iPad应用开发 |
JavaScript | 浏览器端、服务端、PC桌面 |
Go | 高并发、区块链 |
那么如果你作为一个野生程序员的初学者,面对如此之多的编程语言,到底应该先从哪门语言上车呢?如果你想深入的学习其他语言和架构相关的知识之前建议熟练掌握C语言。因为C++/Java/C#等编程语言都模仿了C语言。无论哪种语言,都是实现目标的工具,而不是目标本身。
1.4 C语言概述
C语言凭借其高效率、良好的移植性、功能强大的特性在操作系统、硬件驱动以及系统应用开发占据广阔的市场。
1.4.1 C语言发展简史
-
起源
1972年,贝尔实验室的Dennis Ritch和Ken Thompson在开发Unix操作系统时设计了C语言,该操作系统的90%以上的代码都是由C语言实现,后期的Linux,MacOS,Android,IOS都是基于Unix发展而来的。 -
标准
1987年Dennis Ritch和Brian Kernighan编写了The C Programming Language第一版是公认的C标准实现,而没有定义C库。
而后期ANSI/ISO先后于1990年、1999年和2011年发布了C90标准、C99标准和C11标准,该标准定义了C语言和C标准库。
1.4.2 C语言特性
C语言作为面向过程的高级程序设计语言,能够轻松的实现自顶向下的规划、结构化编程和模块化设计,这样使得程序结构更加简洁,可扩展性强以及更加容易维护。
而且C语言有着高效、功能强大(嵌套汇编)以及可移植性(标准库可移植)等优点,而且也存在着对系统平台库依赖严重,由于编程风格*,经验不足也会容易导致出错,编写代码实现周期长,同样的代码在不同的操作系统(或者编译器)下可能会有不同的表现等缺点。
1.4.3 C语言应用场景
C语言偏向操作系统、硬件驱动、底层应用、嵌入式应用开发,硬件驱动的绝大部分实现是由C语言和汇编语言实现的。
目前主流操作系统(Unix,Linxu,MacOS,Windows,Android,ios)的底层实现都是由C语言和部分汇编实现的。
C++,Java,Python,Swift的编译器或者解释器都是由C语言实现的。
Git,Nginx,Redis,MySQL都是使用C语言实现的,而且都是开放源代码的,可以通过阅读源码提升自己的设计和编码能力。
1.5 C语言开发环境搭建
1.5.1 C语言开发环境概述
目前主流操作系统(Windows,Linux,MacOS)都有完善的C语言集成开发环境,用于编辑、编译、调试、打包部署C程序,下面表格是各个操作系统对应的集成开发环境说明。
操作系统 | 开发工具 |
---|---|
Windows10 | Visual Studio2017 |
macOS10.14 | XCode10 |
Ubuntu18.04 | QT5.10 |
Windows作为世界上最流行的桌面操作系统,最新版本为Windows10 1809,VisualStudio作为Windows上最强大的集成开发环境,可以开发Windows软件,游戏,Web应用等等,最新版本为VisualStudio2017 15.8.7。
Ubuntu作为最受欢迎的桌面版Linux系统之一,推荐采用跨平台的集成开发环境QT来编写C/C++程序。
MacOS平台推荐使用XCode来编写C/C++程序。
除此以外还有些跨平台的C/C++ 开发工具,例如来自Jetbrains公司的CLion以及CodeBlocks也可以用来编写C/C++程序。
1.5.2 Windows10 安装Visual Studio 2017
-
从官网下载Visual Studio 2017 企业版的在线安装程序,如下图所示
-
启动安装程序
根据下载的路径查找安装程序并启动,启动完成后会看到如下图所示的程序
点击继续之后会自动下载组件列表 -
选择安装组件
这里选择通用Windows平台开发和使用C++桌面开发两个组件,然后点击右下角的安装,此时Visual Studio 2017会自动下载和安装所需组件,只需要耐心等待即可,如下图所示 -
首次启动设置
安装完成之后需要设置主题和开发模板,还要登录你的microsoft账号,如下图所示 -
启动界面
-
查看安装Visual Studio 2017的版本
点击帮助菜单,查看关于Microsoft Visual Studio的菜单
关于Visual Studio" title="帮助->关于Visual Studio" name="images/1542038607687.png" data-src="./images/1542038607687.png">
然后会弹出如下版本信息以及包含组件的窗口
1.5.3 VisualStudio2017集成GitHub
- GitHub概述
GitHub是一个免费、基于Git的开源项目托管站点,全世界许多公司的开源项目(例如Spring,Mybatis,阿里巴巴,美团点评等等)的源码都存放在GitHub,个人也可以上传自己的项目到GitHub,可以为自己的就业简历加分。
如果想要使用GitHub,首先需要访问官网,注册账号后即可使用。
1.下载GitHub插件
启动Visual Studio2017后,通过菜单工具->扩展和更新->
扩展和更新" title="工具->扩展和更新" name="images/1542038406895.png" data-src="./images/1542038406895.png">
然后选择扩展和更新的联机菜单来搜索GitHub插件,点击下载。如下图所示
下载界面如下图所示
下载成功之后会出现需要重启Visual Studio的提示
-
安装GitHub
在关闭Visual Studio之后,会自动弹出安装界面,如下图所示
根据提示,选择修改,如下图所示
安装GitHub插件,如下图所示
安装成功,如下图所示 -
登录GitHub
首先启动Visual Studio 2017,切换到团队资源管理器的视图,然后点击GitHub的连接,如下图所示
然后会出现登录界面,如下图所示
输入你在GitHub上注册的账号信息即可登录GitHub,了登录成功之后的界面如下图所示 -
创建项目
在团队资源管理器视图的GitHub下点击创建菜单,如下图所示
然后设置仓库名称、描述、本地路径以及Git忽略和许可证信息,如下图所示
创建完成之后的界面如下图所示
GitHub插件会自动创建仓库,并推送到GitHub网站,可以通过地址https://github.com/ittimeline/modern-c-programming 访问到该项目
- 创建解决方案
在团队资源管理器视图下创建解决方案
设置解决方案的项目模板,名称,路径信息,如下图所示
然后再切换到解决方案视图下查看解决方案
解决方案的目录结构说明
- 源文件用于存放c语言的源代码
- 头文件用于存放C语言库函数依赖的头文件
- 资源文件用于存放项目相关的资源
- 使用Visual Studio 2017实现C语言的helloworld
首先新建筛选器,并命名为Chapter1
然后在筛选器下新建helloworld.c文件
选择添加->新建项
选择基于C++的源文件
编辑运行界面如下图所示
helloworld源码如下所示
#include <stdio.h>
#include <stdlib.h>
/* 基于Visual Studio 2017实现的C语言版helloworld @author tony ittimeline@163.com @version 2018/11/19 15:04:07 */
void main() {
printf("hello world with Windows10 & Visual Studio 2017\n");
//按任意键退出程序,如果不加上 程序执行完成后就会退出
system("pause");
}
程序运行效果如下图所示
- 代码远程推送
首先切换到团队资源管理器,然后点击更改,如下图所示
然后输入代码说明的提交信息,如下图所示,再点击全部提交,会将代码提交到本地
再输入提交信息,并点击同步和推送后会将代码推送到GitHub
推送成功之后会看到如下提示信息
1.6 C语言helloworld实现
helloworld通常是用来形容学习一门编程语言的入门程序,如果你能够独立把helloworld独立编写、编译、运行成功,那么就算是迈入计算机编程的大门了。
1.6.1 helloworld编写
C语言的源文件后缀名是以.c结尾的,可以使用任意的文本编辑工具(例如记事本,Notepad++,UltraEdit,Visual Studio Code等等,)来编写简单的C程序
这里通过Windows10自带的记事本来编写源程序helloworld.c,源程序如下所示
#include <stdio.h>
void main(){
printf("欢迎跟光磊一起学习C语言");
}
1.6.2 编译、运行源程序
之前提到过,计算机底层只能识别二进制的机器语言,这里使用C语言编写的源程序无法试别,因此需要一个编译器来将其转换为二进制的机器指令。
各大操作系统(Windows,Linux,macOS)都有C语言编译器的实现,这里以Visual Studio 2017提供的编译器来编译C程序。
首先通过Windows10提供的搜索功能找到 VS 2017的开发人员命令提示符
然后使用cd命令切换到C语言源程序的所在路径,接着使用cl命令编译源程序,并运行编译、链接生成的可执行文件helloworld.exe,如下图所示
1.7 helloworld深入解析
1.7.1 #include预编译指令
如果想要调用函数的某个方法,必须首先包含该方法所在的头文件,例如调用printf()函数,就是位于系统目录C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\ucrt下的头文件stdio.h。如果该文件在当前目录下,使用#include "头文件"
,如果该头文件在系统目录下,则使用#include <>
。编译器查找当前目下的头文件时如果未找到,还会去系统目录下查找对应的头文件。
#include的作用实际上就是将包含的文件全部复制到原文件中,接下来编写一个C程序来演示#include的作用
首先在c-core-programming项目的头文件文件夹中新建(Ctrl+Shift+A)一个头文件guanglei.h,C语言中的头文件通常是以.h结尾,如下图所示
头文件的源码如下所示
#include <stdio.h>
/* #include预编译指令 @author tony ittimeline@163.com @version 2018/11/19 15:46:07 */
void main() {
printf("执行guanglei.h头文件中的内容\n"); //这里的\n表示换行
getchar(); //等待输入任意字符退出
}
然后在c-core-programming项目的源文件文件夹下创建源文件include.c,该源文件的
源码如下所示
//包含项目路径下的guanglei.h头文件
#include "guanglei.h"
然后运行项目后,发现如下错误
其原因是Visual Studio 2017中的单个解决方案下的程序只能有一个入口,即main方法,为了解决这个问题,需要把之前的源程序helloworld.c的main方法重命名为其他函数(例如helloworld_main)即可。
后续章节中给出的代码默认都是带main方法可以独立运行的,但是GitHub仓库中的代码需要确保只有一个main方法才可运行
再次运行程序是,程序运行结果如下图所示,也再次表明#include的内容会被复制到源文件中。
1.7.2 main函数
在Windows下,一个C语言的应用程序如果想独立运行,必须有main方法作为程序的入口,main方法的声明格式如下代码片段所示,所有的程序代码从main方法开始执行。
void main(){
}
其中void表示该函数没有返回值,而在Linux下,编译器强制要求main函数返回为int,代码片段如下所示
int main(){
}
1.7.3 return语句
return通常用在函数中,表示退出该函数,这也就意味着return之后的代码都不会被执行。而C语言的编译器比较松散,即使函数在定义时有返回值,而实现没有返回值也不会编译错误,但是却会得到一个错误的结果,应用案例(Chapter1/return.c)如下所示
#include <stdio.h>
/* 定义两个整数相减的函数,返回两个整数的差 */
int sub(int one, int two) {
return one - two;
printf("return之后的语句不会在执行了");
}
/* 定义两个整数相加的函数,返回两个整数的和 */
int add(int one, int two) {
return one + two;
}
void main() {
printf("sub方法调用的结果是%d\n", sub(20, 10));
printf("add方法调用的结果是%d\n", add(100, 200));
getchar();
}
1.7.4 断点和调试
在后期程序开发中可能会遇到各种各样的错误,这时我们就需要利用Visual Studio 2017提供的强大的调试功能来查找错误。
在调试之前首先得下断点,如下图所示
然后在运行程序时,程序会在下过断点的地方暂停,然后可以通过点击如下的箭头,选择每次执行一行,还是每次执行一个方法。
还可以通过Visual Studio 2017的菜单 调试->窗口查看内存、寄存器等信息
例如这里通过变量的内存地址查看对应存储的数据
应用程序(debug.c)如下所示
/* 调试方法 @author tony ittimeline@163.com @version 2018/11/19 16:42:07 */
void debug_method() {
printf("使用visual studio的逐过程调试\n");
}
/* 处理数据函数 @author tony ittimeline@163.com @version 2018/11/19 16:42:07 */
void handle_data_method() {
printf("处理数据函数\n");
}
/* visual studio的调试功能应用 通过断点查看内存,逐语句、逐方法调试程序 @author tony ittimeline@163.com @version 2018/11/19 16:42:07 */
void main() {
int age = 29;
printf("整数变量age的地址是%p\n", &age);
printf("整数变量age的值是%d\n", age);
debug_method();
handle_data_method();
system("pause");
}
1.7.5 注释
为了便于程序的后期维护,C语言支持单行注释//
和多行注释/**/
,注释用于描述程序的功能,编译器在编译时会将注释的内容删除,需要注意的是多行注释的内容不能嵌套,否则会发生编译错误,应用案例(comment.c)如下所示
#include <stdio.h>
#include <stdlib.h>
/* C语言注释和单行注释的使用 @author tony ittimeline@163.com @version 2018/11/19 17:15:07 */
void main() {
//打印输出helloworld的两种方式
printf("hello world \n"); // \n表示换行
system("echo hello world\n");
system("pause"); //程序暂停,按任意键退出
}
1.8 Windows常用命令
主流的操作系统(Windows,Linux(Ubuntu),MacOS)及大多数应用程序都提供了基于命令行和图形化界面两种交互方式,而移动端是采用手势触控、语音等进行交互。作为普通用户来讲,图形化界面容易上手,而且交互效果更加直观。
但是作为一个程序员来讲,应该去熟悉各个操作系统的命令行的使用,因为命令行相对图形化界面而言,绝大多数场景下使用更加高效。而且图形化界面本质是对命令行的封装,能用图形化界面实现的基本都可以采用命令行实现。而且在服务器端(CentOS,RedHat)基本上是不会安装图形界面。
Windows系统可以使用快捷键Windows+r调出运行,如下图所示
后输入cmd,便可以进入Windows下的命令行终端,如下图所示
Windows下常用的文件目录和系统应用相关的命令如下所示,只要开启了终端就可以在终端内通过使用文件目录相关和系统应用相关的命令实现快捷操作。
1.8.1 文件目录相关
命令名称 | 功能描述 |
---|---|
dir | 列出当前目录列表 |
cd | 切换目录(只能在根盘符(例如C盘)内切换) |
md | 创建目录 |
del | 删除文件 |
type | 显示文本文件内容 |
cls | 清除屏幕内容 |
exit | 退出终端 |
1.8.2 系统应用相关
命令名称 | 功能描述 |
---|---|
notepad | 记事本 |
calc | 计算器 |
mspaint | 画图 |
explore | 文件资源管理器 |
timedate.cpl | 日期和时间 |
cleanmgr | 磁盘清理 |
desk.cpl | 分辨率设置 |
powercfg.cpl | 电源设置 |
regedit | 注册表编辑器 |
msconfig | 系统配置 |
mstsc | 远程连接 |
firewall.cpl | 防火墙 |
appwiz.cpl | 添加或修改程序 |
tasklist | 查看进程列表 |
taskkill /f /im process.exe | 关闭指定进程 |
msinfo | 系统信息 |
sticky notes | 便签 |
ipconfig | 查看ip |
winver | 查看windows版本 |
echo | 显示文本内容 例如echo %path% 查看系统环境变量 |
dxdiag | DirectX诊断工具 |
1.9 system函数的使用
system函数是位于系统路径下的stdlib.h头文件中,用于调用各大操作系统的应用程序,如下应用案例所示(system.c)展示了读取用户输入的命令后通过system函数执行调用应用程序。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
/* 使用system函数执行用户输入的命令 @author tony ittimeline@163.com @version 2018/11/19 17:19:07 */
void main() {
printf("请输入你要执行的命令\n");
//定义数组保存用户输入的指令
char str[100] = { 0 };
//使用scanf函数读取用户输入的指令
scanf("%s", str);
//执行命令
system(str);
}
在使用scanf()函数时,需要定义常量#define _CRT_SECURE_NO_WARNINGS
来关闭编译器检查。
当使用system调用多个应用应用程序时会产生同步的效果,如下应用案例(system_sync.c)所示
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
/* system函数的同步特性 @author tony ittimeline@163.com @version 2018/11/19 17:21:07 */
void main() {
//system函数的同步特性
system("notepad"); //当打开记事本之后
Sleep(5000);//等待5秒钟
system("tasklist"); //只有自己手动关闭记事本后才会执行查看任务列表
system("taskkill /f /im notepad.exe");//并没有关闭记事本
}
而如果想要system函数异步执行,只需要传递start参数即可,如下应用案例(system_async.c)所示
#include <stdlib.h>
/* 如果想异步执行调用多个应用程序,可以加上start参数实现 但是这只针对系统路径下的应用程序有效 @author tony ittimeline@163.com @version 2018/11/19 17:23:07 */
void main() {
//加上start参数之后会发现记事本和计算器同时打开,此时异步执行
system("start notepad");
system("calc");
}
1.10 C语言编程流程
1.10.1 实现打开windows下的记事本,并在5秒之后关闭
实现思路:
首先使用sytem函数加上start参数异步打开记事本
然后借助Windows.h头文件的Sleep函数实现程序等待5秒
最后借助taskkill /f /im notepad.exe 命令关闭记事本。
完整应用案例(open_and_close_notepad.c)实现如下所示
#include <stdlib.h>
#include <Windows.h>
/* 打开记事本并在5秒之后就关闭 */
void main() {
//异步执行
system("start notepad");
Sleep(5000);
system("taskkill /f /im notepad.exe");
}
1.10.2 实现打开windows下的QQ,并根据用户输入的时间关闭QQ
实现思路
引入头文件
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <Windows.h>
首先定义一个打开QQ的函数openQQ,如下所示
,使用ShellExecuteA()函数可以避免system函数的同步问题,因为system函数的start参数只能作用于不带路径的程序。
/* 打开QQ @author tony ittimeline@163.com @version 2018/11/19 17:27:07 */
void openQQ() {
ShellExecuteA(0, "Open", "\"C:/Program Files (x86)/Tencent/QQ/Bin/QQScLauncher.exe\"", 0, 0, 1);
}
然后定义sleep函数,如下所示:使用printf函数给予用户提示输入指定的秒数,使用scanf函数读取用户输入的数据,使用Sleep()函数暂停
/* 暂停 @author tony ittimeline@163.com @version 2018/11/19 17:27:07 */
void sleep() {
int second = 0;
printf("请输入多久后关闭QQ\n");
scanf("%d", &second);
printf("QQ将在%d秒后关闭\n", second);
int i = 1;
while (i <= second) {
Sleep(1000);
printf("睡了%d秒\n", i);
i++;
}
}
最后定义关闭QQ的函数closeQQ,如下所示,只需要使用命令taskkill /f /im QQ.exe,即可关闭QQ。
/* 关闭QQ @author tony ittimeline@163.com @version 2018/11/19 17:27:07 */
void closeQQ() {
system("start taskkill /f /im QQ.exe");
}
最后在main函数中分别引用上述函数即可实现功能
/* 打开并根据用户输入的时间关闭QQ @author tony ittimeline@163.com @version 2018/11/19 17:27:07 */
void main() {
openQQ();
sleep();
closeQQ();
}
通过以上两个案例总结下C语言的编程流程:
- 需求分析:分析程序要实现哪些功能
- 概要设计:分解步骤,一步一步实现
- 编写代码:使用C语言相关特性详细实现
- 编译程序:使用编译器将源码编译链接成操作系统上的可执行文件
- 运行程序:加载到内存后执行
- 测试和调试程序:观察程序的运行过程,是否符合预期,如果不符合,需要更改。
- 维护和修改程序:根据不同的业务逻辑来调整程序
1.11 C语言的运行流程
C语言编写的程序如果没有使用特定平台的库,那么便可以运行在各大操作系统之上,这里以Ubuntu上的gcc编译器为例,了解C程序的运行流程。
首先编写源程序,推荐使用Ubuntu上的VisualStudio Code编写。
#include <stdio.h>
int main(){
printf("hello world with ubuntu18.04&visual studio code\n");
return 0;
}
C程序运行时首先会进行预编译,目前得知预编译主要是完成以下任务
1 将源文件中包含的头文件的内容复制到源文件中
2 将源文件中采用#define定义的常量值完成替换
3 将源文件中的注释删除。
安装gcc编译器,命令如下所示
$ sudo apt install -y gcc
预编译
然后使用gcc的-E选项预编译helloworld.c,-o是指定预编译输出的文件名,
预编译完成之后使用你喜欢的编辑器(例如Visual Studio Code)查看helloworld.E,便可更加直观的明白预编译具体是做什么事情了。
$ gcc -E helloworld.c -o helloworld.E
转汇编
C语言的源代码计算机是不能识别的,因此在运行之前需要转换成汇编语言,通过gcc编译器的-S选项来实现转换成汇编,命令如下:
$ gcc -S helloworld.c -o helloworld.S
转汇编
计算机最底层只能识别二进制(010101)的指令,编译器会将汇编指令转换成顶层的二进制机器码,可以使用gcc编译器的-c选项来实现编译二进制的机器指令,命令如下:
$ gcc -c helloworld.c -o helloworld.o
windows默认编译生成的是.obj后缀的二进制文件,linux默认生成的是.out后缀的二进制文件。
链接
在编译成机器码之后还不能直接运行,还需要一个链接的过程,就是将C语言的库函数,启动代码以及源码编译后的二进制文件打包到一起组成可执行的二进制文件。
链接不需要加任何参数即可,命令如下。
$ gcc helloworld.c -o helloworld.out
完整的命令如下所示
guanglei@ubuntu:~$ pwd
/home/guanglei
guanglei@ubuntu:~$ cd Desktop/
guanglei@ubuntu:~/Desktop$ ls
helloworld.c
guanglei@ubuntu:~/Desktop$ gcc -E helloworld.c -o helloworld.E
guanglei@ubuntu:~/Desktop$ gcc -S helloworld.c -o helloworld.S
guanglei@ubuntu:~/Desktop$ gcc -c helloworld.c -o helloworld.o
guanglei@ubuntu:~/Desktop$ gcc helloworld.c -o helloworld.out
guanglei@ubuntu:~/Desktop$ ./helloworld.out
hello world with ubuntu18.04&visual studio code
如果想查看你的程序引用了哪些系统库,可以使用ldd命令查看,如下所示
guanglei@ubuntu:~/Desktop$ ldd helloworld.out
linux-vdso.so.1 (0x00007ffcee79e000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7ea52cc000)
/lib64/ld-linux-x86-64.so.2 (0x00007f7ea58bf000)
C程序完整的流程图如下所示