结构体的概念
C语言中有很多的基础数据类型,除了这些我们可以通过一个结构体来把一部分基础数据类型整合为一个新的自定义类型。
struct 结构体的标签 {
成员1 ;
成员2 ;
.....
}; // 最后用一个分号来表示结束
结构体的标签 : 用来区分各个不同类型的结构体 (也可以省略)。
成 员: 指的是该结构体内部的数据,可以是任何数据类型。也可是结构体
结构体声明,定义
struct node {
int a ;
char c ;
double d ;
};
int main(int argc, char const *argv[])
{
//定义结构体 --> 分配内存空间
struct node a ;
}
注意:
1.只有结构体被定义时才会被分配内存空间,结构体声明并不会分配内存空间;
2.定义结构体的步骤:
a.分配一片内存名字叫 a
b.该内存中需要存放一个结构体
c.分被存放的是 int a , char c ,double d
结构体初始化
结构体与普通的变量都是一样的,都设计涉及,初始化,赋值,取值,传值等等操作。由于结构体中的成员类型各有不同因此有两种初始化得到方法:
1.普通初始化
写起来方便,但是改起来一点都不方便。
2.指定成员初始化
写起来虽然累一点点, 但是如果结构体在后期有更新迭代,不会有初始化的效果。
//1.普通初始换
struct node c ={ 100 , 'M' , 3.1456676 } ;
//2.指定成员初始化
struct node d = {
.a = 998, // . 称为成员引用符
.c = 'K',
.d =2345.21345
};
注意:
1.指定成员初始化:
a.成员的次序或数量的变化不会导致初始化有逻辑问题;
b.可以初始化一部分成员;
c.如果结构体新增成员,该初始化语句依然可用。
结构体成员引用
由于结构体内部包含的成员不止一个,所以说需要使用 . 结构体成员的引用符来访问其中指定的成员。
//结构体变量.结构体成员
printf("a:%d c:%c d:%lf
", d.a, d.c , d.d );
d.a = 123 ; d.c = 'M'; d.d = 3.14;
printf("a:%d c:%c d:%lf
", d.a, d.c , d.d );
结构体数组
结构体数组与其他的普通变量的数组并没有差别,只不过他的数组内容为一个一个的结构体。
struct node arr [10];
arr[0].a = 200;
arr[0].c = 'N';
arr[0].d = 889.43;
结构体指针
结构体指针与其他指针没有差别,只不过其指向的是一个结构体。
struct node * p ;
p = &d ;
printf("p->a:%d p->c:%c p->d:%lf
", p->a, p->c , p->d );
注意:
1.普通的结构体成员引用使用 .;
2.指针的结构体成员应用使用 ->。
结构体声明的变异
struct node {
int a ;
char c ;
double d ;
};
//变形 1
struct node {
int a ;
char c ;
double d;
}Even, GEC; // 在声明结构体的时候顺便定义两个结构体变量
Even.a = 100 ; Even.c = 'C'; Even.d = 12312.234;
printf("%d %c %lf
" , Even.a , Even.c , Even.d);
//变形 2
struct {// 结构体的标签可以省略 ,但是省略的话以后没法定义更多的此类型结构体
int a ;
char c ;
double d;
}Jacy; // 在声明结构体的时候顺便定义一个结构体变量
Jacy.a = 200 ;
Jacy.c = 'A';
Jacy.d = 123.2;
printf("%d %c %lf
" , Jacy.a , Jacy.c , Jacy.d);
//示例
struct CuiHua {
int a ;
char c ;
double d ;
struct {
char * p ;
char arr[32];
}Jacy; // 在声明结构体的时候顺便定义个结构体变量
}Even,GEC;// 在声明结构体的时候顺便定义两个结构体变量
struct CuiHua hua = {
.a = 1024 ,
.c = 'B' ,
.d = 123.123,
.Jacy.p = "Hello",//p指向的是数据段中的 Hello 的地址
.Jacy.arr = "GZ2069" // 数组arr 属于栈空间, 它内部存放了"GZ2069"
};
printf("a:%d c:%c d:%lf p:%s arr:%s
",
hua.a,hua.c,hua.d,hua.Jacy.p, hua.Jacy.arr);
hua.Jacy.p=malloc(32);//申请一篇堆空间大小为32字节让P指向hello
strncpy(hua.Jacy.p , "hello" , 32 );
hua.Jacy.arr[0] = 'g'; hua.Jacy.arr[1] = 'z';
printf("a:%d c:%c d:%lf p:%s arr:%s
",
hua.a,hua.c,hua.d,hua.Jacy.p,hua.Jacy.arr);
// 变形 3 [ 推荐使用 ]
typedef struct CuiHua {
int num ;
char *name ;
char class[32];
}stu_info,*stu_info_p; //使用typedef 给结构体取别名
// stu_info 等同于 struct CuiHua
//stu_info_p 等同于 struct CuiHua *
int main(int argc, char const *argv[])
{
stu_info a ; // 定义一个普通的结构体变量
a.num = 100 ;
a.name = calloc(1 , 32 );
strncpy(a.name, "张全蛋",32); //注意拷贝之前必须分配空间给a.name。
strncpy(a.class, "GZ206666",32);
printf("Num:%d Name:%s Class:%s
" ,a.num,a.name,a.class);
stu_info_p c=calloc(1,sizeof(stu_info));//定义一个结构体指针变量
(*c).num = 200 ;
c->name = calloc(1,32);
strncpy( c->name, "张半蛋" , 32);
strncpy(c->class, "GZ206667" , 32);
printf("Num:%d Name:%s Class:%s
",c->num,c->name,c->class);
return 0;
}
CPU 字长
指CPU一次性从内存中获取的数据大小。32位cpu一次性处理4字节2进制数,64位计算机一次性处理8字节2进制数。
地址对齐
CPU字长确定之后就可以确定我们的CPU每一次在读、写内存的时候都是确定大小 ,假设是32位系统,那么每一次读、写内存时 ,都是按照4字节进行操作。
如果是没有对齐的情况下,需要读取8字节则最多需要读取3次才可以把所有数据读取完整。如果是对齐的情况下只需要读取两次。
所以,如果地址没有对齐CPU在操作时需要浪费更多的时间,可以牺牲内存来提高效率。但也可以牺牲效率来提高内存。
普通变量的M值
变量的地址值,能被M值整除。
char c ; // 大小 1 字节 , M值 1
short s ; // 大小 2 字节 , M值 2
int i ; // 大小 4字节 , M值 4
double d ;// 大小 4字节 , M值 4
printf("a:%p
" , &a );
printf("s:%p
" , &s );
printf("i:%p
" , &i );
printf("d:%p
" , &d );
```
注意:
1.如果变量的尺寸小于 4 字节,那么该变量的 m 值等于变量的长度;
2.如果变量的尺寸大于等于 4 字节,则一律按 4 字节对齐;
3.如果变量的 m 值被人为调整过,则以调整后的 m 值为准。
手动干预M值:
char c __attribute__((aligned(4))) ; // 手动干预一般只能往大的设置不可以设置更小
注意:
1.__attribute__是GNU特定的语法,属于C语言的拓展
2.__attribute__写法注意 左右两边都是两个下划线右边有两组括号 (( ))
3.((aligned(4)))一个变量M值只能被提高不可以降低
结构体的M值
结构体的M值取决于结构体中最大成员的M值。结构体的对长字节取决于,结构体中最长字节和系统字长中最短的那个。
typedef struct CuiHua {
int num ; // 4
char c1 ; // 1
char *name ; // 8
char c2 ; // 1
char c3 ; // 1
double 8 ; // 8
char c4 ; // 1
}stu,* p_stu;
int main(int argc, char const *argv[])
{
stu a ;
printf("&a:%p
" , &a);
printf("&a.num:%p
" , &a.num);
printf("&a.name:%p
" , &a.name);
printf("&a.c1:%p
" , &a.c1);
printf("&a.c2:%p
" , &a.c2);
printf("&a.c3:%p
" , &a.c3);
printf("&a.c4:%p
" , &a.c4);
printf("&a.d:%p
" , &a.d);
printf("sizeof(a):%ld
",sizeof(a)); //输出40
return 0;
}
如上图所示,在64位系统下结构体a占40个大小,结构体最大M为8,字体字长也为8,所以结构体M为8。安装结构体中变量定义的方式排序,
每个变量的M值,必须能被变量的起始地址整除,所以得到如上图所示的存储方式,每种颜色代表一个变量。
可移植性
同样一份代码在不同的操作系统下(系统位数),需要考虑可移植性的问题,如数据尺寸的变化,存储位置的变换
//方法1 统一压实结构体成员:
struct CuiHua {
int num ; // 4
char c1 ; // 1
char *name ; // 8
char c2 ; // 1
char c3 ; // 1
double d ; // 8
char c4 ; // 1
}__attribute__((packed)); //把结构体进行压实,不考虑按照顺序排列了,能多挤就多挤,当然变量的起始地址还是会被M整除,该结构体大小为24
struct CuiHua {
int num __attribute__((aligned(4))); // 4
char c1 __attribute__((aligned(1))) ; // 1
char *name __attribute__((aligned(8))); // 4
char c2 __attribute__((aligned(1))); // 1
char c3 __attribute__((aligned(1))); // 1
double d __attribute__((aligned(8))); // 4
}