数码相框(七、电子书之指针和链表操作)

注:本人已购买韦东山第三期项目视频,内容来源《数码相框项目视频》,只用于学习记录,如有侵权,请联系删除。

1. 电子书之指针操作

  • 内存就像是一个一个的格子,格子里面存放的是数据,每一个格子都有一个对应的编号,这格子的编号就是内存的地址。一个格子存放一个字节的数据,即在内存中一个地址里存放一个字节。

  • 在C语言里,每一个变量都有一个存储它的地址,例如,使用C语言定义以下变量:

    char c;
    int a;
    int *p;
    

    其中,

    • &c = 格子的编号 ,格子的编号,即存放变量c的地址。(假设 c 所在的格子的编号为100,那么 &c = 100);
    • 变量 a 是 int 类型,占据4个字节,那么变量 a 的地址是多少?我们假设存储变量 a 这4个字节的的地址依次为200、201、202、203,第一个地址就是变量a的地址,因此&a = 200
    • p 是一个 int 类型的指针变量,该变量存储的是一个地址。
  • 只要是变量,不管什么类型的变量,在内存必定有存储这个变量的格子(地址),为什么是使用4个字节(32bit)来存储一个指针变量?

  • 对于32位的CPU来说,可以访问2^32个地址,即每个地址用32位(4字节)的二进制数表示,换句话说就是变量的地址是32位的。所以,无论什么样的数据类型,它的地址都是4个字节的。

  • 对于上面的指针变量 p,我们假设存储它的地址为600,则 &p = 600;

  • 接着我们定义一个结构体:

    typedef struct {
          
          
        int a;
        int b;
    }T_AB;
    
    /* 定义一个结构体变量tTest */
    T_AB tTest;   /* 对于tTest结构体变量存放在哪里,我们不需要我们关系,编译器会帮我我们做好 */
    

    这个 tTest 结构体变量占据8个字节,这个结构体变量的地址就是存储这个变量的第一个地址。这里我们假设存放 tTest 结构体变量的第一个地址为1000,所以,&tTest = 1000;

  • 接下来写程序进一步细化;

    int main(void)
    {
          
          
       char c;
       int a;
       T_AB tTest;
       int *p;
       int **pp;
      
       ① p = &a;  /* 这操作p变量,操作的是p对应的内存,如上图编号①所示 */*p = 0x12345678; /* 指针变量p所指向的4字节发内存,其内容等于0x12345678,即把变量a的值           
                              修改为0x12345678 */
    
       ③ p = &c;  /* 在我们的假设中,c变量的地址为100,此时存储变量p的地址的内容变为100 */*p =‘A’;  /* 把指针p所指向的地址的内容修改为‘A’, ‘A’的ascii值为0x41。即把变量c的值    
                         修改为0x41 */
       ⑤ p = &tTest; /* 把指针变量p的值修改为结构体变量tTest的地址,在前面的假设中,tTest的地址的
                         值为1000,此时存储变量p的地址的内容被修改为1000 */*p = &tTest; /* p所指向的4字节的内容等钱tTest的地址,其值为1000 */
      
       ⑦ pp = &p;    /* pp所占据的4个字节等于p变量的地址,根据前面的假设,p变量的地址为600 */**pp = 0xABCD1234; /* 可以拆分为 *(*pp),其中*pp表示把pp的值(600)作为地址,访问这个地址
                               (600)的内存,此时地址为600的内存存放的内容为0x3E8=100,那么        
                               *(1000)=0xABCD1234,即访问地址为1000这个内存,它的值设置为0xABCD1234,
                               地址为1000这个变量是tTest.a,即tTest.a = 0xABCD1234 */
        
       /* 口诀:*变量:把变量的值作为地址,去访问这个地址的内存 */
    }
    

    对于上面程序的 ①~⑧ 的过程,如下图所示:
    在这里插入图片描述

  • 实验,实验代码 pointer.c 如下:

    #include <stdio.h>
    
    typedef struct {
          
          
    	int a;
    	int b;
    }T_AB;
    
    int main(int argc, int **argv)
    {
          
          
    	char c;
    	int a;
    	T_AB tTest;
    	int *p;
    	int **pp;
        
    	/* 1. */
    	p = &a;
    	printf("p = 0x%x, a'addr = 0x%x\n", p, &a);
    		
    	/* 2. */
    	*p = 0x12345678;
    	printf("a = 0x%x\n", a);
    
    	/* 3. */
    	p = &c;
    	printf("p = 0x%x, c'addr = 0x%x\n", p, &c);
    
    	/* 4. */
    	*p = 'A';
    	printf("c = %c\n", c);
    
    	/* 5. */
        p = &tTest;
    	printf("p = 0x%x, tTest'addr = 0x%x\n", p, &tTest);
    
    	/* 6. */
    	*p = &tTest;
    	printf("tTest.a = 0x%x, tTest'addr = 0x%x\n", tTest.a, &tTest);
    
    	/* 7. */
    	pp = &p;
    	printf("pp = 0x%x, p'addr = 0x%x\n", pp, &p);
    
    	/* 8. */
    	**pp = 0xABCD1234;
    	printf("tTest.a = 0x%x\n", tTest.a);
    
    	return 0;
    }
    

    在Ubuntu的串口命令中端执行gcc -o pointer pointer.c命令编译,运行./pointer , 运行结果如下图所示:
    在这里插入图片描述

2. 电子书之链表操作

在上一节数码相框(六、在LCD上显示任意编码的文本文件)中,对显示模块、字体模块、编码模块,采用的是一个全局数组维护该模块下不同的显示设备、不同的字体、不同的编码。使用的数组的优点是非常简单,但它的缺点数组的容量被定死了,不方便扩展。 这一节我们把数组换成链表,相对于数组,链表的优点是没有空间限制,方便扩展。

(1) 例如,对于EncodingOpr结构体,我们添加 struct EncodingOpr *ptNext 成员,代码如下图所示:

typedef struct EncodingOpr {
    
    
	char *name;
	int iHeadLen;
	PT_FontOpr aptFontOprSupported[4];
	int (*isSupport)(unsigned char *pucBufHead);
	int (*GetCodeFrmBuf)(unsigned char *pucBufStart, unsigned char *pucBufEnd, unsigned int *pdwCode);
	struct EncodingOpr *ptNext;  /* EncodingOpr 结构体指针,指向下一个EncodingOpr 结构体*/
}T_EncodingOpr, *PT_EncodingOpr;

(2) 利用上面 EncodingOpr 结构体构建一个单向链表,如下图所示:
在这里插入图片描述
(3) 使用单向链表注册 EncodingOpr 结构体的代码如下:

int RegisterEncodingOpr(PT_EncodingOpr ptEncodingOpr)
{
    
    
	PT_EncodingOpr ptCur;
    /* 首先判断链表头是否为空,如果为空,把它指向新添加的EncodingOpr结构体*/
	if (!g_ptEncodingOprHead)
	{
    
    
		g_ptEncodingOprHead   = ptEncodingOpr;
		ptEncodingOpr->ptNext = NULL;
	}
	else
	{
    
    
		ptCur = g_ptEncodingOprHead;
		/* 从链表的头部开始遍历链表,在链表的尾端添加新的EncodingOpr结构体 */
		while(ptCur->ptNext)
		{
    
    
			ptCur = ptCur->ptNext
		}
		ptCur->ptNext = ptEncodingOpr;
		ptEncodingOpr->ptNext = NULL;
	}
	return 0;
}

(3) 使用双向链表实现翻页控制,页描述结构体代码如下:

typedef struct PageDesc {
    
    
	int iPage;  /* 当前页数 */
	unsigned char *pucLcdFirstPosAtFile;   /* 在LCD上第一个字符位于文件的位置*/
	unsigned char *pucLcdNextPageFirstPosAtFile; /* 下一页的LCD上第一个字符位于文件的位置 */
	struct PageDesc *ptPrePage;  /* 上一页链表,指向上一个PageDesc结构体 */
	struct PageDesc *ptNextPage; /* 下一页链表,指向下一个PageDesc结构体 */
} T_PageDesc, *PT_PageDesc;

(4) 使用 PageDesc 结构体构建的双向链表如下图所示:
在这里插入图片描述
(5) 使用双向链表实现的翻页控制代码如下:

static void RecordPage(PT_PageDesc ptPageNew)
{
    
    
	PT_PageDesc ptPage;
    /* 判断链表头是否为空 */
	if (!g_ptPages)
	{
    
    
		g_ptPages = ptPageNew;
	}
	else
	{
    
    
	   /* 添加PageDesc链表节点 */
		ptPage = g_ptPages;
		while (ptPage->ptNextPage)
		{
    
    
			ptPage = ptPage->ptNextPage;
		}
		ptPage->ptNextPage = ptPageNew;
		ptPageNew->ptPrePage  = ptPage;
	}
} 

int ShowNextPage(void)
{
    
    
	int iError;
	PT_PageDesc ptPage;
	unsigned char *pucTextFileMemCurPos;

	if (g_ptCurPage)
	{
    
    
		pucTextFileMemCurPos = g_ptCurPage->pucLcdNextPageFirstPosAtFile;
	}
	else
	{
    
    
		pucTextFileMemCurPos = g_pucLcdFirstPosAtFile;
	}

	iError = ShowOnePage(pucTextFileMemCurPos);
	DBG_PRINTF("%s %d, %d\n", __FUNCTION__, __LINE__, iError);
	if (iError == 0)
	{
    
    
		if (g_ptCurPage && g_ptCurPage->ptNextPage)
		{
    
    
			g_ptCurPage = g_ptCurPage->ptNextPage;
			return 0;
		}
		ptPage = malloc(sizeof(T_PageDesc));
		if (ptPage)
		{
    
    
			ptPage->pucLcdFirstPosAtFile         = pucTextFileMemCurPos;
			ptPage->pucLcdNextPageFirstPosAtFile = g_pucLcdNextPosAtFile;
			ptPage->ptPrePage                    = NULL;
			ptPage->ptNextPage                   = NULL;
			g_ptCurPage                          = ptPage;
			DBG_PRINTF("%s %d, pos = 0x%x\n", __FUNCTION__, __LINE__, (unsigned int)ptPage->pucLcdFirstPosAtFile);
			RecordPage(ptPage);
			return 0;
		}
		else
		{
    
    
			return -1;
		}
	}
	return iError;
}

int ShowPrePage(void)
{
    
    
	int iError;
	
	DBG_PRINTF("%s %d\n", __FUNCTION__, __LINE__);
	if (!g_ptCurPage || !g_ptCurPage->ptPrePage)
	{
    
    
		return -1;
	}

	DBG_PRINTF("%s %d, pos = 0x%x\n", __FUNCTION__, __LINE__, (unsigned int)g_ptCurPage->ptPrePage->pucLcdFirstPosAtFile);
	iError = ShowOnePage(g_ptCurPage->ptPrePage->pucLcdFirstPosAtFile);
	if (iError == 0)
	{
    
    
		DBG_PRINTF("%s %d\n", __FUNCTION__, __LINE__);
		g_ptCurPage = g_ptCurPage->ptPrePage;
	}
	
	return iError;
}

(6) 接下来写代码做实验实现链表的添加、删除,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct NAME{
    
    
	char *name;
	struct NAME *pre;
	struct NAME *next;
}T_Name, *PT_Name;

static PT_Name g_ptNameHead = NULL;


void add_name(PT_Name ptNew)
{
    
    
	PT_Name ptCur;
	
	if (g_ptNameHead == NULL)
	{
    
    
		g_ptNameHead = ptNew;
	}
	else
	{
    
    
		ptCur = g_ptNameHead;
		while(ptCur->next)
		{
    
    
			ptCur = ptCur->next;
		}
		ptCur->next = ptNew;
		ptNew->pre  = ptCur;
	}
}


void add_one_name(void)
{
    
    
	PT_Name ptNew;
	char *str;
	char name[128];
	
	printf("enter the name:");
	scanf("%s",name);

	ptNew = malloc(sizeof(T_Name));
	str  = malloc(strlen(name) + 1);
	strcpy(str, name);
	ptNew->name = str;
	ptNew->pre  = NULL;
	ptNew->next = NULL;
	add_name(ptNew);
}

PT_Name get_name(char *name)
{
    
    
	PT_Name ptCur;

	if (g_ptNameHead == NULL)
	{
    
    
		return NULL;
	}
	else
	{
    
    
		ptCur = g_ptNameHead;
		do
		{
    
    
			if (strcmp(ptCur->name, name) == 0)
				return ptCur;
			else
				ptCur = ptCur->next;
		}while(ptCur);
	}
}

void del_name(PT_Name ptDel)
{
    
    
	PT_Name ptCur;
	PT_Name ptPre;
	PT_Name ptNext;

	if (g_ptNameHead == ptDel)
	{
    
    
		g_ptNameHead = ptDel->next;
		return; 
	}
	else
	{
    
    
		ptCur = g_ptNameHead->next;
		while(ptCur)
		{
    
    
			if (ptCur == ptDel)
			{
    
    
				ptPre  = ptCur->pre;
				ptNext = ptCur->next;
				ptPre->next = ptNext;
				if (ptNext)
				{
    
    
					ptNext->pre = ptPre;
				}
				break;
			}
			else
			{
    
    
				ptCur = ptCur->next;
			}
		}
	}
	free(ptDel->name);
	free(ptDel);
}

void del_one_name(void)
{
    
    
	PT_Name ptFind;
	char name[128];
	
	printf("enter the name:");
	scanf("%s",name);

	ptFind = get_name(name);
	if (ptFind == NULL)
	{
    
    
		printf("don't have this name\n");
	}
	else
	{
    
    
		del_name(ptFind);
	}	
}

void list_all_name(void)
{
    
    
	PT_Name ptCur = g_ptNameHead;
	int i = 0;

	while(ptCur)
	{
    
    
		printf("%02d : %s\n", i++, ptCur->name);
		ptCur = ptCur->next;
	}
}

int main(int argc, char **argv)
{
    
    
	char c;
	while(1)
	{
    
    
		printf("<l> List all the names\n");
		printf("<a> add one name\n");
		printf("<d> del one name\n");
		printf("<x> exit\n");
		printf("Enter the choise: ");

		c = getchar();
		switch(c)
		{
    
    
			case 'l':
			{
    
    
				list_all_name();
				break;
			}
			case 'a':
			{
    
    
				add_one_name();
				break;
			}
			case 'd':
			{
    
    
				del_one_name();
				break;
			}
			case 'x':
			{
    
    
				return 0;
				break;
			}
			default:
			{
    
    
				break;
			}
				
		}
	}
	return 0;
}

编译程序,运行代码,运行结果如下图所示:
在这里插入图片描述
输入“l”列出都有节点的名字,输入“a”添加链表节点,输入“d”删除链表节点,输入“x”退出程序。

猜你喜欢

转载自blog.csdn.net/qq_35031421/article/details/111822786
今日推荐