C的编程机制

C具有良好的可移植性,可以在很多操作系统下使用:UNIX、Linux、MS-DOS、macOS、Windows…在不同的操作系统(OS)或者环境下写C的编译器和过程一般会有所不同

在C语言编写过程中,编写的内容——源代码一般以文本文件存储在以.c为文件拓展名的文件中。比如:example.c,在文件名中,英文句号(.)前面部分称为基本名,英文句号(.)后面部分是文件拓展名

C的基本策略是:用程序把源代码文件——>可执行文件(包括可直接运行的机器语言代码),典型的步骤是使用编译——>链接——>运行,其中编译器源代码转换成中间代码链接器中间代码其他代码合并,生成可执行文件,这种策略使得C可以方便地对程序进行模块化设计。

C模块化的好处是:对于一个项目,可以先独立编译单独的模块,然后再用链接器合并已编译的模块。如果需要更改某个模块,不必因此重新编译其他模块。此外链接器还将你编写的程序和预编译的库代码进行合并。

C编译器和链接器

UNIX C

C最开始为UNIX系统开发而生,这里只放个UNIX下使用C的过程图

UNIX C

GNU&&LLVM

关于GNU和LLVM可以看目录[参考资料]中的维基百科

GNU编译器集合也就是GCC,其中包含C的编译器GCC C,在UNIX、Linux、Windows等操作系统下都有可以使用的GCC编译器,当然也包括C的编译器GCC C,它跟随C标准的改动,对最新的新标准支持较好。一般地,使用gcc命令调用GCC C编译器,而很多使用gcc的系统又使用cc作为gcc的别名。

LLVM项目可以说是cc的另一个替代品,它使用Clang编译器处理C(使用clang调用),Clang对最新的C标准的支持也很好。

对于gcc和clang:

1
2
3
4
5
6
7
8
# 显示所使用的编译器及其版本
$ cc-v
# GCC最基本的用法:(options是所需参数、filenames是文件名)
$ gcc [options][filenames]
# gcc/clang选择调用不同C标准,比如
$ gcc -std=c99 inform.c #调用C99标准
$ gcc -std=clx inform.c #调用C11之前草案标准
$ gcc -std=c11 inform.c #调用C11标准

C中的令牌(Tokens)

令牌是程序的最小元素,令牌的类型有:关键字标识符常量字符串运算符等。

关键字(Keywords)

关键字是C中预定义的保留字,每个保留字均有特定功能或与特定功能相关联,它们对编译器有特殊意义。

C中共有32个关键字:

/ / / /
auto double int struct
break else long switch
case enum register typedef
char extern return union
continue for signed void
do if static while
default goto sizeof volatile
const float short unsigned

标识符(Identifiers)

C中标识符用于明明变量、函数、数组等,可以由由用户定义这些标识符,由字母、数字和下划线组成,关键字(Keywords)不能作为标识符

标识符命名规则:

  • 必须以字母或者下划线开头
  • 只能使用字母、数字和下划线,不允许使用其他特殊字符、标点符号
  • 不能包含空格
  • 不应该是C的关键字
  • 最长为31个字符

常量(Constant)

常量是固定值,就像是常规的变量,在定义后不能进行修改并且在程序执行期间不会改变。这些固定的值,又叫做字面量

常量可以是任何基本数据类型,比如:

  • 整数常量
  • 浮点常量
  • 字符常量
  • 字符串字面值
  • 枚举常量

下面是常量的数据类型的一些实例:

整数常量

整数常量可以是八进制、十进制、十六进制的常量

前缀指定基数:0x/0X表示十六进制,0 表示八进制,十进制一般不带前缀

后缀是U和L的组合:U是无符号整数(unsigned),L是长整数(long)U和L可以是大写/小写的任意顺序,比如:

1
2
3
4
5
6
7
8
9
10
11
12
85         /* 十进制 */
0213 /* 八进制 */
0x4b /* 十六进制 */
30 /* 整数 */
30u /* 无符号整数 */
30l /* 长整数 */
30ul /* 无符号长整数 */
212 /* 合法的 */
215u /* 合法的 */
0xFeeL /* 合法的 */
078 /* 非法的:8 不是八进制的数字 */
032UU /* 非法的:不能重复后缀 */

浮点变量

浮点常量由整数部分、小数点、小数部分和指数部分组成,使用小数或者指数形式来表示。

小数形式表示:必须包含小数点、指数或者同时包含两者;

指数形式表示:必须包含整数部分、小数部分,或同时包含两者,带符号的指数使用e或者E引入,比如:

1
2
3
4
5
3.14159       /* 合法的 */
314159E-5L /* 合法的 */
510E /* 非法的:不完整的指数 */
210f /* 非法的:没有小数或指数 */
.e55 /* 非法的:缺少整数或分数 */

字符常量

字符常量使用单引号括起来,比如:’s’可存储在char类型的简单变量中,字符常量可以是一个普通字符(如’s’)、一个转义序列(如’\n’)或者一个通用字符(如’\u02C0’),在C中,一些特定的字符前面有反斜杠’\‘时,它们就会变成有特殊含义的转义序列(escape sequence)。转义序列用于代表难以表
示或无法输入的字符,像\t代表Tab键,\b代表Backspace键,比如:

转义序列 含义
\ \ 字符
\‘ ‘ 字符
\“ “ 字符
? ? 字符
\a 警报铃声
\b 退格键
\f 换页符
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
\ooo 一到三位的八进制数
\xhh . . . 一个或多个数字的十六进制数

字符串常量

字符串字面值或常量是括在双引号 “” 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。

可以使用空格做分隔符,把一个很长的字符串常量进行分行,比如下面这三种实际显示就是相同的:

1
2
3
4
5
6
7
# 第一种
"hello,world"
# 第二种
"hello,\
dear"
# 第三种
"hello, " "w" "orld"

定义常量

在 C 中,有两种简单的定义常量的方式(#define和const关键字),定义常量时经常以大写英文字母形式。

下面是两个实例:

1.使用 #define 预处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

#define LENGTH 10
#define WIDTH 5
#define NEWLINE '\n'

int main()
{

int area;

area = LENGTH * WIDTH;
printf("value of area : %d", area);
printf("%c", NEWLINE);

return 0;
}

2.使用 const 关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main()
{
const int LENGTH = 10;
const int WIDTH = 5;
const char NEWLINE = '\n';
int area;

area = LENGTH * WIDTH;
printf("value of area : %d", area);
printf("%c", NEWLINE);

return 0;
}

上面两个例子的结果一样,输出结果都是:

1
value of area : 50

字符串(String)

字符串是以null空字符“\0”结尾的一维字符数组,此空字符表示字符串已结束,字符串始终用双引号(“ ”)引起来,C 编译器会在初始化数组时,自动把 ‘\0’ 放在字符串的末尾。在C语言中声明字符串,比如:

1
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

也可以写成下面的:(数组初始化规则)

1
char greeting[] = "Hello";

上面的字符串在内存中可以如下图所示:

C中操作字符串的函数:

序号 函数 & 目的
1 strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
2 strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1); 返回字符串 s1 的长度。
4 strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5 strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>

int main ()
{
char str1[12] = "Hello";
char str2[12] = "World";
char str3[12];
int len ;

/* 复制 str1 到 str3 */
strcpy(str3, str1);
printf("strcpy( str3, str1) : %s\n", str3 );

/* 连接 str1 和 str2 */
strcat( str1, str2);
printf("strcat( str1, str2): %s\n", str1 );

/* 连接后,str1 的总长度 */
len = strlen(str1);
printf("strlen(str1) : %d\n", len );

return 0;
}

输出结果:

1
2
3
strcpy( str3, str1) :  Hello
strcat( str1, str2): HelloWorld
strlen(str1) : 10

运算符

运算符是一种告诉编译器执行特定数学或逻辑操作的符号。C中提供了以下类型的运算符:

  • 算数运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 杂项运算符

算数运算符

给定变量AB,C中常用的算数运算符罗列如下:

运算符 描述 结果
+ 两操作数相加 A+B
- 前一个操作数减去后一个操作数 A-B
* 两操作数相乘 A*B
/ 两操作数相除(分子/分母) A/B
% 取模,整除后的余数 A/B后的余数
++ 自增运算符,整数值+1 A+1或者B+1
自减运算符,整数值-1 A-1或者B-1

特别地,对于a++和++a,相同点都是给a的值+1,不同点是a++是先赋值再+1,而++a则是先+1再赋值

下面是个实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
int main()
{
int c;
int a = 10;
c = a++;
printf("先赋值后运算:\n");
printf("Line 1 - c 的值是 %d\n", c );
printf("Line 2 - a 的值是 %d\n", a );
a = 10;
c = a--;
printf("Line 3 - c 的值是 %d\n", c );
printf("Line 4 - a 的值是 %d\n", a );
printf("先运算后赋值:\n");
a = 10;
c = ++a;
printf("Line 5 - c 的值是 %d\n", c );
printf("Line 6 - a 的值是 %d\n", a );
a = 10;
c = --a;
printf("Line 7 - c 的值是 %d\n", c );
printf("Line 8 - a 的值是 %d\n", a );
}

输出结果为:

1
2
3
4
5
6
7
8
9
10
先赋值后运算:
Line 1 - c 的值是 10
Line 2 - a 的值是 11
Line 3 - c 的值是 10
Line 4 - a 的值是 9
先运算后赋值:
Line 5 - c 的值是 11
Line 6 - a 的值是 11
Line 7 - c 的值是 9
Line 8 - a 的值是 9

关系运算符

给定变量AB,C中常见的关系运算符有:

运算符 描述 实例
== 检查两个操作数的值是否相等,如果相等则条件为真 (A == B)
!= 检查两个操作数的值是否相等,如果不相等则条件为真 (A != B)
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真 (A > B)
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真 (A < B)
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真 (A >= B)
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真 (A <= B)

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>

int main()
{
int a = 21;
int b = 10;
int c ;

if( a == b )
{
printf("Line 1 - a 等于 b\n" );
}
else
{
printf("Line 1 - a 不等于 b\n" );
}
if ( a < b )
{
printf("Line 2 - a 小于 b\n" );
}
else
{
printf("Line 2 - a 不小于 b\n" );
}
if ( a > b )
{
printf("Line 3 - a 大于 b\n" );
}
else
{
printf("Line 3 - a 不大于 b\n" );
}
/* 改变 a 和 b 的值 */
a = 5;
b = 20;
if ( a <= b )
{ printf("Line 4 - a 小于或等于 b\n" );
}
if ( b >= a )
{
printf("Line 5 - b 大于或等于 b\n" );
}
}

输出结果为:

1
2
3
4
5
Line 1 - a 不等于 b
Line 2 - a 不小于 b
Line 3 - a 大于 b
Line 4 - a 小于或等于 b
Line 5 - b 大于或等于 b

逻辑运算符

给定变量AB,C中常见的逻辑运算符有:

运算符 描述 实例
&& 称为逻辑与运算符,如果两个操作数都非零,则条件为真 (A && B)
|| 称为逻辑或运算符,如果两个操作数中有任意一个非零,则条件为真 (A || B)
! 称为逻辑非运算符,用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假 !(A && B)

位运算符

所谓位运算,就是对一个比特(Bit)位进行操作。比特(Bit)是一个电子元器件,8个比特构成一个字节(Byte),它已经是粒度最小的可操作单元了。

C中提供了6种位运算符:

运算符 说明
& 按位与运算
| 按位或运算
^ 按位异或运算
~ 取反运算
<< 左移运算
>> 右移运算

C的位运算比较复杂,深究请看参考资料

赋值运算符

给定变量ABC,C语言中支持的赋值运算符:

运算符 描述 例子
= 简单的赋值运算符,把右边操作数的值赋给左边操作数 C = A + B 将把 A + B 的值赋给 C
+= 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 C += A 相当于 C = C + A
-= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 C -= A 相当于 C = C - A
*= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 C *= A 相当于 C = C * A
/= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 C /= A 相当于 C = C / A
%= 求模且赋值运算符,求两个操作数的模赋值给左边操作数 C %= A 相当于 C = C % A
<<= 左移且赋值运算符 C <<= 2 等同于 C = C << 2
>>= 右移且赋值运算符 C >>= 2 等同于 C = C >> 2
&= 按位与且赋值运算符 C &= 2 等同于 C = C & 2
^= 按位异或且赋值运算符 C ^= 2 等同于 C = C ^ 2
|= 按位或且赋值运算符 C |= 2 等同于 C = C | 2

杂项运算符

运算符 描述 例子
sizeof() 返回变量的大小 sizeof(a) 将返回 4,其中 a 是整数
& 返回变量的地址 &a; 将给出变量a在内存中的实际地址
* 指向一个变量 *a; 将指向一个变量(指针)
?: 条件表达式 如果条件为真 ? 则值为 X : 否则值为 Y

运算符优先级

类别 运算符 结合性
后缀 () [] -> . ++ - - 从左到右
一元 + - ! ~ ++ - - (type)* & sizeof 从右到左
乘除 * / % 从左到右
加减 + - 从左到右
移位 << >> 从左到右
关系 < <= > >= 从左到右
相等 == != 从左到右
位与 AND & 从左到右
位异或 XOR ^ 从左到右
位或 OR | 从左到右
逻辑与 AND && 从左到右
逻辑或 OR || 从左到右
条件 ?: 从右到左
赋值 = += -= *= /= %=>>= <<= &= ^= |= 从右到左
逗号 , 从左到右

参考资料