华清远见嵌入式学习每周汇总

2023-05-16

每周学习总结

  • 第一周数据结构
      • Makefile的编写
      • 顺序表
      • 链表(含约瑟夫环之选猴王)
        • Joseph circle
      • 本周总结
  • 第二周
    • 队列
      • 二叉树
      • 二叉树的创建
    • 本周总结
  • 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 #编译过程中生成的.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>//malloc free
#define N 10//后续若需要改变顺序表的大小 只需改动此处的10即可
typedef int datatype;//后续若需要改动输入数据的数据类型 只需要改动此处的int即可
typedef struct
{
	datatype data[N];
	int last;
}seqlist_t;//定义并重命名结构体为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++;//每次插入后last都要后移一个,保证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;//避免再调用到p时出现段错误(野指针)
}

清空顺序表只需要将作为标志的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;//data area
	struct node_t * next;//point area
}linklist_t;
//create empty head_linklist
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);

//Joseph
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;//存储p的位置 移动tp进行操作即可,不要动原来的链表头节点
	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
			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();
	//	p1=p1->next;
	//	p2=p2->next;
	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;
			//	if(t1->next!=NULL)
			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)//start为开始的位置,需要包括第0个节点。
													//kill为每隔几只猴子 杀一个比如 0 1 2 3 4
													//从0开始 kill=2那就杀3,此时是创建了5个节点
													//createJoseph(p,5)
													//其实逻辑上很好理解,就是不太好确认位置
													//画两次图就好了^^
{
	linklist_t* t=p;
	int i=0;
	for(i=0;i<start;i++)
	{
		t=t->next;
	}
	//move to the front of killone;

	linklist_t* tt;
	while(t!=t->next){
		for(i=0;i<kill;i++)
		{
			t=t->next;
		}
		//kill
		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;
//1.创建一个空的无头链式栈 
void CreateLinkstack(stack_link **ptop)
{
	*ptop=NULL;
}

//2.入栈
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;
}

//3.出栈 
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;
//1.创建一个空的顺序队列
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默认输出流。

	//FILE *stdout;
	printf("hello\n");	//调用标准IO
	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)//从文件流中读取size个字符到s中
int  fputs(const char *s,FILE * stream)//向文件流中写入s
//文件定位
void rewind(FILE *stream)//将stream的文件指针定位到开头位置
int fseek(FILE *stream, long offset, int whence)//将stream的文件指针定位到距离whence偏移offset个字符的位置
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(使用前将#替换为@)

华清远见嵌入式学习每周汇总 的相关文章

  • 消息队列OSQCreate失败:Assertion "OSQCreate" failed at line 71 in ..\LWIP\arch\sys_arch.c错误解决方法

    在STM32F407的上移植正点原子例程中的ucosII和LWIP功能时 xff0c 发现其他任务中创建其他消息邮箱后 xff0c 出现了如下的错误 xff1a Assertion OSQCreate failed at line 67 i
  • 学习Linux 编程的几本好书

    这次涉及到了具体的平台 GNU Linux Linux下开发与明显不同于Windows平台的特点 xff0c 从开发工具到项目组织 xff0c 都有较大的差距 首先声明 xff0c 在做Linux平台开发之前 xff0c 首先要熟练使用Li
  • http parser库的使用方法

    include 34 http parser h 34 include lt stdio h gt include lt stdlib h gt include lt string h gt include lt assert h gt i
  • PIXhawk4飞控学习笔记(一)开发环境

    PIXhawk4飞控学习笔记 xff08 一 xff09 开发环境 PIX4简介开发环境准备PIX4控制板MDK Keil5STM32CUBEMAXQGroundControl地面站 总结 PIX4简介 PX4是Dronecode平台的一部
  • Git常用命令

    1 Git全局设置 当安装Git后首先要做的事情是设置用户名称和email地址 这是非常重要的 xff0c 因为每次Git提交都会使用该用户信息 在Git 命令行中执行下面命令 xff1a 设置用户信息 git config global
  • 在PX4下更换pixhawk的IMU

    写在前面 出于一些原因 xff0c 这篇文章不给出具体的源码 xff0c 因此博主试着将这篇写成了一篇科普性质的文章 xff0c 如果你认真读的话 xff0c 应该会有收获的 为什么要更换pixhawk的传感器 xff1f 大多数的玩家拿到
  • 存储卡插上电脑时显示文件名变乱码请问怎样才能修复???

    存储卡在使用的过程中会出现各种奇怪的错误 xff0c 比如小编今天碰到的一个 xff0c 打开分区提示文件名变乱码 xff01 存储卡插上电脑时显示文件名变乱码请问怎样才能修复 存储卡在使用的过程中会出现各种奇怪的错误 xff0c 比如小编
  • putty使用python模块tkinter显示对话框出现_tkinter.TclError: no display name and no $DISPLAY environment variable

    问题描述 xff1a putty不能显示对话框 出现错误提示 xff1a tkinter TclError no display name and no DISPLAY environment variable 解决办法 xff1a 下载安
  • 有关于ValueError: Variable rnn/basic_lstm_cell/kernel already exists, disallowed.的问题

    很简单 xff0c 重新跑一篇程序 xff0c 我理解为重启核 xff1f xff1f xff1f jupyter中有kernel选项 xff0c 点击选择 Restart amp RunAll xff0c 即可解决问题 Mark
  • 从尾到头打印链表

    题目描述 xff1a 输入一个链表 xff0c 按链表值从尾到头的顺序返回一个ArrayList 分析 xff1a 1 xff0c 新建两个arraylist xff0c 2 xff0c 遍历链表 xff0c 存入第一个arraylist
  • Win10的Linux子系统Ubuntu安装图形界面

    Win10 的 Linux 子系统 Ubuntu 安装图形界面 陈拓 2021 07 25 2021 07 26 1 概述 Win10的linux子系统Windows Subsystem for Linux xff08 简称 WSL xff
  • 得到斐波那契数列的第n个数

    题目 xff1a 现在要求输入一个整数n xff0c 请你输出斐波那契数列的第n项 xff08 从0开始 xff0c 第0项为0 xff09 n lt 61 39 分析 xff1a 1 xff0c 1 xff0c 2 xff0c 3 xff
  • ModuleNotFoundError: No module named 'scipy._lib.decorator'问题解决

    问题来源 xff1a 在导入sklearn库时 xff0c 出现 usr lib python3 dist packages scipy sparse linalg isolve iterative py in 8 9 from scipy
  • 二叉搜索树的后序遍历序列

    题目 输入一个整数数组 xff0c 判断该数组是不是某二叉搜索树的后序遍历的结果 如果是则输出Yes 否则输出No 假设输入的数组的任意两个数字都互不相同 分析 碰到二叉树 xff0c 优先想递归 这里 xff0c 后序数组 xff0c 最
  • 二叉树中和为某一值的路径

    题目 输入一颗二叉树的跟节点和一个整数 xff0c 打印出二叉树中结点值的和为输入整数的所有路径 路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径 注意 在返回值的list中 xff0c 数组长度大的数组靠前 分析 二叉树
  • 复杂链表的复制(java)

    题目 输入一个复杂链表 xff08 每个节点中有节点值 xff0c 以及两个指针 xff0c 一个指向下一个节点 xff0c 另一个特殊指针指向任意一个节点 xff09 xff0c 返回结果为复制后复杂链表的head xff08 注意 xf
  • CMake学习-添加头文件路径,库路径,库

    CMake中 xff0c 添加头文件路径 xff0c 对应的函数叫include directories 然后在参数中 xff0c 把所有需要添加的路径 xff0c 加进去就可以了 添加库路径 xff0c 对应的函数叫LINK DIRECT
  • Docker Run 命令

    docker run 参数 xff1a e xff1a 向容器内传递环境变量 xff0c 启动容器时用户可以动态传参 v 挂载文件 xff0c 把该容器的数据保存到挂载文件上 p 端口映射 xff08 p 8888 3306 8888宿主机
  • 如何复现论文?什么是论文复现?

    参考资料 xff1a 学习篇 顶会Paper复现方法 知乎 如何读论文 xff1f 复现代码 xff1f 复现代码是什么意思 CSDN 我是如何复现我人生的第一篇论文的 知乎 在我看来 xff0c 论文复现应该有一个大前提和分为两个层次 大
  • Kinect 获取深度图计算距离,并进行彩色图和深度图之间的映射

    Kinect 获取深度图计算距离 xff0c 并进行彩色图和深度图之间的映射 最近所进行的项目需要利用KINECT获取深度距离 xff0c 需要得到彩色图中某一点的位置 xff0c 在网上找了很多资料 xff0c 都不是很好 xff0c 碰

随机推荐

  • 十一个顶级的Git 客户端,绝对很实用!

    导读Git是一种免费开源的分布式版本控制系统 xff0c 可用于处理软件开发及另外几种版本控制任务 它旨在处理大大小小的各种项目 xff0c 并确保速度 效率和数据完整性 Linux用户主要可以通过命令行来管理Git xff0c 不过外面有
  • Windows USB串口接收GPS北斗模块数据和数据说明

    陈拓 2022 05 07 2022 05 09 1 简介 本文以GPS 43 北斗卫星定位授时导航模块HT1818Z3G5L为例 xff0c 在Win10下读数据 产品参数 引脚定义 2 连接PC机和HT1818Z3G5L模块 如图 xf
  • Linux也有全功能杀毒软件啦!

    导读近日 xff0c 瑞星公司推出瑞星杀毒软件Linux全功能版 xff0c 它是一款功能齐全 高性能的企业级安全产品软件 xff0c 并且新增国内首家 文件监控 与 网络监控 功能 xff0c 对Linux系统进行系统和网络双层防护 xf
  • 虚拟现实的起源、发展、爆发与沉淀

    虚拟现实的三生三世 闲来无事翻篇外文 xff0c 本博主并非故意蹭热点 xff08 奸笑 xff09 xff0c 结尾我会细说为何是三生三世 xff0c 不是五生五世 xff1a 虚拟现实远早于这个概念被创造和形式化之前 在这篇描写虚拟现实
  • UCOS消息队列的使用【转】

    UCOS消息队列的使用 转 收藏 消息队列的使用 1 需在以下文件中配置如下内容 OS CFG H OS MAX QS N 你需要的值 根据需要自己配置 define OS Q EN 1 Enable 1 or Disable 0 code
  • to_string函数的用法

    to string 函数 xff1a 将数字常量转换为字符串 xff0c 返回值为转换完毕的字符串 头文件 xff1a include lt string gt string s 61 to string i 将整数i转换为字符串表示形式
  • EKF2学习之控制融合模式

    By snowpang 2017 8 10 1 存储控制状态值 xff0c 并开启状态变化检测 control status value bitmask containing filter control status union filt
  • 经典算法 (四) 桶排序

    时隔一年 xff0c 小葵花课堂再次开课 xff0c 这次开课不会像之前那样很早就停课了 xff0c 在此给大家道个歉 xff1a 对不起 嘻嘻 又到了毕业季了 xff0c 废话不多说 xff0c 继续我们的算法学习 一丶算法描述 桶排序
  • 程序调试记录(纯自用)

    Stack类测试 xff1a 在测试Stck类型的变量内容是否正确时 xff0c 经常会通过把所有值pop出来输出的方法 xff0c 这样容易造成一个问题就是 xff0c 栈已经被弄空了 xff0c 以后再用的时候就会是一个空栈 所以 xf
  • vector详解

    引言 emmm 这篇博客写作的缘由其实就是我在日常使用vector的时候发现对vector并不怎么了解所以决定写这篇博客的 写这篇博客 xff0c 我参考了vector C 43 43 Reference中的内容 xff0c 及侯捷先生的
  • Eigen快速入门

    Eigen快速入门 一个简单的例子 span class hljs preprocessor include lt iostream gt span span class hljs preprocessor include 34 Eigen
  • PX4 的 ECL EKF 公式推导及代码解析

    原创作者 USRL所长 64 CSDN 文章来源 https blog csdn net u010307048 article details 100553475 如需转载联系联系原创作者 作者整理的内容如下 xff0c 干货很多 xff0
  • git fatal: The remote end hung up unexpectedly错误解决方法

    在使用git更新或提交项目时候出现 34 fatal The remote end hung up unexpectedly 34 原因是推送的文件太大 那就简单了 xff0c 要么是缓存不够 xff0c 要么是网络不行 xff0c 要么墙
  • 公开课精华|机器人的带约束轨迹规划

    本文章总结于大疆前技术总监 xff0c 目前在卡内基梅隆大学读博的杨硕博士在深蓝学院的关于机器人的带约束轨迹规划的公开课演讲内容 全文约5000字 笔者不是机器人领域的 xff0c 因此特地去了解了一下杨硕博士 xff0c 深感佩服 xff
  • 自动驾驶的重要一环:谈谈感知前沿技术

    本文总结于Waymo研发经理周寅于2021年8月29日在深蓝学院的讲座 讲座内容主要包括自动驾驶系统的总览 xff0c 自动驾驶感知的介绍 xff0c 以及感知的前沿动态和总结 1 自动驾驶系统总览 关于自动驾驶系统 目前主流的L4级别自动
  • 论文精读 | slam中姿态估计的图优化方法比较

    一 摘要 对于位置环境中的自主导航问题 xff0c 同步定位与建图 Simultaneous localization and mapping SLAM 是一个非常重要的工具框架 根据SLAM字面含义可以得知 xff0c 获取正确的环境表征
  • 自动驾驶中的多种卡尔曼滤波算法及推导详解,值得一读!

    鉴于卡尔曼滤波算在多传感器融合系统中使用的普遍性 xff0c 本文将单独就卡尔曼滤波算法及自动驾驶中常用的改进卡尔曼滤波算法进行详细介绍 首先介绍卡尔曼滤波的基本方法 xff0c 然后介绍针对非线性系统改进的扩展卡尔曼滤波 xff0c 最后
  • 入门ROS其实也没有那么难!

    作者 xff1a Tassel 相信提出这个问题的小伙伴已经对ROS有一定的了解 xff0c 但不管是出于工程应用还是理论学习 xff0c 我们都有必要谈谈ROS的概念 xff0c 从概念去理解ROS ROS xff08 机器人操作系统 x
  • 论SLAM技术发展趋势

    2018年7月底 xff0c 深蓝学院发起并承办了 第一届全国SLAM技术论坛 浙江大学章国锋老师 香港科技大学沈劭劼老师 上海交通大学邹丹平老师 中科院自动化所申抒含老师在 圆桌论坛 xff1a SLAM技术发展趋势 上分享了SLAM技术
  • 华清远见嵌入式学习每周汇总

    每周学习总结 第一周数据结构Makefile的编写顺序表链表 xff08 含约瑟夫环之选猴王 xff09 Joseph circle 本周总结 第二周栈队列树二叉树二叉树的创建 本周总结 IO进程标准IO2 题目要求 xff1a 编程读写一