欢迎光临
我们一直在努力

理解结构体

自定义类型:结构体

目录:

  • 自定义类型:结构体
  • 1.结构体类型的声明
  • 2.结构体变量的创建和初始化
  • 3.结构成员访问操作符
  • 4.结构体内存对齐
  • 5.结构体传参
  • 6.结构体实现位段

1.结构体类型的声明

2.结构体变量的创建和初始化

3.结构成员访问操作符

4.结构体内存对齐

5.结构体传参

6.结构体实现位段


1.结构体类型的声明 在声明结构体的时候,可以不完全声明 比如:

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

struct

{
char a;
int b;
float c;
}a[20],*p;
//在上面代码的基础上,下面的代码是否合法
p = &x;
//警告:编译器会把上面两个声明当成完全不同的两个类型,所以是非法的。
// 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能用一次。

2.结构体变量的创建和初始化

struct Student
{
char name[20];
int age;
char sex[10];
char id[20];
};
//按照结构体成员顺序初始化
int main()
{
struct Stu s = { "张三",18,"男","s23424" };
printf("name:%s\\n", s.name);
printf("age:%d\\n", s.age);
printf("sex:%s\\n", s.sex);
printf("id:%s\\n", s.id);
//按照指定顺序初始化
struct Stu s2 = { .age = 18,.name = "Lisi",.sex = "女",.id = "sa23456" };
printf("name:%s\\n", s2.name);
printf("age:%d\\n", s2.age);
printf("sex:%s\\n", s2.sex);
printf("id:%s\\n", s2.id);
return 0;
}

结构体的自引用 在结构中包含一个类型为该结构本身的成员是否可以? 不可以·! 比如,定义一个链表的节点:

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

上述代码如果正确,那sizeof(struct Node)是多少? 正确的自引用方式:

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

在结构体自引用的过程中,夹杂了typedef对匿名结构体类型重命名,也容易引发问题,看看以下的代码的问题:

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

Node是对前面的匿名结构体类型的重命名产生的,但在匿名结构体内部提前使用Node类型创建成员变量,这是不行的。 解决方案如下:定义结构体不要用匿名结构体

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

3.结构成员访问操作符 对于结构体变量如: struct Stu s,则用 . ,s.name、s.age; 对于结构体指针如: struct Stu*cp,则用 ->,cp ->name,cp -> age。 4.结构体内存对齐 计算结构体的大小 ——- 结构体内存对齐 对齐规则: 1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。 2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

  • 对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值。
  • vs中默认对齐数值为8。
  • Linux中gcc没有默认对齐数,对齐数就是成员自身的大小。 3.结构体总大小为最大对齐数(成员变量中对齐数最大的)的整数倍。 4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大齐整数倍处,结构体整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。 结构体的内对齐是拿空间换时间的做法

练习:

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("%zd\\n", sizeof(struct S1));
//12
printf("%zd\\n", sizeof(struct S2));
//8
printf("%zd\\n", sizeof(struct S3));
//16
printf("%zd\\n", sizeof(struct S4));
//32
return 0;
}

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

#pragma pack(1)//设置对齐数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对齐数,还原为默认

int main()
{
printf("%zd\\n", sizeof(struct S));
return 0;
}

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

上面print1和print2函数哪个好? 答案是:print2函数。 原因:函数print1相当于拷贝一个原结构体,在内存中新开辟了一块内存空间去存储,会导致性能下降。 结论: 结构体传参时,要传结构体的地址。 6.结构体实现位段 6.1位段的概念 1.位段的成员必须是int 、unsigned int 或 signed int,在c99中位段成员的类型也可以选择其他类型。 2.位段的成员名后边有一个冒号和一个数字。 比如:

struct A
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};

A就是一个位段类型。 6.2位段的内存分配 1.位段的成员可以使int、unsigned int、signed int或者是char等类型。 2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。 3.位段涉及很多不确定因素,位段不能跨平台。

//一个例子
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;
return 0;
}
//空间是如何开辟的?

在这里插入图片描述 6.3位段的跨平台问题 和结构体相比,位段可以达到同样效果且节省空间,但有跨平台问题。 6.4位段使用的注意事项 内存中每一个字节分配一个地址,一个字节内部的bit位是没有地址的。 所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员。

struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = { 0 };
scanf("%d", &sa._b);//这是错的

//正确示范
int b = 0;
scnaf("%d", &b);
sa._b = b;
return 0;
}```

赞(0)
未经允许不得转载:171主机测评 » 理解结构体
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址