1.结构体的概念及初始化
结构体就是许多值的一个集合,这些值就称作成员变量,结构的成员可以是很多类型的变量。
那初始化结构体的方法如下
声名一个结构体
struct book
{
char name[20];
char author[20];
float price;
char id[13];
};
这个结构体的类型就是struct
给结构体赋值
int main()
{
struct book b1 = { "git","gay",17.6,"aidh" };
struct book b2 = { .id = "aidha",.author = "peng",.name = "abyhiak", .price = 12.3 };
}
给结构体赋值时(1)按默认顺序一个一个赋值。
(2)用.加变量名赋值
在访问结构体时有两种方式
1.用结构体名.跟变量名字 来访问
struct book
{
char name[20];
char author[20];
float price;
char id[13];
};
int main()
{
struct book b1 = { "git","gay",17.6,"aidh" };
struct book b2 = { .id = "aidha",.author = "peng",.name = "abyhiak", .price = 12.3 };
printf("%s", b1.author);
}
2. 把结构体给一个指针存储 指针名+->变量名 这样也可以赋值 访问
struct book
{
char name[20];
char author[20];
float price;
char id[13];
};
int main()
{
struct book b1 = { "git","gay",17.6,"aidh" };
struct book b2 = { .id = "aidha",.author = "peng",.name = "abyhiak", .price = 12.3 };
struct book* s1 = &b1;
printf("%s", s1->author);
}
2.结构体的一些应用
1.特殊类型的结构体
匿名结构体类型
struct //无标签名 匿名结构体只能用一次
{
char name[20];
} s = {"abc"};
如果没有重命名的情况下只能使用一次。
2.结构体自引用
结构体内部还可以包含结构体
所以就可以实现一个链表的形成
struct Node
{
int data;
struct Node* next;
};
这里为了简化这个链表我们给他一个定义
typedef struct Node
{
int data;
struct Node* next; //匿名结构体不能进行自引用操作
}Node;
这里要注意不能省略前面的struct, 因为还没有这种类型不能直接引用Node ,不能用匿名结构体,匿名结构体不支持解引用。
typedef struct node
{
int data;
struct node* next;
}Node;
#include<stdio.h>
#include<stdlib.h>
int main()
{
Node* p=NULL;
Node n1, n2, n3;
n1.data = 10;
n2.data = 20;
n3.data = 40;
n1.next = &n2;
n2.next = &n3;
n3.next = NULL;
for (p = &n1; p != NULL; p = p->next)
{
printf("%d ", p->data);
}
printf("\n");
return 0;
}
这样就形成了一个链表,打印第一个 后他的next是&n2,接着去n2打印第二个,n2的next是n3,接着打印n3的,打印完n3next是NULL,停止循环。
3.结构体传参
#include<stdio.h>
struct s
{
int arr[1000];
int n;
double d;
};
void print(struct s p1)
{
int i = 0;
for (; i < 5; i++)
{
printf("%d ", p1.arr[i]);
}
printf("%d ", p1.n);
printf("%lf ", p1.d);
}
void print1(const struct s* p1)
{
int i = 0;
for (; i < 5; i++)
{
printf("%d ", p1->arr[i]);
}
printf("%d ", p1->n);
printf("%lf ", p1->d);
}
int main()
{
struct s s = { {1,2,3,4,5},100,3.14 };
print(s);
print1(&s);
return 0;
}
给函数传带有结构体的参数时,有以上两种传参方式,第一种就是传给函数整个结构体,第二种就是把结构体的地址传给函数,而这两种方法看似没什么差别,但是将s完全传给函数,参数需要压栈,有时间和空间上的开销 ,结构体过大时会导致性能下降。而第二种只传一个4bit的地址 效率高。所以以后传给函数结构体类型的变量时,还是传结构体变量的地址就行,能够提高效率,减少性能损耗。
3.结构体的内存对齐
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
struct S1 s1 = { 0 };
printf("%zd\0", sizeof(s1));
}
上述代码按理说运行结果应该是6个字节,char 1个,int 4个,char 又一个,加起来时6但是最后结果是12,这是因为结构体在内存中的存储不是紧密相连的。
结构体的内存对齐规则
1.结构体的第一个成员要对齐到偏移量为0的地方(可以理解为结构体开辟的起始地址)。
2.其他成员要对齐到对齐数的整数倍地址处。
对齐数:编辑器默认的对齐数与成员变量的大小中较小的一方。
vs中默认对齐数为8
例如 其中有一个int类型的变量,它的大小是4个字节,默认对齐数为8,4<8,所以此成员变量的对齐数就是4。
3.结构体的总大小是最大的一个对齐数的整数倍
举个例子就是上述代码,第一个元素放到起始地址(偏移量为0),然后第二个是int,int类型的大小是4,vs默认对齐数是8,那么第二个的对齐数就是4(取较小值),要放到偏移量为4的位置,占4个字节。接着char大小是1,默认为8,第三个对齐数就是1,放到8即可。而整体的大小看他们三个中的最大对齐数(4),4过了8以后的整数倍就是12,那么结构体的大小就是12。
4.如果出现了结构体嵌套的情况,嵌套的结构体对齐到本结构体的最大的一个对齐数的整数倍地址处。结构体的整体大小就是最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
struct S3 s3= { 0 };
struct S4 s4 = { 0 };
printf("%zd\0", sizeof(s4));
}
例如上方S4 S3中最大为8 那么放完char ,找到偏移量为8的一位开始存放16个bit(结构体本身大小),存放完到了23接着存放double ,24开始存放,存放到31所有对齐数中最大的就是8 ,那么结构体S4的大小就是32。
对齐方式存在的原因
1.平台(移植)原因
不是所有硬件平台都能访问任意地址的数据的,结构体内存对齐,让每个成员都能处于一个整数倍的位置,有些硬件平台这样可以进行访问。
2.性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。