编程思想:编程时先搭好框架,比较难处理的部分用汉字表示,以后慢慢一步一步进行处理
空链的定义#include<stdio.h> typedef struct POINT{
//此为不完全赋初值,指针被赋值为NULL。若不赋值,则head1中的三个成员是有值的,其值为垃圾数据 |
输出链表中的错误void showPoints(POINT head){
printf("\n输入的点坐标如下:\n"); 2)for( ; ;) 3)格式问题 |
销毁链表中的错误void destroyPointLink(POINT *head){
//正确形式
/* //错误形式2: //错误形式2分析:p = head->next; head->next = p->next; 这两条语句可以实现p的移位,所以不需要for循环进行移位
/* //错误形式1: |
错误形式1分析如下: 一般人的思维都是从后往前释放,则: 1)第一次循环时,找到末节点,释放。灰色表示已经释放,但是释放之后,p节点的前一个节点的链域值并未受到任何改变,依然指向一个已经释放的空间 |
正确思路分析如下:
正确形式为从前往后释放,理解如上,终止条件理解如下: |
插入链表中的分析void insertPoint(POINT *head){
POINT *p; //需要插入的新点
//1.输入新点坐标(newPoint) p = (POINT *)malloc(sizeof(POINT)); //malloc()一定要记得加头文件
//2.输入指定点坐标(oldPoint)
//3.找与oldPoint的row和col相同的前驱节点
//4.完成插入
//自己代码
//老师代码(代码优化)
插入时分析如下: |
排序时的分析void sortByRow_add(POINT *head){ printf("\n下面进行排序(按行坐标升序排序)操作:"); for(pre = head->next; pre; pre = pre->next){ 问题:为什么排序时还要继续交换指针域? 解答:见博客 https://blog.csdn.net/tennysonsky/article/details/51076340 非常详细
********************************************************************************************************************************* 下面这个问题和本题无关,只是记录在此而已: 问题:如何在不引入tmp节点的情况下进行两个节点的交换? (1)两个节点相邻 (2)两个节点不相邻 找不见了,嘤嘤嘤,有机会再碰吧。。。 |
带头结点链表大例题
//带头结点链表大例题
//用链表实现对屏幕上点的管理,并用菜单的形式进行显示
//1.录入功能:用户从键盘输入若干个点坐标信息,用链表形式存储,若用户输入的坐标中,任意一个为0,则,结束输入。
//2.显示功能
//3.插入功能
//4.删除
//5.查找
//6.排序
//7.菜单
/*
1 2
4 6
78 34
56 89
3 9
0 4
*/
#include<stdio.h>
#include<malloc.h>
#include"MEC_menu.h" //正确形式
//#include<MEC_menu.h> //错误形式
/*
1.头文件#include <> :
表示引用标准库头文件,编译器会从系统配置的库环境中去寻找
2.头文件#include "":
一般表示用户自己定义使用的头文件,因为这些文件放在工程目录(也就是编译器的当前目录)下,而不是放在公共头文件目录下。
编译器默认会从当前文件夹中寻找,如果找不到,则到系统默认库环境中去寻找。如果用<>则找不到头文件。
不过保险的话,用""肯定可以找到所有头文件,包括系统库函数头文件和自己定义的头文件
*/
#include<conio.h>
typedef struct POINT{
int row;
int col;
struct POINT *next;
} POINT;
typedef unsigned char boolean;
#define TRUE 1
#define FALSE 0
#define EXIT_SYSTEM 5
void initPointLink(POINT *head);
void showPoints(POINT head); //显示所有点坐标
void showPoint(POINT node); //显示一个点坐标
void destroyPointLink(POINT *head);
POINT *findPrePoint(POINT head, POINT oldPoint);
void insertPoint(POINT *head);
void deletePoint(POINT *head);
void sortByRow_add(POINT *head);
void doFunction(int functionIndex, POINT *head);
void doInitPointLink(POINT *head);
void doInsertPoint(POINT *head);
void doDeletePoint(POINT *head);
void doShowPoints(POINT head);
void doSortByRow_add(POINT *head);
void doSortByRow_add(POINT *head){
char *title = "[坐标点按行值升序排列]";
printf("按任意键进行排序:");
getch();
system("cls");
printf("\n");
showStringAlignCenter(title);
sortByRow_add(head);
printf("\n排序后已经存在的点有:");
showPoints(*head);
}
void doShowPoints(POINT head){
char *title = "[显示坐标点功能]";
printf("按任意键进行显示:");
getch();
system("cls");
printf("\n");
showStringAlignCenter(title);
showPoints(head);
}
void doDeletePoint(POINT *head){
char *title = "[删除节点功能]";
printf("按任意键进行插入:");
getch();
system("cls");
printf("\n");
showStringAlignCenter(title);
deletePoint(head);
printf("按任意键继续......\n");
getch();
printf("删除后已经存在的点有:");
showPoints(*head);
}
void doInsertPoint(POINT *head){
char *title = "[插入节点功能]";
printf("按任意键进行插入:");
getch();
system("cls");
printf("\n");
showStringAlignCenter(title);
insertPoint(head);
printf("按任意键继续......\n");
getch();
printf("\n插入后已经存在的点有:");
showPoints(*head);
}
void doInitPointLink(POINT *head){
char *title = "[录入节点功能]";
printf("按任意键进行录入:");
getch();
system("cls");
printf("\n");
showStringAlignCenter(title);
initPointLink(head);
printf("按任意键继续......\n");
getch();
printf("\n录入后已经存在的点有:");
showPoints(*head);
}
void doFunction(int functionIndex, POINT *head){
switch(functionIndex){
case 0: doInitPointLink(head); break;
case 1: doInsertPoint(head); break;
case 2: doDeletePoint(head); break;
case 3: doShowPoints(*head); break;
case 4: doSortByRow_add(head); break;
}
}
void sortByRow_add(POINT *head){
POINT *pre;
POINT *cur;
POINT tmp;
printf("\n下面进行排序(按行坐标升序排序)操作:");
printf("\n目前已经存在的节点有:");
showPoints(*head);
for(pre = head->next; pre; pre = pre->next){
for(cur = pre->next; cur; cur = cur->next){
if(pre->row > cur->row){
//交换整个节点(但是最终只有数据域正确交换,指针域对应不正确
tmp = *pre;
*pre = *cur;
*cur = tmp;
//交换指针域
tmp.next = pre->next;
pre->next = cur->next;
cur->next = tmp.next;
}
}
}
}
void deletePoint(POINT *head){
/*
1.让用户输入要删除的点
2.找到要删除点的下标
3.进行指针的指向转换,删除该点
*/
POINT *p; //p指向要删除的节点
POINT *prePoint; //要删除的点的前驱节点
POINT oldPoint;
printf("\n下面进行删除操作:");
printf("\n目前已经存在的节点有:");
showPoints(*head);
printf("\n请输入要删除的点的坐标(例如:3 4),任意一个输入为0表示结束:");
scanf("%d%d", &oldPoint.row, &oldPoint.col);
prePoint = findPrePoint(*head, oldPoint);
p = prePoint->next;
/*
findPrePoint函数返回值有三种情况(假设返回值赋值给m):
1. m == NULL; 用户指定的点是第一个有效节点
2. m != NULL && m->next == NULL; 说明m指向末节点,表明用户所指定的点不存在;
3. m != NULL && m->next != NULL; 说明用户指定的点不但存在,而且不是第一个有效节点
*/
if(p == NULL){
head->next = p->next;
printf("操作成功,指定点已删除\n");
}else if(p->next == NULL){
printf("操作失败,指定点不存在\n");
}else{
prePoint->next = p->next;
printf("操作成功,指定点已删除\n");
}
}
void insertPoint(POINT *head){
/*
1.输入新点坐标
2.输入指定点坐标
3.找与oldPoint的row和col相同的前驱节点
4.完成插入
*/
POINT *p; //需要插入的新点
POINT oldPoint; //需要插入新点的位置节点
int row;
int col;
POINT *q; //q表示oldPoint的前驱节点
printf("\n下面进行插入操作:");
printf("\n目前已经存在的节点有:");
showPoints(*head);
//1.输入新点坐标(newPoint)
printf("\n请输入一个新点的坐标(例如:3 4),任意一个输入为0表示结束:");
scanf("%d%d", &row, &col);
p = (POINT *)malloc(sizeof(POINT)); //malloc()一定要记得加头文件
p->row = row;
p->col = col;
p->next = NULL;
//2.输入指定点坐标(oldPoint)
printf("请输入指定插入的点(进行左插入,若指定点不存在,则新点追加到末尾):");
scanf("%d%d", &oldPoint.row, &oldPoint.col);
//3.找与oldPoint的row和col相同的前驱节点
q = findPrePoint(*head, oldPoint);
//1. q == NULL; 用户指定的点是第一个有效节点
//2. q != NULL && q->next == NULL; 说明m指向末节点,表明用户所指定的点不存在;
//3. q != NULL && q->next != NULL; 说明用户指定的点不但存在,而且不是第一个有效节点
//4.完成插入
//自己代码
if(q == NULL){
p->next = head->next;
head->next = p;
}else if(q->next == NULL){
q->next = p;
}else{
p->next = q->next;
q->next = p;
}
//老师代码(代码优化)
/*
if(q = NULL){
q = head;
}
p->next = q->next;
q->next = p;
*/
}
POINT *findPrePoint(POINT head, POINT oldPoint){
POINT *p;
POINT *prePoint = NULL; //此处一定要赋值
for(p = head.next; p && ((p->row != oldPoint.row) || p->col != oldPoint.col); p = p->next){
prePoint = p;
}
//for()循环进行的条件是:1)没找完 ( (p->row != oldPoint.row) || (p->col != oldPoint.col) )
// 2)没找到 p
//理解如下:当p指向最后一个有效节点的时候,prePoint指向倒数第二个有效节点,
// 此时p的值不为0,满足条件继续执行,这时prePoint指向最后一个有效节点,且p的值被赋值为0,跳出循环
// 所以,循环结束之后,prePoint指向最后一个有效节点,而p的值为0
//短路运算:当指针p为0时,则不执行后面的语句,直接短路
//(若是执行后面的语句,相当于对内存中最前面的空间进行访问,这是不允许的,是非法内存访问)
//所以,p与后面的内容是不能交换顺序的
return prePoint;
/*
该函数返回值有三种情况(假设返回值赋值给m):
1. m == NULL; 用户指定的点是第一个有效节点
2. m != NULL && m->next == NULL; 说明m指向末节点,表明用户所指定的点不存在;
3. m != NULL && m->next != NULL; 说明用户指定的点不但存在,而且不是第一个有效节点
*/
}
void destroyPointLink(POINT *head){
POINT *p;
while(head->next){
p = head->next; //正确形式
head->next = p->next;
free(p);
}
/* //错误形式2:p = head->next; head->next = p->next; 这两条语句可以实现p的移位,所以不需要for循环进行移位
while(head->next){
for(p = head->next; p->next; p = p->next){
head->next = p->next;
free(p);
}
*/
/* //错误形式1:
while(head->next){
for(p = head->next; p->next; p = p->next)
;
free(p);
}
//灰色表示已经释放,但是释放之后,p节点的前一个节点的链域值并未受到任何改变,依然指向一个已经释放的空间
//当下一个循环,从第一个节点开始,遍历到好像应该是最后一个节点(灰色前面的那个节点),它的链域值不为NULL,
//还要继续向下移动,这样p指向一个已经释放的空间,指向没有关系,但是要进行访问就不可以了,这就是非法内存访问。
*/
}
void showPoint(POINT node){
printf("(%d %d)", node.row, node.col);
}
void showPoints(POINT head){
POINT *p;
//for(p = head.next; p->next; p = p->next){ //错误形式 //分析:若终止条件写成p->next,则无法输出最后一个点
for(p = head.next; p; p = p->next){ //正确形式 //小错误:for(p = head.next, p, p = p->next){
if(p == head.next){ //只是为了让格式更好看
showPoint(*p);
}else{
printf(","); //只是为了让格式更好看
showPoint(*p);
}
}
printf("\n");
printf("按任意键继续......\n");
getch();
}
void initPointLink(POINT *head){
int row;
int col;
POINT *p;
POINT *q; //p指针始终指向末节点
printf("请输入一个点的坐标(例如:3 4),任意一个输入为0表示结束:");
scanf("%d%d", &row, &col);
//此处要考虑这个判断语句是一次执行还是多次执行,若是一次执行,则用if语句,若是多次执行,则用while语句
while(row && col){
//小错误:while(row & col){
//&是一个位运算符,就是将两个二进制的数逐位相与,就是都是1才是1,只要有一个为0则为0,结果是相与之后的结果。
//&&是一个逻辑运算符,就是判断两个表达式的真假性,只有两个表达式同时为真才为真,有一个为假则为假,具有短路性质。
//处理一个点坐标
p = (POINT *)malloc(sizeof(POINT)); //malloc()一定要记得加头文件
p->row = row;
p->col = col;
p->next = NULL;
for(q = head; q->next; q = q->next)
;
if(head->next == NULL){ //第一个有效节点
head->next = p;
}else{ //非第一个有效节点
q->next = p;
}
q = p;
printf("请输入一个点的坐标(例如:3 4),任意一个输入为0表示结束:");
scanf("%d%d", &row, &col);
}
}
void main(void){
POINT head1 = {0}; //此为不完全赋初值,指针被赋值为NULL。若不赋值,则head1中的三个成员是有值的,其值为垃圾数据
//此链为一个空链(不关心数据域,只要指针域为空即可)
int choiceIndex = 0;
while(choiceIndex != EXIT_SYSTEM){
showMenu();
choiceIndex = getUserChoose(menu, itemCount());
if(choiceIndex < 0){
printf("无效的输入");
}else{
printf("您选择了:%s\n", menu[choiceIndex].item);
doFunction(choiceIndex, &head1);
}
}
destroyPointLink(&head1);
}
//测试各个子函数
/*
initPointLink(&head1); //说明结构体类型的变量不是指针
showPoints(head1);
insertPoint(&head1); //传值还是传址,主要看需不需要更改head1的值
//不能说需要或是不需要,这样不严谨,应该说可能需要更改
//若是新插入的节点插在了第一个有效节点之前。则需要更改头结点的指向,即需要更改头结点的值
//若是新插入的节点不插在第一个有效节点指点,则不需要更改头结点的指向,即不需要更改头结点的值
//既然是可能更改,就一定要考虑到,故传址
showPoints(head1); //进行程序验证,必须考虑所有情况
deletePoint(&head1);
showPoints(head1);
sortByRow_add(&head1);
showPoints(head1);
destroyPointLink(&head1);
//showPoints(head1);
*/
菜单头文件
#include<stdio.h>
#include<conio.h> //getch()函数
#include<stdlib.h> //system()函数 standard library c语言标准库
#include<string.h> //strlen()函数
#include <ctype.h> //isalpha() toupper()函数
#define TRUE 1
#define FALSE 0
typedef unsigned char boolean; //boolean表示逻辑值
typedef struct MEC_MENU{
char *item;
boolean beShow;
char hotKey;
}MEC_MENU;
const char *Title = "微易码屏幕点信息管理小系统"; //const 意思是只能对其内容进行读,不能写
//意思是Title这个指针只能指向"微易码屏幕点信息管理小系统"这个字符串
MEC_MENU menu[] = { //为什么不 写个数 ???? //menu这个为全局的数组
// item beShow hotKey //哇,好整齐
"A)信息录入", TRUE, 'A',
"B)在指定点左侧插入新点", TRUE, 'B',
"C)删除指定点信息", TRUE, 'C',
"D)显示所有点信息", TRUE, 'D',
"E)按行坐标升序排序", TRUE, 'E',
"X)退出", TRUE, 'X',
};
//这两个宏是为了检测用户的输入是否规则,是为了函数isHotkey服务的
//isHotkey()的功能为:若输入查找到了,则输出下标,输入为查找到,根据情况输出下述两种值
#define NOT_HOTKEY -1 //此处为什么不给一个0,因为有可能查找到的值的下标为0,不合适
#define ITEM_IS_HIDDEN -2
#define itemCount() (sizeof(menu) / sizeof(MEC_MENU)) //计算item的个数
void showMenu(); //参数需要munu[]这个数组,但是这个是全局的,没有必要通过参数传递,所以不需要参数了
void showStringAlignCenter(const char *string); //为了横向居中,因为纵向居中比较容易,横向比较困难
int getMaxLengthOfItems(MEC_MENU *menu,int count); //菜单显示横向居中要以最长字符串为标准,此函数为了获得最大字符串的长度
//count的意思:由于要比较每一个item的长度,故要对每个item进行遍历,count为item的个数
//此处不能直接写那个itemCount(),因为它里面的那个menu和此函数的局部变量重合
int getUserChoose(MEC_MENU *menu, int itemCount);
int isHotkey(MEC_MENU *menu, int itemCount, int choose);
int isHotkey(MEC_MENU *menu, int itemCount, int choose){
int index = 0;
while((index < itemCount) && (menu[index].hotKey != choose)) //循环条件:没找完 && 没找到
index++;
if(index >= itemCount){
index = NOT_HOTKEY;
}
return index;
}
int getUserChoose(MEC_MENU *menu, int itemCount){
int choose;
int index;
printf("\n\n请输入一个字母进行选择(不区分大小写):");
choose = getche(); //接收一个字符,有回显
if(isalpha(choose))
choose = toupper(choose);
index = isHotkey(menu, itemCount, choose); //正确形式
// index = isHotkey(menu, itemCount(), choose); //错误形式 自己理解:itemCount()里面的menu是全局变量,和这个函数里面的局部变量重了
printf("\n");
return index;
//isalpha
//功能:判断字符c是否为英文字母
//说明:若为英文字母,返回非0(小写字母为2,大写字母为1)。若不是字母,返回0
//toupper
//功能:将字符c转换为大写英文字母
//说明:如果c为小写英文字母,则返回对应的大写字母;否则返回原来的值。
}
int getMaxLengthOfItems(MEC_MENU *menu,int count){
int i;
int maxLen = strlen(menu[0].item);
for(i = 0; i < count; i++){
// if(strlen(menu[i].item) > (unsigned int)maxLen){ //计算每个item的长度并找出其最大长度
// maxLen = strlen(menu[i].item);
if(strlen((menu+i)->item) > (unsigned int)maxLen){
maxLen = strlen((menu+i)->item);
}
//经验证,两种方式都正确 老师都是用数组形式
//(menu+i)->item <=> menu[i].item
//类似例子见博客 https://blog.csdn.net/weixin_42072280/article/details/82634114
}
return maxLen;
}
void showStringAlignCenter(const char *string){ //为什么要加const 因为下文此函数的实参有一次为Title(const char *)类型
int leftBlankCount;
int i;
leftBlankCount = (80 - strlen(string)) / 2;
for(i = 0; i < leftBlankCount; i++){
printf(" ");
}
printf("%s\n", string);
}
void showMenu(){
int i, j;
int maxLen;
system("CLS"); //DOS命令 清屏操作
//printf("%s\n", Title); //标题横向不居中形式
//此处输出为何直接写指针? 见https://blog.csdn.net/weixin_42072280/article/details/82817691
showStringAlignCenter(Title); //标题横向居中形式
for(i = 0; i < (25-1-itemCount())/2; i++){ //为了在标题和目录中间插入空白行
printf("\n");
}
//目录横向不居中形式
/*
for(i = 0; i < itemCount(); i++) //由于要进行动态显示,故此处条件不能直接写数值,故而用sizeof计算出个数
//#define itemCount (sizeof(menu) / sizeof(MEC_MENU))
{
printf("%s\n", menu[i].item);
}
*/
//目录横向居中形式
maxLen = getMaxLengthOfItems(menu,itemCount());
for(i =0; i < itemCount(); i++){
for(j = 0; j < (80 - maxLen)/2; j++){
printf(" ");
}
printf("%s\n", menu[i].item);
}
}