LINUX 入门 2.1
day4 20240425 耗时:60min
day5 20240428 耗时:120min
课程链接地址
第2章 linux c 通讯录
0 产品+架构设计
考虑做产品的思维+ 架构(注意按从上往下的顺序考虑:用什么数据结构,存储格式)
一般功能:增删查,打印,保存,加载调用
业务逻辑: 输入用户名name+phone等,最贴近上层用户的操作
最底层: 文件需要read write; 数据存用链表或者其他都行
1 实现数据存储——双向链表
从最底层往上开始实现,双向链表:用struct——val+指针(指下一个*next和前一个*pre)
-
封装over
#define NAME_LENGTH 16 #define PHONE_LENGTH 32 struct person{ char name[NAME_LENGTH]; char phone[PHONE_LENGTH]; struct person *next; struct person *pre; } struct contacts{ struct person *people; int count; //数多少人 }
-
头添加item
双向的第一个节点前面插入, 顺序:item指①,①指item, 再 people指item,但是代码逻辑不一样:itemnext指1,prev指空,再item是新的头!!!!
没调函数,用宏实现,注意加\ ,要么放一行里;
#define LIST_INSERT(item, list) do{ \ item->prev= NULL; \ item->next= list; \ list = item; \ } while(0) \
-
删除
传入头list+当前指向item
应该可以不分先后的!!!
item prev的next指向 item的next,把item跳了
item next的prev 指向item 的prev
特殊情况:被删的是第一个头节点,item=list,这种比较简单
最后释放掉item,类似C++delete(item) 防止内存泄漏,野指针
#define LIST_REMOVE(item, list) do{ \ if(item->next != NULL) item->prev->next= item->next; \ if (item->prev !=NULL) item->next->prev= item->prev;\ if( item == list) list = item->next; \ item->next= item->prev= NULL; \ }while(0)
思考题
业务层和支持层未分离,对链表操作会影响name和Phone
//业务层
char name[NAME_LENGTH];
char phone[PHONE_LENGTH];
//支持层
struct person *next;
struct person *pre;
2 架构接口层的实现
interface层:person 结构体的4种操作,不需要知道底层person如何实现,里面是什么,实现业务和数据结构隔离
-
insert
-
delete
-
search
用前面define过的宏操作
strcmp要include <string.h>
-
traversal
// define interface
int person_insert(struct person **ppeople, struct person *ps){
if(ps == NULL)return -1;
LIST_INSERT(ps, *ppeople); //ps插到people里, **people是指针的指针
return 0;
}
int person_delete(struct person **ppeople, struct person *ps){
if(ps == NULL) return -1;
if (ppeople ==NULL) return -2;
LIST_REMOVE(ps, *ppeople);
return 0;
}
struct person* person_search(struct person *people, const char *name){
struct person *item = NULL; //next= null
for(item = people; item != NULL; item = item->next){
if(!strcmp(name, item->name)) break;//一样返回0,!strcmp = 非0就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: %s\n", item->name, item->phone);
}
return 0;
}
3业务逻辑的分析与实现
用枚举enum(默认递增1)把操作定义出来,不在case写比较丑,而且不知道那个操作对应哪个数字
enum{ //4个链表操作+2个文件操作
OPER_INSERT = 1,
OPER_PRINT,
OPER_DELETE,
OPER_SEARCH,
OPER_SAVE,
OPER_LOAD
}
能明显看到套了三层,一层一层包上去
数据结构(直接define宏操作)——接口层——业务层(entry)
// 业务层入口
int insert_entry(struct contacts *cts){
if(cts == NULL) return -1;
struct person *p = (struct person*)malloc(sizeof(struct person));
if(p== NULL) return -2;
//name
INFO("please input name: \n");
scanf("%s", &p->name); //scanf可能有溢出问题,最长16位
//phone
INFO("please input phone: \n");
scanf("%s", &p->phone);
//add people
if(0!= person_insert(&cts->people, p)){
//异常了,就释放
free(p);
return -3;
}
cts->count ++;
INFO("inset success \n");
return 0;
}
int print_entry(struct contacts *cts){
if(cts == NULL) return -1;
// cts->people
person_traversal(cts->people);
}
int delete_entry(struct contacts *cts){
if(cts == NULL) return -1;
//name
INFO("please input name \n");
char name[NAME_LENGTH] = {0};
scanf("%s", name); //指向字符数组的第一个位置的地址
//person
struct person *ps = person_search(cts->people, name);
if(ps == NULL) {
INFO("person don't exist\n");
return -2;
}
INFO("name: %s, phone: %s\n", ps->name, ps->phone);
//delete
person_delete(&cts->people, ps);
free(ps);
return 0;
}
int search_entry(struct contacts *cts){
if(cts == NULL) return -1;
//name
INFO("please input name \n");
char name[NAME_LENGTH] = {0};
scanf("%s", name); //指向字符数组的第一个位置的地址
//person
struct person *ps = person_search(cts->people, name);
if(ps == NULL) {
INFO("person don't exist\n");
return -2;
}
INFO("name : %s, phone: %s\n", ps->name, ps->phone);
// 上面和删除一样
return 0;
}
int main(){
struct contacts *cts=(struct contacts *)malloc(sizeof(struct contacts));
if(cts == NULL) return -1;
memset(cts, 0 , sizeof (struct contacts);
while(1){
int select = 0;
scanf("d", &select);
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:
break;
case OPER_LOAD:
break;
}
}
}
c基础
-
#define
用于定义预处理宏,而预处理宏的命名约定是使用大写字母。这有助于与普通变量和函数名区分开来printf不直接用好处:
#define INFO printf
- 所有关于接口层的都用define宏定义,因为这样可以排除出是哪一层的实现出现了问题!!!!
- 上线产品版:直接改成
#define INFO
不打印没必要的输出
-
操作请查阅:C语言文件操作函数大全(扫完了一遍)
-
struct person *p = (struct person*)malloc(sizeof(struct person));
struct person *p
:声明了一个指向结构体类型person
的指针变量p
。这意味着p
可以存储结构体类型person
的地址。(struct person*)malloc(sizeof(struct person))
:这部分代码是一个动态内存分配的语句。malloc
函数用于在堆上分配一块内存空间,并返回指向该内存空间起始位置的指针。sizeof(struct person)
计算了结构体类型person
所需的字节数,以便确保分配的内存空间足够存储一个person
结构体的数据。(struct person*)
:这是一个类型转换操作,将malloc
返回的void*
类型的指针转换为指向struct person
类型的指针,以便与p
的类型相匹配。
-
memset要include标准库<stdlib.h>
void* memset(void* s, int c, size_t n);
c 是要设置的值,它被解释为一个 unsigned char,所以通常用于设置字节值。
n 是要设置的字节数。使用
malloc
函数分配内存时,得到的内存块中的内容是未定义的,即内存中的数据可能是任意值。为了确保在使用内存之前具有确定的初始状态,可以使用calloc
函数来分配内存,该函数会将分配的内存块中的每个字节都初始化为零。所以malloc出来的东西要Memset置零
-
二级指针:指针的指针 什么时候用
当形参people内容空的,无法执行函数的操作,用二级指针:指针的指针
**people(c比c++呆): 指向people这个指针的指针
传指针的地址 不会空 肯定有地址
-
linux下scanf输入字符串(>16位会溢出) 数组溢出