bilibiliclass54-56_C语言_结构体

结构体

目录

结构体
    结构体类型的声明和变量的定义
    结构的自引用
    结构体初始化
    结构体内存对齐
    结构体传参
 

结构的基础知识:结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

结构体类型的声明和变量的定义

struct tag
{
member-list;//成员列表
}variable-list;//变量列表

简单例子:

创建学生s1,s2,s3,s4,s5

例如描述一个学生:
//声明一个结构体类型
//声明一个学生类型,是想通过学生类型来创建学生变量(对象)
//描述学生:名字+年龄+性别+学号
 

struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s4,s5,s6;//分号不能丢
//s4,s5,s6全局变量

    struct Stu s3;//s3全局变量

int main()
{
    //创建的结构体变量
    //s1,s2局部变量
    struct Stu s1;
    struct Stu s2;    
}

 

特殊的声明

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

使用场景:通常在只需要一次的结构体使用
 

简单例子:

//匿名结构体类型:没有名字,省略掉了结构体标签(tag)
struct
{
int a;
char b;
float c;
}x;//这能在这里创建变量

struct
{
int a;
char b;
float c;
}* px;//匿名结构体指针

int main()
{
px=&x;//警告: 编译器会把上面的两个声明当成完全不同的两个类型,是非法的
}

 

结构的自引用

数据结构:数据在内存中存储的结构
链表:1 -> 2 -> 3 -> 4 -> 5

|-------------------------------------------------------|
|数值域  指针域(存放下一个数据的地址)    |
|-------------------------------------------------------|

//err代码1
结构中包含一个类型为该结构本身的成员是 错误 的

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


否则sizeof(struct Node)=无穷大


正确的自引用方式:
 

//代码2
struct Node
{
    int data;
    struct Node* next;
};


重命名:给struct Node起一个小名叫Node
 

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


结构体变量初始化

 

//初始化:定义变量的同时赋初值。
#include <stdio.h>
struct Point
{
    int x;
    int y;
};

struct Node    //类型声明
{
    int data;
    struct Point p;
    struct Node* next;
}n1 = { 10, {4,5}, NULL };//结构体嵌套初始化

struct Node n2 = { 10, {4,5}, NULL };//初始化

int main()
{
    struct Node n3 = { 10, {4,5}, NULL };//初始化
    printf("%d %d %d %s", n1.data, n1.p.x, n1.p.y, n1.next);
    return 0;
}
//结果:10 4 5 (null)

 

结构体内存对齐

问题:请计算结构体的大小,4个printf的结果是多少?

struct S1
{
char c1;
int i;
char c2;
};

struct S2
{
char c1;
char c2;
int i;
};

struct S3
{
double d;
char c;
int i;
};

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

int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
printf("%d\n", sizeof(struct S3));
printf("%d\n", sizeof(struct S4));
return 0;
}

答案:
12
8
16
32


解析:求结构体大小标准步骤

struct S1//12
{
char c1;
int i;
char c2;
};


1.求出成员大小
char c1;            //1字节
int i;                  //4字节
char c2;            //1字节
2.求出对齐数
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
gcc没有默认对齐数
以VS为例:
char c1;          //1字节/8字节-->1字节
int i;                 //4字节/8字节-->4字节
char c2;          //1字节/8字节-->1字节
3.求出结构体总大小
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
结构体总大小:4的整数倍
4.画出内存结构
第一个成员在与结构体变量偏移量为0的地址处
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
char c1;       //1的整数倍
int i;             //4的整数倍
char c2;       //1的整数倍
存完char c2后偏移量为9,不是4的整数倍,所以为12


 

|            |
|......      |
|------------|<----假设从这里开始放(偏移量为0)
| char c1    |
|------------|偏移量为1
|  浪费      |
|------------|偏移量为2
|  浪费      |
|------------|偏移量为3
|  浪费      |
|------------|偏移量为4<----4的整数倍
|  int i     |
|------------|偏移量为5
|  int i     |
|------------|偏移量为6
|  int i     |
|------------|偏移量为7
|  int i     |
|------------|偏移量为8<----1的整数倍
| char c2    |
|------------|偏移量为9
|  浪费      |
|------------|偏移量为10
|  浪费      |
|------------|偏移量为11
|  浪费      |
|------------|偏移量为12
|......      |
|            |


S2求法同上:

struct S2//8
{
char c1;
char c2;
int i;
};

 

struct S3//16
{
double d;
char c;
int i;
};


1.求出成员大小
double d;    //8
char c;        //1
int i;        //4
2.求出对齐数
double d;    //4
char c;        //1
int i;        //4
3.求出结构体总大小
    4的整数倍
4.画出内存结构
double d;    //4
char c;        //1
int i;        //4

|         |
|......   |
|---------|<----假设从这里开始放(偏移量为0)
|double d |
|---------|偏移量为1
|double d |
|---------|偏移量为2
|double d |
|---------|偏移量为3
|double d |
|---------|偏移量为4
|double d |
|---------|偏移量为5
|double d |
|---------|偏移量为6
|double d |
|---------|偏移量为7
|double d |
|---------|偏移量为8<----1的整数倍
|char c   |
|---------|偏移量为9
|  浪费   |
|---------|偏移量为10
|  浪费   |
|---------|偏移量为11
|  浪费   |
|---------|偏移量为12<----4的整数倍
|int i    |
|---------|偏移量为13
|int i    |
|---------|偏移量为14
|int i    |
|---------|偏移量为15
|int i    |
|---------|偏移量为16


 

struct S4//32
{
char c1;
struct S3 s3;
double d;
};

1.求出成员大小
char c1;//1
struct S3 s3;//16
double d;//8
2.求出对齐数
char c1;//1
struct S3 s3;//8
double d;//4
3.求出结构体总大小
    8的整数倍
4.画出内存结构
char c1;//1
struct S3 s3;//8
double d;//4

|            |
|......      |
|------------|<----假设从这里开始放(偏移量为0)
|char c1     |
|------------|偏移量为1
|  浪费      |
|------------|偏移量为2
|  浪费      |
|------------|偏移量为3
|  浪费      |
|------------|偏移量为4<----4的整数倍
|  浪费      |
|------------|偏移量为5
|  浪费      |
|------------|偏移量为6
|  浪费      |
|------------|偏移量为7
|  浪费      |
|------------|偏移量为8<----8的整数倍
|struct S3 s3|
.............
|struct S3 s3|
|------------|偏移量为24<----4的整数倍
| double d   |
..............
| double d   |
|--over------|偏移量为32<----8的整数倍

int main()
{
printf("%d\n", sizeof(struct S1));//12
printf("%d\n", sizeof(struct S2));//8
printf("%d\n", sizeof(struct S3));//16
printf("%d\n", sizeof(struct S4));//32
return 0;
}

 
为什么存在内存对齐?

1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;
某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地对齐
访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。
例如:win32平台,32根地址线,32根数据线-->一次读4字节

01   01 00 00 |00
char int      |
一次读取       |二次读取
01   00 00 00 01 00 00 00
char --浪费-- int      
一次读取

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

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

让占用空间小的成员尽量集中在一起

 

修改默认对齐数

#pragma 这个预处理指令可以改变我们的默认对齐数

例子:
 

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认


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

offsetof
偏移量求取宏

offsetof简单例子
 

#include <stdio.h>
#include<stddef.h>//offsetof头文件
struct S
{
    char c;
    int i;
    double d;
};
int main()
{
    printf("%d\n",offsetof(struct S,c));//0
    printf("%d\n",offsetof(struct S,i));//4
    printf("%d\n",offsetof(struct S,d));//8
    return 0;
}

百度笔试题:
写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明
考察: offsetof 宏的实现

 

结构体传参

#include <stdio.h>
struct S
{
    char c;
    int i;
    double d;
};

//初始化传地址
void Init(struct S* ps)
{
    ps->c = 'w';
    ps->i = 100;
    ps->d = 3.14;
}

//结构体传值
void Print1(struct S tmp)
{
    printf("%d %c %lf\n",tmp.c,tmp.i,tmp.d);
}

//传地址
void Print2(struct S* ps)
{
    printf("%d %c %lf\n", ps->c ,ps->i ,ps->d);
}

int main()
{
    struct S s;
    Init(&s);
    //不推荐:传结构体
    //若结构体过大,临时拷贝的系统开销大,会导致性能的下降
    Print1(s);

    //推荐:传地址,4-8字节
    Print2(&s);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值