结构体
struct AA
{
int id;
char* name;
char* number;
};
typedef struct AA
{
int id;
char* name;
char* number;
}BB;
//typedef为复杂的声明定义简单的别名,结构体的定义形式如上所示,BB为其别名
int main()
{
struct AA a = { 1,"xin","13532805647" };
a.name = "bb";
printf("%d\t%s\t%s\n", a.id, a.name, a.number);
BB* p = &a;//p指的是a的地址,*p指的是a
p->number = "15367842563";
printf("%d\t%s\t%s\n", p->id, p->name, p->number);
//*p.number = "17825345864";
//.的优先级很高,因此先p.number再*(p.number)
//这时候的p并非结构体而是结构体指针,因此无法更改number的内容
(*p).number = "17825345864";
printf("%d\t%s\t%s\n", (*p).id, (*p).name, (*p).number);
return 0;
}
结构体对齐
以最大的基本类型对齐(整体和内部都是)
struct AA
{
char a;
short b;
int c;
};
// |a1 |b1 b2|
// |c1 c2|c3 c4|
//上述数字为该类型的第几个字节,2*4=8
struct BB
{
char a;
int b;
short c;
};
// |a1 | |
// |b1 b2 b3 b4|
// |c1 c2| |
//上述数字为该类型的第几个字节,3*4=12
int main()
{
printf("%d\n", sizeof(struct AA)); //8个字节
printf("%d\n", sizeof(struct BB)); //12个字节
return 0;
}
struct DD// 24个字节
{
char a;
short* b;
double c;
float d;
short e;
char f;
};
// |a1 |b1 b2 b3 b4|
// |c1 c2 c3 c4 c5 c6 c7 c8|
// |d1 d2 d3 d4|e1 e2|f1 |
struct EE// 3个字节
{
char a;
char b;
char c;
};
// |a1|
// |b1|
// |b3|
struct FF// 12个字节
{
int a;
char b[7];
};
// |a1 a2 a3 a4|
// |b1|b2|b3|b4|
// |b5|b6|b7| |
getchar()
int main()
{
printf("%c\n", getchar());
printf("%c\n", getchar());
printf("%c\n", getchar());
printf("%c\n", getchar());
printf("%c\n", getchar());
//输入字符后可以读取一位
//输入abc\0,实际上需要读取四位,最后的\0也会被获取,所以再输入ab时只能读取a
return 0;
}
链表
#include <stdio.h>
//自己完成一个简易链表
typedef struct Node
{
int id;
char* name;
char* tel;
struct Node* pNext;
//链表中每个成员内除了存放东西以外还需要存放下一个成员的地址
}List;
//将structNode这种类型typedef一个别名
int main()
{
List a = { 1,"佩奇","111",NULL };
List b = { 2,"苏西","222",NULL };
List c = { 3,"丹尼","333",NULL };
List d = { 4,"瑞贝卡","444",NULL };
List e = { 5,"佩德罗","555",NULL };
a.pNext = &b;
b.pNext = &c;
c.pNext = &d;
d.pNext = &e;//将每个成员连起来
//遍历链表
List* p = &a;
while (p != NULL)
{
printf("%d %s %s\n", p->id, p->name, p->tel);
p = p->pNext;
}
return 0;
}
链表添加
typedef struct Node
{
int id;
char* name;
char* tel;
struct Node* pNext;
}List;
List* GetNode(int id, char* name, char* tel);
//void AddNode(List* pHead, List* pEnd, List* pNode);
void AddNode(List** pHead1, List** pEnd1, List* pNode);
int main()
{
List* pHead = NULL;//一级指针(头指针),存的是头节点的地址
List* pEnd = NULL;
//AddNode(pHead, pEnd, GetNode(1, "aa", "111"));
//AddNode(pHead, pEnd, GetNode(2, "bb", "222"));
//AddNode(pHead, pEnd, GetNode(3, "cc", "333"));
//AddNode(pHead, pEnd, GetNode(4, "dd", "444"));
AddNode(&pHead, &pEnd, GetNode(1, "aa", "111"));
//对头节点的地址取地址,也就是二级指针
AddNode(&pHead, &pEnd, GetNode(2, "bb", "222"));
AddNode(&pHead, &pEnd, GetNode(3, "cc", "333"));
AddNode(&pHead, &pEnd, GetNode(4, "dd", "444"));
while (pHead != NULL)
{
printf("%d %s %s\n", pHead->id, pHead->name, pHead->tel);
pHead = pHead->pNext;
}
return 0;
}
List* GetNode(int id, char* name, char* tel)
{
List* pTemp = (List*)malloc(sizeof(List));
//给新结点开辟空间,malloc申请来的都在堆区
pTemp->id = id;
pTemp->name = name;
pTemp->tel = tel;
pTemp->pNext = NULL;
return pTemp;
}
//void AddNode(List* pHead, List* pEnd, List* pNode)
//该函数中的pHead以及pEnd在该函数结束时就被系统回收了,和主函数当中的pHead和pEnd并非是同一个指针变量
//因此在参数设置时,应该使用该指针的地址而并非该指针
// pHead1 == &pHead
// *pHead1 == pHead
//**pHead1 == *pHead
//二级指针 == 头指针的地址(传参)
//对二级指针间接引用 == 头指针
//对头指针间接引用 == 头节点
void AddNode(List** pHead1, List** pEnd1, List* pNode)
{
//是否有节点
if (NULL == *pHead1)
{
//没有
//头指针指向新来节点
*pHead1 = pNode;
}
else
{
//有
//尾节点的下一个指向新来的节点
//pEnd1->pNext = pNode;
//*pEnd1->pNext = pNode;//优先级出问题了
(*pEnd1)->pNext = pNode;
}
//尾节点指向新来的节点
*pEnd1 = pNode;
}
链表插入
typedef struct Node
{
int id;
struct Node* pNext;
}List;
void AddNode(List** ppHead, List** ppEnd, int id);
void InsertNode(List** ppHead, List** ppEnd, List* pNode, int id);
int main()
{
List* pHead = NULL;
List* pEnd = NULL;
List a = { 6,NULL };
AddNode(&pHead, &pEnd, 1);
AddNode(&pHead, &pEnd, 2);
AddNode(&pHead, &pEnd, 3);
AddNode(&pHead, &pEnd, 4);
InsertNode(&pHead, &pEnd, &a, 5);
while (pHead != NULL)
{
printf("%d\n", pHead->id);
pHead = pHead->pNext;
}
return 0;
}
void AddNode(List** ppHead, List** ppEnd, int id)
{
List* pTemp = (List*)malloc(sizeof(List));
pTemp->id = id;
pTemp->pNext = NULL;
if (NULL == *ppHead)
{
*ppHead = pTemp;
}
else
{
(*ppEnd)->pNext = pTemp;
}
*ppEnd = pTemp;
}
void InsertNode(List** ppHead, List** ppEnd, List* pNode, int id)
{
List* pMark = *ppHead;
/*从某种意义上来说,因为已经判断过头插入,且本指针是为了中间插入而定义的,所以猜测可以定义为头结点的下一个,这是有一定道理的,但问题在于判断中间插入时,我们需要找到的是插入位置的前一个节点,那么如果定义为头节点的下一个,那么当我们需要在第二个节点前插入时,我们就无法得到第二个节点的前一个,也就是头节点了*/
//1.头插入 (如果给id赋值为头结点id的内容,那么此时if内条件返回为真,也就能判断出这是头插入-)
if ((*ppHead)->id == id)
{
//新来节点的下一个指向头节点
//如果链表中现在没有节点,这是唯一一个节点,那么新节点的指针域存放NULL没有问题,也就是说可以写成 pNode->pNext = NULL;但如果链表中现在有节点,且要求把新节点插入到最前面,那么新节点的指针域就应该指向原来的头指针(也就是标记着指向头节点的指针)
pNode->pNext = *ppHead;
//头指针指向新来的节点
//如果这是唯一一个节点,那么头指针和尾指针都指向该节点,如果这是插入到第一个节点之前的节点,那么头指针就应该指向这个新的节点
*ppHead = pNode;
return;
}
//中间插入
//遍历链表
//while (pMark != NULL)
/*如果循环条件为pMark != NULL,那么当pMark为4号节点时,pMark->pNext是不存在的,那么也就不存在pMark->pNext->id,所以出现了bug*/
//正确循环条件如下所示:
while (pMark->pNext != NULL)
{
//让标记停在插入位置的前一个节点上
if (pMark->pNext->id == id)
{
//新来节点的下一个指向标记的下一个
//新来节点的指针域放标记节点的指针域,也就是标记的下一个结点的地址
pNode->pNext = pMark->pNext;
//标记的下一个指向新来节点
//标记节点的指针域放新来节点的地址
pMark->pNext = pNode;
return;
}
pMark = pMark->pNext;
}
//尾插入
(*ppEnd)->pNext = pNode;
//尾节点的下一个指向新来节点//当下尾节点的指针域放新来节点的地址
*ppEnd = pNode;
//因为可能后续还需要将新结点插入到链表的尾部,所以需要尾指针,也就是本函数中的ppEnd,用来始终指向链表的尾节点,以便能够将新结点插入到链表的尾部。
//因此本句意思为尾指针指向新来节点以作为尾节点
}
链表删除
typedef struct Node {
int id;
struct Node* pNext;
}List;
void AddNote(List** pHead1, List** pEnd1, int id);
void DeleteNote(List** pHead1, List** pEnd1, int id);
int main()
{
List* pHead = NULL;
List* pEnd = NULL;
AddNote(&pHead, &pEnd, 1);
AddNote(&pHead, &pEnd, 2);
AddNote(&pHead, &pEnd, 3);
AddNote(&pHead, &pEnd, 4);
DeleteNote(&pHead, &pEnd, 4);
while (pHead != NULL)
{
printf("%d\n", pHead->id);
pHead = pHead->pNext;//①
}
return 0;
}
void AddNote(List** pHead1, List** pEnd1, int id)
{
List* pTemp = (List*)malloc(sizeof(List));
pTemp->id = id;
pTemp->pNext = NULL;
//不进行赋空的话,那么有值,在遍历时无法结束,在pHead=pNext!=NULL时会出问题①
if (NULL == *pHead1)
{
*pHead1 = pTemp;
}
else
{
(*pEnd1)->pNext = pTemp;
}
*pEnd1 = pTemp;
}
void DeleteNote(List** pHead1, List** pEnd1, int id)
{
List* pDelete = NULL;
List* pMark = *pHead1;
//头删除
//直接删除头节点的话,2号节点找不到。
//但是头指针先指向2号节点再删除头节点的话,单向链表是找不到头节点的,所以需要一个删除标记。
if ((*pHead1)->id == id)
{
//删除标记指向头节点
pDelete = *pHead1;
//头指针指向头节点的下一个
*pHead1 = (*pHead1)->pNext;
//释放删除标记
free(pHead1);
pHead1 = NULL;
//释放了以后这个指针不在这个程序里,但这个指针及其内容依然存在,要赋空才可以完全消除影响
return;//结束程序
}
//中间删除
while (pMark->pNext != NULL) //遍历链表
{
//遍历标记停在删除节点的前一个节点上
if (pMark->pNext->id == id)
{
//删除标记指向遍历标记的下一个
pDelete = pMark->pNext;
//遍历标记的下一个指向遍历标记的下一个的下一个
pMark->pNext = pMark->pNext->pNext;
//释放删除标记
free(pDelete);
pDelete = NULL;
//判断删除的是否是尾节点
if (pMark->pNext == NULL)
{
*pEnd1 = pMark;
}
//尾删除
//中间删除可以删除尾节点,但是尾指针依然指向删除的节点,所以需要加一条判断其是否为尾节点
//看标记的下一个是NULL还是非空即可,如果NULL则是尾节点,那么让尾指针指向标记节点即可
return;
}
pMark = pMark->pNext;
}
}
随机数
int rand (void);
实际上,rand() 函数产生的随机数是伪随机数,是根据一个数值按照某个公式推算出来的,这个数值我们称之为“种子”.种子在每次启动计算机时是随机的,但是一旦计算机启动以后它就不再变化了;也就是说,每次启动计算机以后,种子就是定值了,所以根据公式推算出来的结果(也就是生成的随机数)就是固定的。
我们可以通过 srand() 函数来重新“播种”:void srand (unsigned int seed);
在实际开发中,可以用时间作为参数,只要每次播种的时间不同,那么生成的种子就不同,最终的随机数也就不同。使用 <time.h> 头文件中的 time() 函数即可得到当前的时间(精确到秒),就像下面这样:srand((unsigned)time(NULL));
int main()
{
//printf("%d\n",time(NULL)/60/60/24/365);
srand((unsigned int)time(NULL));
printf("%d\n",rand() % 10); //0-9
printf("%d\n",rand() %10 + 11); //11-20 - 11 0 - 9
printf("%d\n",rand() % 6 + 12); //12-17 - 12 0 - 5
printf("%d\n",rand());
//如果要规定上下限:比如:int a = rand() % 51 + 13; //产生13~63的随机数
//取模即取余,rand()%51是产生 0~50 的随机数,后面+13保证 a 最小只能是 13,最大就是 50+13=63。
//也就是说想要设范围区间为(max,min),那么只需 rand%(max-min+1)+min 即可。
return 0;
}