深入理解C指针之六:指针和结构体
C的结构体可以用来表示数据结构的元素,比如链表的节点,指针是把这些元素连接到一起的纽带。 结构体增强了数组等集合的实用性,每个结构体可以包含多个字段。如果不用结构体,可能要分别为每个字段声明一个数组,使用结构体,可以声明一个结构体的数组来组合这些字段。
结构体基础
声明结构体的方式有很多种,这里先讨论两种:
//第一种声明方式
struct person{
char* firstname;
char* secondname;
char* title;
unsigned int age;
}; //第二种声明方式
typedef struct _person{
char* firstname;
char* secondname;
char* title;
}Person;
可以直接使用Person声明一个该结构体的实例,使用点表示法来访问其字段,也可以声明一个指向该结构体的指针,使用箭头操作符访问字段。
Person myperson;
myperson.firstname = (char*) malloc (strlen("jack") + 1);
strcpy(myperson.firstname,"jack");
myperson.age = 18; Person* ptrPerson;
ptrPerson = (Person*) malloc (sizeof(Person));
ptrPerson -> firstname = (char*) malloc (strlen("rose") +1);
strcpy(ptrPerson -> firstname, "rose");
ptrPerson -> age = 16;
对于指针的箭头写法 ptrPerson -> age,你也可以使用解引+点的写法 (*ptrPerson).age来代替。这样写稍微复杂了一些而已。
为结构体分配内存时,分配的内存大小至少是各个字段的长度和。不过,实际长度通常大于这个值,结构体的各个字段之间可能会有填充。如果某些数据类型需要对齐到边界,就会产生填充。比如,短整数通常对其到能被2整除的地址上,整数对齐到能被4整除的地址上。这意味着:
* 谨慎使用算数运算符;
* 结构体数组的元素之间可能存在额外的内存。
printf("size of person is %d\n", sizeof(myperson));//size of person is 16
typedef struct _otherPerson{
char* firstname;
char* secondname;
char* title;
short age;
}OtherPerson; OtherPerson otherPerson;
printf("size of otherperson is %d\n", sizeof(otherPerson));//size of otherperson is 16
把 int 改成 short ,发现结构体的大小还是16个字节,这是因为在 short age 处产生了填充。short类型实际还是占用了2个字节,只不过内存里产生了“缝隙”而已。
释放结构体
在为结构体分配内存时,运行时系统不会自动为结构体内部的指针分配内存。当结构体消失时,运行时系统也同样不会为其指针释放内存。假如我们声明了一个Person的结构体,Person person,然后为person的指针动态分配内存,那么使用完毕后必须记得手动释放动态分配的内存。因为person是一个局部变量,函数返回后person会消失,我们如果忘记释放内存,会导致内存泄露。如果使用Person* ptrPerson的方式,还要连ptrPerson的内存也释放掉。
重复分配然后释放结构体会产生一些开销,可能造成巨大的性能瓶颈。一个解决办法是为分配的结构体维护一个表。当用户不再需要某个结构体实例时,将其返回池中。当需要时,从池中获取一个对象。如果池中没有可用元素,就动态分配一个实例。这种方法能够按需使用和重复使用内存。可以用数组或链表等维护结构体池。如果使用数组来管理,需要注意数组的长度应该合适,不能太长或太短。
指针与数据结构
指针可以为数据结构提供更多的灵活性。这些灵活性可能来自动态内存分配,也可能来自切换指针引用的便利。内存无需像数组那样是连续的,只要总的内存大小正确就行。我们使用一个 Empolyee 结构体来说明几种常见数据结构。
typedef struct _empolyee{
char name[32];
unsigned char age;
}Empolyee; typedef int (*COMPARE) (void*, void*);
typedef void(*DISPLAY) (void*); int compareEmpolyee(Empolyee* e1, Empolyee* e2){
return strcmp(e1 -> name, e2 -> name);
} void displayEmpolyee(Empolyee* e){
printf("name is %s age is %d\n", e-> name, e-> age);
}
链表是由一系列互相连接的节点组成的数据结构。通常会有一个节点成为头节点,其它节点顺序跟在头节点后面,最后一个节点称为尾节点。我们可以使用指针动态分配每个节点。链表有好几种类型,最简单的是单链表,一个节点到下一个节点只有一个连接,连接从头节点开始,到尾结点结束。循环链表没有尾节点,链表的最后一个节点又指向头节点。双链表用了两个链表,一个向前链接,一个向后链接,可以在两个方向上查找节点。
这张图大致描述了不同链表之间的链接方法。下面看一下怎么用结构体和指针实现一个简单的链表。
#include <stdio.h>
#include <stdlib.h>
#include <string.h> typedef struct _employee{
char name[32];
unsigned char age;
}Employee; typedef int (*COMPARE) (void*, void*);
typedef void(*DISPLAY) (void*); typedef struct _node{
void* data;
struct _node* next;
}Node; typedef struct _linkedList{
Node* head;
Node* tail;
Node* current;
}LinkedList; int compareEmployee(Employee* e1, Employee* e2){
return strcmp(e1 -> name, e2 -> name);
} void displayEmployee(Employee* e){
printf("name is %s age is %d\n", e-> name, e-> age);
} void initializeList(LinkedList* list){
list -> head = NULL;
list -> tail = NULL;
list -> current = NULL;
}
void addHead(LinkedList* list, void* data){
Node* node = (Node*) malloc (sizeof(Node));
node -> data = data;
if(list -> head == NULL){
list -> tail = node;
node -> next = NULL;
}
else{
node -> next = list -> head;
}
list -> head = node;
} void addTail(LinkedList* list, void* data){
Node* node = (Node*) malloc (sizeof(Node));
node -> data = data;
node -> next = NULL;
if(list -> head == NULL){
list -> head = node;
}
else{
list -> tail -> next = node;
}
list -> tail = node;
} void delete(LinkedList* list, Node* node){
if(node == list -> head){
if(list -> head -> next ==NULL){
list -> head = list -> tail = NULL;
}
else
{
list -> head = list -> head -> next;
}
}
else
{
Node* tmp = list -> head;
while(tmp != NULL && tmp->next !=node)
{
tmp = tmp -> next;
}
if(tmp != NULL){
tmp -> next = node -> next;
}
if(node == list -> tail){
list -> tail = tmp;
}
}
free(node);
} void displayList(LinkedList* list, DISPLAY display){
printf("\n********linkedlist********\n");
Node* current = list -> head;
while(current != NULL){
display(current -> data);
current = current -> next;
}
} main(){
LinkedList linkedList;
Employee *samuel = (Employee*) malloc (sizeof(Employee));
strcpy(samuel -> name,"Samuel");
samuel -> age = 15; Employee *sally = (Employee*) malloc (sizeof(Employee));
strcpy(sally -> name,"Sally");
sally -> age = 12;
Employee *susan = (Employee*) malloc (sizeof(Employee));
strcpy(susan -> name,"Susan");
susan -> age = 18; initializeList(&linkedList);
addHead(&linkedList, samuel);
addHead(&linkedList, sally);
addTail(&linkedList, susan); displayList(&linkedList, (DISPLAY)displayEmployee); Node* getNode(LinkedList* list, COMPARE compare, void* data){
Node* node = list -> head;
while(node != NULL){
if(compare(node->data,data) == 0){
return node;
}
node = node -> next;
}
return NULL;
} Node* susanNode = getNode(&linkedList, (int (*)(void*,void*))compareEmployee, susan);
delete(&linkedList, susanNode);
displayList(&linkedList, (DISPLAY)displayEmployee);
}
addHead 和 addTail 负责往链表的头部和尾部添加新的节点。getNode 通过一个函数指针根据数据查找节点,然后在删除节点的时候把节点传给删除函数。总体来说这个示例非常直白简单。
队列是一种线性数据结构,通常支持两种操作:入队和出队。入队操作把元素添加到队列中,出队操作从队列中删除元素。一般来说,第一个添加到队列的元素也是第一个离开队列的元素,这种行为被称为先进先出。
可以用链表实现队列。入队操作就是添加到链表头,出队操作就是从链表尾删除节点。我们需要再定义一个从链表尾删除节点的方法来支持队列操作。
void deleteTail(LinkedList* list){
Node* node = list -> tail;
if(node == NULL){
}
else
{
if(list -> head == list -> tail){
list -> head = list -> tail = NULL;
}
else
{
Node* tmp = list -> head;
while( tmp -> next != list -> tail){
tmp = tmp -> next;
}
list -> tail = tmp;
tmp = tmp -> next;
list -> tail -> next = NULL;
free(tmp);
}
}
}
栈数据结构也是一种链表。对于栈,元素被推入栈顶,然后被弹出。当多个元素被推入和弹出时,栈的行为是先进后出。
可以用链表来实现栈操作。入栈可以使用 addHead 函数实现,出栈操作需要再定义一个删除头节点的函数。
void deleteHead(LinkedList* list){
Node* node = list -> head;
if(node == NULL){
}
else
{
if(list -> head == list -> tail ){
list -> head = list -> tail = NULL;
free(node);
}
else
{
list -> head = list -> head -> next;
free(node);
}
}
}
最后来看看树。树的子节点连接到父节点,从整体看就像一颗倒过来的树,根节点表示 这种数据结构的开始元素。树可以有任意数量的子节点,但是二叉树比较常见,它的每个节点能有0个、1个或2个子节点。子节点要么是左节点,要么是右节点。没有子节点的节点称为叶子节点。我们可以动态分配节点,按需插入树中。
按照特定顺序向树中插入节点是很有意义的,这样可以让查询等操作变得容易。比如:插入新节点后,这个节点的所有左子节点的值都比父节点小,所有右子节点的值都比父节点的值大,这样的树称为二叉查找树。
typedef struct _tree{
void* data;
struct _tree* left;
struct _tree* right;
}TreeNode; void insertNode(TreeNode** root, COMPARE compare, void* data){
TreeNode* node = (TreeNode*) malloc (sizeof(TreeNode));
node -> data = data;
node -> left = NULL;
node -> right = NULL;
if(*root == NULL){
*root = node;
return;
}
while(1){
if(compare((*root) -> data, data) > 0){
if((*root) -> left != NULL){
*root = (*root) -> left;
}
else{
(*root) -> left = node;
break;
}
}
else{
if((*root) -> right != NULL){
*root = (*root) -> right;
}
else
{
(*root) -> right = node;
break;
}
}
}
} void inOrder(TreeNode* root, DISPLAY display){
if(root!=NULL){
inOrder(root -> left, display);
display(root -> data);
inOrder(root -> right, display);
}
} void postOrder(TreeNode* root, DISPLAY display){
if(root!=NULL){
inOrder(root -> left, display);
inOrder(root -> right, display);
display(root -> data);
}
} void preOrder(TreeNode* root, DISPLAY display){
if(root!=NULL){
display(root -> data);
inOrder(root -> left, display);
inOrder(root -> right, display);
}
} TreeNode* tree = NULL;
insertNode(&tree,(COMPARE) compareEmployee, samuel);
insertNode(&tree,(COMPARE) compareEmployee, sally);
insertNode(&tree,(COMPARE) compareEmployee, susan); inOrder(tree, (DISPLAY)displayEmployee);
postOrder(tree, (DISPLAY)displayEmployee);
preOrder(tree, (DISPLAY)displayEmployee);
这里实现了一个插入节点的方法,以及三种顺序的遍历树的方法。插入节点是按照大小排序来插入的,遍历树则根据何时执行操作(这里是display)分为中序(inOrder)、前序(preOrder)、后序(postOrder)三种方法。
* 中序:先往左,访问节点,再往右。
* 前序:访问节点,往左,再往右。
* 后序:往左,往右,再访问节点。
当然,你也可以自己定义把左边和右边完全翻转过来,先右边后左边。
深入理解C指针之六:指针和结构体的更多相关文章
- 传入的结构体指针强制转为实例化结构体*v
struct val *v = (struct val *)arg;//传入的结构体指针强制转为实例化结构体*v struct val{ int num1; int num2; }; void *te ...
- 结构体指针,C语言结构体指针详解
结构体指针,可细分为指向结构体变量的指针和指向结构体数组的指针. 指向结构体变量的指针 前面我们通过“结构体变量名.成员名”的方式引用结构体变量中的成员,除了这种方法之外还可以使用指针. 前面讲过,& ...
- C语言笔记 08_函数指针&回调函数&字符串&结构体&位域
函数指针 函数指针是指向函数的指针变量. 通常我们说的指针变量是指向一个整型.字符型或数组等变量,而函数指针是指向函数. 函数指针可以像一般函数一样,用于调用函数.传递参数. 函数指针变量的声明: / ...
- C/C++指针、函数、结构体、共用体
指针 变量与地址 变量给谁用的? 变量是对某一块空间的抽象命名. 变量名就是你抽象出来的某块空间的别名. 指针就是地址.指向某个地址. 指针与指针变量 指针是指向某块地址.指针(地址)是常量. 指针变 ...
- C语言-指针、数组、结构体、分支、循环混合使用
1.写一个程序,输出如下内容: //############################################################# //### name number ma ...
- Swift具体解释之六----------------枚举、结构体、类
枚举.结构体.类 注:本文为作者自己总结.过于基础的就不再赘述 ,都是亲自測试的结果.如有错误或者遗漏的地方.欢迎指正,一起学习. 1.枚举 枚举是用来定义一组通用类型的一组相关值 ,关键字enum ...
- 类成员函数指针的特殊之处(成员函数指针不是指针,内含一个结构体,需要存储更多的信息才能知道自己是否virtual函数)
下面讨论的都是类的非静态成员函数. 类成员函数指针的声明及调用: 1 2 3 4 5 6 7 //pr是指向Base类里的非静态成员函数的指针 //其行参为(int, int),返回值为void vo ...
- C++ 结构体指针理解
上一篇基础链接https://www.cnblogs.com/xuexidememeda/p/12283845.html 主要说一下链表里面双重指针 先说一下结构体 typedef struct LN ...
- 37深入理解C指针之---结构体与指针
一.结构体与指针 1.结构体的高级初始化.结构体的销毁.结构体池的应用 2.特征: 1).为了避免含有指针成员的结构体指针的初始化复杂操作,将所有初始化动作使用函数封装: 2).封装函数主要实现内存的 ...
- C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com
原文:C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | I ...
随机推荐
- 同ListView该接口无法通过手势滑动左右切换界面问题解决方法
同ListView该接口无法通过手势滑动左右切换界面问题解决方法 问题描写叙述: 在做OnGestureListener滑动切换窗体的时候,会遇到这种问题.就是当界面中含有ListView的时候.On ...
- resharper 设置代码颜色
- jconsole 连接 eclipse启动项
eclipse 启动java项目默认情况下不开启jmx远程观看,假设需要看看内存使用情况对项目执行的线程等信息,能eclipse添加启动参数: -Dcom.sun.management.jmxremo ...
- 第七章——DMVs和DMFs(3)——用DMV和DMF监控TempDB
原文:第七章--DMVs和DMFs(3)--用DMV和DMF监控TempDB 前言: 我们都知道TempDB是SQLServer的系统数据库,且SQLServer的日常运作严重依赖这个库.因此,监控T ...
- C语言求素数的算法
前言 最后一次是出了素数的问题C语言解决题目(面试),当时用了最粗暴的算法.回来细致參考资料,事实上答案有非常多种: 1,小学生版本号: 推断 x 是否为质数,就从 2 一直算到 x-1. stati ...
- BZOJ 3589 动态树 树链拆分+纳入和排除定理
标题效果:鉴于一棵树.每个节点有一个右值,所有节点正确启动值他们是0.有两种操作模式,0 x y代表x右所有点的子树的根值添加y. 1 k a1 b1 a2 b2 --ak bk代表质疑. 共同拥有者 ...
- AndroidSlidingUpPanel
使用控制和简单的分析方法
滑 - 向上的时间可以飞起来控件的显示区域.分类似至play music有效. 该控件在主界面中有一个例如以下图红色箭头所指的底部触发区域: 该区域点击的时候被隐藏在下方的内容将网上漂移到顶部,直到被 ...
- Cocos2d-x源代码解析(1)——地图模块(3)
接上一章<Cocos2d-x源代码解析(1)--地图模块(2)> 通过前面两章的分析,我们能够知道cocos将tmx的信息结构化到 CCTMXMapInfo.CCTMXTilesetInf ...
- 自己的自定义单元格(IOS)
定义自己的单位格有三种方法 - 代码 - xib - storyboard(推荐) 操作方法故事板 1.在TableView财产Prototype Cells至1.莫感觉1: 2.须要创建自己定义的单 ...
- C#+Mapxtreme 实现一些GIS系统基本的功能
此程序包括了mapxtreme地图相关基本功能的演示其中包括 鹰眼地图,图层控制,发达,缩小,平移地图,地图模糊查询,中点工具,距离测量工具,面积测量工具,图元信息查看工具.适合于企业级开发,可以为您 ...