原文:深入理解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指针之六:指针和结构体的更多相关文章

  1. 传入的结构体指针强制转为实例化结构体*v

    struct val *v = (struct val *)arg;//传入的结构体指针强制转为实例化结构体*v struct val{ int num1; int num2; }; void *te ...

  2. 结构体指针,C语言结构体指针详解

    结构体指针,可细分为指向结构体变量的指针和指向结构体数组的指针. 指向结构体变量的指针 前面我们通过“结构体变量名.成员名”的方式引用结构体变量中的成员,除了这种方法之外还可以使用指针. 前面讲过,& ...

  3. C语言笔记 08_函数指针&回调函数&字符串&结构体&位域

    函数指针 函数指针是指向函数的指针变量. 通常我们说的指针变量是指向一个整型.字符型或数组等变量,而函数指针是指向函数. 函数指针可以像一般函数一样,用于调用函数.传递参数. 函数指针变量的声明: / ...

  4. C/C++指针、函数、结构体、共用体

    指针 变量与地址 变量给谁用的? 变量是对某一块空间的抽象命名. 变量名就是你抽象出来的某块空间的别名. 指针就是地址.指向某个地址. 指针与指针变量 指针是指向某块地址.指针(地址)是常量. 指针变 ...

  5. C语言-指针、数组、结构体、分支、循环混合使用

    1.写一个程序,输出如下内容: //############################################################# //### name number ma ...

  6. Swift具体解释之六----------------枚举、结构体、类

    枚举.结构体.类 注:本文为作者自己总结.过于基础的就不再赘述 ,都是亲自測试的结果.如有错误或者遗漏的地方.欢迎指正,一起学习. 1.枚举 枚举是用来定义一组通用类型的一组相关值 ,关键字enum ...

  7. 类成员函数指针的特殊之处(成员函数指针不是指针,内含一个结构体,需要存储更多的信息才能知道自己是否virtual函数)

    下面讨论的都是类的非静态成员函数. 类成员函数指针的声明及调用: 1 2 3 4 5 6 7 //pr是指向Base类里的非静态成员函数的指针 //其行参为(int, int),返回值为void vo ...

  8. C++ 结构体指针理解

    上一篇基础链接https://www.cnblogs.com/xuexidememeda/p/12283845.html 主要说一下链表里面双重指针 先说一下结构体 typedef struct LN ...

  9. 37深入理解C指针之---结构体与指针

    一.结构体与指针 1.结构体的高级初始化.结构体的销毁.结构体池的应用 2.特征: 1).为了避免含有指针成员的结构体指针的初始化复杂操作,将所有初始化动作使用函数封装: 2).封装函数主要实现内存的 ...

  10. C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com

    原文:C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | I ...

随机推荐

  1. 【C语言探索之旅】 开宗明义及第一课:什么是编程?

    内容简介 1.课程大纲 2.第一部分第一课:什么是编程? 3.第一部分第二课预告:工欲善其事,必先利其器 ​ 课程大纲 不知道为什么,一直对C语言有一种很深厚的“情怀”(类似老罗对锤子手机的那种),说 ...

  2. Linux开机自启动

    永久打开或则关闭 chkconfig iptables on chkconfig iptables off 即时生效,重启后还原 service iptables start service ipta ...

  3. 解决IIS7中出现An error occurred on the server when processing the URL错误提示的方法

    在IIS7上配置一个asp程序,出现了一个错如提示: An error occurred on the server when processing the URL. Please contact t ...

  4. SVN命令汇总

    1.将文件checkout到本地文件夹  svn checkout path(path是server上的文件夹)  比如:svn checkout svn://192.168.1.1/pro/doma ...

  5. jquery php 百度搜索框智能提示效果

    这个程序是利用php+ajax+jquery 实现的一个仿baidu智能提示的效果,有须要的朋友能够下载測试哦. 代码例如以下 index.html文件,保保存成index.htm <!DOCT ...

  6. redhat6.3已安装was6.1你可以不弹出安装程序

    这在为期两天的课程redhat6.3安装was6.1 使用Xmanager打开图形界面.进入/WAS夹,跑./install 它有一个直接跳转,不管是什么反应,起初我以为这个问题的图形界面,搜索了半天 ...

  7. Java得到年在一个季度的错误的第一天

    1.错误叙述性说明 Exception in thread "main" java.lang.IllegalArgumentException: Cannot format giv ...

  8. 2年SQL Server DBA调优方面总结

    原文:2年SQL Server DBA调优方面总结 2年SQL Server DBA调优方面总结 当2年dba 我觉得,有些东西需要和大家分享探讨,先书单. 书单 1.<深入解析SQL Serv ...

  9. LeetCode 48 Anagrams

    Given an array of strings, return all groups of strings that are anagrams. Note: All inputs will be ...

  10. Windows 2008 卸载 IIS7 批处理

    @echo offcolor 0aecho 正在卸载IIS功能,这可能需要几分钟时间...start /w pkgmgr /uu:IIS-WebServerRole;WAS-WindowsActiva ...