IO数据结构

 IO

IO定义

Input、Output输入输出

IO的分类

标准IO,文件IO

文件的分类

按存储类型来分

文本文件:存储的是ASCII码(0~127),以EOF End Of File (-1)作为结束符。 一般存储数据量比较大的信息,读取的速度慢

二进制文件:是数据在内存上的原样存储,存储的是二进制的形式。 一般存储中间变量,数据量比较小的信息,读取的速度快

按操作方式来分

带缓冲区的操作:(标准IO)高级的文件操作,系统会自动的在内存上给我们分配缓冲区。

不带缓冲区的操作:(文件IO)低级的文件操作,系统是不会自动的给我们的程序分配空间,但是如果需要 缓冲区的机制,缓冲区就得自己定义了。

标准IO和文件IO

区别

标准IO:
1.标准IO是由库函数系统提供的,由ANSI C标准定义
2.是带缓冲区的操作,运行效率较高
3.支持跨平台的
4.标准IO操作的依据一般是流指针
文件IO:
1.文件IO是由操作系统提供的,由POSIX(可移植操作系统接口)定义
2.没有缓冲区,运行效率没有标准IO高
3.不支持跨平台的
4.文件IO操作的依据一般是文件描述符(非负整数)

系统调用和库函数

系统调用:操作系统提供给用户直接操作硬件的一组接口

库函数:对系统调用的二次封装 

系统调用和库函数的区别

1.系统调用一般提供基础的功能,库函数提供较为复杂的功能
2.系统调用一般不能重写,库函数可以重写
3.系统调用运行的时间属于机器时间,库函数运行时间属于用户时间
4.系统调用运行空间属于内核空间,库函数运行空间属于用户空间
5.系统调用的返回值一般是非负整数,库函数的返回值不一定
6.系统调用的运行效率没有库函数的高
7.系统调用的移植性没库函数的好

 文件描述符和流指针

文件流指针

文件描述符 

本质是数组下标

标准IO

缓冲区的方式

全缓冲

缓冲区满了才刷新缓冲区,或者强制刷新缓冲区

 

行缓冲 

碰到换行符刷新缓冲区、缓冲区满了刷新、或者强制刷新缓冲区

不缓冲

所有的信息到缓冲区之后直接到文件

 

对文件进行操作

打开文件

fopen:

 

函数功能:打开由参数1描述的文件,打开的方式由参数2确定
函数参数1:需要被打开的文件的路径
函数参数2:打开的方式
r : 以只读的形式打开文件,文件存在则打开,不存在则报错
r+ : 以读、写的形式打开文件,文件存在则打开,不存在则报错
w : 以只写的形式打开文件,文件存在则清空打开,文件不存在则新建
w+ : 以读、写的形式打开文件,文件存在则清空打开,文件不存在则新建
a : 以追加的形式打开文件,文件存在则追加,文件不存在则新建
a+ : 以可读可写(追加)的形式打开文件,文件存在则追加,文件不存在则新建
函数返回值:成功返回:文件流指针
失败返回NULL,并且更新errno
 操作文件
以字符读写

fputc 和 fgetc

函数功能:从stream指示的文件中读取字符
函数参数:需要读字符的文件流
函数返回值:成功返回读到的字符可以是(EOF,表示读完了),失败返回-1

 

函数功能:给stream写了一个字符c
函数参数1:被写的字符
函数参数2:给谁写
函数返回值:成功返回写入的字符,失败返回-1
 以行读写

fgets 和 fputs

函数功能:按行读
函数参数1:读到哪
函数参数2:读多少(实际读到的是size-1,最后一个空间给\0预留) 所以fgets相较于gets是安全的
函数参数3:从哪读
函数返回值:成功返回非0,返回NULL的时候是出错或者没有可读的字符了

 

函数功能:给文件写
函数参数1:从哪写
函数参数2:给谁写
函数返回值:成功返回非负整数,失败返回-1
 以对象读写

fread 和 fwrite

fread:
函数功能:读
函数参数1:读到哪
函数参数2:一次读多少
函数参数3:总共读多少次数
函数参数4:从哪读
函数返回值:成功返回成功读或写的成员的个数,失败返回0

fwrite:
函数功能:写
函数参数1:从哪写
函数参数2:一次写读多少
函数参数3:总共写多少次
函数参数4:往哪写
函数返回值:成功返回成功读或写的成员的个数,失败返回0
 以格式化读写

fscanf 和 fprintf

函数功能:往指定的流里面输出东西
函数参数1:指定的流
函数参数2……:同printf

 

函数功能:从指定的流里面获取信息
函数参数1:指定的流
函数参数2……:同scanf
 关闭文件

fclose:

函数功能:关闭由参数stream指示的流
函数参数:需要被关闭的流
函数返回值:成功返回0,失败返回-1并且设置errno

 main函数传参

标准IO相关函数

fflsh
 
函数功能:强制刷新由参数1指定的流
函数参数如果为NULL,则会刷新所有已打开的输出流
函数返回值:成功返回0,失败返回-1并且更新errno
 feof

判断文件是否到末尾:

time函数
time()
 
函数功能:返回一个从计算机元年到现在的秒数(1970-1-1 0:0:0)
函数参数:如果参数不为NULL,返回值也可以传址调用的形式返回
函数返回值:成功返回到现在的秒数,失败返回-1,并且更新errno
 localtime()

函数功能:获取本地时间
函数参数:秒数
函数返回值:指向年月日结构体的指针
 光标移动和获取光标位置

fseek()和ftell();

fseek:
函数功能:给指定的流从参数3描述的位置移动参数2个大小的位置
参数1:给谁移动光标
参数2:移动多少
参数3:从那开始:
SEEK_SET 从文件开头移动
SEEK_CUR 从文件当前光标指向的位置开始移动
SEEK_END 从文件末尾开始移动
返回值:
成功返回0,失败返回-1并且更新errno
ftell:
函数功能:计算由参数stream指示的流当前光标处在的位置
函数参数:指定的文件流
函数返回值:成功返回当前处在的位置,失败返回-1,并更新errno
 fileno

函数功能:获取文件流指针里面的文件描述符

 文件IO

打开文件

第二个open函数
函数功能:打开文件
函数参数1:需要打开的文件路径
函数参数2:打开的方式
必须包含如下的其中一个
O_RDONLY : 只读
O_WRONLY : 只写
O_RDWR : 可读可写
还可以有以下的:
O_APPEND: 以追加的形式打开
O_CREAT : 文件存在则打开,不存在则按照mode参数描述的权限创建一个普通文件
函数参数3:如果文件存在则打开,不存在则按照此参数描述的创建一个普通文件
函数返回值:成功返回文件描述符(非负的最小整数)file description fd
失败返回-1,并且更新errno
操作文件
read

函数功能:从fd描述的文件中读取count个字节的数据给buf开始的空间
函数参数1:从哪读
函数参数2:读到哪
函数参数3:读多少
函数返回值:成功返回实际上读到的数目,失败返回-1,并且更新errno
 write

函数功能:把数据从buf指示的空间里面写道fd里,写了count个
函数参数1:往哪写
函数参数2:从哪写
函数参数3:写多少
函数返回值:成功返回写入的个数,失败返回-1并且更新errno
 操作LED灯
开灯
sudo sh -c "echo 1 > /sys/class/leds/input1\:\:capslock/brightness"
关灯
sudo sh -c "echo 0 > /sys/class/leds/input1\:\:capslock/brightness"
 读取按键

按键和鼠标由linux输入子系统控制,还可以管理手柄,触摸屏,蜂鸣器等设备 打开键盘的设备文件之后,如果按键被按下或者被抬起,内核会将键盘触发的事件发送给应用程序 应用程序可以使用read函数读取事件,事件的结构体:

struct input_event
{
struct timeval time; //事件发生的时间
unsigned short type; //事件的类型,按键被按下或被抬起 EV_KEY 鼠标移动 EV_REL
unsigned short code; //事件编码,按键的编码
unsigned int value; //按键事件中,0表示按键抬起,1表示按下,2表示连击
}
关闭文件
 
函数功能:关闭文件
函数参数:需要关闭的文件的文件描述符
函数返回值:成功返回0,失败返回-1并且更新errno

 对目录文件的操作

打开目录

opendir

函数功能:打开目录
函数参数:需要被打开的目录
函数返回值:成功返回目录流指针,失败返回NULL,并更新errno
 操作目录
读目录readdir

函数功能:读目录
函数参数:要读取的目录
函数返回值:成功返回结构体指针,读完了返回NULL,失败返回NULL
 stat

函数功能:获取指定文件的全部信息
函数参数1:需要获取的是谁(文件)
函数参数2:保存信息的结构体
函数返回值:成功返回0,失败返回-1,并且更新errno
lstat 和 stat 本身没有什么区别,但是当pathname表示的是一个链接文件的时候,返回的成员信息是链接本
身而不是链接指向的文件
 关闭目录

函数功能:关闭指定的目录文件
函数参数:需要关闭的目录
函数返回值:成功返回0,失败返回-1,并更新errno

 动态库、静态库

动态库和静态库

静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动 态库存在,因此代码体积较小。

静态库
静态库对函数库的链接是放在编译时期(compile time)完成的。
程序在运行时与函数库再无瓜葛,移植方便
浪费空间和资源,因为所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执
行文件(executable file)。
---》编译时把静态库中的相关代码复制到可执行程序中
优点:程序运行时,无需加载库,运行速度更快
缺点:占用更多磁盘和内存空间,静态库升级后,需要重新编译链接

 制作静态库:

 

 

动态库
动态库把对一些库函数的链接载入推迟到程序运行的时期(runtime)。
可以实现进程之间的资源共享。
将一些程序升级变得简单。
甚至可以真正做到链接载入完全由程序员在程序代码中控制。
----》编译时仅记录用到哪个共享库中的哪个符号,不复制共享库中的相关代码
优点:程序不包含库中代码,体积比较小,库升级方便,无需重新编译
缺点:在运行需要加载共享库

制作动态库: 

 

数据结构

数据结构定义

数据:现实生活中一切可以处理的信息
结构:逻辑结构、存储结构
研究的是数据的逻辑结构,存储结构及其操作。
逻辑结构:事物与事物之间在现实生活中的抽象的一种逻辑关系

存储结构

顺序存储:在逻辑上相邻的元素,在物理空间上也相邻
优点:查找方便,
存储空间的利用率可以达到1
缺点:插入和删除不方便,
申请空间的时候,必须是一片连续的空间,对空间的要求比较高
会用空间碎片的产生
链式存储:在逻辑上相邻的元素,在物理空间上不一定相邻
优点:插入元素不需要移动大量的空间
对空间的要求没那么大,
缺点:查找不方便
存储空间的利用率不足1,没有顺序存储的大
索引存储:依据索引表查找数据大概位置,详细查找数据本身(冲突没有解决号的哈希存储)
优点:查找方便
缺点:有索引表的存在,要浪费空间
插入元素,删除元素之后,索引表要更新
哈希存储:根据关键字直接就能定位到记录本身,就能拿到数据
优点:查找方便,插入,删除也方便
缺点:如果哈希函数设计的不合理,查找的效率就会很低

 操作

线性结构

线性结构的顺序存储

顺序表的定义
特点:
1.逻辑上相邻的元素,在物理空间上也相邻===》空间连续
2.大小固定
3.表满不能存,表空不能取
 顺序表的创建

插入元素
 
删除元素
 

显示元素

 

销毁
释放空间
 
顺序表的扩容
 

线性结构的链式存储 

链表的分类:
按方向分:单链表、双链表
按是否循环分:循环链表,不循环链表
按是否带头节点:带头结点的链表,不带头结点的链表

带头结点的不循环单链表
链表

 结点定义

创建链表
 
 插入结点

显示链表
 
给链表删除元素
 
销毁单链表

按找头删、尾删的原则,把所有的数据节点全部删除(free)
当只剩下头节点的时候再释放头节点 

逆置一个单链表

对原来的单链表采用头删,对新链表采用头插

逆着输出一个单链表

循环单链表

带头结点的不循环双链表 

双链表表结点的定义

双链表的创建
 
双链表的删除元素
 
 双链表的显示

线性表,链表,顺序表之间的区别?
线性表是一种逻辑结构
顺序表和链表是线性表在顺序存储下和链式存储下的体现
链表和顺序表的区别:
1.链表是链式存储,顺序表是顺序存储
2.顺序表会有空间碎片产生,链表没有空间碎片产生
3.链表一般多用于插入、删除较多的场景
顺序表一般用于查找较多的场景
4.顺序表的存储空间利用率比链表的大
链表和顺序表怎么选择?
1.从空间来说
顺序表对空间的要求比链表大
顺序表的利用率比链表大
2.从操作来说
多用于查找,用顺序表
多用于插入、删除,用链表
3.从编译环境来说
不支持指针类型操作的编译器不能使用链表

编译器不支持使用指针类型操作,场景又是多插入、删除操作
使用静态链表,本质是二维数组

受限的线性表
栈:受限在了操作上
只允许数据在一段进行插入和删除操作,允许操作的一段叫做栈顶
特点:先进后出(FILO)
队列:受限在了操作上
只允许在一段执行插入操作,另一端执行删除操作,允许插入的一段叫做队尾,允许删除操作的一段叫做队头
特点:先进先出(FIFO)
串:受限在了存储上
只允许存储字符 * ' a — 1

 栈

栈的定义
//栈的定义
typedef struct stack
{
data_type data[SIZE]; //存储空间
//只能在栈顶进行操作
int top; //指示栈顶
}Stack;
创建栈

入栈和出栈
 
打印栈 

链式存储 

线性表的链式存储是一样的
头是栈顶:头插、头删
尾是栈顶:尾插、尾删
操作的时候一般选择头作为操作

共享栈

队列

顺序存储
队列的定义 
//队列的定义
typedef struct queue
{
data_type data[SIZE]; //存储空间
//在队尾插入、队头删除
int tail; //指示队尾
int head; //指示队头
}Queue;
队列的创建
//使用malloc来创建空间,返回创建成功并且清空的首地址
malloc(sizeof(Queue)/*大小*/);
队列入队出队

循环队列

避免假队空的方法:
1.舍弃一片空间 head = (tail+1)%SIZE
2.另外设置一个变量 count == SIZE;
3.设置标志位记录上次执行的操作是入队还是出队 head == tail && flag == 1
链式存储

对链表限制住了插入和删除的位置

双端队列

什么时候考虑用栈? 

入的顺序和出的顺序发生变化

栈的应用

1.printf函数的右结合
2.函数栈
3.中缀表达式 (a+b)/c a+b/c 后缀表达式: ab+c/ abc/+

树形结构

树的定义

树(Tree)是n(n≥0)个节点的有限集合T,它满足两个条件 :
有且仅有一个特定的称为根(Root)的节点;
其余的节点可以分为m(m≥0)个互不相交的有限集合T1、T2、……、Tm,其中每一个集合又是一棵树,并称为其根 的子树(Subtree)。

树的相关概念

度数:
一个节点的子树的个数称为该节点的度数,一棵树的度数是指该树中节点的最大度数。

高度(层数、深度):
节点的层数等于父节点的层数加一,根节点的层数定义为一。树中节点层数的最大值称为该树的高度或深度。

路径:边数:
一个节点系列k1,k2, ……,ki,ki+1, ……,kj,并满足ki是ki+1的父节点,就称为一条从k1到kj的路径,路径的 长度为j-1,即路径中的边数。路径中前面的节点是后面节点的祖先,后面节点是前面节点的子孙。

叶子节点:
度数为0的结点

二叉树

二叉树的定义 :二叉树(Binary Tree)是n(n≥0)个节点的有限集合,它或者是空集(n=0),或者是由一个 根节点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。二叉树与普通有序树不同,二叉树严格区分 左孩子和右孩子,即使只有一个子节点也要区分左右。

满二叉树:有k层,恰好有2 k -1个结点

完全二叉树:在满二叉树的基础上,从右往左,从下往上依次去除结点 

一颗普通的树转成二叉树

树形结构的顺序存储 

平均状况下顺序存储的空间利用率较低,所以不使用顺序存储,使用链式存储来存数据。

树形结构的链式存储

树的结点的定义
typedef int data_type;
typedef struct bintreenode
{
struct bintreenode *Left;
data_type Data;
struct bintreenode *Right;
}BTree;
 树的结点的创建

树的结点的插入
 
树的节点的遍历 

深度优先

先序遍历,后序遍历 

广度优先

层次遍历
队列

树的结点的删除

待删除结点没有孩子,直接删除
待删除结点有一个孩子,子承父位
待删除结点有两个孩子,找右子树中最小的值或者左子树中最大的值来继承 

平衡二叉树

每一个结点的左子树的层数和右子树的层数不超过2
目的:解决树的降维问题(把树将成链表),提高效率

赫夫曼树(哈夫曼树、最优二叉树)

赫夫曼(Huffman)树,又称最优树,是带权路径长度最短的树,有着广泛的应用
从树中一个结点到另外一个结点的分支构成一条路径,分支的数目称为路径的长度。树的路径长度是指从树根到每
个结点的路径长度之和
进一步推广,考虑带权的结点。结点的带权路径长度指的是从树根到该结点的路径长度和结点上权的乘积。树的带
权路径长度是指所有叶子节点的带权路径长度之和,记作 WPL 。WPL最小的二叉树就是最优二叉树,又称为赫夫曼
树

线索二叉树

网状结构

分类:
按有无方向可以分为:有向图、无向图
按是否带权值:带权图和不带权图 

有向图:十字链表法
无向图:多重链表

对于图的知识点

最短路径算法-迪杰斯特拉(Dijkstra)算法 |
弗洛伊德(Floyd)算法求图的最短路径

算法

算法是有限指令的有序集合。
算法是有穷的,程序是无穷的
程序 = 算法 + 数据结构

算法的特征

有穷性:算法必须在有限个语句能描述完
确定性(无二义性):每一条语句只能有一个解释
可行性:能运行的
输入:
输出:

怎么样评判算法的好坏

效率与低存储量需求(时间复杂度,空间复杂度):
时间复杂度:执行这个算法需要花费多少时间。
eg:
int sum = 0;
for(int i = 0; i < n; i++)
{
sum += i;
}
printf("%d\n", sum);
T(n) = n + 2
O(n) = n
只需要记录量级 1
1.顺序执行的代码,只会影响常数项,可以忽略 O(1)。
2.只需要挑选循环中的一个基本操作来分析他的执行步骤的次数与n(问题规模)的关系。
3.如果有多层循环嵌套,只需要关注最深层次循环的个数。
优化时间复杂度:
首先得保证算法的正确性,在这个基础上,思考怎么去减少循环的使用。
空间复杂度:执行这个代码,需要花费多少空间。
设算法对应问题的规模为n,执行算法所占存储空间的量级为D(n),则D(n)为算法的空间复杂度。
优化空间复杂度:
1.在定义的时候,更少的使用空间;
1.字节对齐:
2.位域:
2.在执行的时候,尽量避免开辟不必要的空间,并且功能结束之后,释放掉不用的空
间。
正确性:算法得无误运行。
可读性、可维护性:对人的友好,是从编程规范入手优化。
健壮性、鲁棒性:算法在输入有误的信息的时候,代码还能够按照预想的方式执行,不出现BUG,不会退出。

常见的查找算法

线性结构
顺序查找:遍历
折半查找:二分查找
限制:必须有序、必须是一个顺序表
分块查找:块间有序,块内无序
树状结构
二叉排序树:二叉树的查找
二叉平衡树:避免树降维成链表
遍历:
先序遍历、中序遍历、后续遍历、广度有限、深度优先
散列表(哈希表)

对给定的k,不经任何比较便能获取所需的记录,其查找的时间复杂度为常数级O(C)。这就要求在建立记录表 的时候,确定记录的key与其存储地址之间的关系f,即使key与记录的存放地址H相对应
根据要找的key关键字,直接能找到数据的记录本身。

构建哈希函数

直接地址法
 
平方取中法
 
叠加法
 
质数除余法
 
冲突

什么是冲突:
冲突是指:表中某地址j∈[0,m-1]中己存放有记录,而另一个记录的H(key)值也为j。 

怎么解决冲突

常见的排序算法

内部排序
插入
直接插入:重新构建一个链表
折半插入:和二分查找类似
希尔排序:增量,逐渐减少的,直到增量为1为止
 交换

冒泡:每一次运行总会将最小的或者最大的放到前面,如果需要交换,一直在交换

快速排序*:

选择
简单选择:每一次运行总会将最小的或者最大的放到前面,最后只交换一次
堆(大根堆,小根堆):根>=左右孩子的,牵扯到树的变化,所以只有特定场景下才使用
 多路归并

基数排序
 
外部排序
 

  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值