结构体的声明:
struct 结构体标签
{
成员1;
成员2;
成员3;
…
}
注意:
①struct : 关键字 , 用来告诉编译器 我要声明的东西是一个自定义的类型*(结构体)
②结构体标签:用来区分不同的结构体类型
③成员:指结构体内部由哪些基础数据类型所构成, 可以是任何的数据类型
1.如何定义与初始化
定义与初始化实际为结构体分配内存空间 栈
//普通成员初始化
struct TieZhu c = { "Guide of Programming in the Linux Envirnment" , 3.59 , "张三" } ;
// 指定成员初始化
struct TieZhu c = {
.Price = 3.59 ,
.Name = "张三",
.Book = "Guide of Programming in the Linux Envirnment" ,
.num = 1234654
};
注意:
①普通成员初始化: 写起来方便一点, 但是用起来麻烦一点,不利于代码的更新于迭代。
②指定成员初始化:写起来麻烦一点, 但是用起来比较轻松, 有利于后期代码的更新迭代, 不会照成成员与成员之间初始化错位的问题。
2.成员引用
概念:结构体相当于一个数据的集合,里面有多个不同的数据组合而成,每一个成员都可以引用符来单独引用
语法:
结构体变量名.成员 //普通的结构体变量
结构体指针变量->成员 //结构体指针的访问方法
// 修改结构体变量中某一个成员的值
c.Price = 5 ;
// 如何引用
printf("Book:%s \nPrice:%f \nName:%s\n" , c.Book, c.Price , c.Name);
对结构体的堆内存进行赋值需要注意:
由上图可知如果Book指向的是另外一个堆空间,那么在释放结构体的内存时,需要先释放Book所指向的内存区。
总结:
// 结构体声明 --> 不占用内存空间
struct TieZhu
{
int num ;
char *Book;
float Price;
char Name[32];
};
void main()
{
//结构体变量 定义与初始化
struct TieZhu a = {123, "Book", 3.14, "Even"};
//结构体指针
struct TieZhu *p = calloc(1,sizeof(struct TieZhu));
//结构体数组
int arr[10];
struct TieZhu arr[10];
//成员引用
a.num = 245;
p->num = 250;
arr[0].num = 366;
}
3.结构体声明的变异
结构体声明 --> 不占用内存空间
struct TieZhu
{
int num ;
char *Book;
float Price;
char Name[32];
};
变异1:在声明结构体类型时,顺便定义了变量
struct TieZhu
{
int num ;
char *Book;
float Price;
char Name[32];
}Even, *Jacy; //在声明结构体类型时,顺便定义了两个变量
Even.num = 1024;
Jacy = &Even; //让指针Jacy指向Even的地址
Jacy->Book = "Hello";
变异2: 省略结构体的标签名,但是注意一般在省略标签的情况
下需要在声明语句后面顺便定义变量/指针, 否则后面无法在定义这种类型的结构体变量。
这种写法比较少出现, 如果要出现一般是作为某个结构体内部的成员使用。
struct
{
int num ;
char *Book;
float Price;
char Name[32];
}Even, *Jacy; //在声明结构体类型时,顺便定义了两个变量
Even.num = 1024;
Jacy = &Even; //让指针Jacy指向Even的地址
Jacy->Book = "Hello";
示例:
struct node
{
int i ;
char c ;
float f ;
struct{ // 结构体内部的成员
float ff ;
double d ;
} info ;
};
int main(int argc, char const *argv[])
{
struct node data = {
.c = 'A',
.i = 1024 ,
.f = 3.489 ,
.info.d = 665.1234,
.info.ff = 3.444
};
struct node * p = &data ;
printf("d:%lf , ff:%lf \n" , data.info.d , data.info.ff );
printf("d:%lf , ff:%lf \n" , p->info.d , p->info.ff );
return 0;
}
变异 3 :【推荐使用】
使用 typedef 来给结构体的类型取别名
Tz 相当于 struct TieZhu 可以直接定义普通结构体变量
P_Tz 相当于 struct TieZhu * 可以直接定义结构体指针变量
typedef struct TieZhu
{
int num ;
char *Book;
float Price;
char Name[32];
}Tz , *P_Tz ;
示例:
typedef struct TieZhu
{
int num ;
char *Book;
float Price;
char Name[32];
}Tz , *P_Tz ;
int main(int argc, char const *argv[])
{
Tz b = {
.Book = "Hello" ,
.Name = "Even",
.Price = 3.14
};
P_Tz p = &b ;
printf("Name : %s \n" , b.Name );
printf("Name : %s \n" , p->Name );
return 0;
}
结构体数组:
struct TieZhu arr[5] ; // 拥有5个结构体的元素的数组
结构体数组指针:
struct TieZhu(*p)[5] ; // 指向的是一个 拥有5个元素的结构体数组
结构体指针数组:
struct TieZhu * arr[5]; // 存放的是5个结构体类型的指针的数组
4.结构体尺寸
4.1 CPU字长
概念:字长就是指CPU(处理器)在执行一条指令时,最大一个运算能力,这个运算能力由两部分决定一个就是处理本身的运算能力,另一个就是系统的字长,比如常见的32位/64位系统,如果使用32位系统那么在处理数据的时候每一次最多可以处理32位的数据(4字节)
4.2 地址对齐
概念:每一款不同的处理器,存取内存数据都会有不同的策略,如果是32位的CPU,一般来讲他在存取内存数据的时候,每次至少存取4个字节(即32位),也就是按4字节对齐来存取的。换个角度来讲:CPU有这个能力,他能一次存取4个字节。
接下来我们可以想到,为了更高效地读取数据,编译器会尽可能地将变量塞进一个4字节单元里面,因为这样最省时间。如果变量比较大,4 个字节放不下,则编译器会尽可能地将变量塞进两个 4 字节单元里面,反正一句话:两个坑能装得下的就绝不用三个坑。这就是为什么变量的地址要对齐的最根本原因。
所谓的地址对齐主要思想:
尽可能提高CPU的运行效率(仅可能的少读取、写入内存)
一个数据如能使用一个单元来存放就绝对不使用两个,两个能存放绝对不用三个
4.3 普通变量的M值
概念:一个数据它的大小是固定的(比如整型),如果这个数据他所存放的地址能够被某一个数所整除(4)那么这个数就成为当前数据的M值。
可以根据具体的系统字长以及数据的大小可以计算得出M值。
示例:
int i ; // i 占用 4 字节, 如果i 存放在能被4整除的地址下则是地址对齐, 因此他的M值为4
char c ;// c占用 1 字节, 如果c 存放在能被1整除的地址下则是地址对齐, 因此他的M值为1
short s ;// s 占用 2 字节, 如果s 存放在能被2整除的地址下则是地址对齐, 因此他的M值为2
double d ; // d 占用 8 字节, 如果d 存放在能被4整除的地址下则是地址对齐, 因此他的M值为4
float f ;// f 占用 4 字节, 如果f 存放在能被4整除的地址下则是地址对齐, 因此他的M值为4
注意:
如果一个变量的大小超过4 (8/16/32) M值则按4计算即可
4.4 手残干预M值
char c attribute((aligned(16))) ;
注意:
attribute 机制是GNU特定语法,属于C语言标准的拓展。
attribute 前后都有个两个下划线_
attribute 右边由两对小括号 (( ))
attribute 还支持其它的设置…
一个变量他的M值只能提升不允许降低 , 只能是2的N次幂
4.5 结构体的M值
结构体中有多个成员,取决于成员中M值最大的成员。
结构体的地址,必须能被结构体的M值整除
结构体的尺寸,等于成员中宽度最宽成员的倍数
typedef struct node
{
int i ; // 4
char c ; // 1
short s ; // 2
double d ; // 8
long double ld ; // 16 由于16是最宽的,因此结构体的大小不许能被16整除
float f ; // 4
}Node;
以上结构体在64位系统中:
大小 为 48 --> 能被 long double整除
地址值能被M值4整除
4.6 可移植性
对于可移植的结构体来说一定要解决好该结构体在不同的操作系统(位数)如何统一 该结构体的大小。
方法有两个:
方法1 :
直接使用attribute 进行压实, 每一个成员之间没有留任何的空隙。
typedef struct node
{
int i ;
char c ;
short s ;
double d ;
long double ld ;
float f ;
}__attribute__(( packed ));
方法2 :
对每一个成员进行压实
typedef struct node
{
int i __attribute__((aligned(4))) ;
char c __attribute__((aligned(1))) ;
short s __attribute__((aligned(2)));
double d __attribute__((aligned(4)));
long double ld __attribute__((aligned(4))) ;
float f __attribute__((aligned(4)));
};
注意:
结构体的大小取决于多个因素。
地址对齐,M值的问题, 默认情况下结构体的大小为成员中最大的倍数。
结构体内部的每一个成员的大小都与系统的位数有关。
如果需要实现可移植性,结构体的每一个成员需要使用可移植类型+attribute机制对结构体进行压实。