自定义类型 结构体

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

一、结构体

1.1 结构体的声明

结构体是一些值的集合,这些值称为结构体的成员变量,这些成员变量可以是不同类型的数据,这与数组就有了差异。

下面是结构体声明的方式。

struct S//S叫做结构体的tag 也就是标签
{
    //成员列表
}//变量列表;

举个例子,假设要创建描述一个人的结构体。

struct people
{
    char name[20];
    int age;
    double height;
    double weight;
}zhangsan;

当然,你不一定要在创建结构体时就创建变量,但注意变量列表后面的分号不能丢,即使你没有创建变量。

struct people
{
    char name[20];
    int age;
    double height;
    double weight;
}zhangsan;
int main()
{
    struct people wangwu;
    return 0;
}

在main函数内部创建变量也没有问题

如果你觉得创建变量时总是带着struct和tag太麻烦 可以用类型重命名,例如:

typedef struct people
{
    char name[20];
    int age;
    double weight;
    double height;
}people;
int main()
{
    people wangwu;
    return 0;
}

 这里用typedef重新命名结构体为people 然后直接用people创建了一个wangwu变量,这样是合法的。

可能有人会问,直接省略掉struct的tag——people可以吗?像这样:
 

struct
{
    char name[20];
    int age;
    double weight;
    double height;
};

这样也是可以的,这种结构体叫做匿名结构体,匿名结构体如果要创建变量一定要在创建结构体的同时声明变量。

 在主函数内部做不到创建变量,而且匿名结构体的变量一般只使用一次,两个成员类型完全相同的结构体VS仍然认为它是不同类型的结构体。

struct
{
 int a;
 char b;
 float c; }x;
struct
{
 int a;
 char b;
 float c; 
}a[20], *p;
p = &x;//这是非法的,因为两个匿名结构体是不同类型。

1.2结构体的自引用

先提出一个问题:这样的代码合法吗?如果合法,sizeof(struct Node)又是多少?

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

 这是非法的创建方式,因为struct Node的大小依赖于next的大小,next的大小也依赖于struct Node它们两个是互相影响的关系,如果非要计算会陷入一个死循环,无线套娃。所以这个结构体的大小是未知的。

正确的方式:
 

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

通过创建结构体指针类型的变量,这个变量的大小是确定的。

1.3结构体变量的初始化

struct Point
{
 int x;
 int y; 
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};
struct Stu        //类型声明
{
 char name[15];//名字
 int age;      //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
 int data;
 struct Point p;
 struct Node* next; 
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

1.4结构体的大小计算——内存对齐

先阐述一下内存对齐的规则:

1.结构体的第一个成员总在偏移量为0的位置处。

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的一个对齐数 与 该成员大小的 较小值
VS默认的对齐数是 8,linux环境下没有默认对齐数,成员自身的大小就是它的对齐数。

3.结构体整体的大小对齐到最大成员大小的整数倍处

4.如果出现了嵌套的结构体,内部结构体对齐到自身最大成员大小的整数倍处,

整个结构体的大小对齐到所有(包括内部嵌套的结构体)最大对齐数的整数倍处。

 

 第二条规则中的对齐数就是将成员自己的大小和默认的对齐数比较,较小值的整数倍就是当前这个成员要对齐到的位置。

第三条规则的意思是,所有的成员与默认对齐数比较后得到的所有对齐数中,得到一个最大值,结构体大小要对齐到它的整数倍处。

比如:

struct s
{
    char p;
    int i;
};

第一个成员char总是在偏移量为0的位置,它占了1个字节的空间

 

而第二个成员int的大小是4个字节,它和默认对齐数8比较得到的较小值是4,从而它要对齐到偏移量为4的整数倍处,如图。

 

 

这就是这个结构体的内存分布,偏移量为1~3位置的内存就被浪费了,从而这个结构体的大小为8个字节。

 

 1.5内存对齐的作用

1. 平台原因 ( 移植原因 )
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因
数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

意思就是,有些硬件平台上一次性只读取特定长度的数据,比如32位平台上一次在内存中读取四个字节也就是32个bit位的数据,如果不考虑内存对齐的话,会造成一种数据的二次读取,浪费时间,像这样:

 一次读取只读到了char和int的前三个字节,要完整的读到int还需要读取一次。

如果内存对齐了,时间就会减少很多,也就是说内存对齐是一种用空间换取时间的方式

NOTE:在vs中你可以选择修改默认的对齐数,这需要用到一个宏#pragma pack(修改后的对齐数)

1.5结构体的传参

结构体的传参可以选择直接传结构体过去,也可以选择传结构体的指针过去。

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

如果形参以结构体的形式接收,在函数中访问结构体的对象要用‘.’,比如s.age或是s.height

如果形参以结构体的指针形式接收,在函数中访问结构体对象用箭头,比如s->age 

但两个传参方式我们要选择哪一种呢?答案是选择第二种。

因为函数在传参的过程中参数是需要压栈的,如果传递的是结构体,

结构体大小过大,参数压栈的的系统开销比较大,所以会导致性能的
下降。
如果传递的是一个指针,它同样可以访问到结构体里面的所有元素,但是它最多占8个字节,在结构体太大的时候会是一个很好的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值