注:本人已购买韦东山第三期项目视频,内容来源《数码相框项目视频》,只用于学习记录,如有侵权,请联系删除。
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”退出程序。