C指针之六:指针和结构体


#include<iostream>
#include <windows.h>
using namespace std;

/************************************************************************/
/* 指针和结构体                                                          */
/************************************************************************/
/*
指针和结构体 ,本篇目录
一、介绍
二、结构体释放问题
三、避免malloc/free开销
四、用指针支持数据结构
五、小结

*/

/*
一、介绍
本章重点:
1、结构体声明,
2、结构体访问
3、结构体内存
*/

/*
声明结构体的两个主要方式: 
1、用struct关键字声明
*/
/*
struct _person 
{
	char * pszFirstName;
	char* pszLastName;
	char* pszTitle;
	unsigned int iAge;

} stPerson;
*/
//2、用struct关键字
typedef struct _person
{
	char * pszFirstName;
	char* pszLastName;
	char* pszTitle;
	unsigned int iAge;
}Person;
/************************************************************************/
/* 关于结构体示例的声明                                                  */
/************************************************************************/

// person的示例声明如下
Person person;

// 也可以这样声明一个指针,然后分配内存
Person *ptrPerson =(Person*)malloc(sizeof(Person));

/* 访问方式主要有三个,结构体变量一个,结构体指针两个
 1、结构体变量
 2、结构体指针
 3、结构体指针解引用
 */
void VisitStructWay()
{
	// 1、结构体变量
	person.pszFirstName = (char*)malloc(strlen("Emily") + 1);
	strcpy(person.pszFirstName, "Emily");
	person.iAge = 23;

	// 2、结构体指针
	ptrPerson->pszFirstName = (char*)malloc(strlen("Emily") + 1);
	strcpy(ptrPerson->pszFirstName, "Emily");
	ptrPerson->iAge = 23;

	// 3、结构体指针解引
	(*ptrPerson).pszFirstName = (char*)malloc(strlen("Emily") + 1);
	strcpy((*ptrPerson).pszFirstName, "Emily");
	(*ptrPerson).iAge = 23;

}
/*
	为结构体分配内存
	涉及到内存对齐的问题,后面详细介绍
*/ 

/************************************************************************/
/* 二、结构体释放问题													*/
/************************************************************************/

// 下面这个函数在结构体的初始化阶段,会为每个字段赋值,对于指针字段,我们
//会从堆上分配内存,并把地址赋值给每个指针
void initializePerson(Person *ptrPerson,const char* pszFn,const char* pszLn,
	const char* pszTitle,unsigned int iAge)
{
	ptrPerson->pszFirstName = (char*)malloc(strlen(pszFn) + 1);
	strcpy(ptrPerson->pszFirstName, pszFn);

	ptrPerson->pszLastName = (char*)malloc(strlen(pszLn) + 1);
	strcpy(ptrPerson->pszLastName, pszLn);

	ptrPerson->pszTitle = (char*)malloc(strlen(pszTitle) + 1);
	strcpy(ptrPerson->pszTitle, pszTitle);

	ptrPerson->iAge = iAge;
}

// 释放内存
void deallocatePerson(Person *ptrPerson)
{
	free(ptrPerson->pszFirstName);
	free(ptrPerson->pszLastName);
	free(ptrPerson->pszTitle);

}
// 下面这个函数包含了结构体指针的初始化和释放
void processPerson()
{
	Person *ptrPerson;
	ptrPerson = (Person *)malloc(sizeof(ptrPerson));

	initializePerson(ptrPerson,"Peter","UNderwood","Manager",36);
	deallocatePerson(ptrPerson);
	free(ptrPerson);

}
/************************************************************************/
/* 三、避免malloc/free开销                                               */
/************************************************************************/
/*
	重复分配然后释放结构体会产生一些开销,可能会导致巨大的性能瓶颈。
解决这个问题的技术思想是:
	为分配的结构体单独维护一个表,当用户不再需要某个结构体实例的时候,将其返回到结构体池中,
	当我们需要某个实例的时候,从结构体池中获取一个对象。如果池中没有可用的元素,我们就动态的
分配一个实例。
	这种方法的好处是能够高效地维护一个结构体池,能够按需使用和重复使用内存
*/

// 下面是实现方法
#define D_LIST_SIZE 10
Person *list[D_LIST_SIZE];

// 使用之前先初始化
void initializeList()
{
	for(int i = 0;i<D_LIST_SIZE;i++)
	{
		list[i] = NULL;
	}
}

// 下面用两个函数来添加和获取结构体
// 这个函数获取一个非空的结构体
Person *getPerson()
{
	for(int i = 0;i < D_LIST_SIZE; i++)
	{
		if(NULL != list[i])
		{
			Person *ptr = list[i];
			list[i] = NULL;

			return ptr; // 返回非空的结构体
		}
	}

	Person *ptrPerson = (Person *)malloc(sizeof(Person));
	
	return ptrPerson;
}

// 这个函数要么将结构体返回表,要么把结构体释放掉
Person *returnPerson(Person *ptrPerson)
{
	for(int i = 0; i < D_LIST_SIZE; i++)
	{
		if(NULL == list[i])
		{
			list[i] = ptrPerson;

			return ptrPerson;
		}
	}

	deallocatePerson(ptrPerson);
	free(ptrPerson);

	ptrPerson = NULL;

	return NULL;
}

// 下面函数说明了表的初始化,以及如何将一个结构体添加到表中
void TestStructPool()
{
	initializeList();
	Person *ptrPerson;
	ptrPerson = getPerson();
	initializePerson(ptrPerson, "Mark", "Kevin", "Smith", 35);
	
	returnPerson(ptrPerson);
}
/*
	结构体池,有个问题,就是表长度,如果表太短,就需要频繁分配并释放内存
	如果表太而没有使用结构体,那么就会浪费大量内存,也不能用在其它地方。
	管理表的长度,可以用更复杂的表管理策略来做
*/

/************************************************************************/
/* 四、用指针支持数据结构
/************************************************************************/
/*
	本章分为四个部分
	1、单链表
	2、用指针支持队列
	3、用指针支持栈
	4、用指针支持树

	指针的便利性来自于两个方面:1、动态内存分配,2、自动切换指针引用的便利性
	链表是非常有用的数据结构,可以作为实现队列和栈的基础
	下面用两个函数和两个函数指针说明下
*/
typedef struct _employee
{
	char name[32];
	unsigned char age;
}Employee;

// 比较
int CompareEmployee(Employee* ptrEm1, Employee* ptrEm2)
{
	int iSize = strcmp(ptrEm1->name, ptrEm2->name);
	
	return iSize;
}

// 输出
void displayEmployee(Employee* ptrEmployee) 
{
	printf("%s\t%d\n",ptrEmployee->name,ptrEmployee->age);
}

typedef void(*DISPLAY)(void * );
typedef int(*COMPARE)(void* , void*);

void TestPtrStruct()
{
	Employee em1;
	Employee em2;

	strcpy(em1.name, "KAKA");
	em1.age = 22;

	strcpy(em2.name, "KAKA");
	em2.age = 22;

 	int iResult = CompareEmployee(&em1, &em2);
 
 	cout << "iResult = "<< iResult<< endl;

	
	displayEmployee(&em1);
}
//(一)、单链表
/*
链表有很多类型,最简单的是单链表,一个节点到下一个节点只有一个链接
链接从头结点开始,到尾节点结束
循环链表没有尾节点,链表的最后一个节点又指向头节点
双链表用了两个链表,一个向前链接,一个向后链接

下面介绍单链表
*/
// 节点结构体
typedef struct _node
{
	void *data; // 持有任意类型数据
	struct _node *next; // 指向下一节点的指针
}Node;

// 链表结构体
typedef struct _linkedList 
{
	Node *pHead;
	Node *pTail;
	Node *pCurrent;
} LinkedList;

// 初始化链表
void initializeList(LinkedList *pList);

// 给链表头结点添加数据
void addHead(LinkedList *pList,void* pData);

// 给链表尾节点添加数据
void addTail(LinkedList *pList, void* pData);

// 从链表删除节点
void deleteNode(LinkedList *pList, Node* pNode);

// 返回包含指定数据的节点指针,找到指定节点
Node *getNode(LinkedList *pList,COMPARE,void* pData);

// 打印链表
void displayLinkedList(LinkedList *pList,DISPLAY);

void initializeList(LinkedList *pList)
{
	pList->pHead = NULL;
	pList->pTail = NULL;
	pList->pCurrent = NULL;
}

void addHead(LinkedList *pList, void* pData)
{
	Node *pNode = (Node*)malloc(sizeof(Node));
	pNode->data = pData;
	if(NULL == pList->pHead)
	{
		pList->pTail = pNode;
		pNode->next = NULL;
	}
	else
	{
		pNode->next = pList->pHead;
	}

	pList->pHead = pNode;
}

void addTail(LinkedList *pList, void* pData)
{
	Node *pNode = (Node*)malloc(sizeof(Node));
	pNode->data = pData;
	pNode->next = NULL;
	if(NULL == pList->pHead)
	{
		pList->pHead = pNode; 
	}
	else
	{
		pList->pTail->next = pNode;
	}

	pList->pTail = pNode;
}

void deleteNode(LinkedList *pList, Node* pNode)
{
	if(pNode == pList->pHead)
	{
		if(NULL == pList->pHead->next)
		{
			pList->pHead = pList->pTail = NULL;
		}
		else
		{
			pList->pHead = pList->pHead->next;
		}
	}
	else
	{
		Node *pTemp = pList->pHead;
		while(pTemp != NULL && pTemp->next != pNode)
		{
			pTemp = pTemp->next;
		}
		if(pTemp != NULL)
		{
			pTemp->next = pNode->next;
		}
	}

	free(pNode);
}


Node * getNode(LinkedList *pList, COMPARE compare, void* pData)
{
	Node *pNode = pList->pHead;
	while(NULL != pNode)
	{
		if(0 == compare(pNode->data,pData))
		{
			return pNode;
		}

		pNode = pNode->next;
	}
	return	NULL;
}

void displayLinkedList(LinkedList *pList, DISPLAY display)
{
	printf("\nLinked List\n");

	Node *pCurrent = pList->pHead;
	while(NULL != pCurrent)
	{
		display(pCurrent->data);
		pCurrent = pCurrent->next;
	}
}

// 链表测试
void TestLink()
{
	LinkedList pLinkedList;

	Employee *pSamuel = (Employee *)malloc(sizeof(Employee));
	strcpy(pSamuel->name, "Samuel");
	pSamuel->age = 32;

	Employee *pSally = (Employee *)malloc(sizeof(Employee));
	strcpy(pSally->name, "Sally");
	pSally->age = 22;

	Employee *pSusan = (Employee*)malloc(sizeof(Employee));
	strcpy(pSusan->name, "Susan");
	pSusan->age = 24;

	initializeList(&pLinkedList);

	addHead(&pLinkedList, pSamuel);
	addHead(&pLinkedList, pSally);
	addHead(&pLinkedList, pSusan);

	Node *pNode = getNode(&pLinkedList, (int(*)(void*, void*))CompareEmployee, pSally);

	cout << pNode->data<< endl;
	cout << "Test Show"<< endl;
	displayLinkedList(&pLinkedList,(DISPLAY)displayEmployee);

	deleteNode(&pLinkedList,pNode);
}
/*
	(二)、队列
	队列的基本操作有两种,enqueue(入队),它是在表的末端(称为队尾)插入一个元素;
	dequeue(出队),它是删除(并返回)表的开头的元素

	先入先出的数据结构
	实现队列经常用到链表,入队操作就是将节点添加链表一端,出队操作就是从链表另一端取节点
,并删除节点
	(下面这个实现不是很好)
*/
typedef LinkedList Queue;

// 初始化队列
void initializeQueue(Queue * pQueue)
{
	initializeList(pQueue);
}

// 向队列增加一个节点
void enQueue(Queue * pQueue,void *pNode)
{
	addHead(pQueue, pNode);
}

// 从队列中取数据,并从队列中删除这个数据
void *deQueue(Queue *pQueue)
{
	Node *tmp = pQueue->pHead; // 头指针
	void * pData;

	if(NULL == pQueue->pHead)
	{
		pData = NULL;
	}
	else if (pQueue->pHead == pQueue->pTail)
	{
		pQueue->pHead = pQueue->pTail = NULL;
		pData = tmp->data;
		
		free(tmp);
	}
	else
	{
		while(tmp->next != pQueue->pTail)
		{
			tmp = tmp->next;
		}
	}

	pQueue->pTail = tmp;
	tmp = tmp->next;
	pQueue->pTail->next = NULL;
	pData = tmp->data;

	return pData;
	free(tmp);
}

// 队列测试方法
void TestQueue()
{
	Employee *pJeff = (Employee *)malloc(sizeof(Employee));
	strcpy(pJeff->name, "Jeff");
	pJeff->age = 32;

	Employee *pJames = (Employee *)malloc(sizeof(Employee));
	strcpy(pJames->name, "James");
	pJames->age = 22;

	Employee *pMark = (Employee*)malloc(sizeof(Employee));
	strcpy(pMark->name, "Mark");
	pMark->age = 24;


	Queue qQueueTest;
	initializeList(&qQueueTest);

	enQueue(&qQueueTest, pJeff);

	enQueue(&qQueueTest, pJames);

	enQueue(&qQueueTest, pMark);
	
	void *pData = deQueue(&qQueueTest);
	
	char name[20];
	strcpy(name, ((Employee*)pData)->name);

	cout << name<< endl;
}
/*
	(三)、 栈
	堆栈的基本操作是push(进栈)和push(出栈),前者相当于插入,后者这是删除最后插入的元素
	栈的行为是先进后出
	栈同样可以用链表支持操作
*/
typedef LinkedList Stack;

// 初始化栈
void initializeStack(Stack *pStack)
{
	initializeList(pStack);
}

// 入栈
void push(Stack *pStack,void *pData)
{
	addHead(pStack, pData);
}
/*
	出栈,考虑三种情况:
	1、栈为空,返回NUNULL
	2、栈中有一个元素,如果节点指向尾节点,那么头结点和尾节点是同一个元素。将头结点
和尾节点置为NULL。然后返回数据

	3、栈中有多个元素
	头结点赋值为链表的下一个元素,然后返回数据
*/

void *pop(Stack *pStack)
{
	Node * pNode = pStack->pHead;
	if(NULL ==pNode)
	{
		return NULL;
	}
	else if(pNode == pStack->pTail)
	{
		pStack->pHead = pStack->pTail = NULL;
		
		void * pData = pNode->data;
		free(pNode);

		return pData;
	}
	else
	{
		pStack->pHead = pStack->pHead->next;
		void * pData = pNode->data;
		free(pNode);
		return pData;
	}

}

void TestStack()
{
	Employee *pJeff = (Employee *)malloc(sizeof(Employee));
	strcpy(pJeff->name, "Jeff");
	pJeff->age = 32;

	Employee *pJames = (Employee *)malloc(sizeof(Employee));
	strcpy(pJames->name, "James");
	pJames->age = 22;

	Employee *pMark = (Employee*)malloc(sizeof(Employee));
	strcpy(pMark->name, "Mark");
	pMark->age = 24;

	Stack qStackTest;
	initializeStack(&qStackTest);
	
	push(&qStackTest, pJeff);
	push(&qStackTest, pJames);
	push(&qStackTest, pMark);

	Employee *employee;
	for(int i = 0;i<4;i++)
	{
		employee = (Employee*)pop(&qStackTest);
		printf("Popped %s\n",employee->name );
	}


}
/*
(四)、用指针支持栈
*/

typedef struct _tree
{
	void *pData;
	struct _tree *pLeft;
	struct _tree *pRight;

} TreeNode;

void insertNode(TreeNode **pRoot,COMPARE compare ,void *pData)
{
	TreeNode *pNode = (TreeNode*)malloc(sizeof(TreeNode));
	pNode->pData = pData;
	pNode->pLeft = NULL;
	pNode->pRight = NULL;
	
	if(NULL == *pRoot)
	{
		*pRoot = pNode;

		return;
	}

	while(1)
	{
		if(compare((*pRoot)->pData,pData)>0)
		{
			if(NULL != (*pRoot)->pLeft) // 左节点有值
			{
				*pRoot = (*pRoot)->pLeft;
			}
			else
			{
				(*pRoot)->pLeft = pNode;
				
				break;
			}
			//	*pRoot = (*pRoot).pLeft;
		}
		else
		{
			if(NULL != (*pRoot)->pRight) // 右节点有值
			{
				*pRoot = (*pRoot)->pRight;
			}
			else
			{
				//(*pRoot) = (*pRoot)->pRight;
				(*pRoot)->pRight = pNode;
				break;
			}
		}
	}
}
/*
 遍历二叉树的方式有三种:前序、中序、后序。
 三种遍历方式的技术步骤相同,但是顺序不同
 
 三个步骤:访问节点、往左、往右
 访问节点的三种顺序:

 中序、先往左,访问节点,再往右
 前序、访问节点,往左,再往右
 后序、先往左,再往右,最后访问节点
*/

// 前序
void preOrder(TreeNode *pRoot, DISPLAY display)
{
	if(NULL != pRoot)
	{
		display(pRoot->pData);
		preOrder(pRoot->pLeft, display);
		preOrder(pRoot->pRight, display);
	}
}

// 中序;
void inOrder(TreeNode *pRoot,DISPLAY display)
{
	if(NULL != pRoot)
	{
		inOrder(pRoot->pLeft, display);
		display(pRoot->pData);
		inOrder(pRoot->pRight, display);
	}
}

// 后序
void postOrder(TreeNode *pRoot, DISPLAY display)
{
	if(NULL != pRoot)
	{
		postOrder(pRoot->pLeft, display);
		postOrder(pRoot->pRight, display);
		display(pRoot->pData);
	}
}

void TestTree()
{
	Employee *pSamuel = (Employee *)malloc(sizeof(Employee));
	strcpy(pSamuel->name, "Samuel");
	pSamuel->age = 32;

	Employee *pSally = (Employee *)malloc(sizeof(Employee));
	strcpy(pSally->name, "Sally");
	pSally->age = 28;

	Employee *pSusan = (Employee*)malloc(sizeof(Employee));
	strcpy(pSusan->name, "Susan");
	pSusan->age = 45;

	TreeNode *pTree = NULL;
	insertNode(&pTree, (COMPARE) CompareEmployee, pSamuel);
	insertNode(&pTree, (COMPARE)CompareEmployee, pSally);
	insertNode(&pTree, (COMPARE)CompareEmployee, pSusan);

	// 遍历
	preOrder(pTree, (DISPLAY)displayEmployee);
 	inOrder(pTree, (DISPLAY)displayEmployee);
 	postOrder(pTree, (DISPLAY)displayEmployee);

}
 
int main()
{
	TestTree();
	//TestStack();
	//TestQueue();
	//TestLink();
	//TestPtrStruct();
	//TestStructPool();
// 	const char* pszName = "KAKA";
// 
// 	strcpy(pszName, "JAM");

	system("pause");
	return 0;
}

 

上一篇:剑指Offer 28


下一篇:构建二叉树