C语言学习笔记10

结构体

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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

97Marcus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值