简单介绍
golang 在云计算场景下可以说是风头无量,而且 golang 的很多设计理念基于 c,大家可能想不到,c 也是可以实现面向对象编程的,我们可以参考 golang 中面向对象来实现一波。
注意:面向对象是一种编程思想或者说设计思想,并不是那个语言特有的,所以哪怕 c 也可以实现这种思想。
通讯录整体分析
通讯录首先就是需要设计结构体,通讯录的结构体就比较简单,首先就是通讯录的人和电话。
代码语言:c复制#define NAME_LENGTH 16
#define PHONE_LENGTH 32
struct person
{
char name[NAME_LENGTH]; //名字
char phone[PHONE_LENGTH]; //电话
struct person *next; //双向链表
struct person *prev;
};
接下来就是通讯录的结构,一个双向链表其中每个块是一个 person 结构体。
代码语言:c复制struct contacts{
struct person *people;
int count; //数量
struct contacts *next;
struct contacts *prev;
};
结构体定义完毕后,首先需要定义每一个结构体的方法,首先就是 person 的方法。
代码语言:c复制int person_insert(struct person** ppeople, struct person* ps)
{
if(ps == NULL) return -1;
LIST_INSERT(ps, *ppeople);
return 0;
}
int person_delete(struct person **ppeople, struct person *ps)
{
if(ps == NULL) return -1;
LIST_REMOVE(ps, *ppeople);
return 0;
}
struct person* person_search(struct person *people, const char* name)
{
struct person *item = NULL;
for(item = people; item != NULL; item = item->next)
{
if(!strcmp(name, item->name))
{
break;
}
}
return item;
}
int person_traversal(struct person *people)
{
struct person *item = NULL;
for(item = people; item != NULL; item = item->next)
{
INFO("name: %s, phone: %sn", item->name, item->phone);
}
return 0;
}
看见了这个,如果熟悉 golang 语言的就感觉很熟悉,试想,如果换成 golang 就会这样定义了:
代码语言:golang复制type person struct{
....
}
type contacts struct{
....
person *person
}
func(contacts *cts)person_delete(person **ppeople, person *ps){
...
}
因此说,面向对象其实是一种思想,c 语言这样的面向过程的语言也是可以实现这样的开发。
言归正传,回到通讯录,上述内容查询主要是针对 person 这个结构体操作,我们换需要对通讯录进行操作,情况很类似,如下:
代码语言:c复制#define NAME_LENGTH 16
#define PHONE_LENGTH 32
#define INFO printf
#define BUFFER_LENGTH 100
int insert_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
struct person *p = (struct person *)malloc(sizeof(struct person));
INFO("please input name: n");
scanf("%s", p->name);
INFO("please input phone: n");
scanf("%s", p->phone);
if(0 != person_insert(&cts->people, p)){
free(p);
return -3;
}
cts->count ;
INFO("insert success!");
return 0;
}
int print_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
person_traversal(cts->people);
return 0;
}
int delete_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
INFO("please input name: n");
char name[NAME_LENGTH] = {0};
scanf("%s", name);
struct person* ps = person_search(cts->people, name);
if(ps == NULL){
INFO("person don't existn");
return -2;
}
person_delete(&cts->people, ps);
free(ps);
return 0;
}
int search_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
INFO("please input name: n");
char name[NAME_LENGTH] = {0};
scanf("%s", name);
struct person* ps = person_search(cts->people, name);
if(ps == NULL){
INFO("person don't existn");
return -2;
}
INFO("name: %s, phone: %sn", ps->name, ps->phone);
return 0;
}
代码其实与之前的类似,并没有特别需要讲述的内容。
不过对于通讯录操作跟普通的增删改查还是不太一样的,我们通讯录需要持久化,简单来说就是刷新到硬盘上,这最简单涉及两个操作,一个是把当前的通讯录持久化到硬盘文件,另一个就是将硬盘中文件加载到内存中数据结构中。
首先看持久化到硬盘的操作。
代码语言:c复制int save_file(struct person *people, const char* filename)
{
FILE *fp = fopen(filename, "w"); //打开文件
if(fp == NULL) return -1;
struct person *item = NULL;
for(item = people; item != NULL; item = item->next)
{
fprintf(fp, "name: %s,phone: %sn", item->name, item->phone); //将数据结构以这样字符串形式进行加载
fflush(fp); //刷新到文件
}
fclose(fp); //关闭文件
return 0;
}
持久化是一个比较简单的工作,需要遍历我们的数据结构,然后以我们规定的格式保存起来。
如果从本质上看,各种文件就是不同的数据结构,持久化就是一个数据结构转化为另一个数据结构。当然我们对于特定文件格式转化和解析,比方说 json 和 yaml 可能涉及一些编译原理中正则表达式和有限自动机的一些内容,这不在我们深究的范畴之内。
然后就是加载文件。
这里解析肯定离不开我们保存时候的字符串格式,我们每一行字符串格式就是这样的。
代码语言:c复制name: %s, phone: %s //name: 空格 解析name 逗号 phone: 空格 解析phone
如果只是普通解析当然容易,我们得到字符串,先用逗号分隔成两个字符串,然后每个字符串用空格进行分隔。
这边才用了有点类似编译原理里边状态转换的方式写这个字符串解析。
代码语言:c复制int parser_token(char* buffer, int length, char*name, char* phone)
{
if(buffer == NULL) return -1;
int i = 0, j = 0, status = 0;
for(i = 0; (buffer[i] != ','); i ) //以逗号作为两个字符串分隔点
{
if(buffer[i] == ' ') //然后以空格作为两个字符串的状态分隔点,空格前是一个未找到字符状态,空格后是找到字符状态。
{
status = 1;
}else if(status == 1)
{
name[j ] = buffer[i];
}
}
j = 0;
status = 0;
for(; i < length; i )
{
if(buffer[i] == ' ')
{
status = 1;
}else if(status == 1) //同上
{
name[j ] = buffer[i];
}
}
INFO("name: %s,phone: %s", name, phone);
return 0;
}
当然,解析这个很简单,这里主要是想要大家理解这种状态分隔的思想。
最后就是加载文件的代码:
代码语言:c复制int load_file(struct person **ppeople, const char* filename)
{
FILE *fp = fopen(filename, "r");
if(fp == NULL) return -1;
int count = 0;
while (!feof(fp)) {
char buffer[BUFFER_LENGTH] = {0};
fgets(buffer, BUFFER_LENGTH, fp); //read a line
char name[NAME_LENGTH] = {0};
char phone[PHONE_LENGTH] = {0};
int length = (int)strlen(buffer);
if(0 != parser_token(buffer, length, name, phone)){
continue;
}
struct person *p = (struct person *)malloc(sizeof(struct person));
if(p == NULL) return -2;
memcpy(p->name, name, NAME_LENGTH);
memcpy(p->phone, phone, PHONE_LENGTH);
person_insert(ppeople, p);
(count) ;
}
fclose(fp);
INFO("load %d personal informatiion", count);
return 0;
}
代码较为简单,不做过多解释,最后就是对上述两个函数做一个简单的入口封装。
代码语言:c复制int save_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
INFO("Please input the filename you are willing to saven");
char filename[NAME_LENGTH] = {0};
scanf("%s", filename);
save_file(cts->people, filename);
return 0;
}
int load_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
INFO("Please input the filename you are willing to loadn");
char filename[NAME_LENGTH] = {0};
scanf("%s", filename);
load_file(&cts->people, filename);
return 0;
}
现在基本的功能方面函数已经定义完毕,然后即使一些类似前端的功能实现了。就一股脑全放到这里了。
代码语言:c复制void menu_info(void)
{
INFO("nn****************************************n");
INFO("****1、add person tt 2、print people*******");
INFO("****3、delete person tt 2、search people****");
INFO("****5、save person tt 6、load people*******");
INFO("****other key for existing program *******");
INFO("nn****************************************n");
}
int main()
{
while(1)
{
struct contacts *cts = (struct contacts *)malloc(sizeof(struct contacts));
int select = 0;
scanf("%d", &select);
memset(cts, 0, sizeof(struct contacts));
switch (select) {
case OPER_INSERT:
insert_entry(cts);
break;
case OPER_PRINT:
print_entry(cts);
break;
case OPER_DELETE:
delete_entry(cts);
break;
case OPER_SEARCH:
search_entry(cts);
break;
case OPER_SAVE:
save_entry(cts);
break;
case OPER_LOAD:
load_entry(cts);
break;
default:
goto exit;
}
exit:
free(cts);
return 0;
}
return 0;
}
至此,一个简单通讯录就完成了,知道很多人会说,都什么年代了,还在做通讯录这种老掉牙的东西,这里主要是为了引入两个方向问题:
1、使用向 c 语言其实也可以说实现面向对象,面向对象其实一个思想。
2、持久化和加载文件处理,特别是加载文件方面的解析 token 中的类似编译原理中状态转换的思想。