广告位联系
返回顶部
分享到

C语言struct结构体的介绍 C语言struct结构体的详解

C语言 来源:互联网 作者:秩名 发布时间:2022-04-11 21:28:38 人浏览
摘要

一、本章重点 创建结构体 typedef与结构体的渊源 匿名结构体 结构体大小 结构体指针 其他 二、创建结构体 先来个简单的结构体创建 这就是一个比较标准的结构体 1 2 3 4 5 6 7 struct peop

一、本章重点

  • 创建结构体
  • typedef与结构体的渊源
  • 匿名结构体
  • 结构体大小
  • 结构体指针
  • 其他

二、创建结构体

先来个简单的结构体创建

这就是一个比较标准的结构体

1

2

3

4

5

6

7

struct people

{

    int age;

    int id;

    char address[10];

    char sex[5];

};//不要少了分号。

需要注意的是不要少了分号。

那么这样创建结构体呢?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

struct phone

{

    char brand[10];//品牌

    int price;//价格

};

  

struct people

{

    int age;

    int id;

    char address[10];

    char sex[5];

    struct phone;

};

很显然,一个结构体是能够嵌套另一个结构体的。

没有这样的设计,这样做也行

1

2

3

4

5

6

7

8

9

struct people

{

    int age;

    int id;

    char address[10];

    char sex[5];

    char phone_brand[10];

    int phone_price;

};

但结构体中成员太多了是不利于我们后期的维护的,试问:假设有1000个成员,你能快速的找出你需要的成员吗?当有了分块的结构体,我们是能够迅速的定位和查看的。

????结构体能够嵌套另一结构体,那么结构体能否嵌套自己呢?

1

2

3

4

5

6

struct phone

{

    char brand[10];

    int price;

    struct phone;

};

这样做之后编译器会给你一个报错

原因是什么呢?

因为这个结构体的大小是未定义的,你能算出这个结构体的大小吗?这是不可能的!

既然大小不能确定,那么当你用这个结构体去创建变量,编译器该为这个变量开辟多大的空间呢?所有编译器在设计之初便杜绝了这种可能。

在提一个问题,结构体是否可以嵌套自己的结构体指针呢?

1

2

3

4

5

6

7

8

struct people

{

    int age;

    int id;

    char address[10];

    char sex[5];

    struct people* son[2];

};

答案是:可以

这里并不存在空间该分配多少的问题,因为struct people*是指针类型,它的大小是确定的,在32位机器下是4字节,64为机器是8字节。

三、typedef与结构体的渊源

先上一段代码

1

2

3

4

5

6

7

8

9

10

11

12

13

struct people

{

    int age;

    int id;

}a;//a代表什么?

  

  

int main()

{

    a.age = 20;

    printf("%d\n", a.age);

    return 0;

}

提问:a代表什么?

其实我们可以这样去看这个问题

1

2

3

4

5

6

7

8

9

struct people{int age;int id;} a;

  

  

int main()

{

    a.age = 20;

    printf("%d\n", a.age);

    return 0;

}

对比int b呢?

1

2

3

4

5

6

7

8

9

10

int b;

struct people{int age;int id;} a;

  

  

int main()

{

    a.age = 20;

    printf("%d\n", a.age);

    return 0;

}

显然,struct people{int age;int id;}代表的是结构体类型,就像整形类型一样去创建变量。

那么这里的a就是结构体创建的变量。

这里也能明白结构体创建的最后为什么要保留分号。

那我们再看一段代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

typedef struct people

{

    int age;

    int id;

}a;

  

  

int main()

{

    a.age = 20;

    printf("%d\n", a.age);

    return 0;

}

此时加上typedef,a还能当结构体创建的变量吗?

显然不行,此时编译器会报错。

 理解方式以上述一致。

1

typedef struct people{int age;int id;} a;

类似于

1

2

typedef struct people{int age;int id;} a;

typedef int b;

此时的a就是struct people{int age;int id;}

typedef的作用是把struct people{int age;int id;}这一类型重命名为a。

不知道你有没有见过这样的代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

typedef struct people

{

    int age;

    int id;

}b,a,*c;

  

int main()

{

    a a1;

    b b1;

    c c1 = &a1;

    a1.age = 5;

    b1.age = 6;

    c1->age = 10;

    printf("%d %d %d\n", a1.age, b1.age, c1->age);

    return 0;

}

你知道运行结果吗?

这里的b、a、c是什么呢?

这里我就不啰嗦了,a和b都是struct people{int age;int id;}结构体类型,c是struct people{int age;int id;}*  结构体指针类型。

运行结果:

那么再次提升一下

1

2

3

4

5

6

typedef struct people

{

    int age;

    int id;

}b, a, c[20];

这里的b、a、c代表什么呢?

期待着你动手解决这一问题。

四、匿名结构体

这就是一个匿名的结构体

1

2

3

4

5

6

7

8

9

10

11

12

13

struct

{

    int age;

    int id;

};

  

int main()

{

    struct p1;

    p1.age = 10;

    printf("%d\n", p1.age);

    return 0;

}

匿名结构体能这样创建结构体变量吗?

此时编译器会报错

这样的匿名结构体只能在创建结构体的时候定义好变量。

比如这样

1

2

3

4

5

6

7

8

9

10

11

12

struct

{

    int age;

    int id;

}p1;

  

int main()

{

    p1.age = 10;

    printf("%d\n", p1.age);

    return 0;

}

 接下来我们看下这段代码

1

2

3

4

5

6

7

8

9

10

11

12

13

typedef struct

{

    int age;

    int id;

}people;

  

int main()

{

    people p1;

    p1.age = 10;

    printf("%d\n", p1.age);

    return 0;

}

这里我们重命名这个匿名结构体,即把这个结构体类型重命名为people。

那么我们自然可以用people类型来创建p1。也可创建p2、p3等等。

运行结果:

以下代码合法吗?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

//匿名结构体类型

struct

{

 int a;

 char b;

 float c;

}x;

  

struct

{

 int a;

 char b;

 float c;

}a[20], *p;

  

int main()

{

    //在上面代码的基础上,下面的代码合法吗?

    p = &x;

    return 0;

}

警告: 编译器会把上面的两个声明当成完全不同的两个类型。 所以是非法的。

五、结构体大小

如何求结构体类型的大小?

这需要了解结构体成员在内存是怎么存储的。

你知道下面这段代码的运行结果吗?

1

2

3

4

5

6

7

8

9

10

11

12

13

struct people

{

    char a;

    int b;

    char c;

};

  

int main()

{

    struct people p1;

    printf("%d\n", sizeof(p1));//大小是6吗?

    return 0;

}

char是一字节大小

int是四字节大小

char是一字节大小

直接相加等于6

那么这个结构体的大小是6吗?

但我们发现答案是12

这是为什么呢?

简单来说编译器为了读取内存时提升效率和避免读取出错,它做了内存对齐的操作。

什么是内存对齐?

就是让数据安排在合适的位置上所进行的对齐操作。

为什么要内存对齐?

  • 一、移植原因

1.不是所有的硬件平台都能访问任意地址上的任意数据的;

2.某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

  • 二、性能原因:

为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

对齐规则:

Windows中默认对齐数为8,Linux中默认对齐数为4

  • 一:第一个数据成员从偏移地址为0的地方开始存放。
  • 二:对齐数:等于默认对齐数与与该成员大小的较小值。
  • 三:从第二成员开始,从它对齐数的整数倍的偏移地址开始存放。
  • 四:最后结构体的大小需要调整为最大对齐数的整数倍。

最大对齐数:即所有成员对齐数中最大的对齐数。

1

2

3

4

5

6

struct people

{

    char a;

    int b;

    char c;

};

解析:

第一个为char,直接放在偏移地址为0的位置处。

第二个为int,它的自身大小为4,比默认对齐数小,所以它的对齐数是4。

4是4的整数倍,所以在偏移地址为4处开始放数据。

第三个为char,自身大小为1,比默认对齐数小,它的对齐数是1。

8是1的整数倍,从偏移地址为8的位置开始放。

总大小为9,不是最大对齐数的整数倍

要浪费3个空间,调整后为12。

我们再看看下面这段代码:

将char 和 int成员变量交换位置后,这结构体的大小还是12吗?

1

2

3

4

5

6

7

8

9

10

11

12

13

struct people

{

    char a;

    char c;

    int b;

};

  

int main()

{

    struct people p1;

    printf("%d\n", sizeof(p1));//大小是多少呢?

    return 0;

}

解析:

第一个:直接从0处开始放

第二个是char:对齐数是1,1是1的整数倍,从偏移地址为1的位置开始放。

第二个是int:对齐数是4,4是4的整数倍,从偏移地址为4的位置开始放。

放好各个成员变量后,总大小是8,是最大对齐数(4)的整数倍,不需要调整。

因此这个结构体的大小是8.

再来看看下面这个如何?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

struct people

{

    char a;

    int b;

    short c[2];

    char d;

};

  

int main()

{

    struct people p1;

    printf("%d\n", sizeof(p1));//大小是6吗?

    return 0;

}

解析:

第一个成员是char,直接放再0地址处。

第二个成员是int,对齐数是4,从偏移地址为4的位置处开始存放。

第三个成员是short[2],关于数组,它的对齐数是首元素的大小与默认对齐数的较小值,这里它的对齐数是2,然后从偏移地址为8处开始存放4个字节。

第四个成员是char,对齐数是1,直接放开12位置处。

总大小是13,最大对齐数是4,不是最大对齐数的整数倍,需要对齐到最大对齐数的整数倍,浪费3字节大小,对齐到16.

所以这个结构体的大小是16.

六、结构体指针

先创建一个结构体

1

2

3

4

5

struct people

{

    char a;

    int b;

};

然后用该结构体创建变量,再用结构体指针指向该变量。

1

2

3

4

5

6

int main()

{

    struct people p1;

    struct people* p = &p1;

    return 0;

}

所谓结构体指针,即指向结构体的指针。

正如整形指针,即指向整形的指针。

访问变量方式1:

1

2

3

4

5

6

7

8

9

int main()

{

    struct people p1;

    struct people* p = &p1;

    p1.a = 'a';

    p1.b = 10;

    printf("%c %d\n", p1.a, p1.b);

    return 0;

}

访问变量方式2:

1

2

3

4

5

6

7

8

9

int main()

{

    struct people p1;

    struct people* p = &p1;

    p->a = 'a';

    p->b = 10;

    printf("%c %d\n", p->a, p->b);

    return 0;

}

访问变量方式3:

1

2

3

4

5

6

7

8

9

int main()

{

    struct people p1;

    struct people* p = &p1;

    (*p).a = 'a';

    (*p).b = 10;

    printf("%c %d\n", (*p).a, (*p).b);

    return 0;

}

七、其他

结构体中还有两个常见知识点:

一、位端

二、柔性数组

由于篇幅原因,下期会细细讲解这两个知识点


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.csdn.net/m0_62171658/article/details/123862064
相关文章
  • C++中类的六大默认成员函数的介绍

    C++中类的六大默认成员函数的介绍
    一、类的默认成员函数 二、构造函数Date(形参列表) 构造函数主要完成初始化对象,相当于C语言阶段写的Init函数。 默认构造函数:无参的构
  • C/C++实现遍历文件夹最全方法总结介绍

    C/C++实现遍历文件夹最全方法总结介绍
    一、filesystem(推荐) 在c++17中,引入了文件系统,使用起来非常方便 在VS中,可以直接在项目属性中调整: 只要是C++17即以上都可 然后头文件
  • C语言实现手写Map(数组+链表+红黑树)的代码

    C语言实现手写Map(数组+链表+红黑树)的代码
    要求 需要准备数组集合(List) 数据结构 需要准备单向链表(Linked) 数据结构 需要准备红黑树(Rbtree)数据结构 需要准备红黑树和链表适配策略
  • MySQL系列教程之使用C语言来连接数据库

    MySQL系列教程之使用C语言来连接数据库
    写在前面 知道了 Java中使用 JDBC编程 来连接数据库了,但是使用 C语言 来连接数据库却总是连接不上去~ 立即安排一波使用 C语言连接 MySQL数
  • 基于C语言实现简单学生成绩管理系统

    基于C语言实现简单学生成绩管理系统
    一、系统主要功能 1、密码登录 2、输入数据 3、查询成绩 4、修改成绩 5、输出所有学生成绩 6、退出系统 二、代码实现 1 2 3 4 5 6 7 8 9 10 11
  • C语言实现共享单车管理系统

    C语言实现共享单车管理系统
    1.功能模块图; 2.各个模块详细的功能描述。 1.登陆:登陆分为用户登陆,管理员登陆以及维修员登录,登陆后不同的用户所执行的操作
  • C++继承与菱形继承的介绍

    C++继承与菱形继承的介绍
    继承的概念和定义 继承机制是面向对象程序设计的一种实现代码复用的重要手段,它允许程序员在保持原有类特性的基础上进行拓展,增加
  • C/C++指针介绍与使用介绍

    C/C++指针介绍与使用介绍
    什么是指针 C/C++语言拥有在程序运行时获得变量的地址和操作地址的能力,这种用来操作地址的特殊类型变量被称作指针。 翻译翻译什么
  • C++进程的创建和进程ID标识介绍
    进程的ID 进程的ID,可称为PID。它是进程的唯一标识,类似于我们的身份证号是唯一标识,因为名字可能会和其他人相同,生日可能会与其他
  • C++分析如何用虚析构与纯虚析构处理内存泄漏

    C++分析如何用虚析构与纯虚析构处理内存泄漏
    一、问题引入 使用多态时,如果有一些子类的成员开辟在堆区,那么在父类执行完毕释放后,没有办法去释放子类的内存,这样会导致内存
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计