一:系统编程细节
1. 一切皆文件
1. VFS不仅包括file结构体,还有inode结构体和super block结构体
2. 在linux文件总共分成7种:
(1) 普通文件 :存储普通数据
(2) 目录文件 : 文件系统管理的重要文件类型
(3) 管道文件 :用于进程间通信的特殊文件,也称为命名管道FIFO
(4) 套接字文件 : 网络通信的特殊文件
(5) 链接文件 :间接访问另外一个目标文件
(6) 字符块设备文件 :字符设备在应用层的访问接口
(7) 块设备文件 : 块设备应用层的访问接口
2. 文件操作
1. 应用程序的一切行为都依赖于这个操作系统,但是这个操作系统的内部函数应用层的程序是不能直接被访问的
因此操作系统提供了系统调用接口。
3. 系统I/O
1. open()函数需要注意点:
(1) mode是八进制权限,比如0644或者0755等
(2) 它可以用来打开普通文件,块设备文件,字符设备文件,链接文件和管道文件
(3) open只能用来创建普通文件!每一种特殊文件的创建都有其特定的其他函数
(4) 其返回值就是一个代表这个文件的描述符,是一个非负整数
2. 文件描述符:0,1,2 分别代表: 标准输入,标准输出,标准出错
3. 在内核中打开的文件是用file结构体来表示的,每一个结构体都会有一个指针来指向它。
4. 一个进程与文件的关系:文件描述符的本质
进程控制块结构体
struct task_struct
{
*files--> struct files_struct
{
fd_array[*file]--> struct file
{
a.txt相关属性
};
};
};
5. 文件描述符:用户空间每一次调用open()函数都会使得内核实例化一个file{}结构体
并将一个指向该结构体的指针一次存放在fd_array[]中,该指针所占据的数组下标就是其文件描述符。
6. read()和write()的注意点:
(1) 实际的读/写字节数要通过返回值来判断,参数count只是一个"愿望值"
(2) 当实际的读/写字节数小于count时,有以下两种情况:
<1> 读操作时,文件剩余可读字节数不足count.
<2> 读/写操作期间,进程收到异步信号
(3) 不管是读还是写,文件的位置偏移量(即内核中的f_pos)都会加上实际读/写的字节数,不断地往后偏移
7. lseek()只对普通文件有效,特殊文件无法调整偏移量的。
8. dup2()可以通过第2个参数newfd来指定一个文件描述符,如果这个指定的描述符已经存在,将会被覆盖
9. fcntl()函数里面的 struct flock结构体注意点:
(1) 对一个文件必须有可读可写的权限,才能分别对该文件加读记录锁和写记录锁
(2) 由于缓冲区的原因,应避免在涉及记录锁的场合使用标准I/O ,改用系统I/O函数调用
(3) 记录锁不会被通过fork()系统调用创建出来的子进程继承,但可以在进程执行execve()加载新代码之后继续保持
(4) 记录锁可以是协商性的,也可以是强制性的,默认是协商性的。
(5) 协商性的记录锁不对文件本身做任何处理,因此只能用于相互协作的进程之间。
(6) 强制性的记录锁将会作用于文件本身使得任何进程在读/写已经加了所的文件的相关区域时阻塞或发生错误。
10. 系统I/O 特点:
(1) 更具有通用性,不管是普通文件,管道文件,设备节点文件,套接字文件等都可以使用
(2) 简约性 ,对文件内数据的读写在任何情况下都是不带任何格式的,而且数据的读写也都没有经过任何缓冲处理
4. 标准I/O
1. 标准I/O特点:
(1) 标准I/O提供缓存区,增加数据的吞吐量
(2) 数据将会先存储在一个标准I/O缓冲区中,而后在一定条件下才能被一并flush(冲洗,或称刷新)至内核
而不是像系统I/O那样,数据直接被flush至内核。
2. getchar() 默认从标准输入设备读取一个字符
putchar()默认从标准输出设备输出一个字符
3. fgetc()和fputc()是函数,getc()和putc()是宏定义
4. 两组输入\输出函数一般成对使用,fgetc()和fputc(),getc()和putc(),getchar()和putchar()
5. fgets()与 fgetc()一样,当其返回NULL 时,不能确定究竟是达到文件末尾还是碰到错误,需要用到feof()/ferror()
进一步判断。
6. fgets()每次读取之多不超过size个字节的一行,所谓的一行,即数据之多包含一个换行符'\n'
5. 文件属性
1. 文件属性结构体:
(1) st_ino 文件索引号:无符号整型数据,用来唯一确定分区中的文件
(2) st_nlink 记录该文件的名字(硬链接)总数,文件的别名可以令link或函数link()来创建,当一个文件的引用计数
st_nlink为零时,系统将会释放清空该文件锁占用的一切资源
(3) 文件的大小:这个属性只针对普通文件有效
(4) 文件所占数据块数目,st_blocks,表明该文件实际占用存储器空间,一个数据块一般为512字节
2. 主设备号:标识一种设备类型
次设备号:用来区分本系统中的多个同类设备
3.
6. 目录检索
1. 目录:LInux 中的目录就是一组由文件名和索引号组成的索引表,目录下的文件的真正内容存储在分区中的数据域区域
2. 目录项:目录中索引表的每一项称为"目录项",里面至少存放了一个文件的名字(不含路径部分)和索引号(分区唯一的)
3. 当我们访问某一个文件时,就是根据其所在的目录的索引表中的名字找到其索引号,然后在分区i-node节点域中找到对应的文件i节点的。
7. 触控屏应用接口
1. 对于输入设备的使用,实际上运用了3大块来管理,他们分别是:
输入设备驱动层、输入子系统核心层、事件触发层。
2. input_even的结构体中
(1) time代表输入事件发生的时间戳
(2) type是输入事件的类型
(3) code 是这个事件的代码
(4) value :当code 都不足以区分事件的性质的时候,可以用value来确认。
------------------------------------------------------------------------------------------------------
8. 进程
1. 进程控制块结构体:记录内存资源,CPU资源,文件,信号,各种锁资源等。是记录进程的相关信息的结构体
包含了一个进程的所有信息
2. 当一个程序文件被执行时,内核将会产生这么一个结构体,来承载所有该活动实体,日后运行时所需要的所有资源
随着进程的运行,各种资源被分配和释放,是一个动态的过程
3. 每一个进程的都有自己的身份证号:即PID
4. PID是区分各个进程的基本依据,可以使用命令ps来查看进程的PID
5. 父子进程的一样的部分:
(1) 实际UID和 GID 以及有效 UID 和 GID
(2) 所有环境变量
(3) 进程组的ID和会话 ID
(4) 当前工作路径。除非用chdir()加以修改
(5) 打开的文件
(6) 信号响应函数
(7) 整个内存空间,包括栈,堆,数据段,代码段,标准I/O的缓冲区
6. 父子进程不一样的部分:
(1) 进程号PID.
(2) 记录锁,父进程对某个文件加了把锁,子进程不会继承这把锁。
(3) 挂起的信号,这些信号时所谓的"悬而未决"的信号,等待着进程的响应,子进程也不会继承这些信号
7. 子进程会从fork() 返回值后的下一条逻辑语句开始执行,避免不断调用此函数。
8. 父子进程是相互平等的,它的执行次序是随机的,或者说他们是并发运行的。
9. 父子进程是相互独立的,互不影响的。
10. exec函数簇成功执行后,原有的程序代码都将被指定的文件或脚本覆盖,因此这些函数一旦执行成功
后面的代码是无法执行的。它们也是无法返回的 。
11. exit()会刷新缓存区退出,_exit() 不会刷新,直接退出。
9. 进程通信
1. 进程之间的通信(IPC)方式:
(1) 无名管道(PIPE)和有名管道(FIFO);
(2) 信号
(3) 共享内存
(4) 消息队列
(5) 信号量
(6) 套接字
2. 无名管道是最简单的常用于一对一的亲缘进程间通信的方式
3. 有名管道存在于文件系统中,提供写入原子性特征
4. 信号是唯一一种异步通信方式
5. 共享内存效率最高,但是要结合信号量等同步互斥机制一起使用
6. 消息队列提供一种带简单消息标识的通信方式
7. 套接字是一种更为宽泛意义上的进程间通信方式---它允许进程间跨网络
8. 无名管道只能用于亲缘进程间
9. PIPE 有两个文件描述符,称为半双工通信方式,对写操作不做任何保护。
10. 无名管道PIPE是可以被继承的,由父进程创建
11. 有名管道的特征:
(1) 有名字,存储普通文件系统之中
(2) 任何具有相应权限的进程都可以使用open()来获取FIFO的文件描述符
(3) 跟普通文件一样,使用统一的read()/write()来读/写
(4) 跟普通文件不同,不能使用lseek()来定位,原因和PIPE相同
(5) 具有写入原子性,支持多写者同时进行写操作而数据不会互相践踏
(6) 最先最先被写入FIFO的数据,最先被读出来。
12. 管道文件为何不能用lseek定位的原因:
因为它们的数据不像普通文件那样按块的方式存放在如硬盘,Flash等块设备上,而更像一个看不见源头的水龙头,无法定位。
13. FIFO和PIPE还有一个最大的不同点在于:FIFO具有一种所谓写入原子性的特征。
14. 信号时无法预料的
15. 非实时信号特点:
(1) 不排队,信号的响应会相互嵌套
(2) 如果目标进程没有及时响应非实时信号,那么随后到达的该信号将会被丢弃
(3) 每一个非实时信号都对应一个系统事件,当这个事件发生时,将产生这个信号。
(4) 如果进程的挂起信号中含有实时和非实时信号,
那么进程优先响应实时信号并会从大到小依次响应,而非实时信号没有固定的次序
16. 实时信号的特点:
(1) 实时信号的响应次序按接收顺序排队,不嵌套
(2) 即使相同的实时信号被同时发送多次,也不会被丢弃,而会依次逐个响应
(3) 实时信号没有特殊的系统事件与之对应。
17. 任何进程都可以使用函数kill() 来产生任何信号
18. 如果发生栈溢出,可以使用替补栈:使用替补栈的顺序:
(1) 在堆中申请一块内存,作为替补栈空间
(2) 使用sigaltstack() 通知内核以上替补栈的位置和大小
(3) 当使用sigaction() 为某信号设置响应函数时,将SA_ONSTACK位或到sa_flags中,使得
该响应函数在替补栈中运行。
19. IPC对象:消息队列,共享内存,信号量
20. ftok() 函数中,如果两个参数都一样,那么产生的key值也是一样的。
21. 消息队列提供一种带有数据标识的特殊管道,使得每一段被写入的数据都变成带标识的消息。
22. 共享内存时效率最高的IPC对象。
23. 共享内存一般不能单独使用,需要配合信号量,互斥锁等协调机制,这样可以让各个进程高效交换
数据的同时,不会发生数据践踏,破坏等意外。
24. 共享内存只能以只读或只读写的方式映射,无法以只写方式映射
25. shmat()第2个参数shmaddr一般都设为NULL,让系统自动寻找合适的地址,但当其确实不为空时,那么
要求 SHM_RND 在 shmflg 必须被设置,这样的话系统将会选择比shmaddr小而又最大的页对齐地址
(即为SHMLBA的整数倍)作为共享内存区域的起始地址。如果没有设置SHM_RND,那么shmaddr必须是严格的页对齐地址。
26. 信号量跟其他两个IPC对象(消息队列,共享内存)有极大的不同之处是:它是用来作为旗语的,不是用来传输数据的
27. 临界资源(共享资源) : 多个进程或线程有可能同时访问的资源(变量,链表,文件等)
28. 临界代码: 访问这些临界资源的代码
29. 临界区: 临界代码区域
30. p操作:程序进入临界区之前必须对资源进程申请,这个动作称为p操作。
31. V操作: 程序离开临界区之后必须释放相应的资源,这个动作称为V操作。
32. 信号量的P,V操作最核心的特征是:他们是原子性的
也就是说:对信号量元素的值增加和减少,系统保证在CPU的电子特性级别上不可分割的。
33. semop()函数中
(1) sem_op >0 进行V操作,信号量元素值将会被加上sem_op的值
(2) sem_op =0 等零操作,如果此时semval恰好为0,则semop()立即成功返回。
(3) sem_op <0 进行P操作,即信号量元素的值将会被减去sem_op的绝对值。
10. 线程
1. 线程例程指的是: 如果线程创建成功,那么该线程会立即去执行函数
2. POSIX线程库中所有的API对返回值的处理原则是一致的,成功返回0,失败返回错误码errno
3. 线程属性如果为NULL,则会创建一个标准属性的线程
4. 一条线程如果是可接合的,意味着这条线程在退出时不会自动释放自身资源,而会成为僵尸线程
同时意味着该线程的退出值可以被其他线程获取
5. 如果不需要某条线程退出值的话,最好将线程设置为分离状态,以保证该线程不会成为僵尸线程
6. 线程的调度策略
(1) 当线程的调度策略为SCHED_FIFO:
其静态优先级必须设置为1~99,这将意味着一旦这种线程处于
就绪态时,它能立即抢占任何静态优先级为0的普通线程。
(2) 采用SCHED_FIFO调度策略的线程还遵循以下规则:
<1> 当它处于就绪态时,就会被放入其所在优先级队列的队尾位置
<2> 当被更高优先级的线程抢占后,它会被放入其所在优先级队列的队头位置,当所有优先级
比它高的线程不再运行后,它就恢复运行。
<3> 当它调用sched_yield()后,它会被放入其所在的优先级队列的队尾位置。
(3) 当线程的调度策略为SCHED_RR 时
情况与SCHED_FIFO一样,区别在于:每一个SHCED_RR策略下
的线程都将会被分配一个额度的时间片,当时间片耗光时,它会被放入其所在优先级队列的队尾位置。
(4) 当线程的调度策略为SCHED_OTHER时:
其静态优先级必须设置为0 该调度策略是Linux系统调度的默认策略,处于0优先级别的这些线程按照
所谓的动态优先级被调度,而动态优先级起始于线程的nice值
(5) 每当一个线程已处于就绪态但被调度器调度无视时,其动态优先级会自动增加一个单位,这样能保证这些
线程竞争 CPU 的公平性。
7. 静态优先级:0~99 之间 0 级线程被称为:非实时的普通线程
8. 线程的静态优先级之所以被称为静态,是因为只要不强行使用相关函数修改它,就不会随着线程的执行而发生改变。
9. 静态优先级决定了实时线程的基本调度次序,如果他们的静态优先级一样,那么调度策略会提供进一步的调度依据。
10. 线程的动态优先级:是非实时的普通线程独有的概念.之所以称之为动态,是因为它会随着线程的运行,根据线程的
表现而发生改变。
11.
进程是系统中资源分配的基本单位 (理解:一个进程 = 一个基本资源)
线程是系统调度的基本单位 (理解:我能派最小的单位来帮我处理任务)
11. 线程安全(通信)
1. POSIX信号量:POSIX有名信号量,POSIX无名信号量
2. 不像system-V的信号量可以申请或释放超过1个资源,对于POSIX有名信号量而言,每次申请和释放的资源数都是1
3. 调用sem_wait() 在资源为0 时会导致阻塞,如果不想阻塞等待,可以使用sem_trywait()来替代
4. 进程内部的线程间的同步互斥,可以不需要使用有名信号量,因为这些线程共享同一个内存空间
我们可以定义更加轻量化的,基于内存的(不在任何文件系统内部的)无名信号量来达到目的
5. system-V信号量和POSIX信号量的区别:
(1) sys-V 信号量可以对代表多种资源的多个信号量元素同一时间进行原子性的P/V操作
而POSIX信号量每次只能操作一个信号量
(2) sys-V信号量和named-sem是系统范围的资源,进程消失之后继续存在
而unname-sem 是进程范围的资源,随着进程的退出而消失
(3) sys-V 信号量的P/V操作可以对信号量元素加/减大于1的数值
而POSIX信号量每次P/V操作都是加/减 1
(4) sys-V 信号量甚至还支持撤销操作—— 一个进程对sys-V 信号量进行P/V操作时可以给该操作贴上需要撤销的标识
那么当进程退出之后,系统会自动撤销那些做了标识的操作,而POSIX信号量并没有此功能。
(5) sys-V 信号量和nameed-sem 适用在进程间同步互斥,而unamed-sem 适用在线程间同步互斥
6. 条件变量是一种同步互斥的机制,它必须与互斥锁一起配合使用。
7. 多线程中,一个函数如果同时被多条线程调用,它返回的结果是否严格一致可分为:可重入函数,不可重入函数
严格一致:可重入函数 不严格一致:不可重入函数
8. POSIX.1 - 2001 标准规定:所有的标准库函数都必须是可重入函数,除了一些特定的函数
9. 多线程同时调用了不可重入函数有可能会产生不一致的结果,产生这样的结果的原因有三:
(1) 因为函数内部使用了共享资源
(2) 函数内部调用了其他不可重入函数
(3) 函数执行结果与某硬件设备相关
10. 如果想写一个线程安全的可重入函数的话,只要遵循以下原则即可:
(1) 不使用任何静态数据,只使用局部变量或堆内存
(2) 不调用表 5-50 中的任何非线程安全的不可重入函数。
(3) 如果不能同时满足以上两个条件,可以使用信号量,互斥锁等机制来确保使用静态数据或调用不可重入
函数时的互斥效果,这是编程多线程程序必须注意的地方。
11. pthread_cond_wait();等待的时候会自动解锁,当有数据读取的时候会自动上锁
二:网络编程细节
1. TCP/IP协议
(1) OSI网络分层结构中,每一层与本层的上下两层结构从逻辑上是分开的
(2) 本层的协议:各个层在逻辑上分开的方式使得每一层为上一层提供服务,
依赖于下层的数据并为上一层提供接口,同时各层之间的规
则是相互独立的,例如数据的格式,同行的方式等
(3)协议栈:一个主机上运行的网络规则实现的集合
(4) 7 层模型:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
(5) 主机AB之间的网络通信大概步骤:【A发送:应用层->...物理层,B接收:物理层->...应用层】
(6) ISO与OSI区分: OSI模型:由ISO定义的网络结构的标准模型;ISO 是国际互联网标准化组织
(7) TCP/IP参考模型4个层次: 应用层,传输层,网络层,主机到网络层
(8) TCP/IP四层与OSI七层之间的对比:
应用层:应用层、表示层、会话层
传输层:传输层
网络互连层: 网络层
主机到网络: 数据链路层、物理层
(9) IP协议: 是TCP/IP协议中最重要的协议、它为TCP、UDP、ICMP等协议提供传输的通路
其主要目的是提供子网的互联,使不同的子网之间能够传输数据
(10) Ipv4:点分十进制ip(四组点分二进制) 32 位。ipv6 128 位的ip :解决地址不够用的问题
(11) TCP协议:是在原有IP协议的基础上,增加了确认重发,滑动串口和复用/解复用等机制
提供了一种可靠的,面向连接的字节流服务。
(11) 端口号:是为了区分同一台电脑上不同的进程,16 位无符号整型(0-65535之间)
(12) 实际写代码的过程中给进程指定的端口号一般用1024以上的端口号
(13) 三次握手:建立一条TCP的连接,两个主机之间需要进行三次通信的过程,发生在接口函数connect和accept之间
过程是:客户端发送SYN(同步信号)-->服务器收到之后会主动回复(SYN和应答ACK)
-->客户端收到应答之后,再次回复一个应答给服务器(应答ACK)
(14) 四次握手:终止一个TCP的连接,两个主机之间需要进行四次通信的过程
过程是:客户端给服务器发送FIN(结束信号)-->服务器收到之后给个应答ACK
-->服务器延时一会给客户端发送FIN-->客户端收到以后给一个应答ACK
(15) UDP协议特点 :
<1> UDP是TCP/IP传输协议的一部分
<2> 与TCP传输不一样,它提供无连接、不可靠的传输服务
<3> 将数据发送出去不提供发送数据包的顺序
<4> 接收方不向发送方发送接收的确认信息
<5> 如果出现丢包或者重包现象,也不会向发送方发送反馈
<6> 不能保证数据的一致性
(16) UDP数据传输过程:发送端封装:应用程序-->UDP-->IP-->驱动程序-->以太网
接收端解封:以太网-->驱动程序-->IP-->UDP-->应用程序
(17) TCP数据传输过程:发送端封装:应用程序-->TCP-->IP-->驱动程序-->以太网
接收端解封:以太网-->驱动程序-->IP-->TCP-->应用程序
(18) ARP协议:建立IP地址和硬件地址的对应关系,使得数据能够正确地传输
(19) DNS域名解析: 主机A用ping命令探测主机B,ping会判断发送的是主机名还是IP地址,
调用函数gethostbyname()解析主机名B,将主机名转换成一个32位的IP地址
2. IP地址分类与TCP/UDP端口
(1) IP地址的一般格式: 类别+网络标识+主机标识
<1>类别:用来区分IP地址的类型
<2>网络标识:标识主机所在网络
<3> 主机标识: 标识主机在网络中的标识
(2) A类地址特点:
<1> 网络ID(0-127):占一个字节
<2> 要求网络号的8位二进制必须是以0开头
<3> 主机ID(1-254.254.254);
(3) B类地址特点:
<1> 网络ID(128-191):占两个字节
<2> 要求网络号的16位二进制必须以10开头
<3> 主机ID(1-254.254)
(4) C类地址特点:
<1> 网络ID(192-233):占三个字节
<2> 要求网络号的24位二进制必须是以110开头
<3> 主机ID(1-254)
(5) D类地址特点:
<1> 不区分主机ID和网络ID
<2> 此类地址用于组播
(6) 广播地址:每个网络号下,主机号全部是255
(7) 主机ID全为0的IP地址不分配给任何主机,仅用于表示某个网络的网络地址
(8) 主机ID全为1的地址仅用做广播地址
(9) 主机、网络ID全为1的地址为有限广播地址
(10) 主机、网络ID全为0的地址为主机本身
(11) IP地址 127.0.0.1 是一个特殊的回环接口,常用于本地进行软件测试
3. 主机字节序和网络字节序
(1) 小端字节序:数据的高字节存放在高地址,数据的低字节存放在低地址
(2) 大端字节序:数据的高字节存放在低地址,数据的低字节存放在高地址
4. TCP与UDP通信的区别:
(1) tcp 是面向连接的(connect和accept),是可靠的通信方式(支持错误重传,数据包丢失可以重新发送)
实际应用: 通信质量要求高的场合,用tcp(比如:发送密码,登录)
(2) udp 是无连接的,不可靠的通信方式(丢包)
实际应用:流媒体播放
5. 多路复用
(1) select和poll的区别:
<1> select会将状态不改变的文件描述符从集合中剔除,但是poll不会
<2> poll相对而言简单一些,没有像select一样需要你求最大的文件描述符
也不需要额外去调用其他函数添加,判断文件描述符
6. 接口函数
(1) send 的返回值跟length的大小一样,length设置多少就是多少。
(2) wait 的返回值是所写入的字节数
(3) recv 的返回值有两种情况:
send发了多少个字节,返回值就是多少。
返回值是0 :说明对方断开连接。
返回值 -1 : 接收失败。
7. 无分类细节
(1) 在同一台机器上是不能出现两个端口号一样的进程
(2) Address already in use :绑定失败!
当程序退出之后,绑定的IP和端口号很有可能不会立马释放,
一般系统都会再半分钟释放掉,但是这种情况比较少见,遇到此情况可以稍等一会再重新执行此程序。
(3) 主机转网络:绑定之前, 网络转主机:连接请求以后
(4) tcp 通信采用的是C/S模式,client/server (客户端与服务器)
(5) 求一个任意正数的各个位:除法和取余配合
(6) A、B、C、D 类地址是不可能有相同的
(7) sizeof(size); 括号可以不加,但要有空格隔开。比如:sizeof size
(8) 三次握手,四次握手只在tcp中,udp是没有的。
(9) 绑定失败:Address already in use
解决方法1:过半分钟左右再次运行
解决方法2:修改代码或者传参,更换新的端口号。
解决方法3 :使用setsockopt()函数去设置取消端口绑定限制(跟bind配合使用)
在绑定之前bind使用。
三:shell编程细节
15. 脚本程序:不需要编译器去编译,写好后就能直接运行(跟html一样都是输入脚本语言)
system(madplay 1.mp3 &); 中的&是后台播放
16.反引号 • • 在Tab与Esc之间的符号
17. shell脚本不要随便敲空格
18.字符串比较大小是需要看ASII码的,不是内存大小。
比如:jk 就是大于 abc 因为 第一个字母的ASII 码j>a
四:数据结构细节
1. 链表
1. 链表的线性关系
1. 从数学意义上讲,数据之间的关系有3种,分别是集合,线性关系和非线性关系
2. 线性关系:一堆数据中的任意一个节点有且仅有一个直接前驱节点(除了第一个节点)
并有且仅有一个直接后继节点(除了最后一个节点)
3. 简单的讲线性关系就是“一对一”的关系,而所谓的非线性关系就是非一对一的关系,可以多对一,一对多
2. 链式存储
1. 设计链表节点时,必须至少预留一个可以保存其他节点地址的指针,使得各个节点逻辑连贯,根据不同的情形
节点中可以只保留指向后一个节点的指针,也可以保留指向前后两个节点的指针,还可以将结尾节点的指针指向
第一个节点
2. 链式存储的优点:
(1) 不需要一块连续的内存
(2) 插入和删除效率极高
(3) 节点的扩展极其容易
3. 单向链表,双向链表
1. 单向链表指的就是节点只包含一个指针,该指针用来指向相邻节点
2. 对链表的操作基本的是: 初始化空链表,创建新节点,插入节点,删除节点,移动节点,查找节点和遍历链表
3. 链表中的头节点时一个不带有效数据的节点
4. 传统链表的缺陷:每一条链表都是特殊的,不具有通用性,没有将具体的数据从组织这些数据的逻辑结构体中剥离
4. Linux内核链表
1. 内核链表将传统链表中的“链”抽象出来,使之成为一条只包含前后指针的纯粹的双循环链表
5. 线性表变异体
1. 如果规定线性表的插入和删除操作都必须在线性表的一个端点进行,而不能在其他的任何位置进行,那么这样的线性表
就称为栈,插入改称压栈,删除改称出栈
2. 顺序栈:顺序存储的方式来实现的栈逻辑
链式栈:链式存储的方式来实现的栈逻辑