每周学习总结
- 第一周数据结构
- Makefile的编写
- 顺序表
- 链表(含约瑟夫环之选猴王)
-
- 本周总结
- 第二周
-
- IO进程
- 标准IO
- 2. 题目要求:编程读写一个文件test.txt,每隔1秒向文件中写入一行数据,类似这样:
第一周数据结构
Makefile的编写
搞明白Makefile是什么之前,先来了解一下make工具
1.make是工程管理器,顾名思义,是指管理较多的文件 Make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能构根据文件 时间戳 自动发现更新过的文件而 减少编译的工作量 ,同时,它通过读入Makefile文件中文件的内容来执行大量的编译工作。
2.make工具的作用当项目中包含多个c文件,但只对其中1个文件进行了修改,那用gcc编译会将所有的文件从头编译一遍,这样效率会非常低;所以通过make工具,可以查找到修改过的文件(通过时间戳),只对修改过的文件进行编译,这样大大减少了编译时间,提高编译效率
3.Makefile是Make读入的唯一配置文件(有的编译器支持makefile也作为make读入的配置文件,但哥们才疏学浅不能保证哪个编译器支持,就统一Makefile得嘞。)
4.Makefile的编写格式
格式:
目标:依赖
命令
比如使用make工具编译add.c main.c生成可执行文件add的Makefile命令如下
add:add.o main.o//最终生成可执行文件add 最终目标要写在第一行
gcc add.o main.o -o add
add.o:add.c
gcc -c add.c -o add.o
main.o:main.c
gcc -c main.c -o main.o
但是如若是这样的格式,我们需要写在Makefile中的文件还是太多了,尤其是一个add.o要写三遍,那么后续几十个文件的程序单单写个Makefie不得小5分钟?make工具创作者当然也是想到了这样的情况,于是有了以下的通配符和变量的应用。
CC=gcc
CFLAGS=-g -c -Wall -O
OBJS=add.o main.o
add:$(OBJS)
$(CC) $(OBJS) -o $@
%.o:%.c
$(CC) $(CFLAGS) $^ -o $@
PHONY:clean
clean:
rm *.o add
接下来在终端输入make指令就可以生成可执行文件了。这样子看确实是比上面的内容还要多,但这中算是一劳永逸的办法,后面如果编译过程一致的话,只需要改一下OBJS变量的值和最终生成的可执行文件名字就可以了^^
如果这些符号不理解的话可以参考一下带佬整理的这篇文章,你想知道的Makefile内容都有囊括!上链接!:支持原创
顺序表
顺序表是数据结构的开场白,因为它就是数组,便于理解引入的表的概念。
逻辑结构:线性结构
存储结构:顺序存储
特点:内存空间连续开辟
头节点无前驱,尾节点无后继
顺序表操作:
先来看看Makefile
CC=gcc
CFLAGS=-g -c -Wall -O
OBJS=main.o seqlist.o
seqlist:$(OBJS)
$(CC) $(OBJS) -o $@
PHONY:clean
clean:
rm *.o seqlist
在make之前一定要在main.c里先创建主函数,没有主函数是没有程序入口的,生成不了可执行文件。
再来看看头文件seqlist.h
#ifndef _SEQLIST_
#define _SEQLIST_
#include<stdio.h>
#include<stdlib.h>
#define N 10
typedef int datatype;
typedef struct
{
datatype data[N];
int last;
}seqlist_t;
seqlist_t *Createseqlist(void);
int insert(seqlist_t* ,int,datatype);
int seqlist_full(seqlist_t*);
int Delete(seqlist_t* ,int);
int seqlist_empty(seqlist_t*);
void show_seqlist(seqlist_t*);
int Modvalue_position(seqlist_t* ,int,int);
int checkvalue_position(seqlist_t* ,int);
int Modvalue_value( seqlist_t* ,int,int);
int checkposition_value(seqlist_t* ,int);
int cleanseqlist(seqlist_t* );
void Destroyseqlist(seqlist_t* );
#endif
注意:如果在h文件中定义了全局变量,一个c文件包含同一个h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误,头文件开头结尾分别添加#ifndef #define和#endif就可以避免这种错误。
下面看一下各个函数的作用吧
seqlist_t *Createseqlist(){
seqlist_t *p;
p=(seqlist_t * )malloc(sizeof(seqlist_t));
if(p == NULL)
{
printf ( "Createseqlist failed ! \n " );
return NULL;
}
p->last=-1;
return p;
}
这个last并不是指针,他只是在这里代表目前整个链表的最后一个节点的位置。0~9这里指的也是此节点的位置,每个节点的内容目前还没有赋值
int Insert(seqlist_t *p,int post,datatype x)
{
if(post>p->last+1||post<0||Seqlist_full(p))
{
printf("SequenceList is full\n");
return -1;
}
int i=0;
for(i=p->last;i>=post;i--)
{
p->data[i+1]=p->data[i];
}
p->data[post]=x;
p->last++;
return 0;
}
插入数据的时候只能在last位置的下一个位置插入,就像数组一样,不可以4号位置有元素6号位置有元素而5号位置没有,这样就不符合数组的连续存储原则。
int Delete(seqlist_t* p,int post)
{
if(p->last<post||post<0||Seqlist_empty(p))
{
printf("Can not delete element here\n");
return -1;
}
int i=0;
for(i=post;i<p->last;i++)
{
p->data[i]=p->data[i+1];
}
p->last --;
}
int Seqlist_empty(seqlist_t* p)
{
return p->last == -1;
}
void Show_seqlist(seqlist_t* p)
{
int i=0;
for(i=0;i<=p->last;i++)
{
printf("%d ",p->data[i]);
}
printf("\n");
}
int Modvalue_position(seqlist_t* p,int post,int value)
{
p->data[post]=value;
return 0;
}
int Modvalue_value(seqlist_t* p,int a,int b)
{
int i=0;
for(i=0;i<p->last;i++)
{
if(p->data[i]==a)
p->data[i]==b;
}
return 0;
}
int Checkvalue_position(seqlist_t* p,int post)
{
printf("%d\n",p->data[post]);
}
int Checkposition_value(seqlist_t* p,int value)
{
if(Seqlist_empty(p))
return -1;
else
{
int i;
for(i=0;i<p->last;i++)
{
if(value==p->data[i])
printf("a[%d]=%d ",i,value);
}
}
return 0;
}
int Cleanseqlist(seqlist_t* p)
{
p->last=-1;
return 0;
}
void Destroyseqlist(seqlist_t* p)
{
free(p);
p=NULL;
}
清空顺序表只需要将作为标志的last置为-1即可,因为我们访问操作都是截止到last为止,所以后面申请的N个元素就可以视为无效元素,虽然没有调用free函数释放,但是我们就可以当他们是空的
链表(含约瑟夫环之选猴王)
#ifndef _LINKLIST_H_
#define _LINKLIST_H_
#include<stdio.h>
#include<stdlib.h>
typedef int datatype;
typedef struct node_t
{
datatype data;
struct node_t * next;
}linklist_t;
linklist_t *CreateEmptyLinklist(void);
int Calc_length(linklist_t * p);
int Initialize_linklist(linklist_t *p,int num);
int Insert_position(linklist_t *p,int post,int value);
void Show_linklist(linklist_t *p);
int Delete_position(linklist_t *p,int post);
int Mod_position(linklist_t *p,int post,int value);
int Linklist_empty(linklist_t* p);
int Read_position(linklist_t *p,int post);
int Read_value(linklist_t *p,int value);
int Delete_value(linklist_t *p,int value);
int Linklist_inversion(linklist_t *p);
int Linklist_clean(linklist_t *p);
int Linklist_destroy(linklist_t **p);
linklist_t* Linklist_merge(linklist_t* p1,linklist_t* p2);
int createJoseph(linklist_t *p,int num);
int chooseMonkeyking(linklist_t* p,int start,int kill);
#endif
先来看看结构体,每个linklist_t类型的变量都有一个自己的数据域存储本节点的数据和指向下一个linklist变量的指针域,这样前指后就形成了一个链表。
#include"linklist.h"
linklist_t *CreateEmptyLinklist(void)
{
linklist_t* p=(linklist_t *)malloc(sizeof(linklist_t));
if(p == NULL){
printf("malloc failed\n");
return NULL;
}
p->data=0;
p->next=NULL;
return p;
}
新建链表时需要先创建头节点,后续的所有操作都是在头节点的基础上进行的,头节点是有数据域的,但是我们人为的认定这个数据与不被访问,所以在此赋值一个0作为“标记”。
int Initialize_linklist(linklist_t *p,int num)
{
int i;
linklist_t *t=p;
for(i=0;i<num;i++){
printf("The %d num is: \n",i+1);
p->next=(linklist_t*)malloc(sizeof(linklist_t));
scanf("%d",&(p->next->data));
p=p->next;
}
p=t;
return 0;
}
下面看一下初始化的过程
int Calc_length(linklist_t * p)
{
int length=0;
while(p->next!=NULL)
{
length++;
p=p->next;
}
return length;
}
int Insert_position(linklist_t *p,int post,int value)
{
if(post<0||post>Calc_length(p))
{
printf("position error\n");
return -1;
}
while(post--!=0)
{
p=p->next;
}
linklist_t * t=CreateEmptyLinklist();
t->data=value;
t->next=p->next;
p->next=t;
printf("Insert value by position success!\n");
return 0;
}
void Show_linklist(linklist_t *p)
{
while(p->next!=NULL)
{
p=p->next;
printf("%d ",p->data);
}
printf("\n");
}
删除操作的时候需要注意遍历到删除位置的前一个节点后,不能直接t->next=t->next->next跳过它,因为这样,虽然是访问不到删除的节点了,但是它却仍然存在,占用着内存,会导致内存泄漏,所以我们需要定义一个指向删除节点的linklist_t类型的指针,用于在"删除"操作后进行释放内存空间。释放完成后也要记得tp=NULL,防止产生野指针。
int Delete_position(linklist_t *p,int post)
{
if(post<0||post>Calc_length(p)||Calc_length(p)==0)
{
printf("position error\n");
return -1;
}
linklist_t *tp=p;
while(post--!=0)
{
tp=tp->next;
}
linklist_t *t;
t=tp->next;
tp->next=tp->next->next;
free(t);
t=NULL;
return 0;
}
下面的函数功能都离不开遍历整个链表,对比对应的位置或者值,然后进行具体操作,重点是理解怎么遍历到这个节点的。
int Mod_position(linklist_t *p,int post,int value)
{
if(post<0||post>Calc_length(p)||Calc_length(p)==0)
{
printf("position error\n");
return -1;
}
while(post--!=0)
{
p=p->next;
}
p->next->data=value;
printf("Mod_position success!\n");
return 0;
}
int Linklist_empty(linklist_t *p)
{
if(Calc_length(p)==0)
{
printf("This linklist is empty\n");
return -1;
}
return 0;
}
int Read_position(linklist_t* p,int post)
{
if(post<0||post>Calc_length(p))
{
printf("position error\n");
return -1;
}
int t=post;
while(t--!=0)
{
p=p->next;
}
printf("In position %d,the value is %d\n",post,p->next->data);
return 0;
}
int Read_value(linklist_t *p,int value)
{
int t=0;
while(p->next!=NULL)
{
p=p->next;
if(p->data==value)
printf("position%d's value is %d\n",t,value);
t++;
}
return 0;
}
int Delete_value(linklist_t *p,int value)
{
if(Linklist_empty(p))
{
printf("linklist is empty,PLZ initialize fist\n");
return -1;
}
int i=0;
linklist_t *t=p;
while(t->next!=NULL)
{
t=t->next;
if(t->data==value)
{
Delete_position(p,i);
i--;
}
i++;
}
printf("Delete data by value succeed!\n");
return 0;
}
这里链表的倒置(1 2 3 4 5倒置后为5 4 3 2 1)用到的是头插法,思路如下
定义一个指针p指向头节点的下一个节点,然后再定义一个指针q(用于保存下一个操作的节点),断开头节点(这之前是前期准备),q指向p,让q后移一个,然后改变指针p的next的指向,从q指向头节点的next(头节点断开了,此时为NULL),头节点此时next指向p,p再指向q实现后续的循环。这样p每次都能为头节点贡献一个next指向的新节点,然后再指向q的节点重复。直到全部遍历晚后,原来的1 2 3 4 5因为1是第一个被头节点next指向的,跑到了最后,反而最后被p贡献的5成了头节点next第一个指向的。如此就实现了链表的倒置。
int Linklist_inversion(linklist_t *p)
{
linklist_t* i=p->next;
p->next=NULL;
linklist_t *j;
while(i!=NULL)
{
j=i;
i=i->next;
j->next=p->next;
p->next=j;
}
printf("transform succeed\n");
return 0;
}
若不理解请看带佬的还有递归法倒置
int Linklist_clean(linklist_t *p)
{
linklist_t *t=CreateEmptyLinklist();
while(p->next!=NULL)
{
t->next=p->next;
p->next=p->next->next;
free(t->next);
t->next=NULL;
}
free(t);
t=NULL;
printf("linklist is cleaned\n");
return 0;
}
int Linklist_destroy(linklist_t **p)
{
Linklist_clean(*p);
free(*p);
*p=NULL;
printf("linklist is destoried\n");
return 0;
}
linklist_t * Linklist_merge(linklist_t * p1,linklist_t* p2)
{
linklist_t *L=CreateEmptyLinklist();
linklist_t *t1=p1;
linklist_t *t2=p2;
linklist_t *t = L;
t1=t1->next;
t2=t2->next;
if(t1->data>t2->data)
{
L->next=t2;
t2=t2->next;
L=L->next;
L->next=NULL;
}
if(t1->data<t2->data)
{
L->next=t1;
t1=t1->next;
L=L->next;
L->next=NULL;
}
while(t1!=NULL&&t2!=NULL)
{
if(t1->data<t2->data)
{
L->next=t1;
t1=t1->next;
L=L->next;
L->next=NULL;
}
else{
L->next=t2;
t2=t2->next;
L=L->next;
L->next=NULL;
}
}
if(t1)
L->next=t1;
if(t2)
L->next=t2;
L=t;
return L;
}
Joseph circle
下面就是单项循环列表也就是Joseph circle了,其本质还是链表,只不过在链表最后的那个节点的next不指向NULL了,而是再指向头节点。//这样就可以一直循环起来了(不是)。
int createJoseph(linklist_t *p,int num)
{
linklist_t *t=p;
int i;
for(i=1;i<num;i++)
{
t->next=CreateEmptyLinklist();
t=t->next;
t->data=i;
}
t->next=p;
return 0;
}
int chooseMonkeyking(linklist_t* p,int start,int kill)
{
linklist_t* t=p;
int i=0;
for(i=0;i<start;i++)
{
t=t->next;
}
linklist_t* tt;
while(t!=t->next){
for(i=0;i<kill;i++)
{
t=t->next;
}
tt=t->next;
t->next=t->next->next;
tt->next=NULL;
free(tt);
tt=NULL;
}
printf("%d is the monky king\n",t->data);
}
本周总结
本周上了3节数据结构的课,虽然在学校中学过,但是像华清这样一天讲一个框架的进度,还是需要继续努力,晚自习多看多写多思考。目前写的内容虽然简单,但是思路的开阔也很关键,开阔思路不会因为它简单,拓展的机会就没有,发挥想象力总结会有收获的,加油。
第二周
本周学习完成了数据结构部分进行了一天考试,周五结束的时候开了新课IO,更新的第二周,我感觉像上周一样流水账式的总结除了做复习的参考外没有别的意义,所以我想,从这周开始的更新不再把每周的所有内容展示,只突出个人认为的重点。
栈
栈的特点是只能在一个端进行数据的插入和删除,先入栈的数据后出栈,后入栈的数据先出栈即FILO&LIFO。实现栈的方式有顺序栈和链式栈两种,两种最大的区别就是栈的内存空间开辟是否连续,栈的长度是否固定。由于栈不仅仅只是节点,我们需要控制它实现FILO,就需要再定义一个描述栈的结构体,拿顺序栈来说,我们需要知道栈的首地址,还需要知道栈针的位置,为了判空判满操作的方便,还需要知道栈的长度。拿顺序栈来说,定义描述顺序栈内容的结构体代码如下
typedef int datatype
typedef struct
{
datatype *data;
int length;
int top;
}stack_seq;
栈针是永远保存当前最新入队的元素的下标,空栈时栈针为-1,每次入栈成功,栈针都要自增。
stack_seq *CreateSeqstack(int length)
{
stack_seq *p=(stack_seq *)malloc(sizeof(stack_seq));
if(NULL==p)
{
printf("Create Error\n");
return NULL;
}
p->data=(datatype *)malloc(sizeof(datatype)*length);
if(NULL == p->data)
{
printf("Stack Create Error\n");
return NULL;
}
p->length=length;
p->top=-1;
return p;
}
在创建新栈的时候,需要先创建好保存栈信息的结构体,这样在申请栈的空间的时候直接赋值让成员变量data指向它就好了,省了些许步骤。
链式栈中,做不到单靠下标来去操作栈,所以干脆就不定义描述链式栈的结构体了,按照链表的思想,只需要找到“头”,一步一步地next下去就好了,所以单纯地定义一个节点结构体就好。
typedef int datatype;
typedef struct node_linklist
{
datatype data;
struct node_linklist *next;
}stack_link;
void CreateLinkstack(stack_link **ptop)
{
*ptop=NULL;
}
int inLinkstack(stack_link **ptop,datatype data)
{
stack_link *pnew=(stack_link *)malloc(sizeof(lstack_t));
if(NULL == pnew)
{
printf("malloc new node error.\n");
return -1;
}
pnew->data=data;
pnew->next=*ptop;
*ptop=pnew;
return 0;
}
datatype outLinkstack(lstack_t **ptop)
{
datatype data;
lstack_t *pdel=*ptop;
if(isEmptyLinkStack(*ptop))
{
printf("isEmptyLinkStack error.\n");
return -1;
}
data=pdel->data;
*ptop=pdel->next;
free(pdel);
pdel=NULL;
return data;
}
在入栈的时候,如果按照图中所示,每次入栈的数据直接链接到前一个栈顶的next,根据栈的特性,栈针永远指向当前最新入栈的元素,看似入栈方便,但是,后续出栈的时候,就访问不到下一个作为栈顶的元素了,所以,在入栈的时候我们需要将新入栈的元素插入到上一个栈顶元素的“左”,再将栈针的值赋为新元素的地址。这也就导致了在入栈和出栈的时候需要二级指针来进行操作。
队列
实现队列也是顺序队列和链式队列,队列的特点是先进先出后进后出FIFO&LILO,只允许在队列的两端分别进行插入和删除操作,插入的一端叫队尾,删除的一端叫队头。实现队列的过程中,为了提高效率,以调整指针(顺序队列中为头尾下标)代替队列元素的移动,使用数组实现循环队列。
#define N 6
typedef int datatype;
typedef struct
{
int front;
int rear;
datatype data[N];
}queue_seq;
queue_seq *createQueue(void)
{
queue_seq *p=(queue_t *)malloc(sizeof(queue_seq));
if(NULL == p)
{
printf("createQueue error.\n");
return NULL;
}
p->front=0;
p->rear=0;
return p;
}
入队操作中,如果把某一下标固定地作为队列的标志就会导致在队列出队后再入队出现问题。
入队到顺序队列满。
顺序队列满后,如果队列删除元素后,可以看到队列是有空的,可以再入队了,但是固定下标确定的满传来了错误信息,因为rear的下标等于定义的N的数值-1的数了,按照数组的思想确实已经满了。所以我们采取rear的下标+1(下一个)% N ==head的下标来确定队列满。同样的在计算队列大小的时候也不能单纯的以rear下标减去head下标,也需要适当的模N操作来确定正确的队列大小
(p->rear-p->front + N) % N)+N是因为可能相减得出负数。
树
概念:树(Tree)是(n>=0)个节点的有限集合T,它满足两个条件 :有且仅有一个特定的称为根(Root)的节点;其余的节点可以分为m(m≥0)个互不相交的有限集合T1、T2、……、Tm,其中每一个集合又是一棵树,并称为其根的子树(Subtree)。
特征: 一对多,每个节点最多有一个前驱,但可以有多个后继(根节点无前驱,叶节点无后继)
关于树的一些基本概念
(1)度数:一个节点的子树的个数
(2)树度数:树中节点的最大度数
(3)叶节点或终端节点: 度数为零的节点
(4)分支节点:度数不为零的节点
(5)内部节点:除根节点以外的分支节点 (去掉根和叶子)
(6)节点层次: 根节点的层次为1,根节点子树的根为第2层,以此类推
(7)树的深度或高度: 树中所有节点层次的最大值
二叉树
二叉树(Binary Tree)是n(n≥0)个节点的有限集合,它或者是空集(n=0),或者是由一个根节点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。二叉树与普通有序树不同,二叉树严格区分左孩子和右孩子,即使只有一个子节点也要区分左右。二叉树:节点最大的度数2。
二叉树性质
二叉树第k(k>=1)层上的节点最多为2的k-1次幂个节点。
深度为k(k>=1)的二叉树最多有2的k次幂-1个节点。
在任意一棵二叉树中,树叶的数目比度数为2的节点的数目多一。n0=n2+1
总节点数为各类节点之和:n = n0 + n1 + n2
总节点数: n = 度数0节点个数 + 度数1节点个数 + 度数2节点个数
满二叉树和完全二叉树
满二叉树: 深度为k(k>=1)时节点为2^k - 1(2的k次幂-1)
完全二叉树:只有最下面两层有度数小于2的节点,且最下面一层的叶节点集中在最左边的若干位置上。
二叉树的创建
根据上面的特点可以确定二叉树的结构,自身,左孩子,右孩子,每一个孩子又是一个树节点。
typedef char tree_datatype;
typedef struct tree_t
{
tree_datatype data;
struct tree_t *lchild;
struct tree_t *rchild;
}bitree;
bitree *CreateBitree()
{
char ch;
bitree *r=NULL;
scanf("%c",&ch);
if(ch== '#')
return NULL;
r=(bitree *)malloc(sizeof(bitree));
if(NULL == r)
{
printf("Malloc failed for r\n");
return NULL;
}
r->data =ch;
r->lchild=CreateBitree();
r->rchild=CreateBitree();
return r;
}
void levelOder_linklist(bitree *r)
{
if(NULL == r )
return;
link_ll * p = createLinkList();
inLinklist(p,r);
while(!isEmptyLink(p))
{
r=outLinklist(p);
printf("%c ",r->data);
if(r->lchild!=NULL)
inLinklist(p,r->lchild);
if(r->rchild!=NULL)
inLinklist(p,r->rchild);
}
}
层序遍历中选择用队列暂存遍历出的节点是因为队列的先进先出的特点,使用单向链表当然也可以实现,但是实现的思路也是对单向链表使用队列的思想进行操作。完全没有必要,在调用树的层序遍历时使用的头文件不要忘记将重命名的datatype再该回去qaq。
最后是数据结构的思维导图
本周总结
学完了数据结构给我最直接的感受我用个比喻来说明吧,学数据结构之前,我说话是一个字一个字的说,字与字之间没有很具体的联系,如儿童一样,妈妈,饼干,吃这样,但是学完之后,我就感觉像是把每次要表达的,封装成了一句话,它有我赋予的具体含义,我就可以说,妈妈我想吃这个饼干,或者根据我的需求,改成,爸爸我想给妈妈吃芒果味的曲奇。后续的课程感觉可能就是到达写作文的程度,然后调用别人封装好的“名言警句”去创造自己的文章。再接再厉!
IO进程
IO进程包括标准IO和文件IO,进程包括进程和线程,在学习这部分内容的时候需要记忆的东西很多,不要嫌麻烦,起码把函数名和功能记住,后面再查询man手册也方便。
标准IO
IO:input/output,针对于文件输入输出。
linux有以下文件类型:
.b(块设备) .c(字符设备) .d(目录) -(普通文件) .l(链接文件) .s(套接字) .p(管道)
概念:在C库中定义的一组专门用于输入输出的函数
特点:
1.标准IO通过缓冲机制减少系统调用的次数,提高效率
2.标准IO围绕流进行操作,流用FILE *描述;(FILE 就是一个结构体,用于存放操作文件的相关信息
它在stdio.h文件中定义;vi -t FILE,ctags,ctrl+]:代码追踪,ctrl+t:回退)
3.标准IO默认打开了三个流,stdin(标准输入)、stdout(标准输出)、stderr(标准出错)都是FILE*类型
缓冲机制
全缓存:与文件相关
刷新缓存的条件: 程序正常退出时刷新、缓存区满刷新、强制刷新
行缓存:与终端相关
刷新缓存的条件: \n刷新、程序正常退出时刷新、缓存区满刷新、强制刷新
不缓存:没有缓存区(stderr)
那么缓冲区多大呢?可以由以下代码测出
#include <stdio.h>
int main(int argc, const char *argv[])
{
int i;
for(i = 0; i < 600; i++)
printf("%04d", i);
while(1);
}
while(1)的功能是将程序卡在这,这时就必须ctrl+c结束程序,在上面的程序中,没有\n,没有强制刷新,程序也不是正常的退出,那么这时输出的就是缓冲区满的情况,再以每次输出4个int类型的数据,每个int类型的数据4个字节,每个字节8位,就可以计算出缓冲区的大小了。需要注意的是,在循环中,假设我们不知道缓冲区的大体的大小,结束的条件若给的大了,可能会刷出两个缓冲区的内容甚至更多,可以将printf(“%04d”,i)改为printf(“%08d”,i),只要在终端中输出的数小于我们设置的边界就好,每次压缩一点边界,很快就能测出缓冲区的大小了。测试缓冲区大小也可以直接使用以下关键字,开始需要先使用一下标准IO,只有这样才能打开stdout默认输出流。
printf("hello\n");
printf("%d\n", stdout->_IO_buf_end - stdout->_IO_buf_base);
return 0;
总结一下标准IO中常用的函数
FILE *fopen(const char *path, const char *mode)
int fclose(FILE* stream)
int fgetc(FILE * stream)
int fputc(int c, FILE * stream)
char * fgets(char *s,int size,FILE * stream)
int fputs(const char *s,FILE * stream)
void rewind(FILE *stream)
int fseek(FILE *stream, long offset, int whence)
long ftell(FILE *stream)
在fopen中第二个参数表示的是打开文件流的方式
r:只读,当文件不存在时报错,文件流定位到文件开头
r+:可读可写,当文件不存在时报错,文件流定位到文件开头
w:只写,文件不存在创建,存在清空
w+:可读可写,文件不存在创建,存在清空
a:追加(在末尾写),文件不存在创建,存在追加,文件流定位到文件末尾
a+:读和追加,文件不存在创建,存在追加,文件流定位到文件末尾
注:当a的方式打开文件时,写只能在末尾进行追加,定位操作是无法改变写的,但是可以移动到其他位置读
课后练习
1:实现cat命令
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE *fp;
int ch = 0;
fp = fopen(argv[1], "r");
if(fp == NULL)
{
perror("fopen err");
return -1;
}
while((ch = fgetc(fp)) != -1)
{
printf("%c", ch);
}
fclose(fp);
return 0;
}
2. 题目要求:编程读写一个文件test.txt,每隔1秒向文件中写入一行数据,类似这样:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
该程序应该无限循环,直到按Ctrl-C中断程序。
再次启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
3, 2007-7-30 15:19:02
4, 2007-7-30 15:19:03
5, 2007-7-30 15:19:04
其中使用了两个time.h的函数 time(); //计算秒数和 localtime(); //将秒数转换成年月日时分秒
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>
#include<string.h>
int main()
{
FILE *fp;
fp=fopen("./log.c","a+");
if(NULL == fp)
{
perror("Failed: ");
return -1;
}
time_t now;
struct tm *tm_now;
int i=1;
int s;
time(&now);
tm_now = localtime(&now);
while((s=fgetc(fp))!=EOF)
{
if(s=='\n')
i++;
}
while(1)
{
fprintf(fp,"%d %04d-%02d-%02d-%02d-%02d-%02d\n",i++,tm_now->tm_year+1900,tm_now->tm_mon+1,tm_now->tm_mday,tm_now->tm_hour,tm_now->tm_min,tm_now->tm_sec);
fflush(fp);
sleep(1);
}
fclose(fp);
return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)