C语言学习记录230710~230714合集

        依旧是忙碌的一周,学习总结也耽搁了很久,不过好在每天的学习还是在正常进行,遗憾的一点是前天的gitee每日上传没续上,出了个小白点,那天想的是把通讯录的小程序写完再上传,结果比我想的多花了不少时间,实际上等我发现过了十二点的时候,已经晚了,最后完成通讯录的小程序也是在第二天早上才写完,有种血亏的感觉。但是怎么说,好在学习的成果并不会因此减少,木已成舟,不如以后多加注意。大概C语言的学习会在下礼拜二之前结束,从从头开始学习到现在,写了几个相比比较大一些的程序包括三子棋、扫雷、通讯录,让我收获都很大,到目前为止我十分庆幸自己选择了从头学一次,真的是补了很多东西。话不多说,还是继续总结吧,上一个总结主要是字符串、内存函数的,字符函数我准备今天研究一下,总结的话主要是对这五天的学习总结,这五天主要是花了一天写程序,剩下的主要就是学习了自定义类型、动态内存分配的相关知识。接下来一一进行回忆总结:

        (一)自定义类型

                ①结构体

                结构体属于一种自定义的数据类型,用于描述一个复杂的事务,比如我们要描述一张身份证,身份证上有许多信息,比如出生年月、年龄、地址等等,这些内容不能靠单一的简单数据来描述,这时我们可以通过声明一个结构体来自定义一个数据类型,用以描述。

                结构体的声明格式以前已经说过,这里就不加复数了,这里补充一个结构体的经典类型定义方法,用以简化后续对对应结构体的声明。

typedef struct Stu
{
    int old;
    char name[20];
    char addr[50];
}Student;

                上面的例子通过typedef类型定义的方法,将结构体Stu这一个结构体类型进行类型定义,声明了一个Student的类型名,表明了这个Student可以直接代替struct Stu,后续声明变量等等时候,都可以直接用Student来代替对应的结构体。

                顺便一提,声明一个结构体类型的时候,对应的结构体名称是可以不写的,比如上面的例子,Stu这个结构体类型名可以不写,以后想要声明这个变量只能通过Student这一个改过的类型名来定义。假如没有经过typedef的类型定义,那么这个结构体类型可能就只能用一次,也就是在这次声明结构体类型的同时,进行对应结构体变量的声明,不然后续在其他程序块内,想要声明这个结构体类型,会因为没有结构体类型名而导致无法声明。

                结构体类型声明的时候,结构体成员也可以是结构体,这是允许的,最经典的例子就是链表的节点数据结构,其声明如下:

typedef struct Node
{
    int data;
    struct Node *p;
}Node;

                上面就是链表数据结构中,我们为描述链表节点而声明的一种结构体类型,他有两个成员,第一个成员是数据,第二个成员是一个指向下一个链表的指针。

typedef struct
{
    int data;
    Node *p;
}Node;

                上面的例子中,假如开头我们不定义Node的结构体类型名称的话,会导致编译错误,原因在于程序在编译是顺时编译的,当程序试图编译Node这一个类型定义过后的名称时,因为还为编译到后续Node的位置,程序会无法识别这个类型名到底是什么。

                结构体类型的初始化大致跟数组差不多,拿上面的Node类型结构体初始化举例,例子如下:

Node    data={4,NULL};

                这样就能初始化这样一个Node类型的结构体变量data。

                实际上课程主要讨论的不是这一个问题,因为这些问题大多是之前讲到过的,这里只是略作复习。而主要学习到的新知识,主要是关于结构体类型的大小的,思考下面两个结构体类型,这两个结构体类型在内存中所占的字节大小分别为多少?

struct S1
{
    char a;
    char b;
    int c;
};

struct S2
{
    char a;
    int c;
    char b;
};

                仔细看看,会发现这S1和S2这两个结构体类型好像成员是一模一样的,只是成员的先后顺序有差别,那么实际结果如何呢?假如我们这是一个32位的操作系统,对应编译器的“对齐数”为8,那么S1这一类型结构体所占的字节数为8个字节,而S2这一类型结构体所占的字节数为12个字节。那么为什么会出现这样的情况呢?

                实际上结构体类型所占的内存空间大小存在一套单独的计算规则,这个规则我们称之为“内存对齐”,而对应内存对齐,还有一个新的概念,这个概念叫对齐数,在这里我们可以不需要知道太多,只需要知道对齐数是因实现不同而不同的,比如有的编译器默认对齐数是8,有的编译器默认对齐数是4,而有的编译器对齐数则没有,这里的对齐数实际作用是用来做比较,并依据对齐数来进行内存对齐的。

                以上面的两个例子为例,下面剖析一下为什么两个结构体类型分别占8个和12个字节。(假定是32位的操作系统,并且编译器的默认对齐数为8)

                简单说一下规则:

                以第一个元素的地址为起点,这时这里的内存偏移量为0,也就是说以这个char类型变量的地址为起点,将起点位置视为0。

                规则一:结构体成员变量要对齐到对应各自对齐数的整数倍地址处。

                规则二:成员各自对齐数=编译器默认对齐数与结构体成员类型所占字节大小的较小值

                规则三:整个结构体所占的字节数为所有结构体成员中对齐数最大值的整数倍。

                规则四:嵌套结构体的情况,嵌套的结构体成员要对齐到自己的最大对齐数整数倍位置,结构体的大小为所有对齐数,包括嵌套的结构体最大对齐数的整数倍。

                S1的结构体类型,第一个结构体成为是char类型,我们知道char类型在内存内所占的字节数为1,而它的对齐数为1(1与8比较,1更小),当前符合要求,所以我们看下一个,下一个变量b也是char类型的,它的对齐数也为1,内存内放置变量b的内存位置,对应偏移量为1,是对应对齐数的倍数,符合要求,所以继续对应下一个,这时内存地址偏移为0的地方放了变量a,内存偏移为1的地方,放了变量b。下一个变量c的类型是int类型,int类型占4个字节,那么对应的对齐数为4(4与8比较,4更小),这时为满足第二条规则,本身插入在偏移量为2位置的c,我们应该移动到偏移量为4的位置才将int类型的数据放入。至此我们应该占据了总共8个字节,我们还要满足第三条规则,当前S1结构体内的各个变量对齐数分别为1,1,4,这里我们发现4是最大的对齐数,我们整个结构体所占的字节数量(最后一个存储单元对应偏移量)要为这个对齐数的倍数,8是4的整数倍,符合要求,而这个结构体内也没有嵌套结构体,所以S1所占的字节数为8个字节。下面是刚刚分析过程中的图纸,橙色为变量a所占字节,蓝色为变量b所占自己,紫色为变量c所占字节,右侧数字为偏移量。

                 接下来分析S2结构体为什么占12个字节,长话短说,我们先直接把a,b,c的对齐数指出,跟S1一样,也是1,1,4,唯一的区别在于c移动到了a和b之间,这会导致最终结果有所差异,首先给变量a分配了一个内存单元,然后直接要给变量c分配内存,这时放c的位置是偏移量为1的地方,1不是变量c对齐数4的整数倍,所以需要先将位置移动到偏移量为4的地方,再将放变量c放进去,同时C占了4个字节后,再在偏移量为8的位置插入变量b,因为8是1的整数倍,所以可以顺利插入,这时三个变量占据的字节为9个字节。我们知道当前结构体内最大的对齐数为4,而我们占了9个字节,9不是4的整数倍,所以为了满足第三条规则,我们需要额外多占3个位置,将偏移量偏移至12,所以最后整个结构体所占字节为12个字节。图示如下,两个绿色箭头表示这个结构体所占的内存单元区间。

                 到这一步,差不多就算是把结构体内存对齐的问题搞明白了,那么为什么要有内存对齐这个事情呢?其实主要出于两个方面考虑,其一是计算机读取数据是不能随意读取任意地址的数据的,一般来说会特定一次性读4个8个16个字节等,如果不进行内存对齐,容易导致数据多次读取,比如在0到12的内存区间内存一个char和一个double类型的数据,而计算机一次性只能读8个字节,假如不进行对齐,那么double有一部分数据会在偏移量为9的位置存放,而计算机读取0~8的内存区间,不能一次性读出这个double变量的值,需要读取两次数据才行。而假如有内存对齐,那么double会直接存放到8~16内存区间内,计算机可以通过一次读取,就能知道数值。所以这是一种用空间换取计算机效率的方法。

                知道上面原理以后,其实我们不难方法,为了减少结构体占据的空间大小,声明一个结构体时可以尽量把对齐数小的数据往上放,并且放到一起,这样可以减少结构体的内存空间浪费。

                最后关于结构体,我们这里还给出了一个可以修改系统默认对齐数的预处理指令,#pragma(),使用上跟宏相似,我们只需要在括号内填入我们想要设置的默认对齐数值即可,当括号内不填的时候,则表示按照默认对齐数。

                 最后的最后的最后,还是要强调一点,结构体一般体积不小,为了减少空间浪费,进行函数传参普遍来说会进行传址调用。

                ②位段

                位段的声明跟结构体类型的声明很相似,主要区别体现在成员的声明和成员的类型上。

位段的成员只能为int、unsigned int、signed int以及字符。位段成员名的声明后面会跟一个冒号以及它所占的二进制位数。

                通过上面的话,敏锐的人可能已经发现了,声明后面会标注这个变量所占的二进制位数,换句话说,其实位段就是我们自定义了对应类型所占的二进制位数空间大小。下面给出一个位段的声明:

struct S1
{
 char a:1;
 char b:2;
 char c:3;
 char d:4;
};

                这里面,我们声明了变量为只能占据一个二进制位,b占据两个二进制位,c占据3个二进制位,d占据4个二进制位。那么最后这个S1位段会占据多少字节呢?这里直接给出结果,会占据2个字节。其他的情况下,这个所占字节多少会因实现有所不同,在下面几点原因之下,导致位段的跨平台实际上存在问题。(位段也有类似于对齐数的东西,但这东西因实现不同,我们接下来默认按4个字节考虑)

                原因一:int会被作为有符号还是无符号数会因平台的不同而不同。

                原因二:位段中某个变量类型最大的字节数不能确定,比如16位的系统,int占2个字节,而我们位段规定他占4个字节,这样会出现问题。

                原因三:每一个内存空间内,位段是从左开始分配还是从右开始分配,这是未定义的。

                原因四:当一个内存空间无法同时放下较小成员a和较大成员b时,开辟新的空间后,是把b成员一部分数据放原空间,另一部分放新空间,亦或者是直接把b成员全都放新空间呢?这也是未定义的。

                虽然可以通过上面看到位段的跨平台性不太好,但实际上位段也有必然需要使用的场景,最经典的就是网络数据的传输,普遍是需要用位段来表现各个部分的值。

                ③枚举类型

                枚举类型没啥好讲的,问就是方便,声明以后可以直接用各种带有实际意义的数字代替一串连续的数字,类似于define,但跟define相比又具有编译的检查性,枚举产生的变量是按一种常量考虑的,当出现问题会在编译阶段发现。而define宏的方式,则是直接将程序内的东西进行替换在预处理阶段就给替换完了。

                ④联合体(共用体)

                最后简单说一下联合体,联合体(共用体)声明的关键字是union,除了关键字不同,其他声明的形式与结构体完全一样,这里就不赘述了,联合体跟结构体的差别在于,联合体的成员是共用内存空间的,拿下面的联合体举例:

union S1
{
    char a;
    int b;
};

                这个联合体所占内存为4个字节,而变量a为字符类型,占据一个字节,变量b为整型,占据4个字节,讲道理为5个字节,但实际上因为a和b共用同一个内存空间,所以只用了四个字节。比如b在内存里按十六进制存了01 01 00 00,b的值为00000101,即5,而a的值只读到了末尾的01,所以a的值为1。

                联合体所占空间大小遵循以下规则:

                规则一:联合体的大小最起码是最大成员的大小

                规则二:当最大成员大小不是所有成员最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。

union S2
{
    char a[6];
    int b;
};

                看上面S2的联合体,a的最大对齐数为1(1与8比,1更小),b的最大对齐数为4(4与8比,4更小),但a占据6个字节,b占据4个字节,所以a是最大成员,b有最大对齐数,为满足规则为,整个联合体大小应该为最大对齐数4的倍数,所以联合体所占的字节为8。

                以上就是自定义类型部分的所有总结了,部分比较简单的内容可以参照以前写过的总结。

        (二)动态内存分配

                动态内存分配存在的目的主要是为了解决数组增长等问题,在C99之前,是没有变长数组的说法的,也就是说不能采用变量来定义一个数组,数组的大小必须从一开始就设定下来,这就导致程序的设计不太灵活,为了解决这个问题,当时采用的方法也就是动态内存分配,动态内存分配存在四个密切相关的函数,其实主要是两类函数,第一类是用于向内存申请空间的函数,另一类是用于释放向内存申请空间的函数,接下来会对这部分进行仔细讲解。

                ①内存申请函数malloc、calloc、realloc

                这三个函数都是用来向系统申请内存空间用的,这里要注意一下,申请的内存空间在堆区,而不像那些内部变量、直接定义的数组一样设置在栈区,堆区内就是专门用来存放这些动态申请的内存空间的。

                三个函数本质上都差不多,就是功能上略微有些差异,接下来会先给出三个函数的函数原型,然后以malloc函数为例进行讲解,calloc、realloc则只会说明其功能。

void* malloc (size_t size);
void* calloc (size_t num, size_t size);
void* realloc (void* ptr, size_t size);

                看到malloc函数的函数原型,我们可以知道这个函数的形参有一个,形参的数据类型为size_t,实际上就是无符号整型unsigned int ,这个形参表示的是我们需要问程序申请多大的内存空间用于使用,单位是字节;这个函数的返回值是一个空类型的指针,为什么是空类型不难想象,因为当我们申请一片空间的时候,这个函数的设计人是无法知道我们拿这块空间要用来做什么的,所以返回值是一个空类型的指针,后续使用使需要我们对他进行调整,假如我们需要一个40个字节的空间用于存放10个int类型的数据,可以有下面这样一个经典的形式:

int *p=(int*)malloc(10*sizeof(int));

                我们首先使用sizeof运算符运算出了一个int类型的变量小的字节大小,然后乘上我们需要存放的对应数据数量,从而得到了需要利用malloc函数向系统申请的内存空间大小,然后malloc函数返回了一个空类型的指针,为了便于我们后续使用,我们使用强制类型转换运算符,将这个空类型指针的类型强制转换为一个指向int类型变量的指针。那么后续我们使用这块空间时,系统就可以将之认为是存放int类型的空间了。

                值得注意的一点是当我们向内存申请的空间超过系统允许我们申请的空间量时,系统会返回给我们一个空指针,意味着我们这次申请失败。而当返回空指针时,我们是不知道的,所以为了避免出现返回空指针而我们不知道,导致我们后面调用空指针产生错误,建议在申请了内存空间后,可以设定相关语句检测返回的值是否为空指针,若为空指针则作出提示。

                此外,如果我们输入的实参为0,结果将是未定义的,这个结果将会取决于编译器,当然实际上这种情况也没有任何意义就是了。

                知道malloc以后,我们再说一下calloc,calloc跟malloc的区别在于,malloc只是单纯的申请一片空间,这边内存空间是不会进行初始化的,而calloc在申请了一片内存空间后,还会对这片内存空间进行初始化,还有就是形参不太一样,calloc需要输入两个形参,类型都是size_t,一个形参用于表达需要元素数量,另一个形参表示每个元素所占的字节数,如果跟上面malloc申请用于存放10个int类型的元素的内存空间一样,我们也可以用calloc表示,如下:

int *p=(int*)calloc(10,sizeof(int));

                不难发现,其实本质上没太大差别,10表示我们要10个元素,sizeof(int)表示一个int类型数据所占的字节大小,两者乘积就是需要申请的内存空间大小,但这时跟malloc最大的区别就是,这个calloc申请的内存空间内,所有内存单元内的数据均被初始化为0。忘记说了一点,calloc的返回值跟malloc也一样,所以需要强制类型转化,之后的用法就一模一样了。

                最后再说一下realloc,realloc说起来的话,功能是对原申请的内存空间进行扩容,我个人的猜测,更像是先对原内存空间进行拷贝,然后再新申请一片空间,同时把原来保存下来的数据放到这个新的空间里去。原因是当原有内存空间满了以后,对他进行扩容,若后面的连续内存空间能满足新的内存空间要求,那么就直接向后面增加,如果后面的内存空间因为存放了其他数据已经不连续,那么就会直接新开辟一个内存空间,把原有内存内的数据转移过去的同时,释放了原来申请的内存空间。这也是为什么realloc函数的返回值也是一个空类型指针的原因,因为这个函数可能会变化申请的内存空间地址(申请了新的,释放了老的)。

                realloc函数需要我们输入两个形参,第一个形参是需要扩容的,原来申请的内存空间地址,第二个数据是扩容后的内存空间大小,比方我们原来申请的内存空间大小为40个字节,现在想要再加40个字节,扩容后的总字节数为80个字节,那么第二个实参就输入80个字节。

                ②free函数

                free函数是配套上面三个函数去使用的,当我们向内存申请了内存空间后,使用完是需要释放的,如果使用完不释放,那么只要程序还在运行,这个内存空间就会始终被占用,始终被占用的结果就会导致这块内存空间无法别别人使用,这还是好的情况,如果当申请的内存空间较大,而始终不被使用,可能会导致系统的性能下降,甚至崩溃,这种忘记释放动态申请内存空间而导致内存始终被占用的情况,我们称之为内存泄露。

                free函数的函数原型如下:

void free (void* ptr);

                free函数的函数原型很简单,没有返回值,因为它的主要目的就是释放动态申请的内存,形参是一个空类型的指针,实际上这个指针指向的就是动态申请的内存空间,不难发现这个形参的类型跟malloc、calloc、realloc的返回值是一样的。

                使用free函数有那么几点需要注意,首先free函数只能释放动态申请的内存,释放其他内存的情况是未定义的,可能会产生严重后果;其次当释放动态申请的内存时,放入的内存地址必须是申请的内存空间首地址,如果是这个内存空间的其他地址,也会导致产生未知的错误;第三不能对同一块内存进行重复释放,当第一次进行free释放后,这块内存空间就已经不是动态申请的空间了,如果再对这块空间进行释放,那么就相当于对位置区域的内存空间进行释放,会产生未定义的结果;当然最后最重要的就是不要忘记释放了。

        (三)通讯录小程序

                ①主程序

#include"contact.h"

int main(void)
{
	short int choose=-1;
	Contact contact;
	Contact *(*pc_contact[VALUE_FUNCTION_CONTACT])(Contact* )=    
{0,Add_contact,Delete_contact,Search_contact,Modify_contact,
Sort_contact,Print_contact};
	
	Initialize_contact(&contact);
	do
	{
		Print_menu();
		while(scanf("%hd%*c",&choose)!=1)getchar();
		形式1:switch实现 
//		switch(choose)
//		{
//			case EXIT  :break;
//			case ADD   :Add_contact(&contact);break;
//			case DELETE:Delte_contact(&contact);break;
//			case SEARCH:Search_contact(&contact);break;
//			case MODIFY:Modify_contact(&contact);break;
//			case Sort  :Sort_contact(&contact);break;
//			case Print :Print_contact(&contact);break;
//		}
		//形式2:利用函数指针数组实现选择功能 
		if(choose>0&&choose<7)
			(void)(*pc_contact[choose])(&contact);
		else if(choose!=0)
			printf("输入值不符合要求,请重新输入!\n");
	}while(choose);
	
	printf("退出成功\n");
	
	return 0;
}

                ②头文件

#ifndef CONTACT_H
#define CONTACT_H

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

#define MAX_NAME 20
#define MAX_SEX 20
#define MAX_TEL 50
#define MAX_ADDR 100
#define THRESHOLD_CONTACT 1000
#define VALUE_FUNCTION_CONTACT ((1)+(6))

typedef struct Peomessge
{
	char name[MAX_NAME];
	char sex[MAX_SEX];
	char tel[MAX_TEL];
	char addr[MAX_ADDR];
	short int old;
}Peomessage;

typedef struct Contact
{
	Peomessage peomessage[THRESHOLD_CONTACT];
	int value_message;
}Contact;

enum Choose
{
	EXIT,ADD,DELETE,SEARCH,MODIFY,SORT,PRINT
};

enum Sort_element
{
	OUT,NAME,OLD,SEX,TEL,ADDR
};

//主函数1:初始化通讯录 
void Initialize_contact(Contact* );

//主函数2:打印选择菜单 
void Print_menu(void);

//主函数3:给通讯录输入数据
Contact *Add_contact(Contact* );

//主函数4:删除通讯录中,某一个人的数据 
Contact *Delete_contact(Contact* );

//主函数4.1:寻找通讯录中,某一个人名字所在的对应位置
int Search_point_contact(Contact* ,char* );

//主函数5:查找通讯录中,某一个人的数据并输出
Contact *Search_contact(Contact* ); 

//主函数6:修改通讯录中某一个人的数据 
Contact *Modify_contact(Contact* );

//主函数6.1:打印修改菜单
void Print_modify(void); 

//主函数7:将通讯录按某个词条排列通讯录
Contact *Sort_contact(Contact* );

//主函数7.1:打印排序规则的菜单
void Print_sort(void);

//主函数7.2:比对名字的函数,用于qsort函数
int Cmp_name(const void* ,const void* );

//主函数7.3:比对年龄的函数,用于qsort函数
int Cmp_old(const void* ,const void* );

//主函数7.4:比对性别的函数,用于qsort函数
int Cmp_sex(const void* ,const void* );

//主函数7.5:比对电话的函数,用于qsort函数
int Cmp_tel(const void* ,const void* );

//主函数7.6:比对住址的函数,用于qsort函数
int Cmp_addr(const void* ,const void* );

//主函数8: 打印当前通讯录内的所有信息
Contact *Print_contact(Contact* );

#endif

                ③通讯录功能模块函数

#include"contact.h"

//主函数1:初始化通讯录 
void Initialize_contact(Contact *p_contact)
{
	p_contact->value_message=0;
	memset(p_contact->peomessage,0,sizeof(Peomessage)*THRESHOLD_CONTACT);
}

//主函数2:打印选择菜单 
void Print_menu(void)
{
	printf("\n***********************************\n");
	printf("*         CONTACT  MENU           *\n");
	printf("*---------------------------------*\n");
	printf("*       1.Add       2.Delete      *\n");
	printf("*       3.Search    4.Modify      *\n");
	printf("*       5.Sort      6.Print       *\n");
	printf("*       0.Exit                    *\n");
	printf("*                                 *\n");
	printf("***********************************\n");
}

//主函数3:给通讯录输入数据
Contact *Add_contact(Contact *p_contact)
{	
	printf("请输入名字>>");
	while(scanf("%s%*c",
p_contact->peomessage[p_contact->value_message].name)!=1)getchar();
	printf("请输入年龄>>");
	while(scanf("%hd%*c",
&p_contact->peomessage[p_contact->value_message].old)!=1)getchar();
	printf("请输入性别>>");
	while(scanf("%s%*c",
p_contact->peomessage[p_contact->value_message].sex)!=1)getchar();
	printf("请输入电话号码>>");
	while(scanf("%s%*c",
p_contact->peomessage[p_contact->value_message].tel)!=1)getchar();
	printf("请输入住址>>");
	while(scanf("%s%*c",
p_contact->peomessage[p_contact->value_message].addr)!=1)getchar();
	printf("录入结束\n");
	p_contact->value_message++;
	
	return p_contact;
}

//主函数4:删除通讯录中,某一个人的数据 
Contact *Delete_contact(Contact *p_contact)
{
	assert(p_contact!=NULL&&p_contact->value_message>0);
	char aim_name[MAX_NAME];
	int point;
	
	printf("请输入想要删除的对象名称>>");
	while(scanf("%s%*c",aim_name)!=1)getchar();
	point=Search_point_contact(p_contact,aim_name);
	
	if(point==-1)
		printf("数据库内没有该对象的数据\n");
	else if(point==((p_contact->value_message)-1))
	{
		p_contact->value_message--;
		printf("删除成功!\n"); 
	}
	else
	{
		memmove(&(p_contact->peomessage[point]),&(p_contact->peomessage[point+1]),sizeof(Peomessage)*(p_contact->value_message-(point+1)));
		p_contact->value_message--;
		printf("删除成功!\n");
	}

	return p_contact;
}

//主函数4.1:寻找通讯录中,某一个人名字所在的对应位置
int Search_point_contact(Contact *p_contact,char *aim_name)
{
	assert(p_contact!=NULL&&aim_name!=NULL);
	int i;
	int r=-1;
	
	for(i=0;i<p_contact->value_message;i++)
	{
		if(!strcmp(p_contact->peomessage[i].name,aim_name))
		{
			r=i;
			break;
		}
	}
	
	return r;
}

//主函数5:查找通讯录中,某一个人的数据并输出
Contact *Search_contact(Contact *p_contact)
{
	assert(p_contact!=NULL);
	int m_point;
	char aim_name[MAX_NAME];
	
	printf("请输入想要查找的对象名称>>");
	while(scanf("%s%*c",aim_name)!=1)getchar();
	m_point=Search_point_contact(p_contact,aim_name);
	if(m_point>=0)
	{
		printf("对象数据如下:\n");
		printf("名称>>%s\n年龄>>%hd\n性别>>%s\n电话号码>>%s\n住址>>%s\n",
p_contact->peomessage[m_point].name,p_contact->peomessage[m_point].old,
p_contact->peomessage[m_point].sex,p_contact->peomessage[m_point].tel,
p_contact->peomessage[m_point].addr);
	}
	else
	{
		printf("数据库内没有该对象的数据\n");
	}
	
	return p_contact;
}

//主函数6:修改通讯录中某一个人的数据
Contact *Modify_contact(Contact *p_contact)
{
	assert(p_contact!=NULL);
	short int choose=-1;
	int m_point;
	char aim_name[MAX_NAME];
	
	printf("请输入想要修改的对象名称>>");
	while(scanf("%s%*c",aim_name)!=1)getchar();
	m_point=Search_point_contact(p_contact,aim_name);
	
	
	do
	{
		Print_modify();
		while(scanf("%hd%*c",&choose)!=1)getchar();
		switch(choose)
		{
			case 1:printf("请输入名称>>");
while(scanf("%s%*c",p_contact->peomessage[m_point].name)!=1)getchar();break;
			case 2:printf("请输入年龄>>");
while(scanf("%hd%*c",&p_contact->peomessage[m_point].old)!=1)getchar();break;
			case 3:printf("请输入性别>>");
while(scanf("%s%*c",p_contact->peomessage[m_point].sex)!=1)getchar();break;
			case 4:printf("请输入电话号码>>");
while(scanf("%s%*c",p_contact->peomessage[m_point].tel)!=1)getchar();break;
			case 5:printf("请输入住址>>");
while(scanf("%s%*c",p_contact->peomessage[m_point].addr)!=1)getchar();break;
			case 6:
				printf("请输入名称>>");
while(scanf("%s%*c",p_contact->peomessage[m_point].name)!=1)getchar();
				printf("请输入年龄>>");
while(scanf("%hd%*c",&p_contact->peomessage[m_point].old)!=1)getchar();
				printf("请输入性别>>");
while(scanf("%s%*c",p_contact->peomessage[m_point].sex)!=1)getchar();
				printf("请输入电话号码>>");
while(scanf("%s%*c",p_contact->peomessage[m_point].tel)!=1)getchar();
				printf("请输入住址>>");
while(scanf("%s%*c",p_contact->peomessage[m_point].addr)!=1)getchar();
				break;
			case 7:choose=0;break;
			default:printf("输入值非法,请重新输入\n");choose=1;break;
		}
	}while(choose);
	
	return p_contact;
}

//主函数6.1:打印修改菜单
void Print_modify(void)
{
	printf("\n**********************************\n");
	printf("*         MODIFY  MENU           *\n");
	printf("*--------------------------------*\n");
	printf("*        1.Name   2.Old          *\n");
	printf("*        3.Sex    4.Tel          *\n");
	printf("*        5.Addr   6.All          *\n");
	printf("*        7.end                   *\n");
	printf("**********************************\n");
}

//主函数7:将通讯录按某个词条排列通讯录
Contact *Sort_contact(Contact *p_contact)
{
	assert(p_contact!=NULL);
	short int choose=-1;
	int (*p_function[6])(const void* ,const void* )={0,Cmp_name,Cmp_old,Cmp_sex,Cmp_tel,Cmp_addr};
	do
	{
		Print_sort();
		while(scanf("%hd%*c",&choose)!=1)getchar();
		switch(choose)
		{
			case NAME:qsort(p_contact->peomessage,
p_contact->value_message,sizeof(Peomessage),
p_function[choose]);break;
			case OLD:qsort(p_contact->peomessage,
p_contact->value_message,sizeof(Peomessage),
p_function[choose]);break;
			case SEX:qsort(p_contact->peomessage,
p_contact->value_message,sizeof(Peomessage),
p_function[choose]);break;
			case TEL:qsort(p_contact->peomessage,
p_contact->value_message,sizeof(Peomessage),
p_function[choose]);break;
			case ADDR:qsort(p_contact->peomessage,
p_contact->value_message,sizeof(Peomessage),
p_function[choose]);break;
			case OUT:choose=0;break;
			default:choose=1;printf("输入值非法,请重新输入\n");break;
		}
	}while(choose);
	
	return p_contact;
}

//主函数7.1:打印排序规则的菜单
void Print_sort(void)
{
	printf("\n**********************************\n");
	printf("*          SORT  MENU            *\n");
	printf("*--------------------------------*\n");
	printf("*        1.Name   2.Old          *\n");
	printf("*        3.Sex    4.Tel          *\n");
	printf("*        5.Addr   0.End          *\n");
	printf("**********************************\n");	
}

//主函数7.2:比对名字的函数,用于qsort函数
int Cmp_name(const void *p_pm1,const void *p_pm2)
{
	int r=0;
	
	r=strcmp(((Peomessage*)p_pm1)->name,((Peomessage*)p_pm2)->name);
	
	return r;
}

//主函数7.3:比对年龄的函数,用于qsort函数
int Cmp_old(const void *p_pm1,const void *p_pm2)
{
	int r;
	
	if(((Peomessage*)p_pm1)->old>((Peomessage*)p_pm2)->old)
		r=1;
	else if(((Peomessage*)p_pm1)->old==((Peomessage*)p_pm2)->old)
		r=0;
	else
		r=-1;
		
	return r;
}

//主函数7.4:比对性别的函数,用于qsort函数
int Cmp_sex(const void *p_pm1,const void *p_pm2)
{
	int r=0;
	
	r=strcmp(((Peomessage*)p_pm1)->sex,((Peomessage*)p_pm2)->sex);
	
	return r;
}

//主函数7.5:比对电话的函数,用于qsort函数
int Cmp_tel(const void *p_pm1,const void *p_pm2)
{
	int r=0;
	
	r=strcmp(((Peomessage*)p_pm1)->tel,((Peomessage*)p_pm2)->tel);
	
	return r;
}

//主函数7.6:比对住址的函数,用于qsort函数
int Cmp_addr(const void *p_pm1,const void *p_pm2)
{
	int r=0;
	
	r=strcmp(((Peomessage*)p_pm1)->addr,((Peomessage*)p_pm2)->addr);
	
	return r;
}

//主函数8: 打印当前通讯录内的所有信息
Contact *Print_contact(Contact *p_contact)
{
	int counter=0;
	
	printf("|姓名      |年龄      |性别      |电话号码            |家庭住址            |\n");
	while(counter<p_contact->value_message)
	{
		printf("|%-10s|%-10hd|%-10s|%-20s|%-20s|\n",
p_contact->peomessage[counter].name,
p_contact->peomessage[counter].old,
p_contact->peomessage[counter].sex,
p_contact->peomessage[counter].tel,
p_contact->peomessage[counter].addr);
		counter++;
	}
	
	return p_contact;
}
 

         (四)总结

        以上,就是这几天来的全部学习成果了,结果还算是满意,不过因为最近本职工作属实有点忙,另外家里和个人情感问题上都出了点问题,所以我觉得以后可能总结不会那么频繁,但是学习内容是不会漏的。给自己打个气,加个油,坚持下去!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值