目录
结构体
定义:不同元素的集合。
结构体声明
声明
struct family//声明family结构体
{
char mom[20];
char dad[20];
};
struct stu//声明stu结构体
{
char name[20];
int age;
int tel[11];
struct family fa;
};
特殊声明(匿名结构体声明)
struct
{
char mom[20];
char dad[20];
}family;//声明family 变量
struct
{
char name[20];
int age;
int tel[11];
}stu ;//声明stu 变量
问题:
1、匿名声明能否实现结构体嵌套?
2、能否在程序中对结构变量赋值?
结构体的类型
struct {member-list};
结构类型命名的意义
情况1
struct
{
char name[20];
int age;
}stu1 = {"zhangsan",18,},
stu2 = {"lisi",20},
stu3 = {"wangwu",25};
int main()
{
stu1 = stu2;
printf("%s\n", stu1.name);
SYS;
R0;
}
输出结果为lisi
情况2
int main()
{
struct
{
char name[20];
int age;
}stu1 = { "zhangsan", 18, };
struct
{
char name[20];
int age;
}stu2 = { "lisi", 20 };
stu1 = stu2;
printf("%s\n", stu1.name);
SYS;
R0;
}
匿名申明的变量必须像情况1一样需要一次性全部声明完,才能属于同一个结构类型,否则即使成员完全相同,也会像情况2一样被判断为不兼容的类型,结构变量之间不能赋值。
所以需要结构体类型命名。
结构体类型命名分为两种:
结构标记
structure tag。
struct stu//声明
{
char name[20];
int age;
};
int main()
{
struct stu s1= { "zhangsan", 18 };//初始化结构体变量s1
struct stu s2 = { "lisi", 20 };//初始化结构体变量s2
SYS;
R0;
}
stu 就是结构体标记,但是使用时前面一定要加上struct。
类型定义
利用typedef定义结构体类型
typedef struct
{
char name[20];
int age;
}stu;
int main()
{
stu s1= { "zhangsan", 18 };
stu s2 = { "lisi", 20 };
printf("%s\n",s1.name);
SYS;
R0;
}
利用typedef定义的结构体类型,使用时前面可以不加struct,但是链表不支持,所以还是使用结构标记。
结构体的作用域
每个结构代表一种新的作用域。任何声明在此作用域内的名字都不会和程序中的其他名字冲突
struct stu
{
char name[20];
int age;
};
struct f
{
char name[20];
int age;
};
int main()
{
struct stu s1 = { "zhangsan", 18 };
struct f f1 = { "lisi", 20 };
char name[20] = "wangwu";
printf("%s\n",s1.name);
printf("%s\n", f1.name);
printf("%s\n", name);
SYS;
R0;
}
在上面的代码中name[20]数组出现在不同的结构体以及变量,但相互之间并不冲突。
结构体操作
变量名以及成员
上面的例子中,s1与f2就是结构体的变量名,结f构体中的name与age就是结构的成员,使用时2者是同时使用的。
.操作符
关联使用结构体成员的运算符,(struct tag name).(member)。
printf("%s\n",s1.name);
printf("%s\n", f1.name);
->操作符
关联使用结构体指针与成员的运算符,(struct tag *ps)->(member)。
->操作符等价于(*(struct tag *ps)).(member)。.操作符的优先级比较高,需要将解引用括起来
=操作符
结构体中兼容的结构体可以直接赋值(兼容的意思是拥有相同结构类型的结构体,结构体类型不同,成员全部一样也不是兼容的结构体,创建兼容结构体的方法就是使用结构标记,或者声明结构变量时一起声明)。
结构体可以用=进行赋值(数组不能用=赋值,但是结构体中包含的数组就可以)
//兼容结构体1
struct stu
{
char name[20];
int age;
};
int main()
{
struct stu s1 = { "zhangsan", 18 };
struct stu s2 = s1;
printf("%s\n", s1.name);
printf("%s\n", s2.name);
SYS;
R0;
}
//兼容结构体2
struct
{
char name[20];
int age;
}s1 = { "zhangsan", 18 }, s2;
int main()
{
s2 = s1;
printf("%s\n", s1.name);
printf("%s\n", s2.name);
SYS;
R0;
}
//不兼容的类型
struct
{
char name[20];
int age;
}s1 = { "zhangsan", 18 }, s2;
struct
{
char name[20];
int age;
} s2;
int main()
{
s2 = s1;
printf("%s\n", s1.name);
printf("%s\n", s2.name);
SYS;
R0;
}
不兼容类型的运行结果
结构体初始化
struct
{
char name[20];
int age;
}s1 = { "zhangsan", 18 }, s2;
结构体初始化也可以进行不完全初始化,在初始化时,没有被赋值的部分默认为0。
结构体传参与返回值
传参与返回值
传值传参
struct stu
{
char name[20];
int age;
};
struct stu change_stu(struct stu s1)
{
strcpy(s1.name, "zhangsan");
s1.age = 25;
return s1;
}
int main()
{
struct stu s1 = {"lisi",20};
struct stu s2;
s2 = change_stu(s1);
printf("%s\n", s1.name);
printf("%s\n", s2.name);
SYS;
R0;
}
程序输出的结果是
lisi
zhangsan
结构体在传值传参时,形参是实参的复制体,对形参的改变不影响对实参的改变,结构体也可以作为参数进行返回,但这样做,对空间的消耗很大。
指针传参
struct stu
{
char name[20];
int age;
};
struct stu change_stu(struct stu *s1)
{
strcpy(s1->name, "zhangsan");
s1->age = 25;
return *s1;
}
int main()
{
struct stu s1 = {"lisi",20};
struct stu s2;
s2 = change_stu(&s1);
printf("%s\n", s1.name);
printf("%s\n", s2.name);
SYS;
R0;
}
结构体与数组
结构体数组
结构体可作为元素存储在数组中,结构体中的数组为了节省空间可以使用指针,该方法只能在后台使用,读取时仍然需要使用name数组
struct city
{
char *name;
int code;
};
int main()
{
struct city c[3] = { {"beijing",01}, {"shanghai",02}, {"tianjing",03} };
int i = 0;
i = 0;
while (i < 3)
{
printf("name=%s code=%d\n", c[i].name, c[i].code);
i++;
}
SYS;
R0;
}
结构体在内存中的存储
存储
结构体的存储时按照结构体成员的顺序进行存储的
struct city
{
char name[4];
int code;
}s1;
对齐
结构体的对齐规则:
结构体第一个成员存储在结构体变量偏移量为0的地址处。
其他成员的存储地址为有效对齐值的最小整数倍地址处。
注:
有效对齐值:自身对齐值与指定对齐值中较小的一个(min{自身对齐值,指定对齐值})。
自身对齐值:是成本本身的字节数。
指定对齐值:有宏 #pragma pack(N) 指定的值,这里面的 N一定是2的幂次方.如1,2,4,8,16等.如果没有通过宏那么在32位Linux主机上默认指定对齐值为4,64位的默认对齐值为8,AMR CPU默认指定对齐值为8,VS默认为8;
总对齐时字节大小:min{结构体变量最大成员的字节数,指定对齐值}的整数倍。
struct S1
{
char c1;
int i;
char c2;
};
S1中,第一个成员c1的地址在结构体变量偏移量为0的地址,i的对齐值为min{4,8}=4,依次查看4的整数倍地址能否储存4个字节,0*4=0//该地址存储c1,1*4=4,该地址为空,所以i的地址为4,5,6,7,c2的对齐值为min{1,8}=1,1*8=8//该地址为空,所以i的地址为8
总对齐时min{4,8}=4,但结构体占用的9个字节不是4的整数倍,所以需要扩充,离9最近的4的整数倍为12,多以结构体s1的字节长度是12。
struct S2
{
char c1;
char c2;
int i;
};
S2中 ,第一个成员c1在结构体变量偏移量为0的位置,c2的对齐值为min{1,8}=1,一次查看1的整数倍地址能否储存1个字节,0*1=0//该地址有变量c1,1*1=1//该地址为空可以存储,所以c2的地址是1,i的对齐值为min{4,8}=4,依次查看4的整数倍地址0*4=0//该地址有变量c1在,1*4=4//该地址为空可以存储
总对齐时min{4,8}=4,结构体占用8个字节,是4的整数倍,所以结构体s2的长度是8。
struct S5
{
char c1;
double d;
char c2;
char c3;
};
struct S7
{
char c1;
struct S5 s;
char c2;
char c3[20];
};
int main()
{
struct S7 s;
printf("%d\n", sizeof(s));
SYS;
R0;
}
首先看S5结构体需要多少内存,c1需要8个,d需要8个,c2,c3需要2个,总对齐时min{8,8}=8,超过18个字节的最小整数倍时24,所以S5结构体占用的字节时24给,套入到S7中,c1的结构体变量的偏移量是0,结构体变量s的对齐值是min{24,8}=8,1*8=8,所以8至32是结构体变量的空间,c2的对齐值是1*33=33,所以第33个位置存放c2的变量,之后20个字节存放C3数组
总对齐时,min{24,8}=8,比53个字节大的整数倍是56,所以结构体变量s所占据的字节数是56。
struct S5
{
char c1;
double d;
char c2;
char c3;
};
struct S7
{
char c1;
struct S5 s;
char c2;
int c3[20];
};
int main()
{
struct S7 s = { 'a', 'b', 1.0, 'c', 'd', 'e', {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20} };
printf("%d\n", sizeof(s));
SYS;
R0;
}
c1的偏移量为0,向后7个字节后,放入S5 s所需要的24个字节,之后放入c2,向后3个字节后,放入c3数组,共80个字节
总对齐时,min{24,8}=8,结构体便变量s所占据的字节为116,能整大于116又能整除8的最小整数是120,所以结构体变量s的大小是120
结构体的自引用
结构体可以进行自引用,也就是自己引用自己,但是不能引用自己的变量,因为结构体变量b是一个完整的结构,其中还包含了自己,也是一个完整的结构,该结构下又包含了一个结构变量b,无限循环下去,所以无法创建。
错误
struct stu
{
int a;
struct stu b;
};
int main()
{
struct stu b = { 10, { 2 } };
SYS;
R0;
}
//指针
struct stu
{
int a;
struct stu *b;
};
int main()
{
struct stu b = { 10, NULL };
struct stu a = { 10, &b };
printf("%p",a.b );
SYS;
R0;
}
使用指针能创建,是因为指针的空间大小是确定的,所以声明结构的时候,已经确定了结构体的大小。
结构体位段
位段是节省空间的一种方法,但不适合移植。
位段的内存分配:
位段的成员可以是int,unsigned int, signde int,char(属于整型家族)类型。
位段的空间上是按照需要以4个字节或者1个字节的方式来开辟的。
位段涉及很多不确定的因素,位段不是跨平台的,注重可以移植的程序应该避免使用位段。
struct s
{
char a : 3;//数字表示比特位
char b : 4;
char c : 5;
char d : 4;
};
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
VS中
a设置为3个比特位,b设置4个比特位,c设置5个比特位,d设置4个比特位
首先开辟第一个char变量空间:
a=10,二进制为1010,因为a只有三个位,所以前面的1舍去,010放入内存
b=12,二进制为1100,b有四个位,可以放入。
此时,红色字节只剩下一个位,将其弃置,放入0,并重新开辟绿色字节
c=3,二进制为0011,因为c占据5个位,再补上一个0,00011放入。
剩余三个位,但成员d需要四个位,所以弃置放入0,重新开辟一个蓝色字节
d=4,占据四个位,二进制放入0100,剩余的位置补0
所以在内存中应为,三个字节,内存显示62 03 04
以上仅为VS编译器下的情况,不同编译器情况不同,主要体现在三个方面:
1、数据从哪边开始进行存储。
2、空间不够,再次开辟时是否会占用原空间。
3、数据超过时,是否丢弃。
联合体
公用一块空间的自定义类型
联合体的特点:共用一块空间
union un
{
int i;
char c;
};
int main()
{
union un u = { 0 };
printf("%p\n", &u);
printf("%p\n", &u.i);
printf("%p\n", &u.c);
SYS;
R0;
}
联合体在不同的时间可以存放不同类型的变量,那么大小至少是最大成员的大小。
当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
struct stu
{
char c[57];
};
union un
{
int i;//4
short c[7];//14
struct stu s;//57
};
int main()
{
struct stu s = { 0 };
union un u = { 0 };
printf("%d", sizeof(u));
SYS;
R0;
}
代码中最大成员是结构体变量s,s=57,不能被4整除,所以u的大小,最终是60。
联合体的特别应用
判断大小端字节序
union un
{
char c;
int i;
};
int main()
{
union un u = { 0 };
u.i = 1;
printf("%d", u.c);
SYS;
R0;
}
联合体做不同类型成员的数组
union un
{
int i;
float f;
};
struct st
{
char flag;
union un u;
};
int main()
{
struct st s[4] = { 0 };
s[0].flag = 'i';
s[0].u.i = 1;
s[1].flag = 'f';
s[1].u.f = 2.13f;
s[2].flag = 'f';
s[2].u.f =3.14f;
s[3].flag = 'i';
s[3].u.i = 4;
int i = 0;
for (i = 0; i < 4; i++)
{
if (s[i].flag == 'i')
{
printf("%d ", s[i].u.i);
}
else if (s[i].flag == 'f')
{
printf("%f ", s[i].u.f);
}
}
SYS;
R0;
}
union un
{
float f;
int i;
};
int main()
{
union un u[4] = { 0 };
u[0].i= 1;
u[1].f= 2.13;
u[2].f = 3.14;
u[3].i = 4;
SYS;
R0;
}
实际结果在内存中的排列与数组并无太大的区别,只是使用的时候不算方便
枚举
枚举是一种自定义的类型,成员本身可以初始化值,可以将成员赋值给变量,成员名相当于整型常量数值的另一个名字。
enum color
{
blue,
red=6,
yellow
};
int main()
{
int a = blue;
printf("%d\n", a);
printf("%d\n", blue);
printf("%d\n", red);
printf("%d\n", yellow);
SYS;
R0;
}