导图社区 C语言第五版
这是一个关于C语言第五版的思维导图,《C语言第五版》是一本全面、深入且实用的C语言教材,适合各个层次的读者学习和参考。
编辑于2025-09-05 18:23:23C语言
问题
语句分为哪几类?
明示常量和符号常量之间的关系?
我认为明示常量是符号常量的一种
枚举类型是不是基本数据类型?
我认为不是
->和.叫什么运算符?
现在叫结构成员访问运算符
或点操作符.和箭头操作符->
优点
缺点
常识
基本常识
C语言的发展历史
K&R C
C语言发展之除并没有任何标准,直到出现了K&R C(经典C标准),但是只定义了C语言,没有定义C库。
美国国家标准协会(ANSI)与1983组建了一个委员会,开发了一套新的标准,与1989年正式发布。 该标准(ANSI C)定义了C语言和C标椎库,国际标椎化组织于1990年采用了这套标准。这就是C90标椎(业界人士也称ANSI C 标准)
C89/C90
最早的C语言标准,由美国国家标准学会(ANSI)和国际标准化组织(ISO)于1989年联合发布。
规定了C语言的基本语法、数据类型、运算符等,并定义了一个基本的标准库。
C99
1994年,由ANSI/ISO联合委员会开始修订C标准。
1999年,1994年对C语言的修订引出了ISO 9899:1999的发表,它通常被称为C99。
引入了新的语言特性,如单行注释、布尔类型、变长数组、复合字面量等。 此外,C99标准还增加了更多的库函数,包括对复数数学、格式化输入输出、浮点数环境等的支持。
C11
2011年,国际标准化组织(ISO)和国际电工委员会(IEC)旗下的C语言标准委员会(ISO/IEC JTC1/SC22/WG14)正式发布了C11标准。
在C99的基础上进行了进一步改进,并引入了一些新的特性,如类型泛型、多线程支持、匿名结构体和联合体、对 Unicode 字符的支持等。C11还扩展了标准库,增加了新的头文件和函数,如对多线程编程的支持、原子操作、时间和日期处理等。
C17/C18
2018 年发布,是目前为止最新的 C 语言编程标准,用来修订 C11 标准。
没有引入新的语言特性,只对 C11 进行了补充和修正。
元素,一种概念,代码中不可分割的部分,如各种函数、变量名。
空白,一种概念,用于分割两个标记,如空格、制表符、回车。
标识符命名规则
可以用小写字母、大写字母、数字和下划线_来命名。 而且名称的第1个字符必须是字母或下划线,不能是数字。
C语言的名称区分大小写,即把同一个字母的大小和小写区分为两个不同的字符❤️
同一条声明可声明多个变量
等价
int feet,fathoms;
int feet; int fathoms;
函数传参顺序:从右到左
对C语言来说,真为1假为0
单撇号'内为单个字符',双撇号"内为多个字符(即字符串)。
所有的语句都以分号结束
C标准规定,每一个C/C++程序都必须包含一个main函数,程序在执行时总是从main开始往下执行的,编译器在链接阶段会寻找main函数,如果找不到则会报错。
main函数中必须有一个return语句
`main` 函数的返回值(通常称为 退出状态码)用于告知操作系统程序是否正常结束。 返回 `0`:表示程序成功执行(`EXIT_SUCCESS`)。 返回非零值(如 `1`):表示程序异常终止(`EXIT_FAILURE`),不同非零值可表示不同错误类型。 操作系统依赖:父进程(如Shell脚本)可以通过检查此状态码决定后续操作(例如是否重试、记录错误等)。
C99标准例外:如果 `main` 函数执行到末尾没有 `return`,编译器会隐式返回 `0`,视为成功退出。 注:此规则仅适用于 `main` 函数,其他函数不返回仍会导致编译警告/错误。
名词常识
头文件
如果想让几个源文件可以访问相同的信息,可以把此信息放入一个文件中,然后利用#include指令把该文件的内容带进每个源文件中。 把按照此种方式包含的文件称为头文件(有时称为包含文件),头文件的扩展名为.h。
库文件
库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库, 它提供给使用者一些可以直接拿来用的变量、函数或类。
库是特殊的一种程序,编写库的程序和编写一般的程序区别不大, 只是库不能单独运行。
静态库
静态库在程序的链接阶段被复制到了程序中
命名规则:
Linux:libxxx.a
Windows:libxxx.lib
Linux下静态库的制作:
gcc获得.o文件 gcc -c a.c b.c
将.o文件打包,使用ar工具(archive) ar rcs libxxx.a a.o b.o
优点:
静态库被打包到应用程序中加载速度快。
发布程序无需提供静态库,移植方便。
缺点:
消耗系统资源,浪费内存。
更新、部署、发布麻烦。
动态库(共享库)
动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
命名规则:
Linux:libxxx.so
Windows:libxxx.dll
Linux下动态库的制作:
gcc获得.o文件,得到和位置无关的代码 gcc -c -fpic/-fPIC a.c b.c
gcc得到动态库 gcc -shared a.o b.o -o libxxx.so
优点:
可以实现进程间资源共享(共享库)。
更新、部署、发布简单。
可以控制何时加载动态库。
缺点:
加载速度比静态库慢。
发布程序时需要提供依赖的动态库。
静态库、动态库区别来自链接阶段如何处理,链接成可执行程序。分别称为静态链接方式和动态链接方式。
库的好处:1. 代码保密;2. 方便部署和分发。
内置
编译器自带的类型
声明
引用性声明(声明) (referncing declaration)
不需要建立存储空间的。
告知编译器变量已被创建。
定义性声明(定义) (defining declaration)
需要建立存储空间的。
创建一个变量
初始化
创建一个变量,并为其赋值
符合语句(分程序)(代码块)(语句块)(程序块):在函数中用大括号把许多语句和声明组合到一起,形成单条语句。
语句块与简单的语句不同,语句块不用分号当作结尾。
位操作
保留
原样不动
清零
全部为0
置位
全部为1
取反
1变0,0变1
一元运算需要两个元素:一元(单目)运算符以及该运算符作用的一个变量。
二元运算需要三个元素:二元运算符以及该运算符作用的两个变量。
EOF意思是end of file,本质上是-1。
程序的错误被称为bug
找出并修正程序的错误的过程叫做调试
步长:地址属性。做加1操作需移动的字节数
位宽:所占的内存大小,实质为有多少个二进制数字。
符号常量
使用字符代指具体的常量
赋值
例int a = 5
a就是符号常量
明示常量(宏)
使用#define定义的符号常量。
指针常量:char * const arr; 指针指向的值可以改变,指针的指向不可以改变。
常量指针:char const *arr; 指针的指向可以改变,指针指向的值不可以改变。
指针:变量地址的别称。
数据溢出:溢出的本质是数据二进制的长度超出了对应数据类型的内存大小。
程序会自动将超出内存大小的二进制值抹去。
大于最大值,叫做向上溢出(overflow);小于最小值,叫做向下溢出(underflow)。
嵌套循环:一个循环体内又包含另一个完整的循环结构。
内嵌的循环中还可以嵌套循环,这就是多层循环。
各种语言中关于循环的嵌套的概念都是一样的。
嵌套调用:在调用函数时,被调用函数体内还可以再调用其他函数,甚至是被调用函数本身。
注:被调用函数在执行过程中再次调用被调用函数本身,这被称为直接递归。
注:被调用函数在执行过程中遇到一个调用其他函数的语句,而在执行被被调用函数时又调用被调用函数,这被称为间接递归。
缓冲区
缓冲区(Buffer)又称为缓存(Cache),是内存空间的一部分。 计算机在内存中预留了一定的存储空间,用来暂时保存输入或输出的数据,这部分预留的空间就叫做缓冲区(缓存)。
有时候,从键盘输入的内容,或者将要输出到显示器上的内容,会暂时进入缓冲区, 待时机成熟,再一股脑将缓冲区中的所有内容“倒出”,我们才能看到变量的值被刷新,或者屏幕产生变化。
作用
缓冲区是为了让低速的输入输出设备和高速的用户程序能够协调工作,并降低输入输出设备的读写次数。
例如,我们都知道硬盘的速度要远低于 CPU,它们之间有好几个数量级的差距,当向硬盘写入数据时,程序需要等待,就好像卡顿了一样,用户体验非常差。 计算机上绝大多数应用程序都需要和硬件打交道,例如读写硬盘、向显示器输出、从键盘输入等,如果每个程序都等待硬件,那么整台计算机也将变得卡顿。
但是有了缓冲区,就可以将数据先放入缓冲区中(内存的读写速度也远高于硬盘),然后程序可以继续往下执行, 等所有的数据都准备好了,再将缓冲区中的所有数据一次性地写入硬盘,这样程序就减少了等待的次数,变得流畅起来。
缓冲区的另外一个好处是可以减少硬件设备的读写次数。其实我们的程序并不能直接读写硬件, 它必须告诉操作系统,让操作系统内核(Kernel)去调用驱动程序,只有驱动程序才能真正的操作硬件。
从用户程序到硬件设备要经过好几层的转换,每一层的转换都有时间和空间的开销,而且开销不一定小; 一旦用户程序需要密集的输入输出操作,这种开销将变得非常大,会成为制约程序性能的瓶颈。
这个时候,分配缓冲区就是必不可少的。每次调用读写函数,先将数据放入缓冲区,等数据都准备好了再进行真正的读写操作, 这就大大减少了转换的次数。实践证明,合理的缓冲区设置能成倍提高程序性能。
缓冲区其实就是一块内存空间,它用在硬件设备和用户程序之间,用来缓存数据,目的是让快速的 CPU 不必等待慢速的输入输出设备,同时减少操作硬件的次数。
根据不同的标准,缓冲区可以有不同的分类。
按照缓冲区对应的是输入设备还是输出设备分类
输入缓冲区
输出缓冲区
按照数据刷新(也可以称为清空缓冲区,就是将缓冲区中的数据“倒出”)的时机分类
全缓冲
当缓冲区被填满以后才进行真正的输入输出操作。 缓冲区的大小都有限制的,比如 1KB、4MB 等,数据量达到最大值时就清空缓冲区。
全缓冲的典型代表是对硬盘文件的读写:在实际开发中,将数据写入文件后,打开文件并不能立即看到内容, 只有清空缓冲区,或者关闭文件,或者关闭程序后,才能在文件中看到内容。这种现象,就是缓冲区在作怪。
行缓冲
当在输入或者输出的过程中遇到换行符时,才执行真正的输入输出操作。
行缓冲的典型代表就是标准输入设备(也即键盘)和标准输出设备(也即显示器)。
不带缓冲
不带缓冲区,数据就没有地方缓存,必须立即进行输入输出。
条件编译
条件编译的主要目的是在编译时根据特定的条件选择性地包含或排除代码段。 通过使用预处理器指令,如 #ifdef、#ifndef、#if、#else 和 #endif,可以实现这种有条件的编译操作。
函数类型是指函数返回值的类型
函数类型是指针的函数就是指针型函数
子函数:一个文件中除了main主函数之外的其他程序员自定义函数。
虚实结合:在调用函数时形参和实参之间发生的数据传递。
程序常识
C语言编程原理
C编程的基本策略是,用程序把源代码文件转换为可执行文件(其中包含可直接运行的机器语言代码)。 典型的C实现通过编译和链接两个步骤来完成这一过程。编译器把源代码转换成中间代码,链接器把中间代码和其他代码合并,生成可执行文件。 C使用这种分而治之的方法方便对程序进行模块化,可以独立编译单独的模块,稍后再用链接器合并已编译的模块。通过这种方式,如果只更改某个模块,不必因此重新编译其他模块。另外,链接器还将你编写的程序和预编译的库代码合并。 中间文件有多种形式。我们在这里描述的是最普遍的一种形式,即把源代码转换为机器语言代码,并把结果放在目标代码文件(或简称目标文件)中(这里假设源代码只有一个文件)。 虽然目标文件中包含机器语言代码,但是并不能直接运行该文件。因为目标文件中存储的是编译器翻译的源代码,这还不是一个完整的程序。 目标代码文件缺失启动代码(startup code)。
启动代码充当着程序和操作系统之间的接口。 例如,可以在MS Windows或Linux系统下运行IBMPC兼容机。这两种情况所使用的硬件相同,所以目标代码相同,但是 Windows和Linux 所需的启动代码不同,因为这些系统处理程序的方式不同。 目标代码还缺少库函数。几乎所有的C程序都要使用C标准库中的函数,例如,concrete.c中就使用了printf()函数。目标代码文件并不包含该函数的代码,它只包含了使用printf()函数的指令。printf()函数真正的代码存储在另一个被称为库的文件中。库文件中有许多函数的目标代码。链接器的作用是,把你编写的目标代码、系统的标准启动代码和库代码这3部分合并成一个文件,即可执行文件。对于库代码,链接器只会把程序中要用到的库函数代码提取出来(见图1.4)。
简而言之,文件和可执行文件都由机器语言指令组成的, 但目标文件中只包含编译器为你编写的代码翻译的机器语言代码, 可执行文件中还包含你编写的程序中使用的库函数和启动代码的机器代码。在有些系统中,必须分别运行编译程序和链接程序, 而在另一些系统中,编译器会自动启动链接器,用户只需给出编译命令。
C Primer Plus图1-4
一个C语言程序由一个或多个源程序文件(程序模块)组成
一个源程序文件由以下三个部分组成
预处理指令
C编译系统在对源程序进行“翻译”以前,先由一个预处理器(也称预处理程序、预编译器)对预处理指令进行预处理。
例:#include <stdio.h>
#include<stdio.h>的作用相当于把stdio.h文件中的所有内容都输入该行所在的位置。 实际上,这是一种“拷贝-粘贴”的操作。include 文件提供了一种方便的途径共享许多程序共有的信息。
由预处理得到的结果与程序其他部分一起,组成一个完整的、可以用来编译的最后的源程序,然后由编译程序对该源程序正式进行编译,才得到目标程序。
全局变量
在函数之外进行的数据声明。
函数定义
函数是C程序的主要组成部分,是C程序的基本单位。程序的几乎全部工作都是由各个函数分别完成的, 在程序中,每个函数都用来实现一个或几个特定的功能。编写C程序的工作主要就是编写一个个函数。
一个C语言程序是由一个或多个函数组成的,而函数又可以大概分为三类。
主函数
main函数,每个程序都有且只有一个,每次程序运行时都从main函数开始运行。
子函数
在程序中除main函数之外程序员定义的其他函数。
系统函数
在库文件中已经定义好的函数,用#include指令包含一下头文件就能直接调用。
一个函数包括两个部分。
函数首部。即函数的第1行,包括函数名、函数类型、函数属性、函数参数(形式数)名、参数类型。一个函数名后面必须跟一对圆括号,括号内写函数的参数名及其类型。 如果函数没有参数,可以在括号中写void,也可以什么都不写。
函数体。即函数首部下面的花括号内的部分。 如果在一个函数中包括有多层花括号,则最外层的一对花括号是函数体的范围。
声明部分:定义在本函数中所用到的变量。
执行部分。由若干个语句组成,指定在函数中所进行的操作。
注:在某些情况下也可以没有声明部分,甚至可以既无声明部分也无执行部分。
注:一个源程序文件为一个编译单位,在程序编译时是以源程序文件为单位进行编译的。
注意事项
限定符
const
限定一个变量为只读 (可调用,但不能改变)。
注:在C语言中,用const类型限定符声明的是变量,不是常量。
volatile
直接存取原始内存地址
例:volatile int a
确保本条指令不会被编译器的优化而忽略。
volatile对应的变量可能在你的程序本身不知道的情况下发生改变
比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量
你自己的程序,是无法判定何时这个变量会发生变化
还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候, 通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取, 而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化, 系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
关键字
预处理
预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。
#define指令定义一个宏定义
格式
#define 标识符 文本
将"文本"打包重命名,每当程序识别到"标识符"时,会自动将标识符解压翻译为文本。
注:"文本"可为常量,可为字符串,也可为几段函数。
可以这么理解,假如"文本"为几段函数,那当你引用程序中其他正常函数的函数名时,只能调用一个函数, 而当你引用宏时,会直接将文本中的几段函数从头到尾的运行一遍,此时,宏就相当于其他正常函数的函数名。
作用域
从定义开始到程序结尾或#undef
#undef指令删除一个宏定义
#include指令打开指定的文件,并且把此文件的内容包含到程序中。
#include <文件名>
用于属于C语言自身库的头文件
使用尖括号表示在包含文件目录中去查找,而不在源文件目录去查找。
注:包含目录是由用户在设置环境时设置的。
#include "文件名"
用于所有其他头文件,也包含任何自己编写的文件
使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。
注:包含目录是由用户在设置环境时设置的。
#ifdef:检查是否定义了某个宏,如果定义了,则其后的代码段将被编译。
格式
#ifdef 宏 代码段
#ifndef:检查某个宏是否未定义,如果未定义,则其后的代码段将被编译。
格式
#ifndef 宏 代码段
#if:后面跟一个整型常量表达式,该表达式的值为非零(真)时,其后的代码段将被编译。
#else:当上方#if、#ifdef或#ifndef指令之后的整型常量表达式不为真,而此指令之后的整型常量表达式为真时,执行此指令之下的代码段。
注:类if else语句
#elif:前面的#if、#ifdef 或#ifndef 指令的条件评估为假并且#elif 条件评估为真时,程序会执行#elif语句之后的代码段。
#endif 用于结束一个条件编译块。
#error、#line、#pragma
注:指令都以#开始
#符号不需要在一行的行首,只要它之前只有空白字符就行。在#后是指令名,接着是指令所需要的其他信息。
在指令的符号之间可以插入任意数量的空格或水平制表符
指令总是在第一个换行符处结束,除非明确地指明要延续
如果想在下一行延续指令,我们必须在当前行的末尾使用'\'字符。
指令可以出现在程序中的任何地方
但我们通常将#define和#include指令放在文件的开始,其他指令则放在后面,甚至可以放在函数定义的中间。
注释可以与指令放在同一行
实际上,在宏定义的后面加一个注释来解释宏的含义是一种比较好的习惯。
变量
数据类型
基本(内置)数据类型
派生(构造)数据类型
数组类型
数组
数组(array)是按顺序存储的一系列类型相同的值,是一组有序数据的集合。
由连续的一块内存单元组成。
用一个数组名来代表整个数组
用于识别数组元素的数字被称为下标、索引或偏移量。下标必须是整数,而且要从0开始计数。
用一个数组名和下标来唯一地确定数组中的元素,
由于计算机键盘只能输入有限的单个字符而无法表示上下标,C语言规定用方括号中的数字来表示下标。
数组格式
数据类型 数组名[数组长度]
数据类型 字符[数字或常量表达式]
调用格式
数组名[下标]
数组名[数字或常量表达式]
注:在调用时,先由数组名的地址找到数组首元素的地址,再由数组首元素的地址找到数组首元素的值,然后再找到你要找的元素的值。(个人感悟,不一定对)
注:定义数组时用到的“数组名[数组长度]”和引用数组元素时用的“数组名[下标]”形式相同,但含义不同。
数组长度从1开始,下标从0开始。
注:数组名是指针常量,地址是整个数组的地址,地址大小为数组的大小,保存的值是数组首元素的地址。
多维数组
二维数组
格式
数据类型 数组名[数字1][数字2]
一个“数组名”大数组中包含“数字1”个小数组,而每个小数组又都能包含“数字2”个变量。
三维数组
格式
数据类型 数组名[数字1][数字2][数字3]
一个“数组名”大数组中包含“数字1”个中数组,而每个中数组又都能包含“数字2”个小数组,而每个小数组又都能包含“数字3”个变量。
……
…
…………
………………………
变长数组 VLA
C99允许数组的[ ]中的值是整形变量或是整形表达式。
注:变长数组是指用整型变量或表达式声明或定义的数组,而不是说数组的长度会随时变化,变长数组在其生存期内的长度同样是固定的。
注:变长数组不等于动态数组,本质还是静态数组,也就是说,数组的长度在变量的整个生命周期中是不可变的
注:变长数组只能是局部变量数组
注:变长数组不能在定义的时候进行初始化
例:int n; scanf("%d",&n); int aaa [n];
注:变长数组必须是自动存储类别,即不能使用extern或static存储类别说明符
注:由于变长数组只能是局部变量,且必须是自动存储类别,因此变长数组分配在栈上
注:可变长数组对于多维数组也适用(如array[a][b] )
注:需编译器支持此特性
注:VS系列编译器均不支持该特性
字符数组
用来存放字符数据的数组是字符数组。在字符数组中的一个元素内存放一个字符。
相当于翻译器,将存储在元素中的数字以ASCII码格式翻译成相应的字符并打印出来。
注:计算机只能识别二进制语言❤️。
定义
char c[] ={'I',' ', 'a','m',' ','h','a','p','p','y','/0'}
char c[]={"I am happy"}
注:花括号可省略
注:用字符数组存放字符串
注:由于字符型数据是以整数形式(ASCII代码)存放的,因此也可以用整型数组来存放字符数据,但比较浪费存储空间。
注:字符数组中的数字也是字符,不是单纯的数字。
字符串
由多个字符组成,其末尾一定有/0。
注:/0为空字符,C语言用它标记字符串的结束。
凡是指向或保存字符串地址的,其指向或保存的都是字符串首字符的地址。
注:C语言中没有字符串类型,也没有字符串变量,字符串是存放在字符型数组中的。
在C语言中,是将字符串作为字符数组来处理的。
注:C语言规定了一个“字符串结束标志”,以字符\’0'作为结束标志。
如果字符数组中存有若干字符,前面9个字符都不是空字符('\0'),而第10个字符是个'\0',则认为数组中有一个字符串,其有效字符为9个。
注:C系统在用字符数组存储字符串常量时会自动加一个0'作为结束符。
指针数组
一个数组的元素值皆为指针则是指针数组。
注:数组名是指针变量,保存数组首元素的地址。
注:数组中的每一个元素都属于同一个数据类型,不能把不同类型的数据放在同一个数组中。
结构体类型
结构体就是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。
结构体类似一个大包裹,并没有实际改变什么,只是给成员变量加了个前缀,帮助程序员更好地理解程序。
定义
先定义结构体类型,再声明结构体变量。
struct 结构体名 { 成员变量 };
注:前面定义的是结构体数据类型,它只是约定它要声明的变量包括哪些成员,需要分配多大的存储空间。它本身不是变量,不能用于存储数据。 只有用它声明了结构体变量,才能使用结构体变量来进行数据的存储。因此,定义了结构体之后,还要用它声明结构体变量,然后使用结构体变量。
struct 结构体名 结构体变量名
定义结构体类型的同时声明结构体变量
struct 结构体名 { 成员变量 }结构体变量名;
直接声明结构体类型变量
struct { 成员变量 }结构体变量名;
注:由于没有设置结构体的名字,这种显示对结构体类型的的使用是一次性的。
注:使用struct关键字创建。
注:不可在结构体中再包函一个结构体。
共用(联合)体类型
它允许在同一个内存位置存储不同的数据类型,或者说同一个内存空间有多种解释方式。
定义
先定义结构体类型,再声明结构体变量。
struct 结构体名 { 成员变量 };
注:前面定义的是结构体数据类型,它只是约定它要声明的变量包括哪些成员,需要分配多大的存储空间。它本身不是变量,不能用于存储数据。 只有用它声明了结构体变量,才能使用结构体变量来进行数据的存储。因此,定义了结构体之后,还要用它声明结构体变量,然后使用结构体变量。
struct 结构体名 结构体变量名
定义结构体类型的同时声明结构体变量
struct 结构体名 { 成员变量 }结构体变量名;
子主题直接声明结构体类型变量
struct { 成员变量 }结构体变量名;
注:由于没有设置结构体的名字,这种显示对结构体类型的的使用是一次性的。
注:使用union关键字创建。
注:共用体的大小取决于最大的成员的大小。
注:对共用体的任何成员的修改都会影响到其他成员。
注:共用体的成员可以具有不同的数据类型,但它们在内存中的起始位置是相同的。
注:共用体的所有成员共享同一块内存空间,因此同一时间只能存储其中一个成员的值。
枚举类型
为常量赋予有意义的名称,从而增强代码的可读性和可维护性。
使用 enum 关键字定义,通过为一组相关常量命名来创建一种新的数据类型。
格式
不指定值的枚举
enum 枚举名称 { 枚举元素1, 枚举元素2, ... 枚举元素N }
指定部分值的枚举
enum 枚举名称 { 枚举元素1 = 值1, 枚举元素2, ... 枚举元素N }
完全指定值的枚举
enum 枚举名称 { 枚举元素1 = 值1, 枚举元素2 = 值2, ... 枚举元素N = 值N }
注:相当于给常量重命名,注意一个常量可以有多个名字。
唯一性:在同一枚举类型中,每个枚举常量的值必须唯一,即不能有两个或多个枚举常量具有相同的值。
整数类型:枚举常量的值必须是整数类型,虽然在大多数编程语言中枚举常量本身不直接表现为整数,但在底层它们通常由整数(通常是 int 类型)表示。
自定义值:定义枚举时,可以为枚举元素指定自定义的整数值,但需确保这些值的唯一性和整数类型。
自动递增赋值:
如果没有为枚举常量显式指定值,编译器会自动为它们分配值。这些值通常从 0 开始,依次递增。 例如,在一个包含三个枚举常量的枚举类型中,第一个枚举常量会被分配值 0,第二个为 1,第三个为 2。
如果为第一个枚举常量显式赋值,后续的枚举常量没有显式赋值,则后续枚举常量会从该值开始依次递增。 例如,如果给第一个枚举常量显式赋值为 1,则第二个枚举常量的值默认为 2,第三个默认为 3,依此类推。
如果中间的某个枚举常量显式赋了某个值,而下一个常量没有显式赋值,那么其默认值将从上一个枚举常量的值开始依次递增。 例如,如果给第一个枚举常量显式赋值为 1,则第二个枚举常量的值默认为 2,再给第三个枚举常量显式赋值为 10,则第四个枚举常量的值默认为 11,依此类推。
注:枚举名称(通常使用全大写表示)和其中的元素名称均为标识符(需要满足标识符的命名规则)。
注:枚举类型和宏定义是差不多的,只有细微区别,宏运行是在预处理阶段完成的,枚举类型是在与编译阶段完成的。
函数类型
函数类型用来定义函数,描述一个函数的接口,包括函数返回值的数据类型和参数的类型。
基本类型(包括整型和浮点型)和枚举类型变量的值都是数值,统称为算术类型(arithmetic type)。
算术类型和指针类型统称为纯量类型(scalar type),因为其变量的值是以数字来表示的。
数组类型和结构体类型统称为组合类型(aggregate type)
共用体类型不属于组合类型,因为在同一时间内只有一个成员具有值。
指针类型
指针是一种数据类型,是变量地址的别称,它可以用来定义变量,指针变量保存的值是其它变量的地址。
注:由于地址是固定长度,所以指针变量的长度是固定长度,
注:由于不同地址的步长不一样,所以要不同指针类型的变量来保存
注:const修饰的指针,const在*左表示指针指向的内容不可修改,const在*右表示指针的指向不可修改。
格式
数据类型 * 变量名1 = &变量名2
“变量名1”是指向“数据类型”变量的指针。
把变量名2的地址赋给变量名1。
注:指针变量只能保存正常变量的地址,不能保存常量。
运算符
*
间接(解引用)(间接寻址)运算符
给出存储在指针指向地址上的值
设int pooh = 5,ptr = &pooh, val = *ptr,则val = pooh = 5
&
取地址运算符
给出变量的存储地址
如果 pooh是变量名, 那么&pooh是变量的地址。
多级指针
层层转包,单线联系
n级指针
数据类型 (n个*) 变量名1 = (相同的)数据类型 (n-1个*) 变量名2
注:一级指针保存变量的地址,二级以上指针只能保存前一级指针的地址。
注:只有在多级指针前面加上对应多的“*”运算符才能取到最初变量保存的值,不然取到的就只是对应级指针的地址。
设a、b、c、d、e,分别为一/二/三/四/五级指针,int i = 2;int * a = &i; int ** b = &a;……;int ***** e = &d;
*e获取d的地址,**e获取c的地址,***e获取b的地址,****e获取a的地址,*****e获取i的变量值。
数组指针
数组的指针是指数组的首地址。
字符指针
和字符串指针是一个意思,只是偏向强调的重点不同, 字符指针强调指向的是一个字符的地址, 而字符串指针强调的是指向一个字符串的首字符的地址。
字符串指针
指向一个字符串的首字符的地址。
注:指针指向的值保存在数据段的or段,该数据不可被修改。
函数指针
格式
定义
数据类型 (*指针变量名) (实参的数据类型)
初始化
指针变量名 = 函数名
将"函数名"函数的入口地址赋值给函数指针变量"指针变量名"
调用
(*指针变量名) (实参表)
(*指针变量名) (纯数据)
例:(*指针变量名) (6,7,3,9)
(*指针变量名) (kxjdsjskshvsk,dkish7646kdj)
万能指针
void *
可以接收任何类型指针的地址
注:想要对void类型指针变量进行解引用,就要将void类型指针变量进行强制类型转换,从void*强制转换为这个指针中存储的地址指向的值的类型。
文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息 (如文件的名字,文件状态及文件当前的位置等)。 这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE。
格式
FILE* 指针名
注:stdio.h头文件把3个文件指针与3个标准文件相关联,C程序会自动打开这3个标准文件。
标准文件
标准输入
标准输出
标准错误
文件指针
stdin
stdout
stderr
通常使用的设备
键盘
显示器
显示器
void(空)类形
无返回值
系统定义
size_t
类型
typedef 定义的无符号整数类型
用途
用于表示对象的大小(以字节为单位)或数组的索引
注:在各个C标准库中定义。
注:size_t 的具体底层类型取决于编译器和平台。
time_t
类型
算术类型(整数或浮点数),能够表示时间
用途
表示文件时间戳、程序计时、日期计算等
定义在标准头文件 <time.h> 中
注:time_t 是C语言中表示时间的基石类型, 对于处理任何与时间相关的C程序都至关重要,尤其是在需要跨平台或长期运行的系统中。
注:不同类型的数据在内存中占用的存储单元长度是不同的。
注:数据类型相互转换方法
在变量前面加上一个数据类型名,并用小括号将这个类型名括起来。
如:c=(unsigned long)a*b
注:强制类型转换运算符优先级高于算术运算符
注:隐式类型转换
char→short→int→long→float→double
运算符(操作符)
转义字符
函数
函数是实现了某种功能的代码块,是完成特定任务的独立程序代码单元。语法规则定义了函数的结构和使用方式。
声明格式
返回值的数据类型 函数名 (形式参数列表) ;
注:一般在文件开头声明,为了让此函数之上的函数能够调用此函数。和全局变量要尽量在文件开头定义的原因差不多。
注:函数声明的作用是告诉编译器下方被调用函数是个函数,以及这个函数的具体信息。
注: 函数声明既可以放在某个函数的内部,也可以放在所有函数的外面。 如果放在函数内部,则被声明函数只能在该函数内部被调用。 如果放在所有函数的外面,则声明后面的所有函数都能调用被声明函数。
定义格式
返回值的数据类型 函数名 (形式参数列表) { 函数体 }
在程序中,函数名一般会在三种途径被使用
程序开头函数的声明
函数的调用
函数的定义
注:形式参数列表可以不存在,这也就意味着,此函数不接收任何外来数据。
注:函数体包括上下(左右)的花括号,为一个完整的函数去除函数头之后剩下的所有。
注:如果没有声明返回值的数据类型,则默认为int类型。
注:当函数的返回值类型为void时,则表示函数不会返回任何值。在这种情况下,函数体内部不能使用 return 语句返回值。 当函数的返回值类型不为void时,则表示函数会返回值。在这种情况下,函数体内部必须使用 return 语句返回值。
注:在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。
注:每个函数都是相互独立的,可以嵌套调用,但不能嵌套定义。
调用格式
函数名(实际参数列表)
注:被调用函数必须已经被定义,必须在调用语句之前被声明。
注:函数的调用和宏的替换差不多,函数的调用就是将函数的调用语句替换成被调用函数整个本身,在执行完一遍被调用函数之后,再返回函数的调用语句,继续往下执行。
注:其实没有参数需要传递,也必须输入"()"
如果函数声明时定义了固定数量的参数(例如 `void func(int a, int b)`),调用时必须传递所有参数,否则编译器会报错。
注:可嵌套。
注:函数声明和函数原型和函数头的形式一样,但各自所代表的概念不一样。
函数原型
一种概念,包含函数的返回值的数据类型,函数名,形式参数列表三种信息,算是这三种信息的统称。
函数声明
将函数原型在函数定义之上声明,使得函数定义之上的其他函数能够识别并调用此函数。
以;结尾,是一条单独的语句。
函数头
和函数体在一起,组成一个完整的函数,也可以说是一个完整的函数去除函数体的部分。
注:形参和实参是变量在特定情况下的特定称谓,如果想了解形参和实参的详细信息,请往上看"变量"。
注:可在函数定义开头添加static关键字,把函数定义成内部(静态)函数,只能被同一个文件内的其它函数调用。
注:可在函数定义开头添加extern关键字,把函数定义成外部函数,可以被其它文件内的函数调用。
注:C语言规定:函数默认为外部函数。
注:各种函数格式中的size_t、int、void*等前缀都是为了方便理解,在实际操作中是不会写出来的。
注:函数从用户使用的角度来说又分为两种
库函数:由C系统提供,用户无须定义,也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用。
下列带.h的皆为头文件,前面的CXX为C标准,只有编译器支持该C标准,才能包含该头文件。
用户定义函数:由用户按需要写的函数。对于用户自定义函数,不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。
C89
assert.h断言
ctype.h字符分类
errno.h错误码
float.h浮点数宏定义
limits.h最大值与最小值
locale.h本地化
math.h数学运算
提供常用的数学运算,包括:正弦/余弦/正切的三角函数、指数函数、对数函数、取绝对值、取模运算、向上取整、向下取整等。
基本算术运算函数
fabs
计算浮点数的绝对值
格式
double fabs(double x)
fabs(参数1)
注:"参数"可为计算式,此函数只是确保返回值为正数
fabsf
计算 float 类型浮点数的绝对值
格式
float fabsf(float x)
fabsf(参数)
注:"参数"可为计算式,此函数只是确保返回值为正数
注:C99标准引入
fabsl
计算 long double 类型浮点数的绝对值
格式
long double fabsl(long double x)
fabsl(参数)
注:"参数"可为计算式,此函数只是确保返回值为正数
注:C99标准引入
fmod
计算两个浮点数相除的余数
格式
double fmod(double x, double y)
fmod(参数1,参数2)
fmin
返回两个浮点数中的较小值
格式
double fmin(double x, double y)
fmin(参数1,参数2)
fmax
返回两个浮点数中的较大值
格式
double fmax(double x, double y)
fmax(参数1,参数2)
幂函数和指数函数
pow
计算幂
格式
double pow(double x, double y)
pow(参数1,参数2)
计算以"参数1"为底的"参数2"次方值,然后将结果返回。
注:参数1和参数2必须为数字。
注:在使用pow函数时,要注意底数和指数数字的正负,不要违反基本的数学常识。
sqrt
计算平方根
格式
double sqrt(double x)
sqrt(参数1)
cbrt
计算立方根
格式
double cbrt(double x)
cbrt(参数1)
exp
计算自然指数
格式
double exp(double x)
exp(参数1)
计算以"e(自然对数的底数)"为底的"参数1"次方值,然后将结果返回。
对数函数
log
计算自然对数
格式
double log(double x)
log(参数1)
log10
计算以10为底的对数
格式
double log10(double x)
log10(参数1)
log2
计算以2为底的对数
格式
double log2(double x)
log2(参数1)
log1p
计算1 + x的自然对数
格式
double log1p(double x)
log1p(参数1)
三角函数和反三角函数
sin
计算正弦
格式
double sin(double x)
sin(参数1)
cos
计算余弦
格式
double cos(double x)
cos(参数1)
tan
计算正切
格式
double tan(double x)
tan(参数1)
asin
计算反正弦
格式
double asin(double x)
asin(参数1)
acos
计算反余弦
格式
double acos(double x)
acos(参数1)
atan
计算反正切
格式
double atan(double x)
atan(参数1)
atan2
计算两个变量的反正切
格式
double atan2(double y, double x)
atan2(参数1,参数2)
双曲函数和反双曲函数
sinh
计算双曲正弦
格式
double sinh(double x)
sinh(参数1)
cosh
计算双曲余弦
格式
double acosh(double x)
cosh(参数1)
tanh
计算双曲正切
格式
double tanh(double x)
tanh(参数1)
asinh
计算反双曲正弦
格式
double asinh(double x)
asinh(参数1)
acosh
计算反双曲余弦
格式
double acosh(double x)
acosh(参数1)
atanh
计算反双曲正切
格式
double atanh(double x)
atanh(参数1)
其他数学函数
ceil
向上取整
格式
double ceil(double x)
ceil(参数1)
floor
向下取整
格式
double floor(double x)
floor(参数1)
round
四舍五入
格式
double round(double x)
round(参数1)
trunc
截断小数部分
格式
double trunc(double x)
trunc(参数1)
hypot
计算直角三角形的斜边长度
格式
double hypot(double x, double y)
hypot(参数1,参数2)
将参数1和参数2分别平方再相加,再将这个结果平方根。
setjmp.h长跳转
signal.h信号
stdarg.h可变参数
stddef.h常用定义
stdio.h输入/输出
stdlib.h通用工具
提供内存分配与释放、随机数的生成、进程退出、执行命令行、字符串转数值类型、二分查找算法、快排算法、求绝对值等操作。
malloc
用于申请一块连续的指定大小的内存块区域,以void*类型返回分配的内存区域地址。
格式
void *malloc(unsigned size)
malloc(数字)
注:实现原理:链表连接所有空闲的空间,组成最终分配的空间。
注:返回类型为无类型(void)指针,也即是通用指针,本质上是内存地址,需要进行类型转换。
注:在使用完malloc函数开辟的空间之后,需要释放(free函数),否则会造成内存泄漏。
注:在使用malloc函数开辟的空间中,不要移动指针,否则可能出现申请的空间和释放的空间大小不一致。
free
释放不需要的内存空间。
格式
void free(void *ptr)
free(参数)
注:在申请内存之后,需要及时将不再使用的内存空间进行释放,否则会导致内存耗尽。
calloc
为n个元素的数组分配内存,每个元素长度为size个字节
格式
void *calloc(unsigned n,unsigned size)
calloc(数字1,数字2)
注:与malloc() 函数的区别:calloc()函数会将所有字节置0来初始化该内存空间
realloc
为数组分配空间之后,若发现数组过大或过小,realloc() 函数可以调整已申请内存的大小。
格式
void *realloc(void *ptr,unsigned size)
realloc(参数,数字)
ptr指向原先通过malloc()、calloc()或realloc()获得的内存块。
size为内存块的新大小。
注:当分配内存减小时,realloc()仅改变索引信息
注:当分配内存扩大时
当前内存后还有空间:直接扩展这段内存空间,realloc()函数返回原指针。
当前内存后空闲字节不够:使用堆中第一个能够满足这一要求的内存块,并将目前的数据复制到新位置,将原来的空间释放掉,返回新的内存块地址。
申请失败:返回NULL,此时,原来指针仍然有效。
注:当ptr为空指针时,realloc()函数作用等同于malloc()函数;
abs
计算 int 类型整数的绝对值
格式
int abs(int n)
abs(参数)
注:"参数"可为计算式,此函数只是确保返回值为正数
labs
计算 long 类型整数的绝对值
格式
long labs(long n)
labs(参数)
注:"参数"可为计算式,此函数只是确保返回值为正数
llabs
计算 long long 类型整数的绝对值
格式
long long llabs(long long n)
llabs(参数)
注:"参数"可为计算式,此函数只是确保返回值为正数
注:C99标准引入
atoi
将数字字符的字符串转换为整型返回
格式
int atoi (const char * str)
atof(参数)
注:如果字符串从开头就有连续的空格字符,则跳过这些连续的空格字符,找到不是空格的字符。
注:如果跳过这些空格字符后的第一个字符不是数字字符,则直接返回0。
注:如果跳过这些空格字符后的第一个字符是数字字符,则从这个数字字符开始转换,并向后找连续的数字字符转换, 如果连续中断,找到不是数字字符的字符,则在此截断寻找,返回前面已经转换好的连续的数字字符字面整型值。 (这里截断向后寻找后,不管后面有没有数字字符函数都不管)
注:如果字符串全部为空格字符,返回0;如果为空字符串,返回0;
atof
把字符串转换成浮点数,直至遇到第一个空格。
格式
double atof(const char *nptr)
atof(参数)
itoa
将整数转换为字符串。
格式
char* itoa(int value,char* string,int radix)
itoa(数字,参数,数字)
value是要转换的整数值。
string是存储转换后值的字符数组。
radix代表进制,radix=10就是十进制,radix=2就是二进制。
注:第二个参数只能是char*,不能是string;
注:第二个参数在使用之前必须提前分配存储空间。
qsort
对任意类型的数组进行排序
格式
void qsort ( void * base, size_t num, size_t size, int ( * comparator ) ( const void *, const void * ) )
qsort(参数1,参数2,参数3,参数4)
参数1为待排序数组的首地址,也就是数组名。(没有中括号)
参数2为数组元素的个数。(常量或常量表达式)
参数3为一个数组元素的大小(所占字节数)。(常量或常量表达式)
参数4为一个函数名。(没有小括号)
注:函数需用户自定义,作用是将传进来的两个参数进行比较,如果参数p1<参数p2,则返回一个小于0的数,如果参数p1=参数p2,则返回0;如果参数p1>p2,则返回一个大于0的数。
注:函数的作用仅仅是比较两个参数的大小,并通过返回值的形式告诉qsort()函数比较的结果,在运行期间是不能更改参数1或参数2的值的。
所以为保险起见,我们可以给两个参数前加上const修饰,来使参数指向的数值无法改变。
例:qsort(arr, num, sz, compar)
注:arr为数组名,num为常量表达式,sz为常量表达式,compar为函数名。
在qsort()函数调用完自定义函数后,会接收到自定义返回的一个有符号的整型数字,当接收到自定义返回大于0的数字时,qsort()函数就会将这两个元素做交换。 而如果接收到自定义函数返回小于等于0的数时,qsort()函数不对其进行交换。
srand
设置随机数生成器的种子值(开始点)
格式
void srand(unsigned int seed)
srand(参数)
rand
伪随机数生成器,用于生成一个范围在 0 到 RAND_MAX (通常至少为 32767 )之间的整数。
格式
int rand(void)
rand()
注: rand()生成的序列是确定性的(基于初始种子值),每次程序运行时若种子相同,则序列相同。
使用 srand(seed)设置随机种子。常用 time(0) (需包含 time.h)作为种子,确保每次运行结果不同。
string.h字符串处理
time.h系统时间
C99
complex.h复数运算
fenv.h浮点数环境
inttypes.h整数类型
iso646.h备选拼写
stdbool.h布尔值
stdint.h精确整数
tgmath.h
wchar.h
wctype.h
C11
stdalign.h
stdatomic.h
stdnoreturn.h
threads.h
uchar.h
windows平台
Linux平台
GCC编译器内置
__builtin_popcountll
用于高效计算一个 64 位无符号整数的二进制表示中 1 的个数
格式
int __builtin_popcountll(unsigned long long x)
语句
链表
一种常见的数据结构,它通过一系列节点(Node)来存储数据元素。
与数组不同,链表中的元素在内存中不是连续存储的,而是通过指针或引用链接在一起。
注:链表的基本结构包括节点和指针,节点通常包含两部分:数据域和指针域(或称为链接域)。
数据域用于存储数据元素,而指针域则用于指向链表中的下一个节点。
注:可以使用结构体来定义链表节点。
分类
单向链表
每个节点只有一个指针,指向下一个节点。
例
节点定义:使用结构体来定义链表节点。每个节点包含一个数据域(data)和一个指针域(next),其中指针域指向下一个节点。
typedef struct Node { int data; // 数据域 struct Node* next; // 指针域 } Node;
链表初始化:创建一个头节点(head),并将其指针域置为空(NULL),表示链表为空。
Node* initList() { Node* head = (Node*)malloc(sizeof(Node)); // 创建头节点 if (!head) { exit(1); // 内存分配失败,退出程序 } head->next = NULL; // 初始化头节点的指针域为空 return head; }
插入节点:在链表中插入节点时,需要指定插入位置和前一个节点。根据插入位置的不同,可以分为头插法(在链表头部插入节点)和尾插法(在链表尾部插入节点)。以下以尾插法为例,展示插入节点的实现方法。
void insertNode(Node* head, int data) { Node* newNode = (Node*)malloc(sizeof(Node)); // 创建新节点 if (!newNode) { exit(1); // 内存分配失败,退出程序 } newNode->data = data; // 设置新节点的数据域 newNode->next = NULL; // 初始化新节点的指针域为空 // 找到链表的最后一个节点 Node* cur = head; while (cur->next != NULL) { cur = cur->next; } // 将新节点插入到链表尾部 cur->next = newNode; }
删除节点:在链表中删除节点时,需要指定要删除的节点或其前驱节点。以下以删除指定值的节点为例,展示删除节点的实现方法。
void deleteNode(Node* head, int data) { if (head == NULL || head->next == NULL) { // 空链表或只有一个节点的链表 return; } // 如果要删除的节点是头节点 if (head->next->data == data) { Node* temp = head->next; // 暂存要删除的节点 head->next = temp->next; // 修改头节点的指针域,跳过要删除的节点 free(temp); // 释放要删除的节点的内存空间 return; } // 如果要删除的节点不是头节点 Node* cur = head; while (cur->next != NULL && cur->next->data != data) { // 找到要删除的节点的前驱节点 cur = cur->next; } if (cur->next == NULL) { // 未找到要删除的节点 return; } Node* temp = cur->next; // 暂存要删除的节点 cur->next = temp->next; // 修改前驱节点的指针域,跳过要删除的节点 free(temp); // 释放要删除的节点的内存空间 }
遍历链表:遍历链表时,可以从头节点开始,依次访问每个节点的数据域,直到遇到空指针为止。以下是一个简单的遍历链表的函数。
void traverseList(Node* head) { Node* cur = head->next; // 从头节点的下一个节点开始遍历 while (cur != NULL) { printf("%d ", cur->data); // 访问当前节点的数据域 cur = cur->next; // 移动到下一个节点 } printf("\n"); }
查找节点:在链表中查找特定值的节点时,通常需要从头节点开始遍历链表,逐个比较节点的数据域直到找到目标节点或遍历完整个链表。以下是一个简单的查找节点的函数实现:
Node* findNode(Node* head, int data) { Node* cur = head->next; // 从头节点的下一个节点开始遍历 while (cur != NULL && cur->data != data) { cur = cur->next; } return cur; // 如果找到目标节点,返回该节点;否则返回NULL }
反转链表:反转链表是一个常见的链表操作,它将链表中的节点顺序进行反转。以下是反转链表的函数实现:
Node* reverseList(Node* head) { Node* prev = NULL; Node* cur = head->next; Node* next = NULL; while (cur != NULL) { next = cur->next; // 保存当前节点的下一个节点 cur->next = prev; // 将当前节点的指针指向前一个节点 prev = cur; // 移动prev到当前节点 cur = next; // 移动cur到下一个节点 } head->next = prev; // 反转后,新的头节点是prev return head; }
链表排序:链表排序通常使用插入排序、归并排序等算法。由于链表不是连续存储的,所以一些针对数组的排序算法(如快速排序、堆排序)可能不适合直接应用于链表。以下是使用插入排序算法对链表进行排序的示例:
void insertionSortList(Node** head_ref) { Node* sorted = NULL; Node* current = *head_ref; Node* next = NULL; while (current != NULL) { next = current->next; // 保存下一个节点 // 将current节点插入到已排序链表sorted中 if (sorted == NULL || sorted->data >= current->data) { current->next = sorted; sorted = current; } else { Node* prev = NULL; while (sorted != NULL && sorted->data < current->data) { prev = sorted; sorted = sorted->next; } current->next = sorted; prev->next = current; } current = next; // 移动到下一个节点 } *head_ref = sorted; // 排序后的链表 }
双向链表
双向链表则包含两个指针,一个指向前一个节点,一个指向后一个节点;
循环链表
循环链表则是将链表的最后一个节点指向第一个节点,形成一个环形结构。
特点
动态分配:链表中的节点可以在运行时动态分配和释放,因此链表的大小可以根据需要动态调整。
插入和删除操作方便:在链表中插入或删除一个节点时,只需要修改相关节点的指针即可,不需要移动其他节点,因此操作效率较高。
空间利用率高:链表不需要像数组那样预先分配连续的内存空间,因此可以充分利用零散的内存空间。
没有越界问题:由于链表是通过指针链接的,因此不存在数组中的越界问题。
文件
0-1-1_1
0-1-1_2
0-1-1_3
0-1-2
switch后的()中 必须为整型
0-1-3
0-1-4
0-1-5
0-2-1
1-4
0-0-1
0-0-2
0-0-3