C++笔记day19 链表 函数指针 回调函数

133 阅读7分钟

链表

引出- 数组缺陷

1. 数组是一个静态空间,一旦分配内存,就不可以动态扩展。
   空间可能分配多或者分配的少,操作不精准

2. 对于头部的插入删除效率低

链表

1. 由节点组成
   而节点由  数据域  和  指针域组成
       数据域是维护数据的
       指针域 维护下一个节点的位置

2. 链表可以解决数组的缺陷

链表的分类

第一种分类方式:
    1. 静态链表:在栈上分配内存
    2. 动态链表:在堆区分配内存

第二种分类方式:
    1. 单向链表:指针域只有后面的指针
    2. 双向链表:指针域有后面和前一个节点的指针
    3. 单向循环列表:最后一个节点的指针域记录第一个节点
    4. 双向循环列表:第一个节点和最后一个节点可以互相链接

image.png

静态链表和动态链表

静态链表分配在栈上

示例

//节点结构体
struct LinkNode 
{
	int num;//数据域
	struct LinkNode* next;//指针域
};

void test01() 
{
	//创建节点
	struct LinkNode node1 = { 10,NULL };
	struct LinkNode node2 = { 20,NULL };
	struct LinkNode node3 = { 30,NULL };
	struct LinkNode node4 = { 40,NULL };
	struct LinkNode node5 = { 50,NULL };

	//建立关系
	node1.next = &node2;
	node2.next = &node3;
	node3.next = &node4;
	node4.next = &node5;

	//遍历链表
	struct LinkNode* pCurrent = &node1;
	while (pCurrent != NULL) 
	{
		printf("%d\n", pCurrent->num);
		pCurrent = pCurrent->next;
	}
}

int main(void)
{
	test01();
	system("pause");
	return EXIT_SUCCESS;
}

动态链表分配到堆区

实现链表的初始化以及遍历功能

示例

//节点结构体
struct LinkNode
{
	int num;//数据域
	struct LinkNode* next;//指针域
};

void test01() 
{
	//创建节点 在堆上分配内存 是动态链表
	struct LinkNode* node1 = malloc(sizeof(struct LinkNode));
	struct LinkNode* node2 = malloc(sizeof(struct LinkNode));
	struct LinkNode* node3 = malloc(sizeof(struct LinkNode));
	struct LinkNode* node4 = malloc(sizeof(struct LinkNode));
	struct LinkNode* node5 = malloc(sizeof(struct LinkNode));

	//给数据域赋值
	node1->num = 100;
	node2->num = 200;
	node3->num = 300;
	node4->num = 400;
	node5->num = 500;

	//建立关系
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = node5;

	//遍历列表
	struct LinkNode* pCurrent = node1;
	while (pCurrent != NULL) 
	{
		printf("%d\n", pCurrent->num);
		pCurrent = pCurrent->next;
	}

	free(node1);
	free(node2);
	free(node3);
	free(node4);
	free(node5);

	node1 = NULL;
	node2 = NULL;
	node3 = NULL;
	node4 = NULL;
	node5 = NULL;

}
int main(void)
{
	test01();
	system("pause");
	return EXIT_SUCCESS;
}

链表基本使用

带头节点链表和不带头节点链表

image.png

带头好处:带着头节点的链表永远固定了头节点的位置

初始化链表  init_LinkList

image.png

遍历链表    foreach_LinkList

插入链表    insert_LinkList  利用两个辅助指针 实现插入

image.png

image.png

示例

//初始化链表
struct LinkNode* initLinkList() 
{
	//创建头节点
	struct LinkNode* pHeader = malloc(sizeof(struct LinkNode));

	if (pHeader == NULL) 
	{
		return;
	}
	
	//初始化头节点
	//pHeader->num = -1; //头节点 不维护数据域
	pHeader->next = NULL;

	//记录一个尾节点的位置,方便插入新的数据
	struct LinkNode* pTail = pHeader;

	int val = -1;

	while (1) 
	{
		//让用户初始化几个节点,如果用户输入的是-1,代表插入结束
		printf("请初始化链表,输入-1代表结束\n");
		scanf("%d", &val);
		if (val == -1)
		{
			break;
		}
		//如果输入不是-1,插入节点到链表中
		struct LinkNode* newNode = malloc(sizeof(struct LinkNode));
		newNode->num = val;
		newNode->next = NULL;

		//更改链表节点的指针指向
		pTail->next = newNode;
		//更改新的尾节点的指向
		pTail = newNode;
	}
	return pHeader;
}

//遍历链表
//遍历链表
void foreachLinkList(struct LinkNode* pHeader) 
{
	if (pHeader == NULL) 
	{
		return;
	}

	struct LinkNode* pCurrent = pHeader->next;//指定第一个有真实数据的节点

	while (pCurrent != NULL) 
	{
		printf("%d\n", pCurrent->num);
		pCurrent = pCurrent->next;
	}
}


//插入链表
struct LinkNode* insertLinkList(struct LinkNode* pHeader, int oldVal, int newVal) 
{
	if (pHeader == NULL) 
	{
		return;
	}

	//创建两个临时的节点
	struct LinkNode* pPre = pHeader;
	struct LinkNode* pCurrent =  pHeader->next;

	while(pCurrent != NULL) 
	{
		if (pCurrent->num == oldVal) 
		{
			break;
		}
		//如果没找到对应的位置,辅助指针向后移动
		pPre = pCurrent;
		pCurrent = pCurrent->next;
	}
	//创建新节点
	struct LinkNode* newNode = malloc(sizeof(struct LinkNode));
	newNode->num = newVal;
	newNode->next = NULL;

	//建立关系
	newNode->next = pCurrent;
	pPre->next = newNode;
}

删除链表    delete_LinkList  利用两个辅助指针 实现删除

示例

//删除链表
void deleteLinkList(struct LinkNode* pHeader, int val) 
{
	if (pHeader == NULL) 
	{
		return;
	}
	struct LinkNode* pPre = pHeader;
	struct LinkNode* pCurrent = pHeader->next;

	while (pCurrent != NULL) 
	{
		if (pCurrent->num == val) 
		{
			break;
		}
		//没哟找到数据。辅助指针向后移动
		pPre = pCurrent;
		pCurrent = pCurrent->next;
	}
	if (pCurrent == NULL) //没有找到用户要删除的数据
	{
		return;
	}
	//更改指针指向进行删除
	pPre->next = pCurrent->next;

	//删除掉待删除的节点
	free(pCurrent);
	pCurrent = NULL;
}

清空链表   

clear_LinkList   将所有有数据节点释放掉,可以再使用
//清空链表,清空后还是可以插入的
void clearLinkList(struct LinkNode* pHeader) 
{
	if (pHeader == NULL) 
	{
		return;
	}
	struct LinkNode* pCurrent = pHeader->next;
	while (pCurrent != NULL) 
	{
		//先保存住下一个节点的位置
		struct LinkNode* nextNode = pCurrent->next;
		
		free(pCurrent);

		pCurrent = nextNode;
	}
	pHeader ->next =NULL;
}

销毁链表   

destroy_LinkList  将整个链表释放掉,不可以再使用
//销毁链表,将整个链表释放掉,不可以再使用
void destroyLinkList(struct LinkNode* pHeader) 
{
	if (pHeader == NULL)
	{
		return;
	}
	//先清空链表
	clearLinkList(pHeader);
	//再释放头节点
	free(pHeader);
	pHeader = NULL;

}

反转链表

image.png

示例

//反转链表
reverseLinkList(struct LinkNode* pHeader) 
{
	if (pHeader == NULL) 
	{
		return;
	}
	struct LinkNode* pPre = NULL;
	struct LinkNode* pCurrent = pHeader->next;
	struct LinkNode* nextNode =NULL;
	while (pCurrent != NULL) 
	{
		nextNode = pCurrent->next;
		pCurrent->next =pPre;
		pPre = pCurrent;
		pCurrent =nextNode;
	}
	pHeader->next = pPre;
}

//返回链表节点个数
int sizeLinkList(struct LinkNode* pHeader) 
{
	if (pHeader == NULL) 
	{
		return -1;
	}

	struct LinkNode* pCurrent = pHeader->next;
	int num = 0;
	while (pCurrent != NULL) 
	{
		num++;
		pCurrent = pCurrent->next;

	}
	return num;
}

函数指针

函数名本质就是一个函数指针

可以利用函数指针 调用函数

函数指针定义方式

1、先定义出函数类型,再通过类型定义函数指针

typedef void(FUNC_TYPE)(int, char);

2、定义出函数指针类型,通过类型定义函数指针变量

typedef void( * FUNC_TYPE2)(int, char);

3、直接定义函数指针变量

void(*pFunc3)(int, char) = func;

函数指针和指针函数

函数指针  指向了函数的指针

指针函数  函数返回值是指针的函数

函数指针数组

void(*pArray[3])();

示例

void func(int a, char c) 
{
	printf("hello world\n");
}

void test01() 
{
	//1.先定义出函数类型,再通过类型定义函数指针
	typedef void (FUNC_TYPE)(int, char);

	FUNC_TYPE* pFunc = func;
	//pFunc(10, 'a');

	//2.定义出函数指针类型,通过类型定义函数指针
	typedef void (* FUNC_TYPE2)(int, char);

	FUNC_TYPE2 pFunc2 = func;
	//pFunc2(10, 'b');

	//3.直接定义函数指针变量
	void(*pFunc3)(int, char) = func;
	pFunc3(30, 'c');

	//函数指针 和 指针函数
	//函数指针: 指向了函数的指针
	//指针函数: 函数的返回值是指针的函数
}

//函数指针的数组
void func1() 
{
	printf("func1调用了\n");
}
void func2()
{
	printf("func2调用了\n");
}
void func3()
{
	printf("func3调用了\n");
}

void test02() 
{
	void(*pArray[3])();
	pArray[0] = func1;
	pArray[1] = func2;
	pArray[2] = func3;
	for (size_t i = 0; i < 3; i++)
	{
		pArray[i]();
	}
}
int main(void)
{
	//test01();
	test02();//三个函数返回值一样并且形参列表一样,才能够一次给他调用了
	system("pause");
	return EXIT_SUCCESS;
}

函数指针做函数参数(回调函数)

利用回调函数实现打印任意类型数据

示例

//提供一个打印函数,可以打印任意类型的数据
void printText(void * data,void (*myPrint)(void *))
{
	myPrint(data);
}

void myPrintInt(void* data) 
{
	int* num = data;
	printf("%d\n", *num);
}
void test01() 
{
	int a = 10;
	printText(&a,myPrintInt);
}

struct Person 
{
	char name[64];
	int age;
};

void myPrintPerson(void* data) 
{
	struct Person* p = data;
	printf("name =%s, age=%d\n", p->name, p->age);
}

void test02() 
{
	struct Person p = { "Tom",18 };
	printText(&p, myPrintPerson);
}


int main(void)
{
	test01();
	test02();
	system("pause");
	return EXIT_SUCCESS;
}

提供能够打印任意类型数组函数

示例

//提供一个函数,实现可以打印任意类型的数组
void printAllArray(void * pArray, int eleSize, int len,void(*myPrint) (void*))
{
	char* p = pArray;
	for (size_t i = 0; i < len; i++)
	{
		//获取数组中每个元素的首地址
		char* eleAddr = p + eleSize * i;
		//printf("%d\n",*(int *)eleAddr); 不够通用
		
		//交还给用户
		myPrint(eleAddr);
	}
}

void myPrintint(void* data) 
{
	int* num = data;
	printf("%d\n", *num);
}

void test01() 
{
	int arr[5] = { 1,2,3,4,5 };
	int len = sizeof(arr) / sizeof(int);
	printAllArray(arr, sizeof(int), len, myPrintint);
}

struct Person 
{
	char name[64];
	int age;
};

void myPrintStruct(void *data) 
{
	struct Person* p = data;
	printf("name = %s age =%d\n", p->name, p->age);
}

void test02() 
{
	struct Person personArray[] =
	{
		{"aaa",10},
		{"bbb",12},
		{"ccc",30},
		{"ddd",40}
	};
	int len = sizeof(personArray) / sizeof(struct Person);
	printAllArray(personArray, sizeof(struct Person), len, myPrintStruct);
}


int main(void)
{
	test01();
	test02();
	system("pause");
	return EXIT_SUCCESS;
}

利用回调函数 提供查找功能

示例

//查找数组中的元素是否存在
//参1 数组首地址;参2 每个元素的大小;参3 数组元素个数;参4 查找的数据
int findArrayEle(void * pArray,int eleSize,int len,void *data,int(*myCompare)(void *,void *)) 
{
	char* p = pArray;

	for (size_t i = 0; i < len; i++)
	{
		//每个元素的首地址
		char* eleAddr = p + eleSize * i;
		//if(数组中的变量的元素 == 用户传入的元素)
		if (myCompare(eleAddr, data)) 
		{
			return 1;
		}
	}
	return 0;
}

int myComparePerson(void* data1, void* data2) 
{
	struct Person* p1 = data1;
	struct Person* p2 = data2;
	//if (strcmp(p1->name, p2->name) == 0 && p1->age == p2->age) 
	//{
	//	return 1;
	//}
	//else 
	//{
	//	return 0;
	//}
	return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}

void test02()
{
	struct Person personArray[] =
	{
		{"aaa",10},
		{"bbb",12},
		{"ccc",30},
		{"ddd",40}
	};
	int len = sizeof(personArray) / sizeof(struct Person);
	printAllArray(personArray, sizeof(struct Person), len, myPrintStruct);

	//查找数组中制定的元素是否存在
	struct Person p = { "bbb",12 };
	int ret = findArrayEle(personArray, sizeof(struct Person), len, &p,myComparePerson);

	if (ret)
	{
		printf("found the element\n");
	}
	else 
	{
		printf("unfound the element\n");
	}
}

作业:超难

提供一个函数,实现对任意类型的数组进行排序,排序规则利用选择排序,排序的顺序 用户可以自己指定。

示例

void selectSort(void * pAddr,int eleSize,int len,int (*myCompare)(void *,void *)) 
{
	char* temp = malloc(eleSize); //循环中开辟的数据可以放在上面,释放一次就好了
	for (size_t i = 0; i < len; i++)
	{
		int minOrMax = i;
		for (size_t j = i+1; j < len; j++)
		{
			//定义出j元素的地址
			char* pJ =(char*)pAddr + eleSize * j;
			char* pMinOrMax = (char*)pAddr + eleSize * minOrMax;
			//if(pAddr[j]<pAddr[minOrMax]) 
			if (myCompare(pJ,pMinOrMax)) 
			{
				minOrMax = j;//更新最小值或最大值下标
			}
		}
		if (i != minOrMax) 
		{
			//交换i和minOrMin 下标元素
			char* pI = (char*)pAddr + i * eleSize;
			char* pMinOrMax = (char*)pAddr + eleSize * minOrMax;

			memcpy(temp, pI, eleSize);
			memcpy(pI, pMinOrMax, eleSize);
			memcpy(pMinOrMax, temp, eleSize);
		}
	}
	if (temp != NULL) 
	{
		free(temp);
		temp = NULL;
	}
}

int myCompareInt(void * data1,void *data2)
{
	int* num1 = data1;
	int* num2 = data2;

	if (*num1 > *num2)//从大到小排序
	{	
		return 1;
	}
	return 0;

}

void test01() 
{
	int arr[] = { 10,30,20,40,50,60 };
	int len = sizeof(arr) / sizeof(int);
	selectSort(arr, sizeof(int), len, myCompareInt);

	for (size_t i = 0; i < len; i++)
	{
		printf("%d\n", arr[i]);
	}

}

struct Person 
{
	char name[64];
	int age;
};

int myCompareStruct(void* data1, void* data2) 
{
	struct Person* p1 = data1;
	struct Person* p2 = data2;

	if (p1->age < p2->age) 
	{
		return 1;
	}
	return 0;
}

void test02() 
{
	struct Person pArray[] =
	{
		{"aaa",10},{"bbb",30},{"ccc",20},{"ddd",40}
	};

	int len = sizeof(pArray) / sizeof(struct Person);
	selectSort(pArray, sizeof (struct Person), len, myCompareStruct);

	for (size_t i = 0; i < len; i++)
	{
		printf(" name = %s age = %d\n", pArray[i].name, pArray[i].age);
	}
}

int main(void)
{
	test01();
	test02();
	system("pause");
	return EXIT_SUCCESS;
}