基本上每门语言都是用"helloworld"作为她的第一讲,C语言也不例外。
传统HelloWorld
传统的教材都是让你安装一种IDE集成环境,然后照例子敲入代码,按下ctrl+r之类的运行程序,感受一下运行的结果。HelloWorld程序如下:
- 在编辑器中敲入:
#include <stdlib.h>
#include <stdio.h>
void main() {
printf("Hello World\n");
}
- 按下Ctrl+r运行,当然我没有IDE环境,就用Linux终端代替一下了哈。如下:
- 当然这里有几个问题
- 当时你其实不知道这个程序是怎么被编译出来的。
- 当时你也不知道她在什么环境下运行的。
- 当时你肯定也不会去想,我这样写在其它计算机上能运行吗?
- 现在你肯定也没有考虑过,我能不能换个花样玩玩呢?
老生常谈
- 为何我说一个gcc程序和一个编辑器vim程序就是c的开发环境呢?而传统的书籍,特别是国内的c程序数据,以来就让你装一个大得一逼的IDE环境,以前是vb6.0几百MB,现在是vs2015之类的好几GB。而我所说的gcc、vim充其量就几个MB。因为IDE环境集成了太多功能了,编辑、编译、调试、自动补全、语法错误提示等等。我一般不用这样环境,初学者更不应该用,她会使你太依赖IDE,不了解原理,也少了很多乐趣。
- 其实所有程序都是编译器或者解释器读取一个纯文本中的代码,然后对要么生成目标二进制文件(编译型语言),要么直接就运行(解释型语言)了。对于C语言,当然是编译后,生成有特定格式二进制文件,在计算终端中运行,终端也是个程序,她是操作系统的一部分,操作系统也是程序(一两句话说不清这个关系>_<)。像C这样的语言,有语法解释,中间文件编译,目标程序链接这三部曲构成,当然还可以细分。语法解释主要是判断语法正确与否,然后是将
#include<stdlib.h>
这样的语句进行预处理生成最终的代码源文件,然后编译成与特定类型的操作系统相关的目标代码,这个目标代码其实可以在只要与当时生产的操作系统类型相同的操作系统中重复使用的,然后是链接成目标文件,这个文件大多数也可以在相同的操作系统中直接使用,但是由于有的程序会依赖特定的库,所以出表现出不能运行成功的情况罢了。比如刚才的helloworld程序在类Unix系统中,使用如下操作生成可执行文件:
然后是运行:
可以执行环境就是开启一个终端程序,然后./a.out
运行。
- 这段代码在绝大多数类Unix操作系统中都被编译运行,甚至这个二进制文件如果在内核相同操作系统也可以直接运行,而不需要重新编译。但是你可能注意到这个程序在编译是产生了警告,因为她的main()入口函数不是标准的,即不是可移植的。
守则一:一个负责的程序员编写程序要考虑可移植性
- 标准的可以移植性入口函数应该是这样的:
int main(int argc, const char *argv[]) { ... }
其中的形式参数的作用就是接收运行时传入的命令参数,后面我们会讨论到。
- 你真没想过怎么将这段代码换个花样玩?
这个不行,作为一个程序员,脑洞太小不好,脑洞需要大开的,有多大得开多大。
换着花样玩
使用全局复用
全局变量基本是没种语言都支持的,即为全局,即是对所有人可见
-
有一天你的老板说,现在我们生意不好,我们要改程序输出的内容,发发牢骚,而不是友好的问候。恰恰这要交给你来做,而且当时没有使用任何全局变量或着宏来代替这些输出内容,而且涉及的几十个文件可能是零零散散的分布在好几百个位置中,那么恭喜你,即使使用多文件文本替换也是挺麻烦的事,而且你总是要修改好几十个文件。如果当时使用全局变量也很好用修改的。
- 找一个这些文件都会引用的头文件,比如叫着utils.h,添加如下外部变量声明:
extern char g_SayHello[];
- 再任何一个.c文件都可以,但是推荐还是utils.c中,这个就是规范,下次,接手项目的人要修改哪个文件.h有类似上面的外部引用,自然而然的就在对应的.c中寻找其定义了:
char g_SayHello[] = "Kick the bucket!";
- 修改helloworld.c,其实这个名字不合适,最好加一个前缀,表面她含有入口函数,main_helloworld.c:
...
#include "utils.h"
...
int main(int argc, const char *argv[]) {
printf("%s\n", g_SayHello);
return EXIT_SUCCESS;
}
- 加入新的源文件一起编译、运行如下:
[zhoukai@zhoukai-MBPR:tmp]$ gcc main_helloworld.c utils.c
[zhoukai@zhoukai-MBPR:tmp]$ ./a.out
Kick the bucket!
[zhoukai@zhoukai-MBPR:tmp]$
使用宏复用
宏是c语言强有力的特性之一,不使用宏,你会做很多不讨喜的工作,但是滥用宏也会不讨喜,所有任何东西都有利有弊。就像没有坏人,就体现不出好人;没有细菌这样的微生物,满世界都是尸体一样。这个是一个哲学问题〜〜〜
- 继续上面的情景。你好不容易完成了需求,结果你老板说,我们需要在不同操作系统上让运行程序发不同的牢骚〜〜〜,虽然你心中有一万匹草泥马在狂奔,但为了工资忍了吧。显然上面的代码在不同的计算机上发布时,如果在编译后需要让程序运行时显现一些不同的东西,是需要修改源代码的,很不方便,做这样的事情,宏的优点就体现出来了,因为在编译时,可以给定参数定义一个宏让源代码相同的程序有不同的行为:
- 还是utils.h,添加如下宏:
//稍作解释,下面的宏定义是关联预编译条件宏使用,即如果没有定义宏,则定一个默认的宏
#ifndef SAY_HELLO
#define SAY_HELLO "Kick the bucket!"
#endif
- 修改main_helloworld.c:
int main(int argc, const char *argv[]) {
printf("%s\n", SAY_HELLO);
return EXIT_SUCCESS;
}
- 编译不同的行为的程序:
[zhoukai@zhoukai-MBPR:tmp]$ gcc main_helloworld.c utils.c -DSAY_HELLO=\"Drop\ dead\!\" -o a.out
[zhoukai@zhoukai-MBPR:tmp]$ gcc main_helloworld.c utils.c -DSAY_HELLO=\"Go\ to\ hell\!\" -o ab.out
[zhoukai@zhoukai-MBPR:tmp]$ gcc main_helloworld.c utils.c -DSAY_HELLO=\"Damn\ you\!\" -o abc.out
[zhoukai@zhoukai-MBPR:tmp]$ ./a.out
Drop dead!
[zhoukai@zhoukai-MBPR:tmp]$ ./ab.out
Go to hell!
[zhoukai@zhoukai-MBPR:tmp]$ ./abc.out
Damn you!
[zhoukai@zhoukai-MBPR:tmp]$
稍作解释,gcc最简单的使用就是
gcc <源文件名>
,然后就会生成默认的程序文件a.out,但是一般都希望又一个自定义的程序文件名,所以加上选项参数-o <目标名>
;在不加-c <源文件名>
的情况下都是直接完成三部曲,生成可执行文件的;其它还有很多可选的参数,后面慢慢说。
- 可以看到不同的程序使用同样的源代码编译的,但是编译时可以重定义宏,从而改变其行为。这里使用了可选参数-Dmacro="string"(加上''只是因为shell环境需要转义双引号),这样可以将编译时宏参数带入预处理,从而替换默认的宏参数。
结束语
为什么要写这么多,其实也不是高深的代码。目的就是一个,你在编写代码的时候是否比别人多想了一步呢?是否考虑过代码的可移植性呢?是否考虑过代码的可复用性呢?是否考虑过代码的可维护性呢?这些!都是一名合格的程序员应该考虑的问题。