结构体笔记(结构体嵌套、自引用,结构体指针)

结构体(struct)

1、基本概念

结构体-----将不同类型的数据成员组织到统一的名字之下,适用于对关系紧密,逻辑相关、具有相同或不同类型的数据进行处理

2、结构体定义格式

定义结构

为了定义结构,您必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:

struct 标签名

{

类型 变量名;

类型 变量名;

······

} 结构变量 (结构体名字);

struct tag
{
member_list
member_list
member_list

​ …

​ } variable_list ;

tag 是结构体标签。

member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。

variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。下面是声明student结构的方式:

struct student
{
    char  name[50]; //名字
    int   id;      //学号
} student1;

(声明结构体类型仅仅是声明了一个类型,系统并不为之分配内存,就如同系统不会为类型 int 分配内存一样。只有当使用这个类型定义了变量时,系统才会为变量分配内存。所以在声明结构体类型的时候,不可以对里面的变量进行初始化。)

定义了一个tag为student的结构体和一个结构变量student1,如果省略变量名(student1),就变成了对结构的声明,上述结构体声明也可分开写

struct student
{
    char  name[50]; //名字
    int   id;      //学号
} ;
struct student student1;

与上面效果相同,可理解为struct student类似于int,而我们用的是student1类似于变量,如果省略结构名,则称之为无名结构,这种情况常常出现在函数内部,或者说你只需要student1这一个变量,

后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名

struct 
{
    char  name[50]; //名字
    int   id;      //学号
} student1;

(在声明结构体时常常与typedef函数配合使用)

3、结构体变量的初始化

和其它类型变量一样,对结构体变量可以在定义时指定初始值。

例:

struct student
{
    char  name[50];
    int   id;
} charon = {"charon", 666};
int main()
{
    printf ( "name : %s\nid: %d\n", charon.name, charon.id );

输出:

4、结构体成员的访问

访问结构体变量的成员必须使用成员选择运算符(也称圆点运算符),格式为:结构体变量名.成员名

若使用指针对结构体成员进行访问,*格式为:指针->成员名 等价于 (指针).成员名 ​

相同点:两个操作符都是二元操作符,且其有操作符是结构体成员的名称。

不同点:“ . ”操作符左边的操作数是一个“结构体”的表达式,而“ -> ”操作符左边的操作数是一个指向结构体的指针。

例:

typedef struct
{
    int num;
    float score;
    char name[10];
} STUDENT;
STUDENT temp;
STUDENT * p = &temp;

在这里temp.score代表的是结构体temp里的成员score。

p->score代表指向temp结构体成员score的指针。

为了使用方便和直观,C语言允许把(*temp).score用p->score来替换。

也就是p->score等价与(*temp).score。

所以在结构体中“ . ”和“ -> ”的用法相似,但是并不等价。

5、typedef函数

为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等),

(注意与#define的区别,typedef 是用来定义一种类型的新别名的,它不同于宏#define,宏是简单的字符串替换)

例:

为int定义了一个新的名字INTEGER,也就是说INTEGER与int是同义词,也可以为结构体定义一个别名

typedef struct student STUDENT;

或者

typedef struct student
{
  int num;
} STUDENT;

上述两条语句是等价的,二者都是为struct student结构体类型定义了一个新的名字STUDENT,即STUDENT与struct student是同义词,所以下列两条语句等价

STUDENT stu1,stu2;
struct student stu1, stu2;

6、结构体嵌套

就是在一个结构体内包含了另一个结构体作为其成员

相互引用

使用typedef
typedef struct date
{
    int year;
    int month;
    int day;
} DATE;
typedef struct student
{
    long studentID;
    char studentName[10];
    char studentSex;
    DATE birthday;
    int score[4];
} STUDENT;
STUDENT charon;

上面代码中,定义了结构体变量birthday和charon,并给struct date和struct student分别取别名为DATE和STUDENT,

当出现结构体嵌套时,必须以级联方式访问结构体成员,即通过成员选择运算符逐级找到最底层的成员时再引用

charon.birthday.day = 10;
printf ( "%d", charon.birthday.day );
不使用typedef

上面是使用了typedef的结构体嵌套,也可以不使用,代码如下

struct STUDENT
{
    long studentID;
    char studentName[10];
    char studentSex;
    int score[4];
    struct DATE
    {
        int year;
        int month;
        int day;
    } birthday;
} student;

定义了结构体变量student和birthday,引用成员的的方法和上面类似

student.birthday.day = 100;
printf("%d", student.birthday.day);

C语言允许对具有相同结构体类型的变量进行整体赋值,注意:对字符数组型结构体成员进行赋值时一定要使用strcpy()

strcpy(stu1.studentName, “张三”);

而不能写成

stu2.studentName = stu1.studentName

因为结构体成员studentName是一个字符型数组,studentName是该数组的名字,代表字符型数组的首地址,是一个常量,不能作为赋值表达式的左值。

自引用(self reference)(重点)

使用typedef 时

错误的方式:

typedef struct tag{ 
  int value; 
  charon *link; /* 虽然也使用指针,但这里的问题是:charon尚未被定义 */ 
} charon; 

这里的目的是使用typedef为结构体创建一个别名charon。但是这里是错误的,因为类型名的作用域是从语句的结尾开始,而在结构体内部是不能使用的,因为还没定义。
正确的方式:有三种,差别不大,使用哪种都可以。

当你使用typedef去代替整个结构体并且以原结构体名代替的时候,如果此时你内部还自嵌套了,那么内部的成员变量之前一定要加上struct。

/* 方法一 */ 
typedef struct tag_1{ 
  int value; 
  struct tag_1 *link;  
} charon; 
 
 
/* 方法二 */ 
struct tag_2;  //结构体的不完全声明
typedef struct tag_2 charon; 
struct tag_2{ 
  int value; 
  charon *link;   
}; 
 
 
/* 方法三 */ 
struct tag_3{ 
  int value; 
  struct tag *link;  
}; 
typedef struct tag_3 charon; 

不使用typedef时

错误的方式:

struct tag_1
{
    struct tag_1 A;  /* 结构体 */
    int value;
};

这种声明是错误的,因为这种声明实际上是一个无限循环,成员b是一个结构体,b的内部还会有成员是结构体,依次下去,无线循环。在分配内存的时候,由于无限嵌套,也无法确定这个结构体的长度,所以这种方式是非法的。

正确的方式: (使用指针):

struct tag_1{ 
  struct tag_1 *A; /* 指针 */ 
  int value; 
}; 

由于指针的长度是确定的(在64位机器上指针长度为8),所以编译器能够确定该结构体的长度。

可以用sizeof查看:

在这里插入图片描述

​如何理解结构中包含一个指向结构本身的指针?

至于指向自己的问题,结构体只是个数据类型,不是一个变量。

只有在声明一个结构体变量的时候才会在内存中分配一段空间,指针中存放的正是变量在内存中的地址。所以不是指针指向了结构体自己,而是指针指向了结构体变量。

C语言中的类型有完整和不完整之分,只有完整类型的变量才能够使用。完整代表已确定了类型的大小,底层只需要大小属性,而不需要类型属性。

指针类型都是完整的,因为指针类型的大小都是确定的。struct tag * 是指针类型,而不是结构类型。结构的成员类型都是完整的,那么结构类型就是完整的。

程序员可以使用强制类型转换来将一段内存转换为需要的数据类型,例如下面有一个数组a,现在将其强制转换为一个结构体类型stu:

#include <stdio.h>
typedef struct student
{
    int   id;
    char  name[50];
} stu;

int a[10]={1,2,3,4,5};

int main()
{
    stu *student1;
    student1 =(student*)a;
    printf("student1->name=%d\n",student1->name);
    printf("student1->id=%d\n",student1->id);
    return 0;
}

在这里插入图片描述

可以看到a[10]被强制转换为student结构体类型,当然不使用强制类型转换也是可以的,只是编译器会报警报。

“一个结构体里包含一个指针,这个指针指向的数据类型是这个结构体的数据类型”。

例子:

typedef struct Node
{
    int data;
    Node* next;
} node;
int main(){
    node node1;
    node node2={666};
    node node3;
    node1.next = &node2;
    node2.next = &node3;
    node3.next = NULL;
    cout<<&node2<<endl;
    cout<<node1.next<<endl;
    cout<<node2.data<<endl;
    cout<<node1.next->data<<endl;
    return 0;
}

在这里插入图片描述
![assets/image-20221125140606-4tpsamp.png]在这里插入图片描述

参考链接

结构体(结构体嵌套、结构体指针、结构体参数传递) - 蓝海人 - 博客园 (cnblogs.com)

如何理解结构中包含一个指向结构本身的指针? - 知乎 (zhihu.com)

详解C语言中结构体的自引用和相互引用_C 语言_脚本之家 (jb51.net)

C 结构体 | 菜鸟教程 (runoob.com)

​  知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

使用者可以对本创作进行转载、节选、混编、二次创作,但不得运用于商业目的,且使用时须进行署名,采用本创作的内容必须同样采用本协议进行授权。

  • 14
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_charon_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值