前言
对于编程语言来说,经常看到有因为各自支持的语言阵营而互怼的,其实根本没那个必要,都只是一种工具而已。当多数主流语言都会使用时也许你就不会有偏见了,本质不过都是用来描述计算机的一个任务,只是每门语言设计时考虑的侧重点不一样而已。大家好不要停留在语言层面去争执,不如把时间花在计算机实现原理和结构的本质上,这样更能理解编程语言每一行描述的东西在计算机是干什么的。本系列将总结现在IT领域主流的那些编程语言的相关知识。
关于C语言
C语言是经典的语言,很多其他语言的运行环境也是用C来写的,对于写程序的人则能不懂C语言呢!提到C首先必然会让人关联到指针,当年在大学让你困惑的指针却是C语言威力无穷的基础。C语言可能从更高层面的设计和编写效率上有所欠缺,但却足够经典且容易操控底层。指针虽然风险不小,但却十分强大。此外ANSI C也增强了C程序在不同操作系统的迁移性,下面列一些C语言的一些基础知识。
翻译阶段
编写好的C程序需要先编译成可执行的机器指令才能运行,这便是翻译工作。翻译的主要步骤是编译和链接,编译就是源代码到目标代码,而链接是将各个目标文件链接起来从而形成一个可执行的程序,当然链接器也会引入被程序所用到的所有标准C函数库的函数。有时编译过程还会将预处理作为一个阶段,它主要是对源文件进行一些处理,比如将#define替换成实际值、将#include指定的文件内容填充进来。下面是使用gcc来编译并链接的例子,经过编译和链接后得到可执行程序,这两个步骤通过gcc来完成,命令为gcc hello.c -o hello
,终运行./hello会输出“hello world”。
#include<stdio.h>
int main()
{
printf("hello world");
}
复制代码
假如我们编写了多个c文件,则编译器会分别编译成多个obj目标文件,然后再通过链接器将所有目标文件链接起来生成可执行文件。
关于扩展名
注意windows系统的目标文件扩展名为obj,一般链接完成后也不会被删除。而unix-like系统的目标文件扩展名为o,一般在链接完成后会被删除。windows系统的可执行文件扩展名为exe,而unix-like系统的可执行文件名可以任意命名。此外,C语言源文件一般后缀为c,而头文件后缀为h,虽然没有强制规定但大家都会去遵守这个约定。
关于编译器
翻译阶段需要将C语言代码变为可执行程序,这些工作由C编译器完成。C编译器也有很多,常见的如下:
- GCC,GCC即(GNU Compiler Collection,GNU编译器套件),由GNU开发的GPL许可的编译器自由软件。刚开始只作为C语言编译器,但后来发展成多种语言编译器,比如C、C++、Java、Android、Objective-C和Fortran等等。现在很多unix-like操作系统自带GCC,将其作为标准编译器。
- MS C,与微软的Visual Studio一起集成发布,由微软提供的一套完整的集成开发环境,编译后能在微软的所有操作系统上运行。比如VS一般会使用CL编译器。
- Clang,它是一个基于LLVM的C/C++/Objective-C轻量级编译器,常用于Mac系统下。
- Turbo C,这是一个比较流行的C编译器,小巧快速。
- cc,即C Compiler,这是一个unix系统古老的编译器,很多经典书籍会看到这个编译器。为保持兼容,现在的linux系统会将cc作为一个符号连接指向gcc,即/usr/bin/cc -> gcc。
gcc编译例子
以linux系统的gcc为例,看几个编译例子。假如hello.c的代码如下,
#include<stdio.h>
int main()
{
printf("hello world");
}
复制代码
我们直接使用如下的gcc命令对其进行编译,而且不带任何参数,此时将生成一个名为out.a的可执行文件,通过./a.out
能够输出“hello world”。
gcc hello.c
复制代码
假如添加name.h/name.c和adder.h/adder.c两对头文件和源文件,而且将hello.c稍作修改,三个文件代码分别如下。
//name.h
char* get_name();
//name.c
char* get_name() {
char* name = "seaboat : ";
return name;
}
复制代码
//adder.h
int add(int a, int b);
//adder.c
int add(int a, int b) {
return (a + b);
}
复制代码
//hello.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include"adder.h"
#include"name.h"
int main()
{
char* name = get_name();
char* hello = "hello world";
char* output = (char*)malloc(strlen(hello) + strlen(name));
sprintf(output, "%s%s", name, hello);
printf("%s\n", output);
int a = 1;
int b = 3;
printf("a + b = %d\n", add(a, b));
}
复制代码
则通过如下的命令可以对多个源文件进行编译和链接,终生成一个名为a.out的可执行文件。当我们通过./a.out执行可执行文件时,它将输出“seaboat : hello world a + b = 4”。
gcc name.c adder.c hello.c
复制代码
我们还可以通过下面两个命令对name.c和adder.c两个文件编译生成目标文件,分别为adder.o和name.o。然后再通过下面第三行命令来编译hello.c源文件,编译完后它会自动与name.o和adder.o两个目标文件进行连接。
gcc -c adder.c
gcc -c name.c
gcc name.o adder.o hello.c
复制代码
此外,还能够通过下面的命令来给多个源文件进行编译并生成各自对应的目标文件,这意味着不对它们进行链接。
gcc -c name.c adder.c hello.c
复制代码
对于多个目标文件,如果要将他们链接可以通过下面的命令,便能够生成可执行文件。
gcc name.o adder.o hello.o
复制代码
如果我们想对生成的可执行文件进行命名,那么可以通过下面行命令来实现,将生成一个名为hello的可执行文件。类似地,也可以对多个目标文件进行连接时指定可执行文件名,如下面第二行命令,将生成一个名为hello2的可执行文件。
gcc name.c adder.c hello.c -o hello
gcc name.o adder.o hello.o -o hello2
复制代码
关于字符集
编写C语言时源代码可以包括如下字符集:
- 英语大写小写字母
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
复制代码
- 十进制的阿拉伯数字
0 1 2 3 4 5 6 7 8 9
复制代码
- 其它符号
! " # % & ' () * + , - . / :
; < = > ? [ ] \ ^ _ { } | ~
复制代码
- 空白符
空格、水平制表符、垂直制表符、换行、换页
复制代码
关于注释
C语言提供的注释方式有两种:以/*
开始而以*/
结束来注释多行代码,以//
开始来注释单行代码。一般来说对源码中进行注释则意味着编译时会被预处理器清除掉,用空格来替代。
/*
种注释方式
*/
//第二种注释方式
复制代码
关于标识符与关键词
标识符就是我们开发人员对变量、函数、类型、结构体、宏等等的起名,C语言也要求我们要按照它的规定来取名。按照规定,标识符可以由英文大小写字母(A~Z, a~z)、阿拉伯数字(0~9)、和下划线(_)组成。需要注意以下几点:
- 要求不能以字母开头。
- C语言对大小写字母敏感。
- C语言不会对标识符的长度进行限制,但标准允许编译器忽略第31位以后的字符,具体截取前多少位则由不同的编译器来实现,当截取的字符串相同时则认为是同一个标识符。
- 标识符不应该乱取名,尽量要让标识符名字具有相应的意义。
当然C语言还保留了32个特殊的关键词,我们命名的标识符不能与它们相同,否则就会报错。这32个关键词如下:
关键字 | 说明 |
---|---|
auto | 声明自动变量 |
break | 跳出当前循环 |
case | 开关语句分支 |
char | 声明字符型变量或函数 |
const | 声明只读变量 |
continue | 结束当前循环,开始下一轮循环 |
default | 开关语句中的“默认”分支 |
do | 循环语句的循环体 |
double | 声明双精度变量或函数 |
else | 条件语句否定分支(与if连用) |
enum | 声明枚举类型 |
extern | 声明变量或函数是在其它文件中声明 |
float | 声明浮点型变量或函数 |
for | 一种循环语句 |
goto | 无条件跳转语句 |
if | 条件语句 |
int | 声明整型变量或函数 |
long | 声明长整型变量或函数 |
register | 声明寄存器变量 |
return | 子程序返回语句(可以带参数,也可不带参数) |
short | 声明短整型变量或函数 |
signed | 声明有符号类型变量或函数 |
sizeof | 计算数据类型或变量长度(所占字节数) |
static | 声明静态变量 |
struct | 声明结构体变量或函数 |
switch | 用于开关语句 |
typedef | 用以给数据类型取别名 |
unsigned | 声明无符号类型变量或函数 |
union | 声明共用体类型 |
void | 声明函数无返回值或无参数,声明无类型指针 |
volatile | 说明变量在程序执行中可被隐含地改变 |
while | 循环语句的循环条件 |
作者简介:笔名seaboat,擅长人工智能、计算机科学、数学原理、基础算法。书籍:《Tomcat内核设计剖析》、《图解数据结构与算法》、《人工智能原理科普》。
专注于人工智能、读书与感想、聊聊数学、计算机科学、分布式、机器学习、深度学习、自然语言处理、算法与数据结构、Java深度、Tomcat内核等。