根据上一篇文章的介绍,此时可以用 *p 来访问结构体变量 stu1 的值,用 (*p).num 来访问 stu 的成员变量。C 语言为了使用方便和直观,定义可以把 (*p).num 改用 p->num 来代替,它表示 p 所指向的结构体变量中的 num 成员。
也就是说,以下 3 种形式 等价 :
(1)结构体变量.成员名: stu1.num
(2)( 指针变量名).成员名:`( p).num (3)指针变量名->成员名: p->num`
指向结构体数组的指针
在上一篇文章中,已经介绍了可以使用指向数组或数组元素的指针,同样地,对于结构体数组及其元素也可以用指针变量来指向,例如:
struct student { int num; char name[20]; char sex; int age; float score; char addr[30]; }; struct student stu[3] = {{10101, "Li Lin", 'M', 18, 87.5, "Beijing"}, {10102, "Amey", 'M', 17, 92, "Shanghai"}, {10103, "Bingo", 'F', 20, 100, "Fujian"}}; struct student *p = stu;
此时,指针变量 p 指向数组首个元素的地址,即 &stu[0] ,也就是数组名 stu ,具体的细节上篇文章已经介绍过,我们这里不再赘述。
结构体指针使用场景
(1)函数参数:用指向结构体变量(或数组)的指针作实参,将结构体变量(或数组)的地址传给形参。
void printStudentInfo(struct student *p);
因为如果我们直接用结构体变量(不是结构体指针)作为实参时,由于采取的是“值传递”的方式,将结构体变量所占用的内存单元的内容全部顺序传递给形参,形参也必须是同类型的结构体变量。在函数调用期间,形参也要占用内存单元,这种传递方式将带来较大的时间和空间开销,同时也不利于将在函数执行期间改变形参结构体的值(结果)返回给主调函数,因此一般比较少直接“用结构体变量做实参”,而是改用指针的形式。
(2)链表链表是一种常见的且很重要的数据结构,一般用于 动态地 进行存储分配。常见的有单链表和双链表等,一般可以用结构体来表示链表的节点,如下为常见的“单链表”节点的声明:
struct ListNode { int val; struct ListNode *next; };
其中, val 表单链表节点的值, next 指针用于指向链表的下一个节点。
例如,面试比较常考察的“反转单链表”的题目:
struct ListNode *reverseList(struct ListNode *head) { if (head == NULL) { return NULL; } if (head->next == NULL) { return head; } struct ListNode *reversedHead = NULL; struct ListNode *prevNode = NULL; struct ListNode *currentNode = head; while (currentNode != NULL) { struct ListNode *nextNode = currentNode->next; if (nextNode == NULL) { reversedHead = currentNode; } currentNode->next = prevNode; prevNode = currentNode; currentNode = nextNode; } return reversedHead; }
(3)二叉树
struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; };
其中 val 表示二叉树叶子节点的值, left 指向节点的左子树, right 指向右子树。
例如,之前闹得沸沸扬扬的 Google 面试“翻转二叉树”的题目:
struct TreeNode *invertTree(struct TreeNode *root) { if (root == NULL) { return NULL; } root->left = invertTree(root->left); root->right = invertTree(root->right); struct TreeNode *temp = root->left; root->left = root->right; root->right = temp; return root; }
动态开辟和释放内存空间