在数据结构中,我们已经学习到了简单的静态链表以及单链表和双链表,它们各有优缺点,但是有个共同的问题是他们呢无法存储不同的数据。下面提供了一种方法,可以将不同节点的数据链接起来。

下面的代码都是基础的C语言代码,涉及到的知识点基本覆盖到C语言学习的所有知识面,尤其是使用了宏,减少了重复的代码。

无论是C语言大佬还是C语言萌新,都可以食用此链表,理解起来可能很复杂,以下是源码,建议好好理解。

Main.c

#include "stdafx.h"
#include"Teacher.h"
#include "Student.h"
#include"MainCmd.h" int main()
{
CALL_PROC(Main, MainCmd) return 0;
}

Stdafx.h

#pragma once
#include<iostream>
using namespace std;

Teacher.h

#pragma once
#include"List.h"
#include"CmdMap.h" DECLEAR_PROC(Teacher) struct TEACHER
{
LINKER linker; char szName[20];
char szAddr[20];
int nAge;
}; extern LINKER* g_pTeacher; LINKER* CreateTeacher();
void InputTeacher(LINKER* pLink);
void OutputTeacher(const LINKER* pLink);
LINKER* FindTeacher(LINKER* pHead); void CreateTeacherList();
void AddTeacher();
void DeleteTeacher();
void FindTeacherNode();
void InsertTeacher();
void ShowTeacherList();
void ClearTeacherList();

Teacher.cpp

#include"Teacher.h"
#include"Student.h"
#include"stdafx.h"
#include"CmdFun.h" FUN_TABLE g_TeacherTable = { CreateTeacher,&InputTeacher,&OutputTeacher,&FindTeacher }; LINKER* CreateTeacher()
{
char szName[12] = "";
cout << "请输入创建的结点类型:(Student Or Teacher)";
cin >> szName;
if (!strcmp(szName, "Student"))
{
STUDENT* pNew = new STUDENT;
if (NULL != pNew)
{
pNew->linker.pFunTab = &g_StudentTable;
pNew->linker.pNext = NULL;
pNew->linker.pPrev = NULL;
}
return (LINKER*)pNew;
}
if (!strcmp(szName, "Teacher"))
{
TEACHER* pNew = new TEACHER;
if (NULL != pNew)
{
pNew->linker.pFunTab = &g_TeacherTable;
pNew->linker.pNext = NULL;
pNew->linker.pPrev = NULL;
}
return (LINKER*)pNew;
} return NULL;
} void InputTeacher(LINKER* pLink)
{
TEACHER* pTemp = (TEACHER*)pLink;
cout << "请输入姓名:";
cin >> pTemp->szName;
cout << "请输入住址:";
cin >> pTemp->szAddr;
cout << "请输入年龄:";
cin >> pTemp->nAge; } void OutputTeacher(const LINKER* pLink)
{
TEACHER* pTemp = (TEACHER*)pLink;
cout << "***********************" << endl;
cout << "姓名:" << pTemp->szName << endl;
cout << "住址:" << pTemp->szAddr << endl;
cout << "年龄:" << pTemp->nAge << endl;
} LINKER* FindTeacher(LINKER* pHead)
{
TEACHER* pTemp = (TEACHER*)pHead; cout << "请输入要查找的姓名:";
char szBuf[20];
cin >> szBuf; while (NULL != pTemp->linker.pNext)
{
if (0 == strcmp(szBuf, pTemp->szName))
{
return (LINKER*)pTemp;
}
pTemp = (TEACHER*)pTemp->linker.pNext;
} return NULL;
} CMD_MAP_BEGIN(Teacher)
CMD(Create, 创建, CreateTeacherList)
CMD(Add, 添加, AddTeacher)
CMD(Delete, 删除, DeleteTeacher)
CMD(Find, 查找, FindTeacherNode)
CMD(Insert, 插入, InsertTeacher)
CMD(Show, 创建, ShowTeacherList)
CMD(Clear, 清空, ClearTeacherList)
CMD(Back, 返回, NULL)
CMD_MAP_END() LINKER* g_pTeacher = NULL; CmdFun(Teacher)

Student.h

#pragma once
#include"List.h"
#include"CmdMap.h" DECLEAR_PROC(Student) struct STUDENT
{
LINKER linker; char szID[20];
char chSex;
int nGrade;
}; struct STAFF
{
LINKER linker; char szID[20];
char chSex;
int nGrade;
}; LINKER* CreateStudent();
void InputStudent(LINKER* pLink);
void OutputStudent(const LINKER* pLink);
LINKER* FindStudent(LINKER* pHead); void CreateStudentList();
void AddStudent();
void DeleteStudent();
void FindStudentNode();
void InsertStudent();
void ShowStudentList();
void ClearStudentList();

Student.cpp

#include"Student.h"
#include"Teacher.h"
#include"stdafx.h"
#include"CmdFun.h" FUN_TABLE g_StudentTable = { &CreateStudent,&InputStudent,&OutputStudent,&FindStudent }; LINKER* CreateStudent()
{
char szName[12] = "";
cout << "请输入创建的结点类型:(Student Or Teacher)";
cin >> szName;
if (!strcmp(szName, "Student"))
{
STUDENT* pNew = new STUDENT;
if (NULL != pNew)
{
pNew->linker.pFunTab = &g_StudentTable;
pNew->linker.pNext = NULL;
pNew->linker.pPrev = NULL;
}
return (LINKER*)pNew;
}
if (!strcmp(szName, "Teacher"))
{
TEACHER* pNew = new TEACHER;
if (NULL != pNew)
{
pNew->linker.pFunTab = &g_TeacherTable;
pNew->linker.pNext = NULL;
pNew->linker.pPrev = NULL;
}
return (LINKER*)pNew;
} return NULL;
} void InputStudent(LINKER* pLink)
{
STUDENT* pTemp = (STUDENT*)pLink;
cout << "请输入ID:";
cin >> pTemp->szID;
cout << "请输入性别:";
cin >> pTemp->chSex;
cout << "请输入分数:";
cin >> pTemp->nGrade; } void OutputStudent(const LINKER* pLink)
{
STUDENT* pTemp = (STUDENT*)pLink;
cout << "***********************" << endl;
cout << "ID:" << pTemp->szID << endl;
cout << "性别:" << pTemp->chSex << endl;
cout << "分数:" << pTemp->nGrade << endl;
} LINKER* FindStudent(LINKER* pHead)
{
if (NULL == pHead)
{
return NULL;
} STUDENT* pTemp = (STUDENT*)pHead; cout << "请输入要查找的ID:";
char szBuf[20];
cin >> szBuf; while (NULL != pTemp)
{
if (0 == strcmp(szBuf, pTemp->szID))
{
return (LINKER*)pTemp;
}
pTemp = (STUDENT*)pTemp->linker.pNext;
} return NULL;
} CMD_MAP_BEGIN(Student)
CMD(Create, 创建, CreateStudentList)
CMD(Add, 添加, AddStudent)
CMD(Delete, 删除, DeleteStudent)
CMD(Find, 查找, FindStudentNode)
CMD(Insert, 插入, InsertStudent)
CMD(Show, 创建, ShowStudentList)
CMD(Clear, 清空, ClearStudentList)
CMD(Back, 返回, NULL)
CMD_MAP_END() LINKER* g_pStudent = NULL; //CmdFun(Student)

void CreateStudentList()
{
switch (CreatList(&g_pStudent, CreateStudent))
{
case ELIST_MEMORY_FAIL:
cout << "动态内存分配失败" << endl;
break;
case ELIST_OK:
cout << "创建链表成功" << endl;
break;
case ELIST_PARAM:
cout << "传参不合理" << endl;
break;
case ELIST_CREATE_FAIL:
cout << "链表已存在,创建链表失败" << endl;
break;
default:
break;
}
} void AddStudent()
{ switch (Add(g_pStudent))
{
case ELIST_MEMORY_FAIL:
cout << "动态内存分配失败" << endl;
break;
case ELIST_OK:
cout << "增加结点成功" << endl;
break;
case ELIST_NOTEXIST:
cout << "链表不存在" << endl;
break;
default:
break;
}
} void DeleteStudent()
{
LINKER* pNode = FindStudent(g_pStudent); switch (DeleteNode(&g_pStudent, pNode))
{
case ELIST_OK:
cout << "删除结点成功" << endl;
break;
case ELIST_PARAM:
cout << "没有找到要删除的结点" << endl;
break;
case ELIST_NOTEXIST:
cout << "链表不存在" << endl;
break;
default:
break;
}
} void FindStudentNode()
{
LINKER* pNode = FindStudent(g_pStudent);
if (NULL != pNode)
{
cout << "查找成功,该结点信息如下:" << endl;
OutputStudent(pNode);
}
else
{
cout << "查找失败" << endl;
}
} void InsertStudent()
{
LINKER* pNode = FindStudent(g_pStudent);
if (NULL == pNode)
{
cout << "没有找到要插入的位置" << endl;
} int mode;
cout << "输入1表示前插输入0表示后插:";
cin >> mode; switch (Insert(&g_pStudent, pNode, (MODE)mode))
{
case ELIST_MEMORY_FAIL:
cout << "动态内存分配失败" << endl;
break;
case ELIST_OK:
cout << "插入成功" << endl;
break;
case ELIST_NOTFIND:
cout << "没有找到要插入的位置,插入失败" << endl;
break;
case ELIST_PARAM:
cout << "传入的参数有误" << endl;
break;
case ELIST_NOTEXIST:
cout << "链表不存在,插入失败!" << endl;
break;
default:
break;
}
} void ShowStudentList()
{
switch (ShowList(g_pStudent))
{
case ELIST_PARAM:
cout << "链表不存在" << endl;
break;
case ELIST_OK:
cout << "链表已全部显示" << endl;
break;
default:
break;
}
} void ClearStudentList()
{
switch (ClearList(&g_pStudent))
{
case ELIST_PARAM:
cout << "链表不存在" << endl;
break;
case ELIST_NOTEXIST:
cout << "链表清空链表失败" << endl;
break;
case ELIST_OK:
cout << "清空链表成功" << endl;
break;
default:
break;
}
}

MainCmd.h

#pragma once
#include"CmdMap.h" DECLEAR_PROC(Main);

MainCmd.cpp

#include"stdafx.h"
#include"MainCmd.h"
#include"Student.h"
#include"Teacher.h" void Teacher();
void Student(); CMD_MAP_BEGIN(Main)
CMD(Teacher, 老师, &Teacher)
CMD(Student, 学生, &Student)
CMD(Exit, 退出, NULL)
CMD_MAP_END() void Teacher()
{
system("cls");
CALL_PROC(Teacher, Teacher);
system("cls");
} void Student()
{
system("cls");
CALL_PROC(Student, Student);
system("cls");
}

List.h

#pragma once

#define ELIST_OK 1
#define ELIST_CREATE_FAIL -1
#define ELIST_MEMORY_FAIL -2
#define ELIST_NOTEXIST -3
#define ELIST_PARAM -4
#define ELIST_NOTFIND -5 struct LINKER;
struct FUN_TABLE
{
LINKER* (*pfnCreateNode)();
void (*pfnInput)(LINKER* pNode);
void (*pfnOutput)(const LINKER* pNode);
LINKER* (*pfnFind)(LINKER* pHead);
}; extern FUN_TABLE g_TeacherTable;
extern FUN_TABLE g_StudentTable; struct LINKER
{
FUN_TABLE* pFunTab; LINKER* pNext;
LINKER* pPrev;
}; enum MODE { before = 1, after = 0 }; int CreatList(LINKER** ppHead, LINKER* (*pfnCreateNode)());
int Add(LINKER* pHead);
int DeleteNode(LINKER** ppHead, LINKER* pNode);
LINKER* FindNode(LINKER* pHead);
int Insert(LINKER** ppHead, LINKER* pNode, MODE mode);
int ShowList(LINKER* pHead);
int ClearList(LINKER** ppHead);

List.cpp

#include "List.h"
#include"stdafx.h" int CreatList(LINKER** ppHead, LINKER* (*pfnCreateNode)())
{
if (NULL == ppHead)
{
return ELIST_PARAM;
}
if (NULL != *ppHead)
{
return ELIST_CREATE_FAIL;
} LINKER* pNew = pfnCreateNode();
if (NULL == pNew)
{
return ELIST_MEMORY_FAIL;
} pNew->pFunTab->pfnInput(pNew);
*ppHead = pNew; return ELIST_OK;
} int Add(LINKER* pHead)
{
if (NULL == pHead)
{
return ELIST_NOTEXIST;
}
while (NULL != pHead->pNext)
{
pHead = pHead->pNext;
} LINKER* pNew = pHead->pFunTab->pfnCreateNode();
if (NULL == pNew)
{
return ELIST_MEMORY_FAIL;
} pNew->pFunTab->pfnInput(pNew);
pHead->pNext = pNew;
pNew->pPrev = pHead; return ELIST_OK;
} int DeleteNode(LINKER** ppHead, LINKER* pNode)
{
if (NULL == ppHead || NULL == pNode)
{
return ELIST_PARAM;
}
if (NULL == *ppHead)
{
return ELIST_NOTEXIST;
} if (NULL == pNode->pPrev)
{
*ppHead = pNode->pNext;
}
else
{
pNode->pPrev->pNext = pNode->pNext;
}
if (NULL != pNode->pNext)
{
pNode->pNext->pPrev = pNode->pPrev;
} delete pNode;
pNode = NULL; return ELIST_OK;
} LINKER* FindNode(LINKER* pHead)
{
if (NULL == pHead)
{
return NULL;
} return pHead->pFunTab->pfnFind(pHead); } int Insert(LINKER** ppHead, LINKER* pNode, MODE mode)
{
if (NULL == ppHead)
{
return ELIST_PARAM;
}
if (NULL == *ppHead)
{
return ELIST_NOTEXIST;
}
if (NULL == pNode)
{
return ELIST_NOTFIND;
} LINKER* pNew = (*ppHead)->pFunTab->pfnCreateNode();
if (NULL == pNew)
{
return ELIST_MEMORY_FAIL;
}
pNew->pFunTab->pfnInput(pNew); if (1 == mode)
{
if (NULL == pNode->pPrev)
{
*ppHead = pNew;
}
else
{
pNode->pPrev->pNext = pNew;
}
pNew->pPrev = pNode->pPrev;
pNew->pNext = pNode;
pNode->pPrev = pNew;
}
else if (0 == mode)
{
if (NULL != pNode->pNext)
{
pNode->pNext->pPrev = pNew;
}
pNew->pNext = pNode->pNext;
pNew->pPrev = pNode;
pNode->pNext = pNew;
}
else
{
return ELIST_PARAM;
} return ELIST_OK;
} int ClearList(LINKER** ppHead)
{
if (NULL == ppHead)
{
return ELIST_PARAM;
}
if (NULL == *ppHead)
{
return ELIST_NOTEXIST;
} LINKER* pTemp = *ppHead;
while (NULL != pTemp)
{
*ppHead = pTemp->pNext;
delete pTemp;
pTemp = *ppHead;
} return ELIST_OK;
} int ShowList(LINKER* pHead)
{
if (NULL == pHead)
{
return ELIST_PARAM;
}
while (NULL != pHead)
{
pHead->pFunTab->pfnOutput(pHead);
pHead = pHead->pNext;
}
cout << endl; return ELIST_OK;
}

CmdFun.h

#pragma once
#pragma once
#include"stdafx.h"
#include"List.h" #define CmdFun(name)\
void Create##name##List()\
{\
switch (CreatList(&g_p##name, Create##name))\
{\
case ELIST_MEMORY_FAIL:\
cout << "动态内存分配失败" << endl;\
break;\
case ELIST_OK:\
cout << "创建链表成功" << endl;\
break;\
case ELIST_PARAM:\
cout << "传参不合理" << endl;\
break;\
case ELIST_CREATE_FAIL:\
cout << "链表已存在,创建链表失败" << endl;\
break;\
default:\
break;\
}\
}\
void Add##name()\
{\
switch (Add(g_p##name))\
{\
case ELIST_MEMORY_FAIL:\
cout << "动态内存分配失败" << endl;\
break;\
case ELIST_OK:\
cout << "增加结点成功" << endl;\
break;\
case ELIST_NOTEXIST:\
cout << "链表不存在" << endl;\
break;\
default:\
break;\
}\
}\
\
void Delete##name()\
{\
LINKER* pNode = Find##name(g_p##name);\
\
switch (DeleteNode(&g_p##name, pNode))\
{\
case ELIST_OK:\
cout << "删除结点成功" << endl;\
break;\
case ELIST_PARAM:\
cout << "没有找到要删除的结点" << endl;\
break;\
case ELIST_NOTEXIST:\
cout << "链表不存在" << endl;\
break;\
default:\
break;\
}\
}\
\
void Find##name##Node()\
{\
LINKER* pNode = Find##name(g_p##name);\
if (NULL != pNode)\
{\
cout << "查找成功,该结点信息如下:" << endl;\
Output##name(pNode);\
}\
else\
{\
cout << "查找失败" << endl;\
}\
}\
\
void Insert##name()\
{\
LINKER* pNode = Find##name(g_p##name);\
if (NULL == pNode)\
{\
cout << "没有找到要插入的位置" << endl;\
}\
\
int mode;\
cout << "输入1表示前插输入0表示后插:";\
cin >> mode;\
\
switch (Insert(&g_p##name, pNode, (MODE)mode))\
{\
case ELIST_MEMORY_FAIL:\
cout << "动态内存分配失败" << endl;\
break;\
case ELIST_OK:\
cout << "插入成功" << endl;\
break;\
case ELIST_NOTFIND:\
cout << "没有找到要插入的位置,插入失败" << endl;\
break;\
case ELIST_PARAM:\
cout << "传入的参数有误" << endl;\
break;\
case ELIST_NOTEXIST:\
cout << "链表不存在,插入失败!" << endl;\
break;\
default:\
break;\
}\
}\
\
void Show##name##List()\
{\
switch (ShowList(g_p##name))\
{\
case ELIST_PARAM:\
cout << "链表不存在" << endl;\
break;\
case ELIST_OK:\
cout << "链表已全部显示" << endl;\
break;\
default:\
break;\
}\
}\
\
void Clear##name##List()\
{\
switch (ClearList(&g_p##name))\
{\
case ELIST_PARAM:\
cout << "链表不存在" << endl;\
break;\
case ELIST_NOTEXIST:\
cout << "链表清空链表失败" << endl;\
break;\
case ELIST_OK:\
cout << "清空链表成功" << endl;\
break;\
default:\
break;\
}\
}

CmdMap.h

#pragma once
#include"stdafx.h"
struct CMD_MAP
{
const char* pStrName;
const char* pStrInfo;
void (*pfn)();
}; #define CMD_MAP_BEGIN(name)\
void name##Proc(const char *pStrApp)\
{\
CMD_MAP *pCmdMap = NULL;\
char szCmd[256] = "";\
while(true)\
{\
pCmdMap = GET_CMD_MAP(name);\
cout << pStrApp << ">";\
cin >> szCmd;\
\
while(NULL != pCmdMap->pStrName)\
{\
if(0 == strcmp(pCmdMap->pStrName,szCmd))\
{\
if(NULL != pCmdMap->pfn)\
{\
pCmdMap->pfn();\
}\
break;\
}\
pCmdMap++;\
}\
if (NULL == pCmdMap->pStrName)\
{\
cout << "您输入的命令有误!" << endl;\
}\
else\
{\
if(NULL == pCmdMap->pfn)\
{\
break;\
}\
}\
}\
}\
CMD_MAP g_##name[] = { #define CMD(name,info,fn)\
{#name,#info,fn}, #define CMD_MAP_END()\
{NULL,NULL,NULL}}; #define GET_CMD_MAP(name) g_##name #define DECLEAR_PROC(name)\
void name##Proc(const char *pStrApp);\
extern CMD_MAP g_##name[]; #define CALL_PROC(name,app_name) name##Proc(#app_name);

运行结果:

  创建通用型链表 增加节点: 

  显示增加的节点:

  

  

  

使用C语言实现简单的通用的链表的更多相关文章

  1. 一个简单的通用Makefile实现

    一个简单的通用Makefile实现   Makefile是Linux下程序开发的自动化编译工具,一个好的Makefile应该准确的识别编译目标与源文件的依赖关系,并且有着高效的编译效率,即每次重新ma ...

  2. 李洪强漫谈iOS开发[C语言-042]-简单计算器

    李洪强漫谈iOS开发[C语言-042]-简单计算器

  3. 谁说C语言很简单?

    前两天,Neo写了一篇<语言的歧义>其使用C语言讨论了一些语言的歧义.大家应该也顺便了解了一下C语言中的很多不可思异的东西,可能也是你从未注意到的东西. 是的,C语言并不简单,让我们来看看 ...

  4. Linux 用C语言实现简单的shell(2)

    不知不觉两周没有发文了,因为“一万美金的福特奖学金答辩”,ACM比赛,网络论文阅读和网络大作业一大堆事把时间冲散了,所以先写一篇博文补上之前一坑. 之前发了一篇关于linux 用C语言实现简单shel ...

  5. Java语言实现简单FTP软件------>FTP软件主界面的实现(四)

    首先看一下该软件的整体代码框架                        1.首先介绍程序的主入口FTPMain.java,采用了一个漂亮的外观风格 package com.oyp.ftp; im ...

  6. Java语言实现简单FTP软件------>源码放送(十三)

    Java语言实现简单FTP软件------>FTP协议分析(一) Java语言实现简单FTP软件------>FTP软件效果图预览之下载功能(二) Java语言实现简单FTP软件----- ...

  7. Java语言实现简单FTP软件------>上传下载管理模块的实现(十一)

    1.上传本地文件或文件夹到远程FTP服务器端的功能. 当用户在本地文件列表中选择想要上传的文件后,点击上传按钮,将本机上指定的文件上传到FTP服务器当前展现的目录,下图为上传子模块流程图 选择好要上传 ...

  8. C语言,简单计算器【上】

    由于工作需要最近在研究PHP扩展,无可避免的涉及到了C语言.从出了学校以后C语言在实际工作中还没有用到过,所以必须要先进行一点复习工作.个人认为对于熟悉一样东西说最好的方法是上手实践.于是便想起了当时 ...

  9. 用c语言实现简单的五子棋

    用c语言实现简单的五子棋 这个小游戏是从零开始的实现的,框架灵感来自于小游戏<走迷宫>. 游戏代码配置: 二维数组+简单逻辑+getch读取键盘+windows函数(刷屏,改颜色,改窗口大 ...

  10. 008_用go语言实现简单的冒泡排序

    冒泡排序是各个语言中的基本排序算法,本次我们用go语言实现简单的冒泡排序 package main import "fmt" // [13,10,5,7,2] // [10,13, ...

随机推荐

  1. 移动端pdf预览---vue-pdf

    <template> <div class="mainBody"> <!-- <div v-if="isLoading" c ...

  2. MYSQL 最左匹配原则的原理

    https://blog.csdn.net/Andrew_Chenwq/article/details/125242197最左匹配原则最左匹配原则就是指在联合索引中,如果你的 SQL 语句中用到了联合 ...

  3. ASP.NET Core - 选项系统之选项验证

      就像 Web Api 接口可以对入参进行验证,避免用户传入非法的或者不符合我们预期的参数一样,选项也可以对配置源的内容进行验证,避免配置中的值与选项类中的属性不对应或者不满足预期,毕竟大部分配置都 ...

  4. 数据文件的读写—R实现

    数据文件的读写 在R语言中可以读写的最基本的数据格式就是text,以及csv文件.用read.table()或者read.csv函数就可以,相应的写入函数是write.table(),write.cs ...

  5. kubernetes核心实战(八)--- service

    13.service 四层网络负载 创建 [root@k8s-master-node1 ~/yaml/test]# [root@k8s-master-node1 ~/yaml/test]# vim m ...

  6. pychearm日常用法

    一 常用快捷键 编辑类:Ctrl + D             复制选定的区域或行Ctrl + Y           删除选定的行Ctrl + Alt + L     代码格式化Ctrl + Al ...

  7. [Nginx]安装第三方调试模块——echo | #解决异常#unknown directive “echo”

    前言 echo 模块/指令: 在Nginx中是一个第三方开发者----agentzh(章亦春)开发的.功能强大的调试工具. location = /helloworld/ { default_type ...

  8. AndroidApp加固与脱壳

    0x01 APP加固 01.为什么要加固 APP加固是对APP代码逻辑的一种保护.原理是将应用文件进行某种形式的转换,包括不限于隐藏,混淆,加密等操作,进一步保护软件的利益不受损坏.总结主要有以下三方 ...

  9. C# 控制系统任务栏的显示与隐藏

    [DllImport("user32.dll")] public static extern int FindWindow(string lpClassName, string l ...

  10. workerman、websocket简单聊天功能从0到1实现

    一.workerman安装,测试环境为linux 1.在网站根目录,用命令行安装,要是出现root权限提示,输入y回车就行,或者新建一个用户再进入安装 Linux系统可以使用以下脚本测试本机PHP环境 ...