C学习笔记(二)

C学习笔记(二)

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个关键字:

////
autodoubleintstruct
breakelselongswitch
caseenumregistertypedef
charexternreturnunion
continueforsignedvoid
doifstaticwhile
defaultgotosizeofvolatile
constfloatshortunsigned

标识符(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中操作字符串的函数:

序号函数 & 目的
1strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
2strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。
3strlen(s1); 返回字符串 s1 的长度。
4strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6strstr(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后的余数
++自增运算符,整数值+1A+1或者B+1
自减运算符,整数值-1A-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||从左到右
条件?:从右到左
赋值= += -= *= /= %=>>= <<= &= ^= |=从右到左
逗号,从左到右

参考资料

作者

Spoience

发布于

2020-07-12

更新于

2020-07-12

许可协议

评论