C 语言基础
本文档用于 HDU-PHOENIX 战队视觉/算法组培训,用于速成 C 语言基础知识。
本文档仅包含最基础的内容,更多的知识需要自行学习。
推荐阅读:
- 《C Primer Plus》
- 学校发的 C 语言教材(谭浩强的除外)
0. C 语言简介
C 语言是一种通用的高级编程语言,由美国计算机科学家丹尼斯·里奇(Dennis Ritchie)在 1972 年设计开发。C 语言是一种结构化语言,它的设计目标是提供一种能够以简单的方式编写复杂的程序的语言。
C 语言是一种面向过程的语言,它的语法结构简单、灵活,同时又具有很强的表达能力,可以直接访问计算机硬件,可以用来编写操作系统、编译器、数据库、网络等系统软件,也可以用来编写应用软件。
C 语言是一种编译型语言,编译型语言是指程序在运行之前需要先编译成机器码,然后再运行。C 语言的编译器有很多,比如 GCC、Clang、MSVC 等。
C 语言是一种静态类型语言,静态类型语言是指在编译时就确定了变量的数据类型,变量的数据类型在编译时就已经确定,不会发生变化。
一个简单的 C 语言程序如下:
1 |
|
printf
是 C 语言的标准输出函数,用于输出内容到控制台,\n
表示换行。相对应地,scanf
是 C 语言的标准输入函数,用于从控制台输入内容:
1 |
|
这个程序会提示输入一个整数,然后输出这个整数。
1. C 语言基本数据类型
C 语言的基本数据类型有字符型、整型、浮点型和指针,以下是 C 语言的基本数据类型的长度和能表示的范围:
类型 | 关键字 | 格式化输出符 | 长度 | 所示范围 |
---|---|---|---|---|
字符型 | char | %c | 1 Byte, 8bit | |
无符号字符型 | unsigned char | %c | 1 Byte, 8bit | |
短整型 | short | %d | 2 Byte, 16bit | |
无符号短整型 | unsigned short | %d | 2 Byte, 16bit | |
整型 | int | %d | 4 Byte, 32bit | |
无符号整型 | unsigned int | %d | 4 Byte, 32bit | |
长整型 | long | %ld | 4 Byte, 32bit | |
无符号长整型 | unsigned long | %ld | 4 Byte, 32bit | |
双长整型 | long long | %lld | 8 Byte, 64bit | |
无符号双长整型 | unsigned long long | %lld | 8 Byte, 64bit | |
(单精度)浮点型 | float | %f | 4 Byte, 32bit | |
双精度浮点型 | double | %lf | 8 Byte, 32bit |
指针
指针是一个变量,其值为另一个变量的地址。
指针的长度取决于操作系统的位数,32 位操作系统的指针长度为 4 Byte,64 位操作系统的指针长度为 8 Byte。
举个例子, 32 位操作系统最多只能寻址 4GB 的内存空间。
定义基本数据类型:
1 | char c = 'a'; // 字符型 |
2. C 语言运算符
C 语言的运算符包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、三目运算符、逗号运算符、取地址运算符、取值运算符、自增自减运算符、sizeof 运算符、类型转换运算符等。
2.1 赋值运算符
赋值运算符用于给变量赋值,赋值运算符的优先级最低。
1 | int a = 1; |
2.2 算术运算符
算术运算符用于进行基本的数学运算。
运算符 | 说明 | 示例 |
---|---|---|
+ | 加法 | res = a + b |
- | 减法 | res = a - b |
* | 乘法 | res = a * b |
/ | 除法 | res = a / b |
% | 取模(取余数) | res = a % b |
+= | 加法赋值运算符 | a += b |
-= | 减法赋值运算符 | a -= b |
*= | 乘法赋值运算符 | a *= b |
/= | 除法赋值运算符 | a /= b |
%= | 取模赋值运算符 | a %= b |
其中加减法还有自增自减运算符:
运算符 | 说明 | 示例 |
---|---|---|
++ | 自增 | a++,++a |
-- | 自减 | a--, --a |
例如:
1 | int a = 1; |
使用自增自减运算符的时候需要注意,一条语句中最多使用一个自增或自减运算符,也不要出现 a = a++
或 a = ++a
这样的语句,例如:
1 | int i = 1; |
以上代码符合语法但是未定义行为,不同编译器可能会有不同的结果。
2.3 关系运算符
关系运算符用于比较两个值的大小,返回值为真(1)或假(0)。
运算符 | 说明 | 示例 |
---|---|---|
== | 等于 | res = a == b |
!= | 不等于 | res = a != b |
> | 大于 | res = a > b |
< | 小于 | res = a < b |
>= | 大于等于 | res = a >= b |
<= | 小于等于 | res = a <= b |
2.4 逻辑运算符
逻辑运算符用于进行逻辑运算,返回值为真(1)或假(0)。
运算符 | 说明 | 示例 |
---|---|---|
&& | 逻辑与 | res = a && b |
|| | 逻辑或 | res = a|| b |
! | 逻辑非 | res = !a |
2.5 位运算符
位运算符用于对二进制数进行位运算。
运算符 | 说明 | 示例 |
---|---|---|
& | 位与 | res = a & b |
| | 位或 | res = a| b |
^ | 位异或 | res = a ^ b |
~ | 位取反 | res = ~a |
<< | 位左移,第二个数表示位移量 | res = a << b |
>> | 位右移,第二个数表示位移量 | res = a >> b |
&= | 位与赋值运算符 | a &= b |
|= | 位或赋值运算符 | a|= b |
^= | 位异或赋值运算符 | a ^= b |
<<= | 位左移赋值运算符 | a <<= b |
>>= | 位右移赋值运算符 | a >>= b |
例如:
1 | int a = 3; // 0011_2 |
2.6 三目运算符
三目运算符用于简化 if-else 语句,语法为 条件表达式 ? 表达式 1 : 表达式 2
,如果条件表达式为真则返回表达式 1 的值,否则返回表达式 2 的值。
例如:
1 | int a = 1; |
2.7 逗号运算符
逗号运算符用于连接两个表达式,返回值为最后一个表达式的值。
例如:
1 | int a = 1; |
2.8 取地址运算符和取值运算符(解引用)
取地址运算符用于获取变量的地址。
例如:
1 | int a = 1; |
取值运算符(解引用)用于获取指针指向的变量的值。
例如:
1 | // 接上面的代码 |
2.9 sizeof 运算符
sizeof 运算符用于获取变量或类型的长度,返回值为 size_t 类型。
例如:
1 | int a = 1; |
2.10 类型转换运算符
类型转换运算符(强制类型转换、显式类型转换)用于将一个数据类型转换为另一个数据类型。
强制类型转换的形式为 (type)var
,例如:
1 | int a = 1; |
当我们把两个不同类型的数据进行运算时,C 语言会自动进行类型转换(隐式类型转换),将较小的数据类型转换为较大的数据类型,顺序为 char -> short -> int -> long -> long long -> double <- float
。而当两个同级别的数据类型进行运算时,C 语言不会将数据类型进行转换。例如:
1 | int a = 1; |
注意,将一个较大的数据类型转换为较小的数据类型时,可能会造成数据丢失、精度丢失或者溢出。
例如:
- 将一个浮点数转换为整数时,会将小数部分截断。
- 将 double 转换为 float 时,会造成精度丢失。
2.11 运算符优先级
C 语言的运算符都具有优先级,高优先级先执行,低优先级后执行,同优先级按一定顺序执行,优先级如下表所示:
优先级 | 运算符 | 结合顺序 |
---|---|---|
1 | [], (), ., -> | 从左到右 |
2 | -, ~, ++, –, *, &, !, (type), sizeof | 从右到左 |
3 | /, *, % | 从左到右 |
4 | +, - | 从左到右 |
5 | <<, >> | 从左到右 |
6 | >, >=, <, <= | 从左到右 |
7 | ==, != | 从左到右 |
8 | & | 从左到右 |
9 | ^ | 从左到右 |
10 | | | 从左到右 |
11 | && | 从左到右 |
12 | || | 从左到右 |
13 | ?: | 从右到左 |
14 | =, /=, *=, %=, +=, -=, <<=, >>=, &=, ^=,|= | 从右到左 |
15 | , | 从左到右 |
3. C 语言保留字(关键字)
C 语言的保留字(关键字)是一些具有特殊含义的单词,不能用作变量名、函数名等标识符。除了之前提到的数据类型关键字(如 int
、char
等)之外,C 语言还有一些其他的保留字,如下表所示:
关键字 | 说明 | 关键字 | 说明 | 关键字 | 说明 |
---|---|---|---|---|---|
流程控制 | 存储类说明符 | typedef | 定义类型 | ||
if | 条件语句 | extern | 外部变量或函数 | volatile | 不对变量进行优化 |
else | if 语句中条件为假时执行的分支 | static | 静态变量 | 长度计算 | |
switch | 多分支条件语句 | register | 寄存器变量 | sizeof | 计算数据类型长度 |
case | switch 语句中的分支 | auto | 自动变量 | ||
default | switch 语句中的默认分支 | 类型限定符 | |||
for | for 循环 | void | 空类型 | ||
do | do-while 循环的循环体 | signed | 有符号数 | ||
while | while 循环 | unsigned | 无符号数 | ||
continue | 跳过当前循环的剩余部分 | enum | 枚举类型 | ||
break | 跳出当前循环或分支 | struct | 结构体类型 | ||
goto | 跳转到指定标签 | union | 共用体类型 | ||
return | 返回函数值 | const | 常量 |
加上之前提到的数据类型关键字,C 语言一共有 32 个关键字。
4. C 语言注释
C 语言的注释有两种形式,单行注释和多行注释(块注释)。
1 | // 这是单行注释,注释内容在 // 后面,直到行尾 |
5. 流程控制
C 语言的流程控制有顺序结构、选择结构和循环结构。
5.1 顺序结构
顺序结构是程序按照代码的顺序执行,没有分支和循环。正常情况下,C 语言的代码都是按照顺序结构执行的。
1 | int a = 1; // step 1 |
5.2 选择结构
选择结构有 if 语句、switch 语句。
5.2.1 if 语句
if 语句用于根据条件执行不同的代码块,语法如下:
1 | if (condition) { |
else 语句是可选的,可以省略,同时也可以有多个 else if 语句。
例如:
1 | int a = 1; |
5.2.2 switch 语句
switch 语句用于根据不同的条件执行不同的代码块,语法如下:
1 | switch (expression) { |
switch 语句中的 expression 必须是整型或字符型,case 后面的 constant 必须是整型常量或字符常量。
例如:
1 | int a = 1; |
5.3 循环结构
循环结构有 for 循环、while 循环、do-while 循环。
5.3.1 for 循环
for 循环用于重复执行一段代码,一个完整的 for 语句包括初值、条件和增量三部分,语法如下:
1 | for (initialization; condition; increment) { // 第一次执行时执行 initialization |
例如:
1 | for (int i = 0; i < 10; i++) { |
输出:
1 | 0 1 2 3 4 5 6 7 8 9 |
5.3.2 while 循环
while 循环用于重复执行一段代码,语法如下:
1 | while (condition) { |
例如:
1 | int i = 0; |
输出:
1 | 0 1 2 3 4 5 6 7 8 9 |
5.3.3 do-while 循环
do-while 循环用于重复执行一段代码,与 while 循环的区别是 do-while 循环会先执行一次循环体,然后判断条件是否为真,语法如下:
1 | do { |
例如:
1 | int i = 0; |
输出:
1 | 0 1 2 3 4 5 6 7 8 9 |
5.4 跳转语句
C 语言的跳转语句有 break、continue、goto 和 return。
5.4.1 break
break 语句用于跳出当前循环或 switch 语句,程序会继续执行循环或 switch 语句后面的代码。
例如:
1 | for (int i = 0; i < 10; i++) { |
输出:
1 | 0 1 2 3 4 |
5.4.2 continue
continue 语句用于跳过当前循环的剩余部分,继续执行下一次循环。
例如:
1 | for (int i = 0; i < 10; i++) { |
输出:
1 | 0 1 2 3 4 6 7 8 9 |
5.4.3 goto
goto 语句用于跳转到指定标签,语法如下:
1 | goto label; |
例如:
1 | int i = 0; |
这个代码构成了一个循环。
5.4.4 return
return 语句用于返回函数值,结束函数的执行。
例如:
1 | int add(int a, int b) { |
6. 数组与字符串、指针
6.1 数组
数组是一种存储多个相同类型数据的数据结构,数组的元素可以通过下标访问,数组的下标从 0 开始。
定义数组语法为 type name[length]
,其中 type
是数组元素的类型,name
是数组的名字,length
是数组的长度。
例如:
1 | int a[5]; // 定义一个长度为 5 的整型数组 |
访问数组元素:
1 | int a[5] = {1, 2, 3, 4, 5}; |
数组长度是固定的,初始化完成后不能改变。可以通过 sizeof
运算符获取数组的长度。
1 | int a[5] = {1, 2, 3, 4, 5}; |
遍历一个数组常常使用循环语句。
数组在内存中是连续存储的,数组变量名其实就是数组首元素的地址。
1 | int a[5] = {1, 2, 3, 4, 5}; |
数组元素的下标反应了元素相对于数组首元素的偏移量。
6.2 字符串
字符串是一种特殊的字符数组,字符串以 '\0'
结尾,'\0'
是字符串结束标志。
定义字符串语法为 char name[length]
,其中 name
是字符串的名字,length
是字符串的长度。使用双引号 "
来定义字符串常量,字符数组只能在初始化时可以被字符串赋值。在格式化输入输出时,用 %s
表示字符串。
例如:
1 | char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 定义一个字符串,内容为 "Hello" |
字符串有专门的处理函数,在 string.h
头文件中:
函数名 | 说明 |
---|---|
strlen(str) | 获取字符串长度,不包括结束标志 |
strcpy(dest, src) | 复制字符串,将 src 复制到 dest |
strcat(dest, src) | 连接字符串,将 src 连接到 dest 后面 |
strcmp(str1, str2) | 比较字符串,比较 str1 和 str2 的大小,返回值为负数、0 或正数 |
strchr(str, c) | 查找字符,返回字符串 str 中第一次出现字符 c 的位置 |
strstr(str1, str2) | 查找字符串,查找字符串 str1 中第一次出现字符串 str2 的位置 |
strtok(str, delim) | 分割字符串,将字符串 str 按照分隔符 delim 分割 |
sprintf(str, format, …) | 格式化输出到字符串,将格式化输出的结果存入字符串 str |
sscanf(str, format, …) | 从字符串读取格式化输入,从字符串 str 中读取格式化输入 |
6.3 指针
指针是 C 语言的一个重要概念,也是 C 语言特有的东西。指针本质上是一个整型变量,存储的是一个内存地址。
定义指针语法为 type *name
,其中 type
是指针指向的数据类型,name
是指针的名字。使用 &
运算符获取变量的地址,使用 *
运算符获取指针指向的变量的值。
例如:
1 | int a = 1; |
数组就是指针,指针就是数组。
使用指针我们可以做一些有趣的事情,例如:
1 | short arr[5] = {1, 2, 3, 4, 5}; |
由于 short
类型占 2 个字节,char
类型占 1 个字节,所以输出的结果是 1 0 2 0 3 0 4 0 5 0
。
linux 系统字节序为小端序(smalldian)。
我们来看另一个例子:
1 | float f = 1.5; |
这个例子中,我们将 float
类型的指针转换为 int
类型的指针,然后输出这两个变量的值,结果是 1.500000
和 1069547520
。
结果解释
我们来解释一下这个结果。int
和 float
的长度都是 4 个字节,但他们的存储方式不同。
4 个字节一共 32 位,最高位是符号位(S):
对于 int
类型,最高位是符号位,剩下的 31 位是数值位(V),例如 10
的二进制表示是:
对于 float
类型,最高位是符号位;接下来的 8 位是指数位(E);最后的 23 位是尾数位(M),以二进制小数形式存储:
其计算方式为:
并规定:
- 当 E 全为 1 时,表示的是特殊数
- 当 M 全为 0 时,表示的是无穷大;
- 当 M 不全为 0 时,表示的是 NaN(Not a Number);
- 当 E 全为 0 时,表示的是非规格化数
- 此时
;
- 此时
- 当 E 不全为 0 且不全为 1 时,表示的是规格化数,此时 E 取值范围为 1 ~ 254
也因此float
最大取值的绝对值为
最小取值(规格化数)的绝对值为
所以1.5
的二进制表示为:
这个二进制数转换为十进制数为 1069547520
。
7. 函数
函数表示了一段特定的功能,可以重复调用。函数的定义包括函数名、返回值类型、参数列表和函数体。
7.1 定义一个函数
不能在函数内部定义函数,函数的定义必须在函数外部。
1 | return_type function_name(parameter_list) { |
其中 return_type
是返回值类型,function_name
是函数名,parameter_list
是参数列表,{}
中是函数体。
例如:
1 | int add(int a, int b) { |
函数的声明和定义可以分开,声明函数时只需要写函数的返回值类型、函数名和参数列表,不需要写函数体:
1 | int add(int a, int b); // 函数声明 |
编写 C 语言函数要注意以下几点:
- 函数需要先声明再调用;
- 函数不能重名;
- 函数的参数个数是任意的;
- 函数的参数可以有默认值,有默认值的参数必顫放在参数列表的最后;
- 函数可以没有返回值,此时返回值类型为
void
,但不建议这样做。
7.2 函数的参数传递
函数的参数有两种传递方式:传值和传址。这与函数的调用过程有关。
7.2.1 传值调用
C 语言的函数参数传递是传值的,即将实参的值复制一份给形参,函数内部对形参的修改不会影响实参。
例如,假设我希望输出函数 y = x + 1
,但是我不希望修改 x
的值:
1 |
|
不仅仅传入参数是传值的,函数的返回值也是传值的。函数的返回值是一个临时变量,函数返回后这个临时变量就会被销毁。
7.2.2 传址调用
但是某些情况下,我们希望函数内部对形参的修改能够影响实参,例如下面这个例子:
1 |
|
发现输出结果并不是我们期望的 a = 2, b = 1
,这是因为函数参数传递是传值的,函数内部对形参的修改不会影响实参。
为了解决这个问题,我们可以使用指针来传递参数,这样函数内部对形参的修改就会影响实参。
1 |
|
严格意义上来说,这个函数依然是传值调用,只不过传递的是指针的值,而不是变量的值。由于指针指向了变量的地址,所以函数内部对指针所指的目标的修改会影响到外部变量。
7.3 递归函数
递归函数是指在函数内部调用自身的函数。递归函数有两个要素:递归边界和递归式。
例如,计算阶乘的递归函数:
1 |
|
递归函数的调用过程如下:
1 | factorial(5) |
递归函数的优点是代码简洁,但是递归函数的缺点是递归深度过深时会导致栈溢出。
7.4 函数指针
与数据类型一样,函数也具有指针。函数指针是指向函数的指针,函数指针的声明和定义如下:
1 | return_type (*function_pointer)(parameter_list); |
其中 return_type
是函数的返回值类型,parameter_list
是参数列表。
例如:
1 |
|
8. 结构体、联合体和枚举
除了基本数据类型,C 语言还提供了结构体、联合体和枚举这三种自定义数据类型。自定义数据类型可以更好地组织数据,提高代码的可读性。
定义自定义数据类型必须在函数外部定义,不能在函数内部定义。
8.1 结构体
结构体是一种自定义的数据类型,可以包含多个不同类型的数据。其大小是内部所有成员大小相加。
结构体的定义如下:
1 | struct struct_name { |
其中 struct_name
是结构体的名字,member1
、member2
是结构体的成员,type1
、type2
是成员的数据类型。
例如,定义一个学生结构体:
1 | struct student { |
结构体的成员通过 .
运算符访问:
1 | struct student s; |
如果结构体的成员是指针,则通过 ->
运算符访问:
1 | struct student *p = &s; |
8.2 联合体
联合体是一种特殊的结构体,联合体的所有成员共用同一块内存,联合体的大小等于最大的成员的大小。联合体的定义如下:
1 | union union_name { |
例如,定义一个共用体:
1 | union data { |
联合体的访问与结构体类似,通过 .
或 ->
运算符访问。
1 | union data d; |
8.3 枚举
枚举是一种自定义的数据类型,枚举的成员是常量,枚举的定义如下:
1 | enum enum_name { |
例如,定义一个颜色枚举:
1 | enum color { |
此时 RED
、GREEN
、BLUE
是常量,其值分别为 0
、1
、2
。
规定后一个枚举成员的值比前一个枚举成员的值大 1,如果需要指定枚举成员的值,可以在定义时赋值:
1 | enum color { |
1 | enum color { |
枚举的成员可以通过枚举名访问:
1 | enum color { |
- 标题: C 语言基础
- 作者: null-qwerty
- 创建于 : 2024-11-01 20:41:47
- 更新于 : 2024-11-01 21:40:40
- 链接: https://www.null-qwerty.top/2024/11/01/C-语言基础/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。