结构体类型创建和声明
- 结构体的创建
struct tag //结构体名 (可省略)
{
member-list;//结构体成员列表 (不可省略)
}variable-list;//结构体变量列表 (可省略)
需要注意的是:
1. 结构体的名字和变量列表是可以省略的,但成员列表不可以省略,因为结构体也是一个类型,如果不给出成员列表,那么这个结构体的大小就不能确定,开辟多大的空间也就不确定,所以这是不允许的。
2. 虽然结构体的名字在创建时可以省略,但如果你想要在main函数或者其他地方定义这个结构体变量时,就无法做到了,所以建议结构体名字在创建时最好不要省略。
3. 结果体变量可有可无,但通常在创建时就也把结构体变量一块创建。
例如,下面就是一个结构体的创建。
struct stu
{
char name[20];
int age;
char sex[5];
char id[20];
}; //分号不能丢
这就是一个结构体的创建,需要注意的是:结构体的第一个元素的地址和结构体变量本身的地址在数值上是相等的,并且第一个元素的地址最小,往后地址按照特定的大小(地址不一定连续,结构体存在内存对其的问题)逐渐增大。
再来看一个例子:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}arr[20],*p;
//上面两个结构体在声明时都省掉了结构体标签(tag),问题来了,请问p=&x;合法吗?
解释:上面的两个结构体的类型虽然一样,但是是两个结构体,编译器会把这两个结构体当成完全不同的两个类型,所以是非法的,不可取。
结构体的自引用
- 先来看一个例子:
struct node
{
int data;
struct node next;
};
//问题:这样创建结构体可以吗?
解释:不可以,因为无法判断结构体的大小。
2. 正确的自引用方式
struct node
{
int data;
struct node *next;
};
//或者
typedef struct node
{
int data;
struct node *next;
}node; //typedef 作用是声明类型,将struct node这个结构体类型声明为node,方便后面定义结构体变量
- 如果想要在一个结构体里面套另外一个结构体,可以这样:
//struct B;
struct A
{
int a;
struct B *pb;
};
struct B
{
int b;
struct A *pa;
//struct A p;
};//这里如果想要在B结构体里面声明A结构体的变量(而非指针),那么A结构体的创建需要在B结构体创建之前。
结构体变量的自定义和初始化
- 这里只需要记住一句话:结构体和数组一样,不可以整体赋值,但是可以整体初始化,如果有结构体嵌套的情况,其初始化方式和多维数组类似。(比较简单,代码就不要演示了)。
结构体内存对齐
- 这个是结构体比较重要的一个知识点,关于内存对齐的问题上一个博客有详细介绍,这里就不多说了。
结构体传参
- 结构体传参的问题也相对简单,只需记住,结构体传参时,和数组传参一样,同样会形式临时变量,但其和数组最大的区别就是,数组在传参时,会发生降级(也叫降维,不管几维数组,都会降级为指针),而结构体传参时,不会降级,而是原生拷贝 所以当结构体的大小非常大时,并且调用结构体的次数非常多时,结构体传时就会形成非常大的栈帧空间, 它会不断开辟栈帧,释放栈帧,从而引发性能问题。所以,结构体传参时,需要显式的以指针的形式传参。
位段
位段有两点规定:
- 位段的成员必须是int 、unsigned int、或 signed int、char类型。
- 位段的成员名后边有一个冒号和一个数字。
比如:
struct A
{
int a:2; //表示使用int(32位)中的2个bit位;
int b:5; //表示使用int(32位)中的5个bit位;
int c:10; //表示使用int(32位)中的10个bit位;
int d:30; //表示使用int(32位)中的30个bit位;
}; //A就是一个位段
int main()
{
printf("%d\n",sizeof(A));
return 0;
}
位段中数据怎么存放,看图就懂了;
当存放d不够时,就会再开辟4个字节;所以一共就占用8个字节。
- 需要注意:
- 位段涉及很多不确定因素,所以位段是不跨平台的。
- 位段中成员在内存中分配的顺序不确定
- 位段在使用时,某个成员数据溢出,不会对其他成员造成影响
枚举
举个例子;
enum color
{
blue, //也可以赋值,如blue=1,
yellow,
red
};
enum color clr =blue;
clr=5;//错误
以上 blue,yellow,red 都是枚举常量。这些枚举常量都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。需要注意,在定义了枚举变量后,给其赋值时,需要用枚举常量进行赋值,这样不会出现类型的差异。否则,会编译出错。
联合(共用体)
- 联合体里面的成员公用一段空间(所以也叫做共用体)
看例子:
union u
{
char c;
int i;
};
int main()
{
union u n;
printf("%d\n",sizeof(u));
return 0;
}
成员的存储看图:
这里涉及到一个非常重要的知识点(判断当前计算机的大小端存储)
代码如下:
#include<stdio.h>
union u
{
int i;
char c;
};
union u n;
int main()
{
n.i=0x11223344;
n.c=0x55;
printf("%x\n",n.i);
return 0;
}
解释:首先44是低位,当最后打印i的值时,如果计算机是小端存储,那么存储的方式为数据的低位在低地址处,这时44应该存放在低地址处,之后,因为c占一个字节,将55赋值给44,输出的结果就是11223355;否则就是大端存储,输出结果是55223344;
联合体大小的计算
- 联合体需要内存对齐
union u1
{
char c[5];
int i;
};
union u2
{
short c[7];
int i;
};
//u1,u2的大小分别是多少?
解释:因为c[5]占用5个字节,i占用4个字节,所以从c[5]是u1中最大的成员,其大小是5个字节,又因为5不能整除最大对齐数4,所以应该对齐到最大对齐数的整数倍处,即8的位置,所以u1的大小是8;同理,u2的大小是16;
联合和解构的巧妙使用
问题描述:将long类型的IP地址,转化为十进制的表示形式;
union ip_addr
{
unsigned long addr;
struct
{
unsigned char c1;
unsigned char c2;
unsigned char c3;
unsigned char c4;
}ip;
};
int main()
{
union ip_addr my_ip;
my_ip.addr =123456789;
printf("%d.%d.%d.%d\n",my_ip.ip.c4,my_ip.ip.c3,my_ip.ip.c2,my_ip.ip.c1);
return 0;
}