由浅入深:C语言笔记

11 mins.12k60

RE:C 从C语言重新开始的现世界生活

因为在刷leetcode和准备考试的时候,发现自己的虽然可以较为熟练的掌握C语言,但是一些C语言的细枝末节仍有疑问,一些库函数和特殊形式并不熟悉,因此通过系统整理C语言,由浅入深,整理一下知识体系。

本文章内容仅作个人整理所用,其中部分内容有所省略,不过应该对于除了计算机专业且需要“精通”C语言的读者,这些内容应该足够使用。

C程序基本概念

本节讲解C语言程序的基本概念。

C程序是包含声明的一系列文本文件(包含头文件(.h)和源文件(.c))。它们会经过编译变成可执行程序,在操作系统调用其 主函数 时被执行。

C程序文本中包含很多词语,其中一部分词语在C语言中具有指定意义,称为 关键字 ,如returnif等。其余词语可作为 标识符 ,用来标识对象、函数、结构体、联合体、或枚举标签、枚举成员、 typedef 类型别名、标号或宏。

标识符具有指定的有效范围,称为 作用域 ,超出这个范围,标识符的合法性或意义就会发生改变。同时,一些标识符具有链接,使他们可以在不同作用域表示同一实体。

C程序的结构

本节介绍一个C程序中的基本最小结构。

C 程序主要包括以下部分:

  • 预处理器指令
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释 (可选)

以一个简单C程序为例:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main()
{
printf("Hello, World! \n");
/* 一个 C 程序 */
return 0;
}

其中:

  1. 程序的第一行 #include <stdio.h> 是预处理器指令,告诉 C 编译器在实际编译之前要包含 stdio.h 文件。
  2. 下一行 int main() 是主函数,程序从这里开始执行。
  3. 下一行 // 将会被编译器忽略,这里放置程序的注释内容。它们被称为程序的注释。
  4. 下一行 printf(…) 是 C 中另一个可用的函数,会在屏幕上显示消息 “Hello, World!”。
  5. 下一行 return 0; 终止 main() 函数,并返回值 0

接下来各节将详细介绍C程序中各部分的详细内容。

表达式

本节介绍C程序中表达式的构成。

表达式是运算符及其运算数的序列,它指定一个运算。

表达式求值可以产生结果(例如求值 2+2 产生结果 4 ,可能产生副效应(例如求值 printf(“%d”,4) 会将字符 ‘4’ 送到标准输出流),并可以指代对象或函数。

常用运算符:

数学运算符 赋值运算符 位运算运算符 自增自减 比较运算符 成员 其他
a+b
a-b
a*b
a/b
a%b
a = b
a+=b
a-=b
a/=b
a*=b
a%=b
a>>b
a<<b
~a
a & b
a | b
a ^ b
++a
–a
a++
a–
a > b
a < b
a == b
a >= b
a <= b
a != b
a[b]
a -> b
a.b
*a
&a
a, b
sizeof
? :
(…)

运算数可为其他表达式,或初等表达式(例如 1+2*3 中,运算符 + 的运算数是子表达式 2*3 和初等表达式 1 )。

初等表达式可以是下列之一:

  1. 常量及字面量(例如 2 或 “Hello, world” )
  2. 适合的已声明标识符(例如 n 或 printf )
  3. 泛型选择

常量:
某些类型的常量值可用称为字面量(对于左值表达式)和常量(对于右值表达式)的特殊表达式嵌入到 C 程序源代码中,。

  1. 整数常量是十进制、八进制或十六进制的整数类型数字。
  2. 字符常量是 int 类型,适于转换到字符类型、 char16_t 、 char32_t (C11 起)或 wchar_t 类型的单个字符。
  3. 浮点常量是 float 、 double 或 long double 类型值。
  4. 字符串字面量是类型为 char[] 、 char16_t[] 、 char32_t[] 或 wchar_t[] 的一系列字符,表示空终止字符串。
  5. 复合字面量是直接嵌入程序代码的结构体、联合体或数组类型的值。

提示

int类型在计算机中占4个字节,char类型通常占1个字节,float类型占4个字节,long类型和double类型占8个字节。

一个字节形如:[00000000](8位二进制)

语句

本节介绍C程序中什么是语句,以及语句的规范

语句是带顺序执行的 C 程序段。任何函数体都是一条复合语句,继而为语句或声明的序列:

1
2
3
4
5
6
7
int main(void)
{ // 复合语句的开始
int n = 1; // 声明(非语句)
n = n+1; // 表达式语句
printf("n = %d\n", n); // 表达式语句
return 0; // 返回语句
} // 复合语句之结尾,函数体之结尾

语句的类别:

  1. 复合语句
  2. 表达式语句
  3. 选择语句
  4. 迭代语句
  5. 跳转语句

标号 是标识语句的标识符。标号出现在标号语句中,形式为标识符后跟冒号 (:) 。 标号语句可作为跳转语句的目标。

复合语句 是由一对大括号 ({ 和 }) 括起的语句和声明序列。复合语句可用作单个语句。例如:

1
2
3
4
{
int n = 10;
printf("n = %d\n", n);
}

表达式语句 是由表达式后跟分号 (;) 组成的语句。例如:

1
2
n = n + 1;
printf("n = %d\n", n);

选择语句 根据条件的真假选择执行不同的语句。C 语言提供两种选择语句: if 语句和 switch 语句。例如:

1
2
3
4
5
if (n > 0) {
printf("n is positive\n");
} else {
printf("n is non-positive\n");
}

迭代语句 重复执行一条或多条语句,直到某个条件不再满足为止。C 语言提供三种迭代语句: while 语句、 do 语句和 for 语句。例如:

1
2
3
4
while (n > 0) {
printf("n = %d\n", n);
n--;
}

跳转语句 改变程序的执行顺序。C 语言提供四种跳转语句: goto 语句、 continue 语句、 break 语句和 return 语句。例如:

1
return 0;

if语句

本节介绍C程序中if语句的用法。

if 语句用于根据条件的真假选择执行不同的代码块。它的基本语法如下:

1
2
3
4
5
if (condition) {
// 条件为真时执行的代码
} else {
// 条件为假时执行的代码
}

其中,condition 是一个表达式,且必须是标量类型。else 部分是可选的。

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main() {
int number = 10;
if (number > 0) {
printf("The number is positive.\n");
} else {
printf("The number is non-positive.\n");
}
return 0;
}

switch语句

本节介绍C程序中switch语句的用法。

switch 语句用于基于一个表达式的值选择执行多个代码块中的一个。它的基本语法如下:

1
2
3
4
5
6
7
8
9
10
11
switch (expression) {
case constant1:
// 执行代码块1
break;
case constant2:
// 执行代码块2
break;
...
default:
// 执行默认代码块
}

其中,expression 是一个整数类型或枚举类型的表达式。每个 case 标签后跟一个常量表达式,表示可能的值。default 标签是可选的,当没有匹配的 case 时执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int main() {
int day = 3;
switch (day) {
case 1:
printf("Monday\n");
break;
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break;
default:
printf("Other day\n");
}
return 0;
}

while语句

本节介绍C程序中while语句的用法。

while 语句用于重复执行一段代码块,直到指定的条件不再满足为止。它的基本语法如下:

1
2
3
while (condition) {
// 循环体代码
}

其中,condition 是一个表达式,且必须是标量类型。当 condition 为真时,执行循环体代码;当 condition 为假时,退出循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int main() {
int count = 1;
while (count <= 5) {
printf("Count: %d\n", count);
count++;
}
return 0;
}
// 输出:
// Count: 1
// Count: 2
// Count: 3
// Count: 4
// Count: 5

for语句

本节介绍C程序中for语句的用法。

for 语句用于重复执行一段代码块,通常用于已知循环次数的情况。它的基本语法如下:

1
2
3
for (initialization; condition; increment) {
// 循环体代码
}

其中:

  • initialization:在循环开始前执行的初始化语句。
  • condition:每次循环开始时检查的条件表达式。
  • increment:每次循环结束后执行的增量语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main() {
for (int i = 1; i <= 5; i++) {
printf("Iteration: %d\n", i);
}
return 0;
}
// 输出:
// Iteration: 1
// Iteration: 2
// Iteration: 3
// Iteration: 4
// Iteration: 5

do…while语句

本节介绍C程序中do…while语句的用法。

do…while 语句用于至少执行一次循环体代码,然后根据条件决定是否继续执行。它的基本语法如下:

1
2
3
do {
// 循环体代码
} while (condition);

其中,condition 是一个表达式,且必须是标量类型。循环体代码至少执行一次,然后检查 condition;如果为真,则继续执行循环体代码,否则退出循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main() {
int count = 0;
do {
count++;
printf("Count: %d\n", count);
} while (count <= 5);
return 0;
}
// 输出:
// Count: 1
// Count: 2
// Count: 3
// Count: 4
// Count: 5
// Count: 6

continue语句

本节介绍C程序中continue语句的用法。

continue 语句用于跳过当前循环的剩余部分,并立即开始下一次循环迭代。它通常用于循环体内的条件判断中。其基本语法如下:

1
continue;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main() {
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
printf("Odd number: %d\n", i);
}
return 0;
}
// 输出:
// Odd number: 1
// Odd number: 3
// Odd number: 5
// Odd number: 7
// Odd number: 9

break语句

本节介绍C程序中break语句的用法。

break 语句用于立即终止当前循环或 switch 语句,并跳出循环体或 switch 结构。其基本语法如下:

1
break;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int main() {
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // 当 i 等于 5 时跳出循环
}
printf("Number: %d\n", i);
}
return 0;
}
// 输出:
// Number: 1
// Number: 2
// Number: 3
// Number: 4

return语句

本节介绍C程序中return语句的用法。

return 语句用于从函数中返回一个值,并终止函数的执行。其基本语法如下:

1
return expression;

其中,expression 是要返回的值,可以是任何与函数返回类型兼容的表达式。

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int add(int a, int b) {
return a + b; // 返回两数之和
}
int main() {
int sum = add(5, 10);
printf("Sum: %d\n", sum);
return 0;
}
// 输出:
// Sum: 15

goto语句

本节介绍C程序中goto语句的用法。

goto 语句用于无条件地跳转到程序中的另一个位置。它通过标号来指定跳转目标。其基本语法如下:

1
2
3
goto label;
...label:
// 目标代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main() {
int count = 1;
start_loop:
if (count <= 5) {
printf("Count: %d\n", count);
count++;
goto start_loop; // 跳转回循环开始
}
return 0;
}
// 输出:
// Count: 1
// Count: 2
// Count: 3
// Count: 4
// Count: 5

声明和定义

本节介绍如何在C程序中引入变量。

声明是引入 标识符 ,并指定其代表一定含义和性质的C语音构造。

声明的基本格式为指定符 声明符,如int n声明了一个整型变量n,其中指定符指定声明内容的类型或类型修饰,声明符则是引入的标识符内容。

定义是一个提供所有关于其所声明标识符信息的声明。即给出声明的具体含义内容。

区分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 函数
void function (int n); // 声明
void function (int n) {
return n;
} // 定义

// 对象
int n; // 声明
int n = 10; // 定义

// 结构体
struct Stack; // 声明
struct Stack {
int top;
int e[maxsize];
}; // 定义

重复声明

同一标识符不可以重复声明,除非以下场景:

  1. 具有外部链接的声明
    1
    2
    3
    4
    5
    6
    7
    extern int x;
    int x = 10; // OK
    extern int x; // OK

    static int n;
    static int n = 10; // OK
    static int n; // OK
  2. struct 和 union 声明可以重复

函数

本节介绍C程序中函数的构成

函数是将一个标识符(函数名)关联到一条复合语句(函数体)的 C 语言构造。每个 C 程序都从 main 函数开始执行,也从它或者调用其他用户定义函数或库函数终止。

一个函数可以在标识符后的"()"中接受0个或多个形式参数,并可以通过return向调用者返回一个值。

1
2
3
int func(int a, int b) { // 定义一个名为func的函数,规定其返回结果为int整型,接受整型变量a和b为参数
return a + b; // 返回两者之和
} // int
静态变量和内联函数

一个函数必须且仅可以被定义一次,除了内联函数(inline)

inline 指定符的目的是提示编译器做优化,譬如函数内联,这要求编译方能见到函数的定义。编译器能(并且经常)为了优化的目的,忽略 inline 指定符的存在与否。

若编译器进行函数内联,则它会以函数体取代所有对它的调用,以避免函数调用的开销(将数据置于栈上和取得结果),这可能会生成更大的可执行文件,因为函数可能会被重复多次。结果同仿函数宏,只是用于该函数的标识符和宏指代可见于定义点的定义,而不指代调用点的定义。

不管是否进行内联,内联函数都保证下列语义:

任何拥有内部链接的函数都可以声明成 static inline ,没有其他限制。

一个非 static 的内联函数不能定义一个非 const 的函数局部 static 对象,并且不能使用文件作用域的 static 对象。

函数的声明:函数声明引入指代函数的标识符,并可选地指定该函数的参数类型(原型)。函数声明(不同于定义)可以出现于块作用域和文件作用域中。

  • 在函数声明的声明文法中, type-specifier (类型说明符)序列,可选择地由声明器修饰,指代返回类型(可以是任何异于函数和数组的类型)。
  • 声明符指定函数名和参数类型(如果有的话)。参数类型可以省略(旧式 C 函数声明),但建议总是提供参数类型(原型)。

函数的定义:函数定义提供函数的具体内容。它包括函数头和函数体。函数头指定函数名、返回类型和参数列表。函数体是一个复合语句,包含函数执行的代码。

1
2
3
4
int add(int a, int b); // 函数声明
int add(int a, int b) { // 函数定义
return a + b;
}

预处理器指令

本节介绍C程序中预处理器指令的作用。

预处理器指令是以 # 字符开头的行,指示预处理器对源文件进行某种操作。预处理器在实际编译之前处理源文件。

每个指令占据一行,且拥有下列格式:

  • # 字符
  • 预处理指令( define 、 undef 、 include 、 if 、 ifdef 、 ifndef 、 else 、 elif 、 endif 、 line 、 error 、 pragma 之一)[1]
  • 实参(依赖于指令)
  • 换行符

允许空指令(跟随换行符的 # ),而它无效果。

常用的预处理器指令有:

  1. #include :包含头文件
  2. #define :定义宏
  3. #undef :取消宏定义
  4. 条件编译指令: #if#ifdef#ifndef#else#elif#endif

C程序的编译与执行

编译器如同下列阶段以此准确顺序发生一般处理 C 源文件。

  • 阶段1

    1. 以实现定义方式,映射源文件(通常是以某种多字节,例如 UTF-8 编码的文本文件)的单独字节,为源字符集的字符。特别是以换行字符替换依赖 OS 的行尾指示符。
      源字符集是包含作为单字节子集的基本源字符集的多字节字符集,后者由以下 96 个字符组成:
      a) 5 个空白字符(空格、水平制表、垂直制表、换页、换行)
      b) 10 个数字字符,从 ‘0’ 到 ‘9’
      c) 52 个字母,从 ‘A’ 到 ‘Z’ 以及从 ‘a’ 到 ‘z’
      d) 29 个标点字符: _ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " '
    2. 以对应的单字节表示替换三标符。
  • 阶段2

    1. 凡在反斜杠出现于行尾(立即为换行符所后随)时,删除反斜杠和换行符,把二个物理源码行组合成一个逻辑源码行。这是单趟操作:以二个反斜杠结束,后随一个空行的行不会把三行组合为一。
    2. 若此步骤后,非空源文件不以换行符结束(无论是原本就无换行,还是以反斜杠结束),则行为未定义。
  • 阶段3

    1. 处理预处理器指令和宏扩展,用空格来替代注释。
  • 阶段4

    1. 执行预处理器。
    2. #include 指令所引入的每个文件都经历阶段 1 到 4 ,递归执行。
    3. 此阶段结束时,从源码移除所有预处理器指令。
  • 阶段5

    1. 将字符常量及字符串字面量中的所有字符及转义序列从源字符集转换成执行字符集(可为如 UTF-8 的多字节字符集,只要来自阶段 1 中所列的基本源字符集的所有 96 个字符拥有单字节表示)。若转义序列所指定的字符不是执行字符集的成员,则结果是实现定义的,但保证不是空(宽)字符。
  • 阶段6

    1. 连接相邻的字符串字面量。
  • 阶段7

    1. 发生编译:按照语法和语义分析记号,并将它们翻译成翻译单元。
  • 阶段8

    1. 发生链接:将翻译单元和满足外部引用所需的库组件到汇集成程序映像,它含有在其执行环境(操作系统)中执行所需的信息。

类型

本节介绍C程序中常用的数据类型。

C语言是一种强类型语言,所有变量和表达式都有一个确定的数据类型。C语言提供了多种基本数据类型和复合数据类型。

基本数据类型

  1. 整型(int):用于表示整数值。可以是有符号(signed int)或无符号(unsigned int)。
  2. 字符型(char):用于表示单个字符。可以是有符号(signed char)或无符号(unsigned char)。
  3. 浮点型(float):用于表示单精度浮点数。
  4. 双精度浮点型(double):用于表示双精度浮点数。
  5. 长双精度浮点型(long double):用于表示扩展精度浮点数。

复合数据类型

  1. 数组(array):用于存储一组相同类型的元素。
  2. 结构体(struct):用于将不同类型的数据组合在一起,形成一个新的数据类型。
  3. 联合体(union):类似于结构体,但所有成员共享同一块内存。
  4. 枚举(enum):用于定义一组命名的整数常量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 示例代码
#include <stdio.h>
struct Person {
char name[50];
int age;
};
enum Color { RED, GREEN, BLUE };
int main() {
struct Person p1;
p1.age = 25;
enum Color favoriteColor = BLUE;
printf("Age: %d, Favorite Color: %d\n", p1.age, favoriteColor);
return 0;
}

类型组别

  • 对象类型:所有不是函数类型的类型
  • 字符类型: char 、 signed char 、 unsigned char
  • 整数类型: char 、有符号整数类型、无符号整数类型、枚举类型
  • 实数类型:整数类型和实浮点类型
  • 算术类型:整数类型和浮点类型
  • 标量类型:算术类型和指针类型
  • 聚合类型:数组类型和结构体类型
  • 派生声明器类型:数组类型、函数类型和指针类型

主函数

本节介绍C程序中主函数的作用。

每个要在宿主环境运行的编码 C 程序都含有称作 main 的函数定义。程序从 main 函数开始执行,并在 main 函数终止时结束。

主函数的定义形式如下:

1
2
3
4
int main(void) {
// 程序代码
return 0;
}

主函数可以有两种形式:

  1. int main(void):不接受任何参数。
  2. int main(int argc, char *argv[]):接受命令行参数。

其中,argc 是命令行参数的数量,argv 是一个字符串数组,包含所有命令行参数。

主函数必须返回一个整数值,通常返回 0 表示程序成功执行,非零值表示程序异常终止。

1
2
3
4
5
6
7
8
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Number of arguments: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("Argument %d: %s\n", i, argv[i]);
}
return 0;
}

值类别

C中每个表达式(带有参数的运算符、函数调用、常量、变量名等)以两个独立属性刻画: 类型和值类别

每个表达式属于三个值类别之一:左值、非左值对象(右值)以及函数指代器。

  • 左值 是指代对象的表达式。左值 具有可辨识的存储位置 (即地址),因此可以出现在赋值语句的左侧。例如,变量名 x 是一个左值,因为它指代一个存储位置,可以通过 &x 获取其地址。

  • 非左值对象(右值) 是不指代对象的表达式。右值没有可辨识的存储位置,通常是临时值或常量。例如,表达式 x + 1 是一个右值,因为它计算出一个临时结果, 没有固定的存储位置

  • 函数指代器 是指代函数的表达式。函数指代器可以用来调用函数,但不能作为赋值语句的左侧。例如,函数名 func 是一个函数指代器,可以通过 func() 调用该函数。

1
2
3
4
5
6
7
int x = 10; // x 是一个左值
x = 20; // 可以将值赋给 x
int *p = &x; // 可以获取 x 的地址

int y = x + 5; // x + 5 是一个右值

int result = func(); // func 是一个函数指代器

上一篇更回味

  • 生活方式

      提高生活精力:学会科学休息

      引言在现代社会,快节奏的生活和工作压力使得我们常常感到疲惫不堪。无论是学生还是打工人群体中,都有着一个普遍烦恼:为什么经过假期放松后,非但没感觉到精力充沛,反而...

    • 下一篇更精彩

    • 信息系统设计与开发

        信息系统——面向对象的开发方法

        信息系统的开发方法以为开发过程提供高效、高质量措施为目的,在行业中现行的主要方法可以按照时间过程维度和关键分析要素进行分类: 按时间过程分类: 生命周期法(Li...

      • 评论区

        你认为这篇文章怎么样?
        • 0
        • 0
        • 0
        • 0
        • 0
        • 0
        评论
        • 按正序
        • 按倒序
        • 按热度
        来发评论吧~
        Powered by Waline v2.15.8
        感谢您阅读: 「由浅入深:C语言笔记 | Thanafox's Blog」