【C结构体】结构体都不会,学啥数据结构(进阶版)

简介: 【C结构体】结构体都不会,学啥数据结构(进阶版)

一.结构体

  • 数据经常会以组的形式存在,例如:用结构体描述一个复杂对象的基本信息–学生,这些值能够存储在一起,访问起来就会简单一些,但是由于这些值的类型互不相同,则无法使用数组存储,因此便有了结构体

1-1结构体类型的声明

251a5f277bfd4b5fbb1253a03b3af40b.png

用结构体描述一个复杂对象的基本信息:学生
struct Stu
{//Stu是结构体标签,struct Stu才是结构体类型,相当于int
  char name[20];名字
  int age;年龄
  char sex[2];性别
  char id[20];学号
};分号不能丢

数组元素可以通过下标访问,是因为数组的元素长度相同,但是结构体的成员变量的类型不同,因此不能使用下标访问结构体的成员变量

1-2结构体的自引用

正确的结构体自引用:结构体的
struct Stu1
{
  int a;
  char b;
  struct Stu1* c;结构体指针变量
};
错误的结构体自引用:
struct Stu2
{
  int a;
  char b;
  struct Stu2 c;结构体
};

结构体包含一个类型为该结构体本身的成员是非法的,有点像永不终止的递归程序; 但是结构体内部包含一个指向该结构体本身的指针是合法的,在单链表中很常见

单链表中结构体中包含结构体指针的使用:
typedef struct Node
{
  int date;
  struct Node* ps;但不能写成ListNode* ps
//因为这个typedef声明的类型名直到声明的末尾才被定义
}ListNode;

此处的typedef是给struct Node进行了类型重命名,也类似于int,以后就可以直接用Node定义结构体变量

typedef类型重命名前定义结构体变量:
struct Node newnode;
typedef类型重命名后定义结构体变量:(是不是少些了个struct,适当偷懒)
ListNode newnode;

1-3结构体变量的定义和初始化

结构体的初始化方式和数组的初始化方式很像,结构体成员是否包含数组分别对于一维和二维数组的初始化很像

struct Stu
{
  char name[20];
  int age;
  char sex[2];
}student1={{宋小宝},18,男};
  • 结构体的成员变量不能是结构体本身,但是可以是另一个结构体
typedef struct date
{
  int year;
  int month;
  int day;
}Date;
struct Stu
{
  Date a;
  int number;
};

图解:

7b3ade6eef714de8bc21c349b77118f3.png

1-4结构体内存对齐(求结构体所占字节数)

什么是偏移量和对齐数?

  • 偏移量:相对于第一位置存放开始,到第二个位置存放开始之间的地址差

偏移量可以使用offsetof宏来求得(定义与stddef.h)

offsetof(type,member)例子即是offsetof(struct Stu,age)

对齐数:该成员变量的字节数和编译器默认对齐数(VS默认是8)中的最小值

每一个成员变量都有自己的对齐数

结构体内存对齐规则:


第一个成员在与结构体变量偏移量为0的地址处

从第二个成员开始的每个成员变量要对齐到对齐数的整数倍处

结构体的整体大小就是最大对齐数的整数倍

如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍中,结构体的整体大小就是所有最大对齐数的整数倍

概念麻烦,实际简单的一批

给你举个例子画个图吧
struct AKIGN
{
  char a;第一个数不用考虑内存对齐,直接放
  int b;第二个数放的位置(偏移量)要是int字节(4)的整数倍,也就是4
  char c;第三个数放的位置(偏移量)要是char字节(1)的整数倍,也就是9
};

由于结构体的整体大小就是所有最大对齐数(这里是4)的整数倍,所以结果总共是占12个字节

  • 图解:
  • 577307bddc1f43cdb56e26c58a708cf1.png

  • 总之就是要综合考虑数据类型和偏移量,匹配整数倍就可以啦,是不是很简单


为什么存在内存对齐?


1.1、平台原因:不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据

2、性能原因:,对于访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问,使得处理起来速度快。


本质:


空间换时间\


怎样设计可以减少空间浪费?


使得空间小的成员尽量集中在一起


学会了?来几道测试题?

// 练习2
struct S2
{
  char c1;   1
  char c2;   1-2
  int i;     4-8
};
printf("%d\n", sizeof(struct S2));//要是double(8)个字节的整数倍,答案:占8个字节
//练习3
struct S3
{
  double d;     1-8
  char c;       9
  int i;        12-16
};
printf("%d\n", sizeof(struct S3));//要是double(8)个字节的整数倍,答案:占16个字节
//练习4-结构体嵌套问题
//如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍中
struct S4
{
  char c1;           1
  struct S3 s3;      8-16
  double d;          16-24
};
printf("%d\n", sizeof(struct S4));//要是double(8)个字节的整数倍,答案:占24个字节

修改默认对齐数

#include <stdio.h>
#pragma pack(4)//设置默认对齐数为4
struct S1
{
 char c1;
 double i;默认对齐数(4)和成员变量的字节数(8)比较取最小的为默认对齐数(4):4个字节
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为8
struct S2
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S1));答案是16个字节
    printf("%d\n", sizeof(struct S2));答案是6个字节
    return 0;
}

1-5结构体传参

结构体传参的两种方式:(不考虑效率而言)

1.传结构体本身:
print_struct(struct Stu);
2.传结构体地址:
print_struct(&struct Stu);

关于效率高的传参方式:传结构体指针

函数形参要压栈,而结构体指针相对占用栈内存小

如果希望传过去修改成员,则只有传结构体指针

如果不希望被修改也可以使用const修饰,进行保护

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;
}

1-6结构体实现位段(bit field)

有的时候我们存放的一个数据并不需要一个字节那么大的空间,而只需要几个比特位就够了,比如我们用二进制表示灯泡的开关,只需要0和1就足够表示这两种状态,那么怎么实现这样减少空间的浪费呐?这就需要用到我们强大的位段。


位段位段,位顾名思义就是二进制位,段就是路段,长度,综合就是二进制位的长度

位段和结构体的声明的区别:


他的成员后面相对于结构体多一个冒号和整数,这个整数指定该位段所占用的位的数目。

位段成员必须声明为int ,unsigned int ,signed int 或char类型

struct S
{
 char a : 3;
 char b : 4;
 char c : 5;
 char d : 4;
};
int main()
{
  struct S s = { 0 };
  s.a = 10;
  s.b = 12;
  s.c = 3;
  s.d = 4;
  printf("%d\n", sizeof(struct S));
}

e6503f22e9f1473f86a769b3f1e4eefc.png

注意:注重可移植性的程序应该避免使用位段:(理由如下)


int 位段被当成有符号数还是无符号数是不确定的。

位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机

器会出问题。

位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

联合体:联合体就是共用同一块内存

#include<stdio.h>
union UN//如果省去UN就是匿名结构体类型,只能使用一次
{
  int a;
  char ch;
};
int main()
{
  //判断猜测:共用同一块内存
  union UN u;
  printf("%p\n", &(u.ch));//地址1
  printf("%p\n", &(u.a));//地址1
  printf("%d\n", sizeof(u));//4
  u.a = 1;
  printf("%d\n", u.a);//1
  //通过共用一块内存通过改变u.c就能改变u.a
  u.ch = 0;
  printf("%d\n", u.ch);//0
    //判断是大端还是小端
  printf("%d\n", u.ch);//0
  return 0;
}

4c3f5259e33741909adde8a2ae084961.png

判断机器是大端机还是小端机:

union un
{
  int a;
  char c;
};
int test1()
{
  int n = 1;;
  return *(char*)&n;
}
int test2()
{
  union un u;
  u.a = 1;
  return u.c==1;
}
//这里都是使得int类型的变量赋值为1,也就是十六进制的都是00 00 00 01,然后通过让char或者联合本身的特点访问其中的低地址的内容,如果最低的字节内容是1,则是整数的最低字节的内容放在了低地址处,则是小端,反之。。
int main()
{
  int ret=test1();
  if (ret == 0) printf("大端\n");
  else if(ret==1) printf("小端\n");
}

计算联合体的大小:

目录
相关文章
|
2天前
|
存储 编译器 Linux
【C语言】【数据结构】自定义类型:结构体
这是一篇对结构体的详细介绍,这篇文章对结构体声明、结构体的自引用、结构体的初始化、结构体的内存分布和对齐规则、库函数offsetof、以及进行内存对齐的原因、如何修改默认对齐数、结构体传参进行介绍和说明。
39 0
|
2天前
|
C语言 开发者
【数据结构】C语言结构体详解
【数据结构】C语言结构体详解
56 0
|
10月前
|
C语言
【C语言进阶篇】看完这篇结构体文章,我向数据结构又进了一大步!(结构体进阶详解)(下)
【C语言进阶篇】看完这篇结构体文章,我向数据结构又进了一大步!(结构体进阶详解)(下)
272 0
|
10月前
|
存储 编译器 C语言
【C语言进阶篇】看完这篇结构体文章,我向数据结构又进了一大步!(结构体进阶详解)(上)
【C语言进阶篇】看完这篇结构体文章,我向数据结构又进了一大步!(结构体进阶详解)
102 0
|
程序员 C语言
【C语言】学数据结构前必学的结构体struct详细
本文讲解:学数据结构前必学的结构体struct详细。
|
存储 C语言
数据结构的桥梁---结构体(重要)
数据结构的桥梁---结构体(重要)
81 0
|
存储 编译器 C语言
深入理解C语言结构体(数据结构基础)
一:结构体定义与初始化引用 1:结构体是什么?它的特点? <1>相对于数组存储结构的区别? 数组是一种存储结构,一种可以存放相同类型的存储结构。比如int类型的存储结构就只能存放Int类型的数据,但是你若是想要描述清除一个学生的身份信息,一个数组绝对是不行的,比如名字,学号等这些,必须要使用多个数组来说明学生的信息。我们还需要构造关联的索引形成一一的对应,如果大型项目这样做,那么就会无比臃肿了。
200 0
深入理解C语言结构体(数据结构基础)
数据结构学习(结构体复习)
结构体 为什么会出现结构体 为了表示一些复杂的数据,而普通的基本类型变量无法满足要求 什么叫做结构体 结构体是用户根据实际需要自己定义的复合数据类型 如何使用结构体 两种方式: struct Student st = {1000,"zhangxu",20}; struct Student *pst = &st; 1.
884 0
|
22小时前
|
前端开发 JavaScript 算法
JavaScript 中实现常见数据结构:栈、队列与树
JavaScript 中实现常见数据结构:栈、队列与树
|
2天前
|
存储 NoSQL C语言
数据结构——顺序栈与链式栈的实现-2
数据结构——顺序栈与链式栈的实现
数据结构——顺序栈与链式栈的实现-2