我们今天使用的 Windows、Linux、Mac OS 等操作系统都是由一种叫做 Unix 的系统演化而来。Unix 作为80年代主流的操作系统,是整个软件工业的基础,是现代操作系统的开山鼻祖,C语言就是为 Unix 而生的。
Unix 和C语言的开发者是同一人,名字叫丹尼斯·里奇(Dennis MacAlistair Ritchie)。
?C语言和 Unix 之父——丹尼斯·里奇
2011年10月12日(北京时间为10月13日),丹尼斯·里奇去世,享年70岁。
1967年,26岁的丹尼斯·里奇进入贝尔实验室开发 Unix,并于 1969 年圣诞节前推出第一个试运行版本。这个时候的 Unix 是用汇编语言写的,移植性非常差,要想让 Unix 运行在不同型号的机器上,就得针对每个型号的机器重写一遍操作系统,这显然是一个不可能完成的任务。
为了提高通用性和开发效率,丹尼斯·里奇决定发明一种新的编程语言——C语言。紧接着,丹尼斯·里奇就用C语言改写了 Unix 上的C语言编译器,他的同事汤姆森则使用C语言重写了 Unix,使它成为一种通用性强、移植简单的操作系统,从此开创了计算机编程史上的新篇章,C语言也成为了操作系统专用语言。
到了80年代,C语言越来越流行,广泛被业界使用,从大型主机到小型微机,各个厂商群雄并起,推出了多款C语言的编译器。这些编译器根据行业和厂商自己的需求,进行了各种扩展,C语言进入了春秋战国时代,逐渐演变成一个松散杂乱的大家族。
为统一C语言版本,1983 年美国国家标准局(American National Standards Institute,简称 ANSI)成立了一个委员会,专门来制定C语言标准。1989 年C语言标准被批准,被称为 ANSI X3.159-1989 "Programming Language C"。这个版本的C语言标准通常被称为?ANSI C。又由于这个版本是 89 年完成制定的,因此也被称为?C89。
后来 ANSI 把这个标准提交到 ISO(国际化标准组织),1990 年被 ISO 采纳为国际标准,称为?ISO C。又因为这个版本是1990年发布的,因此也被称为?C90。
ANSI C(C89)与 ISO C(C90)内容基本相同,主要是格式组织不一样。
因为 ANSI 与 ISO 的C标准内容基本相同,所以对于C标准,可以称为 ANSI C,也可以说是 ISO C,或者 ANSI / ISO C。以后大家看到 ANSI C、ISO C、C89、C90,要知道这些标准的内容都是一样的。
目前常用的编译器,例如微软编译器、GCC、LLVM/Clang 等,都能很好地支持 ANSI C 的内容。
在 ANSI C 标准确立之后,C语言的规范在很长一段时间内都没有大的变动。1995 年C程序设计语言工作组对C语言进行了一些修改,增加了新的关键字,编写了新的库,取消了原有的限制,并于 1999 年形成新的标准——ISO/IEC 9899:1999 标准,通常被成为?C99。
但是这个时候的C语言编译器基本已经成熟,各个组织对 C99 的支持所表现出来的兴趣不同。当 GCC 和其它一些商业编译器支持 C99 的大部分特性的時候,微软和 Borland 却似乎对此不感兴趣,或者说没有足够的资源和动力来改进编译器,最终导致不同的编译器在部分语法上存在差异。
例如,ANSI C 规定,局部变量要在函数开头定义,而 C99 取消了这个限制,变量可以在任意位置定义,我们将在《C语言变量的定义位置以及初始值》一节中详细介绍。
C11 标准由国际标准化组织(ISO)和国际电工委员会(IEC) 旗下的C语言标准委员会于 2011 年底正式发布,支持此标准的主流C语言编译器有 GCC、LLVM/Clang、Intel C++ Compile 等。
C11 标准主要增加了以下内容:
2018 年,ISO/IEC 又发布了 C11 标准的修正版,称为 C17 或者 C18 标准。和 C11 标准相比,C17 并没有添加新的功能和语法特性,仅仅修正了 C11 标准中已知的一些缺陷。
截至到 2022 年 7 月份,新的 C2x 标准尚未发布,最新的 C 语言标准仍是 C17。
现有的教程(包括书籍、视频、大学课程等)大都是针对 C89 编写的,这是C语言的核心,后来的 C99、C11 新增的特性并不多,只是在“打补丁”。本教程虽然基于 C99 标准,但是绝大部分内容还是 C89 的,我会在 C89 和 C99 有差异的语法上给出重点说明。
通过上节《C语言的三套标准:C89、C99和C11》的介绍可以发现,C语言并没有一个官方机构,也不属于哪个公司,它只有一个制定标准的委员会,任何其他组织或者个人都可以开发C语言的编译器,而这个编译器要遵守哪个C语言标准,是 100% 遵守还是部分遵守,并没有强制性的措施,也没有任何约束。
换句话说,各个厂商可以为了自己的利益、根据自己的喜好来开发编译器。
这就导致了一个棘手的问题,有的编译器遵守较新的C语言标准,有的编译器只能遵守较老的C语言标准,有的编译器还进行了很多扩展。比如:
初学者经常会遇到这种情况,有些代码在微软编译器下能够正常运行,拿到 GCC 下就不行了,一堆报错信息;或者反过来,在 GCC 上能运行的代码在微软编译器下不能运行。这是因为不同的编译器支持不同的标准,并且每个编译器都进行了自己的扩展,假如你使用了微软编译器私有的扩展函数,那么拿到 GCC 下肯定是不支持的。
我们知道,大部分软件都需要先安装才能使用,例如 QQ、360、迅雷等,要先从网上下载一个安装包,然后安装到计算机的C盘或者D盘等。大部分程序还会在开始菜单或者桌面上生成一个快捷方式,用户只要点击快捷方式,就可以启动软件。
那么,一个程序为什么要安装呢?下载后直接使用不可以吗?下面我们就来分析一下。
不同的操作系统,安装软件的方法虽然不一样,但基本原理是相同的,主要的思想就是将程序的二进制可执行文件拷贝到某个目录,设置一些路径。如果程序运行时需要一些库,将这些库拷贝到系统目录即可。
程序的安装基本上要经过下面四个步骤:
1) 将程序的可执行文件从安装包所在的位置,拷贝到要安装的目录。
安装程序的时候,程序会给用户指定一个默认的安装目录,如果用户需要,也可以自定义安装,改变安装目录。一般所谓的“绿色软件”到此就安装结束了,可以使用了。
2) 如果有必要,可以向系统目录拷贝一些动态链接库(DLL)。(可选操作)
有的程序,比如大型游戏,可能需要很多动态链接库(DLL)的支持,这时候程序可能会将这些 DLL 拷贝到系统库的默认目录,Win7 下一般拷贝到C:\\Windows\System32\
(读者不妨打开该目录,会看到很多 DLL 文件)。
有些程序用到的 DLL 文件不是系统必需的,只能由程序自己使用,这样放在系统目录里就不太合适,安装的软件多了,就会造成系统臃肿,所以这些 DLL 会被拷贝到程序的安装目录。
3) 向系统注册表中写入相应的设置项,注册程序或者库的安装信息。(可选操作)
安装前,用户可能会对软件做一些设置,安装时,这些设置就会被写入注册表。另外,当安装程序将 DLL 文件拷贝到系统目录时,一些 DLL 还需要向系统注册,告诉系统我在这里,不然使用的时候可能会找不到。
4) 在开始菜单或者桌面上位程序创建快捷方式。(可选操作)
创建快捷方式主要是为了用户使用方便,有了快捷方式,就不用再到安装目录去启动程序了。
由此可见,程序在安装前后并没有什么区别,只不过是进行了一些设置,有的设置是程序运行所必须的,有的是为了让用户更加方便。
经过多年的发展,Windows 安装包的制作技术已经非常成熟,有不少现成的工具,无需我们自己编写代码,例如 Inno Setup、NSIS、Advanced Installer、Setup Factory、Smart Install Maker、Nullsoft、InstallShield,Advanced Installer 等。
软件安装的过程大部分是文件拷贝的过程,如果不需要写入注册表,不需要向不同的目录拷贝 DLL,那么使用 WinRAR 也可以制作简单的安装包,大家可以自行谷歌或百度。这里我们教大家使用 VS2022 制作安装包。
?
图 1 制作安装包的 C 语言项目
以图 1 中的 C 语言项目为例,编译后会生成一个名为“Demo.exe”的可执行文件,在 Demo 项目的本地文件夹中可以找到它,利用 VS2022 将 Demo.exe 制作成安装包需要经历以下几个步骤。
Installer Projects 是 VS2022 自带的一款制作安装包的插件,借助它,在 VS 中编写的程序可以打包成标准的 Windows 安装包。
安装 Installer Projects 的过程很简单,在 VS2022 菜单栏中依次选择“扩展 -> 管理扩展”,会弹出如下的窗口:
?
图 2 安装 Installer Projects 组件
搜索框中手动输入“Visual Studio Installer Projects”找到 Installer Projects 组件,点击“下载”。整个安装的过程非常简单,这里不再给出具体图示。
安装完成后,重启 VS2022,就可以使用 Installer Projects 组件了。
在 Demo 项目所在的解决方案中,再新建一个制作安装包的项目(也可以单独创建一个制作安装包的项目):
?
图 3 新建制作安装包的项目
在弹出的如下窗口中,选择创建“Setup Project”项目:
?
图 4 创建 Setup Project 项目
注意,如果找不到 Setup Project 项目,表明你的 VS2022 尚未安装 Installer Projects,返回第 1 步安装成功后再继续往下进行。
在弹出的如下窗口中,可以自定义项目的名称和存储位置:
?
图 5 自定义项目名称和存储位置
创建完成后,当前解决方案中就包含了两个项目,一个是 Demo 项目,另一个是 DemoSetup 项目:
?图 6 一个解决方案中包含两个项目
鼠标右击图 6 中的 DemoSetup 项目名,依次选择“View -> 文件系统”,可以看到 3 个文件夹:
在应用程序文件夹处单击鼠标右键选择“添加(Add) -> 文件夹(Floder)”,如下图所示:
?
图 7 应用程序文件夹中添加文件夹
我们不妨将文件夹命名为 Demo。这个文件夹就是程序安装后所在安装路径下生成的、包含程序相关组件的文件夹。例如,用户选择将程序安装到 D:\Program Files\ 目录,那么安装时会先在该目录创建一个名称为 Demo 的新文件夹,再将程序的所有组件拷贝到 Demo 中,最终程序的所有文件是在 D:\Program Files\Demo\ 目录下。
接下来,向 Demo 文件夹中添加程序要用到的所有文件,如下图所示:
?
图 8 应用程序文件夹中添加文件
添加好以后如下图所示:
?
图 9 添加好的文件
Demo.exe 是我们编译好的可执行文件,demo.ico 和 uninstall.ico 是图标,用来创建快捷方式。程序图标必须是 .ico 格式,可以通过 jpg、png 等常见图片格式在线转换。文章最后会给出两个图标的下载地址。
制作安装包之前,可以将程序使用到的所有文件都拷贝到一个目录下,这样就可以一次性添加到 Demo。
如果想为安装包增加卸载功能的话,还需要将C:\Windows\System32\msiexec.exe
也添加到 CDemo 中。
快捷方式存在于两个地方,分别是桌面和开始菜单,用户的程序菜单
用来存放开始菜单中的快捷方式,用户的桌面
用来存放桌面上的快捷方式。
在 "Demo.exe" 上单击鼠标右键选择创建 Demo.exe 的快捷方式,如下图所示:
?
图 10 添加 Demo.exe 的快捷方式
鼠标右击新生成的快捷方式(Shortcut to Demo.exe),选择“属性窗口”,下图所示:
?
图 11 快捷方式的属性
Name 表示快捷方式的名称,一般是程序名,这里重置为 Demo;Description 表示对快捷方式的说明,也就是鼠标悬浮时显示的文本;Target 表示快捷方式指向哪个可执行程序;Icon 表示快捷方式的图标,将其设置为 demo.ico 。
按照同样的方式为 msiexec.exe 也生成快捷方式,并将 Name 设置为“卸载Demo”,将 Icon 设置为“uninstall.ico”。
在“用户的程序菜单”下新建文件夹 Demo,将两个快捷方式移动到此文件夹下。然后再为 demo.exe 创建一个快捷方式,并移动到“用户的桌面”下。
这样,程序安装后在桌面和开始菜单中都有快捷方式,都可以启动了。
上面我们虽然添加了系统自带的卸载程序,并为卸载程序创建了快捷方式,但目前依然无法实现卸载功能,因为还不知道要卸载哪个程序。
在图 11 属性面板的基础上,鼠标点击项目名 DemoSetup,可以看到和此项目有关的属性,如下图所示:
?图 12 项目属性面板
ProductCode 是产品代码,即产品 ID,我们需要将它告诉卸载程序。每个程序的 ID 都不同,有了它,卸载程序才知道卸载哪一个软件。本例中,程序 ID 为 {FF9566C2-47D6-4B1F-95D4-67C4EDD2F7D6}。
在卸载程序 msiexec.exe 快捷方式的属性面板中,将 Arguments 的值设置为/x{FF9566C2-47D6-4B1F-95D4-67C4EDD2F7D6}
,如下图所示:
?图 13 实现卸载功能
这样,卸载程序就知道卸载哪个软件了。
在项目名称 DemoSetup 上点击鼠标右键,选择“属性”,弹出如下的对话框:
?
图 14 设置系统必备
点击“Prerequisites...”(系统必备),弹出如下窗口:
?
图 15 自选系统必备的组件
可以在这里选择程序需要的系统组件。我们的程序不需要任何组件,所以将【创建用于安装系统必备组件的安装程序】前的对勾取消,然后点击“确定”按钮,就设置完成了。
至此,我们的安装包就编辑完成了。
鼠标右击 DemoSetup 项目名,选择“生成”或者“重新生成”:
?
图 16 生成安装包
生成项目后,可以在 Debug 目录下看到 CDemoSetup.msi 文件,这就是我们制作好的安装包,双击运行,就可以安装我们的程序了。
安装完成后,就可以在开始菜单和桌面上看到快捷方式了,如下图所示:
?图 17 安装好的应用程序
点击 Demo,可以看到程序的运行结果;点击“卸载 Demo”,可以卸载 Demo 应用程序。
程序图标下载地址:http://pan.baidu.com/s/1pK0fwIR? 提取密码:iux9
一段C语言代码,在编译、链接和运行的各个阶段都可能会出现问题。编译器只能检查编译和链接阶段出现的问题,而可执行程序已经脱离了编译器,运行阶段出现问题编译器是无能为力的。
如果我们编写的代码正确,运行时会提示没有错误(Error)和警告(Warning),如下图所示:
?
图1:Dev?C++?的提示
?图2:VC 6.0 的提示
?图3:C-Free 5.0 的提示
对于 VS、GCC、Xcode 等,如果代码没有错误,它们只会显示“生成成功”,不会显示“0个错误,0个警告”,只有代码真的出错了,它们才会显示具体的错误信息。
错误(Error)表示程序不正确,不能正常编译、链接或运行,必须要纠正。
警告(Warning)表示可能会发生错误(实际上未发生)或者代码不规范,但是程序能够正常运行,有的警告可以忽略,有的要引起注意。
错误和警告可能发生在编译、链接、运行的任何时候。
例如,puts("C语言中文网")
最后忘记写分号;
,就会出现错误,如下图所示:
?
图4:VS2015 的错误提示
?图5:Dev C++ 的错误提示
?
图6:VC 6.0 的错误提示
?图7:C-Free 5.0 的错误提示
可以看出,C-Free 的错误提示信息比较少,不方便程序员纠错。VC 和 VS 的错误信息类似,只是中英文的差别。
下图分析了 VC 6.0 的错误信息:
?
图8:错误信息说明
翻译:源文件?E:\cDemo\hello.c 第5行发生了语法错误,错误代码是?C2143,原因是?'return' 前面丢失了 ';'。
我敢保证,你写的代码肯定会发生错误,一定要有分析错误的能力,这是一个合格的程序员必备的技能。
前面我们给出了一段最简单的C语言代码,并演示了如何在不同的平台下进行编译,这节我们来分析一下这段代码,让读者有个整体的认识。代码如下:
#include <stdio.h>
int main()
{
???????? puts("C语言中文网");
???????? return 0;
}
先来看第 4 行代码,这行代码会在显示器上输出“C语言中文网”。前面我们已经讲过,puts 后面要带( )
,字符串也要放在( )
中。
在C语言中,有的语句使用时不能带括号,有的语句必须带括号。带括号的称为函数(Function)。
C语言提供了很多功能,例如输入输出、获得日期时间、文件操作等,我们只需要一句简单的代码就能够使用。但是这些功能的底层都比较复杂,通常是软件和硬件的结合,还要要考虑很多细节和边界,如果将这些功能都交给程序员去完成,那将极大增加程序员的学习成本,降低编程效率。
好在C语言的开发者们为我们做了一件好事,他们已经编写了大量代码,将常见的基本功能都完成了,我们可以直接拿来使用。但是现在问题来了,那么多代码,如何从中找到自己需要的呢?一股脑将所有代码都拿来显然是非常不明智的。
这些代码,早已被分门别类地放在了不同的文件中,并且每一段代码都有唯一的名字。使用代码时,只要在对应的名字后面加上( )
就可以。这样的一段代码能够独立地完成某个功能,一次编写完成后可以重复使用,被称为函数(Function)。读者可以认为,函数就是一段可以重复使用的代码。
函数的一个明显特征就是使用时必须带括号( )
,必要的话,括号中还可以包含待处理的数据。例如puts("C语言中文网")
就使用了一段具有输出功能的代码,这段代码的名字是?puts,"C语言中文网"?是要交给这段代码处理的数据。使用函数在编程中有专业的称呼,叫做函数调用(Function Call)。
如果函数需要处理多个数据,那么它们之间使用逗号,
分隔,例如:
pow(10, 2);
该函数用来求10的2次方。
需要注意的是,C语言中的函数和数学中的函数不是同一个概念,不要拿两者对比。函数的英文名称是 Function,它还有“功能”的意思。大陆将 Function 翻译为“函数”,而台湾地区翻译为“函式”,读者要注意区分。
C语言自带的函数称为库函数(Library Function)。库(Library)是编程中的一个基本概念,可以简单地认为它是一些列函数的集合,在磁盘上往往是一个文件夹。C语言自带的库称为标准库(Standard Library),其他公司或个人开发的库称为第三方库(Third-Party Library)。
关于库的概念,我们已在《不要这样学习C语言,这是一个坑!》中进行了详细介绍。
除了库函数,我们还可以编写自己的函数,拓展程序的功能。自己编写的函数称为自定义函数。自定义函数和库函数在编写和使用方式上完全相同,只是由不同的机构来编写。
示例中第2~6行代码就是我们自己编写的一个函数。main 是函数的名字,( ) 表明这是函数定义,{ } 之间的代码是函数要实现的功能。
函数可以接收待处理的数据,同样可以将处理结果告诉我们;使用return
可以告知处理结果。示例中第5行代码表明,main 函数的处理结果是整数 0。return 可以翻译为“返回”,所以函数的处理结果被称为返回值(Return Value)。
第2行代码中,int 是 integer 的简写,意为“整数”。它告诉我们,函数的返回值是整数。
需要注意的是,示例中的自定义函数必须命名为 main。C语言规定,一个程序必须有且只有一个 main 函数。main 被称为主函数,是程序的入口函数,程序运行时从 main 函数开始,直到 main 函数结束(遇到 return 或者执行到函数末尾时,函数才结束)。
也就是说,没有 main 函数程序将不知道从哪里开始执行,运行时会报错。
综上所述:第2~6行代码定义了主函数 main,它的返回值是整数 0,程序将从这里开始执行。main 函数的返回值在程序运行结束时由系统接收。
关于自定义函数的更多内容,我们将在《C语言函数详解(包括声明、定义、使用等)》一章中详细讲解,这里不再展开讨论。
有的教材中将 main 函数写作:
void main()
{
???????? // Some Code...
}
这在 VC6.0 下能够通过编译,但在 C-Free、GCC中却会报错,因为这不是标准的 main 函数的写法,大家不要被误导,最好按照示例中的格式来写。
还有最后一个问题,示例中第1行的#include <stdio.h>
是什么意思呢?
C语言开发者们编写了很多常用函数,并分门别类的放在了不同的文件,这些文件就称为头文件(header file)。每个头文件中都包含了若干个功能类似的函数,调用某个函数时,要引入对应的头文件,否则编译器找不到函数。
实际上,头文件往往只包含函数的说明,也就是告诉我们函数怎么用,而函数本身保存在其他文件中,在链接时才会找到。对于初学者,可以暂时理解为头文件中包含了若干函数。
引入头文件使用#include
命令,并将文件名放在< >
中,#include 和 < > 之间可以有空格,也可以没有。
头文件以.h
为后缀,而C语言代码文件以.c
为后缀,它们都是文本文件,没有本质上的区别,#include 命令的作用也仅仅是将头文件中的文本复制到当前文件,然后和当前文件一起编译。你可以尝试将头文件中的内容复制到当前文件,那样也可以不引入头文件。.h
中代码的语法规则和.c
中是一样的,你也可以#include <xxx.c>,这是完全正确的。不过实际开发中没有人会这样做,这样看起来非常不专业,也不规范。
较早的C语言标准库包含了15个头文件,stdio.h 和 stdlib.h 是最常用的两个:
初学编程,有很多基本概念需要了解,本节就涉及到很多,建议大家把上面的内容多读几遍,必将有所收获。
本节开头的示例是一个C语言程序的基本结构,我们不妨整理一下思路,从整体上再分析一遍:
1) 第1行引入头文件 stdio.h,这是编程中最常用的一个头文件。头文件不是必须要引入的,我们用到了 puts 函数,所以才引入 stdio.h。例如下面的代码完全正确:
int main()
{
???????? return 0;
}
我们没有调用任何函数,所以不必引入头文件。
2) 第2行开始定义主函数 main。main 是程序的入口函数,一个C程序必须有 main 函数,而且只能有一个。
3) 第4行调用 puts 函数向显示器输出字符串。
4) 第5行是 main 函数的返回值。程序运行正确一般返回 0。
空格、制表符、换行符等统称为空白符(space character),它们只用来占位,并没有实际的内容,也显示不出具体的字符。
制表符分为水平制表符和垂直制表符,它们的 ASCII 编码值分别是 9 和 11。
- 垂直制表符在现代计算机中基本不再使用了,也没法在键盘上直接输入,它已经被换行符取代了。
- 水平制表符相当于四个空格,对于大部分编辑器,按下 Tab 键默认就是输入一个水平制表符;如果你进行了个性化设置,按下 Tab 键也可能会输入四个或者两个空格。
对于编译器,有的空白符会被忽略,有的却不能。请看下面几种 puts 的写法:
#include<stdio.h>
int main()
{
???????? puts("C语言");
???????? puts("中文网");
???????? puts
????????("C语言中文网");
????????puts
???????? (
???????? "C语言中文网"
???????? )
????????;????????
????????puts ???????? ("C语言中文网");
???????? puts???????? ( ???????? "C语言中文网" ???????? )???????? ;
???????? return 0;
}
运行结果:
看到输出结果,说明代码没有错误,以上几种 puts 的用法是正确的。puts
和()
之间、" "
和()
之间可以有任意的空白符,它们会被编译器忽略,编译器不认为它们是代码的一部分,它们的存在只是在编辑器中呈现一定的格式,让程序员阅读方便。
需要注意的是,由" "
包围起来的字符串中的空白符不会被忽略,它们会被原样输出到控制台上;并且字符串中间不能换行,否则会产生编译错误。请看下面的代码:
#include<stdio.h>
int main()
{
???????? puts("C语? ?言? ?中文网");
???????? puts("C语言
???????? 中文网");
???????? return 0;
}
第 5~6 行代码是错误的,字符串必须在一行内结束,不能换行。把这两行代码删除,运行结果为:
程序员要善于利用空白符:缩进(制表符)和换行可以让代码结构更加清晰,空格可以让代码看起来不那么拥挤。专业的程序员同样追求专业的代码格式,大家在以后的学习中可以慢慢体会。
对于初学者来说,本节涉及到的代码比较复杂,名字也不容易记住,大家只需要把代码复制到编译器,看一下运行效果就可以。本节重在演示C语言能做什么,而不是教授大家知识点(这些知识点也不是C语言的重点),所以,不理解的就放过吧,不会影响后面的学习。
C语言不总是“黑底白字”,它也可以是彩色的,可以调用Windows.h
头文件下的SetConsoleTextAttribute
函数改变文字和背景颜色。
调用形式为:
SetConsoleTextAttribute( HANDLE hConsoleOutput, WORD wAttributes );
hConsoleOutput
表示控制台缓冲区句柄,可以通过GetStdHandle(STD_OUTPUT_HANDLE)
来获得;wAttributes
表示文字颜色和背景颜色。
WORD
在windows.h
中定义,等同于unsigned short
,使用低4位表示文字(前景)颜色,高4位表示文字背景颜色,所以它的取值为xx
。x为一位16进制数,即0~F
都可以使用,可以随意组合。
0~F 分别代表的颜色如下:
0 = 黑色 ? ?8 = 灰色 ? ?1 = 淡蓝 ? ? ?9 = 蓝色
2 = 淡绿 ? ?A = 绿色 ? ?3 = 湖蓝 ? ? ?B = 淡浅绿 ?
C = 红色 ? ?4 = 淡红 ? ?5 = 紫色????? D = 淡紫 ?
6 = 黄色 ? ?E = 淡黄 ? ?7 = 白色????? F = 亮白
例如,将背景设置为淡绿色,文字设置为红色:
#include <stdio.h>
#include <windows.h>
int main(){
????????HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); ????????SetConsoleTextAttribute(hConsole, 0x2C );
????????puts("C语言中文网");
????????return 0;
}
运行结果:
如果只希望设置文字颜色,背景保持黑色,那么也可以只给出一位16进制数,例如:
SetConsoleTextAttribute(hConsole, 0xC ); //将文字颜色设置为红色 SetConsoleTextAttribute(hConsole, 0xF ); //将文字颜色设置为白色
再来看一个例子:
#include <stdio.h>
#include <windows.h>
int main(){
???????? HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
???????? SetConsoleTextAttribute(hConsole, 0xC );
???????? puts("红色文字");
???????? SetConsoleTextAttribute(hConsole, 0xF );
???????? puts("白色文字");
???????? SetConsoleTextAttribute(hConsole, 2 );
???????? puts("淡绿色文字");
???????? return 0;
}
运行结果:
对于初学者来说,本节涉及到的代码比较复杂,名字也不容易记住,大家只需要把代码复制到编译器,看一下运行效果就可以。本节重在演示C语言能做什么,而不是教授大家知识点(这些知识点也不是C语言的重点),所以,不理解的就放过吧,不会影响后面的学习。
C语言不仅可以编写“黑屏”的控制台程序,还可以编写拥有漂亮界面的Windows程序(GUI程序),只是绝大多数C语言教程都没有告诉你而已,让你以为C语言程序只能是“黑乎乎”的。
开发 GUI 程序需要选择的项目类型是“Win32项目”,其他操作和控制台程序一样。
第一个带界面的C语言程序:
#include <windows.h>
int WINAPI WinMain(
????????HINSTANCE hInstance,
????????HINSTANCE hPrevInstance,
????????LPSTR lpCmdLine,
????????int nCmdShow
){
????????int nSelect = MessageBox(NULL, "你好,欢迎来到C语言中文网!", "Welcome", MB_OKCANCEL | MB_ICONEXCLAMATION);
????????return 0;
}
运行结果:
你看,弹出一个带界面的欢迎窗口。控制台程序以main
为入口函数,Windows程序以WinMain
为入口函数。
上面的例子仅仅是一个提示框,下面我们来创建一个真正的窗口:
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(
????????HINSTANCE hInstance,
????????HINSTANCE hPrevInstance,
????????PSTR szCmdLine,
????????int iCmdShow
){
???????? static TCHAR szAppName[] = TEXT("HelloWin");
???????? HWND? ? ? ? ? ?hwnd;
???????? MSG????????? ? ? msg;
???????? WNDCLASS? wndclass;
? ? ? ? ?wndclass.style? ? ? ? ? ? ? ? ? = CS_HREDRAW | CS_VREDRAW;
? ? ? ? ?wndclass.lpfnWndProc? ? ?= WndProc;
???????? wndclass.cbClsExtra? ? ?? ?= 0;
???????? wndclass.cbWndExtra? ? ? = 0;
???????? wndclass.hInstance? ? ? ? ??= hInstance ;
? ? ? ? ?wndclass.hIcon? ? ? ? ? ? ? ?? = LoadIcon (NULL, IDI_APPLICATION);
???????? wndclass.hCursor? ? ? ? ? ?? = LoadCursor (NULL, IDC_ARROW);
???????? wndclass.hbrBackground? = (HBRUSH) GetStockObject (WHITE_BRUSH);
???????? wndclass.lpszMenuName??= NULL ;
???????? wndclass.lpszClassName? = szAppName ;
???????? if( !RegisterClass(&wndclass) ){
????????????????MessageBox(
????????????????????????NULL,
????????????????????????TEXT("This program requires Windows NT!"),
????????????????????????szAppName,
????????????????????????MB_ICONERROR
????????????????);
????????return 0 ;
????????????????}
????????// creation parameters
????????hwnd = CreateWindow(
????????????????szAppName, // window class name
????????????????TEXT("Welcome"), // window caption
????????????????WS_OVERLAPPEDWINDOW, // window style
????????????????CW_USEDEFAULT, // initial x position
????????????????CW_USEDEFAULT, // initial y position
????????????????500, // initial x size
????????????????300, // initial y size
????????????????NULL, // parent window handle
????????????????NULL, // window menu handle
????????????????hInstance, // program instance handle
????????????????NULL
????????);
????????ShowWindow (hwnd, iCmdShow) ;
????????UpdateWindow (hwnd) ;
????????while( GetMessage(&msg, NULL, 0, 0) ){
????????????????TranslateMessage(&msg);
????????????????DispatchMessage (&msg);
????????}
????????return msg.wParam;
????????}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
????????HDC hdc;
????????PAINTSTRUCT ps;
????????RECT rect;
????????switch (message){
????????????????case WM_PAINT:
????????????????????????hdc = BeginPaint (hwnd, &ps) ;
????????????????????????GetClientRect (hwnd, &rect) ;
????????????????????????DrawText(
????????????????????????????????hdc,
????????????????????????????????TEXT("你好,欢迎来到C语言中文网"),
????????????????????????????????-1,
????????????????????????????????&rect,
????????????????????????????????DT_SINGLELINE | DT_CENTER | DT_VCENTER
????????????????????????) ;
????????????????????????EndPaint (hwnd, &ps) ;
????????????????????????return 0 ;
????????????????case WM_DESTROY:
????????????????????????PostQuitMessage(0) ;
????????????????????????return 0 ;
????????}
????????return DefWindowProc(hwnd, message, wParam, lParam) ;
}
运行结果:
你看,创建一个简单的窗口就需要这么多C语言代码,根本记不住,学起来太吃力了。
实际开发中,我们一般不使用这种粗暴的方式创建GUI程序,而是使用 C/C++ 的界面库,它们已经把这些繁杂琐碎的代码给封装好了,让我们“站在了巨人的肩上”,例如针对C语言的 GTK,针对C++的 MFC、Qt、Duilib 等。