当前位置: 首页 > news >正文

自定义类型:结构体

C语言已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类型还是不够的,假设我想描述学生,描述⼀本书,这时单⼀的内置类型是不行的。 描述一个学生需要名字、年龄、学号、身高、体重等; 描述一本书需要作者、出版社、定价等。C语言为了解决这个问题,增加了结构体这种自定义的数据类型,让程序员可以自己创造适合的类型。

 1.结构的声明

struct tag
{
   member-list;
}variable-list;

描述一个学生:

struct Stu
{
   char name[20];
   int age;
   char sex[5];
   char id[20];
};

1.1 结构体变量的定义和初始化

//代码1:变量的定义
struct Point
{
   int x;
   int y;
}p1;     //声明类型的同时定义变量p1
struct Point p2;  //定义结构体变量p2

//初始化
struct Point p3 = {10, 20};

//代码2
struct Stu
{
   char name[20];
   int age;
};
struct Stu s1 = {"zhangsan", 20};//初始化
struct Stu s2 = {.age = 20, .name = "lisa"};//指定顺序初始化

//代码3
struct Node
{
   int data;
   struct Point p;
   struct Node* next;
}n1 = {10, {4, 5}, NULL};//结构体嵌套初始化

struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

1.2 结构体访问操作符

1.2.1 结构体成员的直接访问

结构体成员的直接访问是通过点操作符(.)访问的。点操作符接受两个操作数。如下:

#include <stdio.h>
struct Point
{
   int x;
   int y;
}p = {1, 2};
int main()
{
    printf("x: %d y: %d\n", p.x, p.y);
return 0;
}

使用方式:结构体变量.成员名 

1.2.2 结构体成员的间接访问

#include <stdio.h>
struct Point
{
   int x;
   int y;
};
int main()
{    
    struct Point p = {3, 4};
    struct Point *ptr = &p;
    ptr->x = 10;
    ptr->y = 20;
    printf("x: %d y: %d\n", ptr->x, ptr->y);
return 0;
}

使用方法:结构体指针->成员名

综合举例:

#inlcude <stdio.h>
#include <string.h>
struct Stu
{
   char name[20];
   int age;
};

void print_stu(struct Stu s)
{
     printf("%s %d\n", s.name, s.age);
}
 
void set_stu(struct Stu* ps)
{
     strcpy(ps->name, "lisa");
     ps->age = 20;
}

int main()
{
    struct Stu s = { "zahngsan", 20};
    print_stu(s);
    set_ stu(&s);
    print_stu(s); 
    return 0;
}

1.3结构的特殊声明

在声明结构时,可以不完全声明。

//匿名结构体类型
struct 
{
   int a;
   int b;
   float c;
}x;


//匿名结构体指针类型
struct
{
   int a;
   char b;
   float c;
}a[20], *p;

上面的两个结构在声明的时候省略掉了结构体标签(tag)。

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

p = &x;

警告:

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

匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次。

2.结构体的自引用

在结构中包含一个类型为该结构本身的成员是否可以呢?

如,定义一个链表的节点:

struct Node 
{
   int data;
   struct Node next;
};

上面的代码正确吗?如果正确,那sizeof(struct Node)是多少?

仔细分析,其实是不行的,因为一个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。

正确的自引用方式:

struct Node 
{
   int data;//数据域
   struct Node* next;//指针域
};

在结构体自引用使用的过程中,夹杂了typedef对匿名结构体类型重命名,也很容易引入问题,如:

typedef struct 
{
   int data;
   struct Node* next;
}Node;

因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。

解决方案如下:

typedef struct Node
{
   int data;
   struct Node* next;
}Node;

定义结构体尽量不要使用匿名结构体了。

3.结构体内存对齐

我们已经掌握了结构体的基本使用了。

现在我们深入讨论一个问题:计算结构体的大小。

这也是一个特别热门的考点:结构体内存对齐

3.1对齐规则

首先得掌握结构体的对齐规则:

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。
- VS 中默认的值为 8
- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的
整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
//练习1
struct S1
{
   char c1;
   int i;
   char c2;
};

由上图可知,到c2总共占用了9个字节,再根据第4条规则可知,最大对齐数为4,所以该结构体的大小是12个字节。 

//练习2
struct S2
{
   char c1;
   char c2;
   int i;
};

 

由上图可知,到i总共占用了8个字节,再根据规则4,最大对齐数为4,所以该结构体的大小为8个字节。

//练习3
struct S3
{
   double d;
   char c;
   int i;
};

 

由图可知,到i总共占用16个字节,再根据规则4,最大对齐数为8,所以该结构体的大小为16个字节。

//练习4-结构体嵌套问题
struct S4
{
   char c1;
   struct S3 s3;
   double d;
};

 

由图可知到d总共占用了32个字节,再根据规则4,最大对齐数为8,所以改结构体的大小为32个字节。

3.2为什么存在内存对齐?

平台原因(移植原因):

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

 总的来说:结构体的内存对齐是拿空间来换取时间的做法。

所以在设计结构体的时候,我们既要满足对齐,又要节省空间,要让占用空间小的成员尽量集中在一起。

例如:

struct S1
{
   char c1;
   int i;
   char c2;
};
//变成
struct S2
{
   char c1;
   char c2;
   int i;
};

3.3修改默认对齐数

#pragma这个预处理指令,可以改变编译器的默认对齐数。

#include <stdio.h>
#pragma pack(1)//设置默认对齐数为1
struct S
{
   char c1;
   int i;
   char c2;
};
#pragma pack()//取消设置的对齐数,还原为默认
int main()
{
    printf("%d\n", sizeof(struct S));
    return 0;
}

结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。

4.结构体传参

struct S
{
   int data[1000];
   int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
     printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
   printf("%d\n", ps->num);
}
int main()
{
    print1(s);//传结构体
    print2(&s);//传地址
    return 0;
}

上面的print1print2函数哪个更好?

答案是:print2

原因:

结论:结构体传参的时候,要传结构体的地址。

相关文章:

  • 【Java】——数组深度解析(从内存原理到高效应用实践)
  • CentOS 7上手动强制升级docker-compose(不使用yum)
  • 苍穹外卖项目结构
  • Loadrunner报错Virtual User Generator 初始化用户生成器失败 初始化失败
  • AI产品的架构(分四层)
  • ArkUI —— 组件导航
  • zk基础—1.一致性原理和算法二
  • Android:Dialog的使用详解
  • K8S 存储:emptyDir、hostPath、local详解
  • 观察者模式在Java单体服务中的运用
  • 质检LIMS实验室系统在塑料制品的应用 LIMS系统重塑塑料制品质检
  • leetcode hot100 多维动态规划
  • hadoop集群的常用命令
  • 《Spring Boot全栈开发指南:从入门到生产实践》
  • JAVA反序列化深入学习(十一):Spring1
  • 【Pandas】pandas Series to_latex
  • 力扣hot100_贪心算法
  • 实现基于Vue的后台管理系统权限控制与动态路由
  • 【避坑指南】RAGFlow + Ollama + Deepseek 构建本地知识库
  • 逻辑损失以及梯度下降的实现
  • 桂林疫情/seo人员工作内容
  • 怎么做淘宝网站/阿里指数数据分析平台官网
  • 广东省建设厅安全员b证报名网站/深圳最新疫情最新消息
  • 西安黄页网/百度官方优化软件
  • 毕业设计开题报告网站开发/营销手机都有什么功能啊
  • 枞阳县建设局网站/百度推广在哪里