一、什么是结构体?
结构体也叫结构,是由一系列具有相同类型或不同类型的数据构成的数据集合。
二、结构体的声明
struct tag
{
member_list;
}variable_list;
//比如描述一个学生
struct Stu
{
char name[20];//姓名
int age;//年龄
char sex[3];//性别
char id_nm[2o];//学号
};//分号不可以丢掉,切记!!!
特殊的声明:结构体在声明的时候可以不完全声明,比如:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
以上两个结构体在声明的时候都省略了结构体的标签(tag)。
如果在这个时候,将结构体x的地址赋值给结构体指针p会发生什么呢?
p = &x;
在LInux平台下显示,类型不匹配,也就是说原本想表达的意思用一个结构体定义两个变量,但是由于未加标签,系统认为这两个不是相同的类型,所以在进行赋值的时候系统会出现警告。所以在我们使用结构体的时候尽量不要省略掉结构体标签,方便自己,也方便别人。
三、结构体的成员
结构体的成员可以是标量、数组、指针,甚至是其他结构体 。
访问:结构体标量访问成员是通过点操作符(.)访问的。点操作符接受两个操作数。例如:
struct Stu
{
char name[20];
int age;
};
struct Stu s;//定义结构体变量
我们可以看到s有成员name和age两个成员,在访问s成员时就可以用点操作符。
struct Stu s;
strcpy(s.name,"xiaoming");
s.age = 20;//访问结构体成员
当我们得到的不是一个结构体变量,而是一个结构体指针的时候那该如何访问结构体成员呢?
struct Stu
{
char name[20];
int age;
}s;
void print(struct S *ps)
{
printf("name - %s age = %d\n",(*ps).name,(*ps).age);
printf("name - %s age = %d\n",ps->name,ps->age);
}
我们可以观察到,在我们得到的是结构体指针的时候,对结构体内成员访问有两种方法,一种是解引用指针以后跟上面的访问方式相同利用点操作符进行访问;另外一种方法就是用指针加->指向结构体内成员直接进行访问。
四、结构体的自引用
大家都知道在数组里面可以有二维数组,也就是说一个数组里面的元素还是一个数组,那么结构体的里面是否可以放置一个结构体呢?或者说是结构体成员里面是否可以 包含自己本身呢?
这里编译器里面有个警告,意思是代码中并没有有效代码,但是显而易见,结构体内放置结构体时候可以的。这时候我们再做一个测试。
我们发现当结构体自己引用自己的时候是不行的。但是结构体在引用自己的时候可以指针的形式引用自己,如:
struct Test
{
int a;
struct Test* p;
};
再比如两个结构体之间是否能够相互包含?
struct A
{
int a;
struct B* pb;
};
struct B
{
int b;
struct A* pa;
};
经过实践发现,在不同编译器下有不同的结果,在这里建议大家在结构体互相包含的时候,应该提前声明一下。
struct B;
struct A
{
int a;
struct B* pb;
};
struct B
{
int b;
struct A* pa;
};
五、结构体变量的定义和初始化
我们知道结构体就是一种类型,跟整形、字符型、浮点型一样,那么该如何定义这个类型的变量以及如何初始化呢?
struct Point
{
int x;
int y;
}p1;//声明结构体类型的同时定义结构体变量p1
struct Point p2;//定义结构体变量
struct Point p3 = {x,y};//定义结构体变量p3的同时对其进行初始化赋值
结构体的初始化与数组初始化的时候是一样的。
六、结构体的内存对齐问题
我们在进行对结构体使用的时候有没有想过结构体的大小问题?
struct Test
{
char c1;
int i;
char c2;
};
printf("%d\n",sizeof(struct Test));
同样的代码我们换个顺序呢?
struct Test
{
int i;
char c1;
char c2;
};
printf("%d\n",sizeof(struct Test));
怎么仅仅是换了个顺序结构体的大小就不同了?
其实结构体再设计的时候有一些的对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处
- 其他成员要对其到对齐数的整数倍的地址处。对齐数=编译器默认的一个的对齐数与该成员大小的较小值。(在vs下默认对齐数为8,在Linux下默认对齐数为4)
- 结构体的总大小为最大对齐数的整数倍
- 如果嵌套结构体的话,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍。
但是为什么要存在这些规则呢?
1.不是所有平台都能访问任意地址上的任意数据的;某些平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说结构体的内存对齐是拿空间来换取时间的做法,增加了访问效率。在设计结构体的时候,我们既要满足内存对齐,又要节省空间,所以我们尽量让占用空间小的成员集中在一起。
七、结构体的传参问题
先来看两个例子;
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4},1000};
void print1(struct S s)
{
printf("%d\n",s.num);
}
void print1(struct S* s)
{
printf("%d\n",s->num);
}
int main()
{
print1(s);
print1(&s);
}
我们分别调用这两个print函数,查看其执行时间;
发现在给print函数传参的时候,对函数传结构体的名字所执行的速度比直接传结构体地址执行的速度慢了一倍之多。原因是:在调用函数的时候,需要传参,而在传参的时候参数需要压栈,如果在传一个结构体对象的时候,结构体过大,参数压栈的系统开销也很大,这样的话就会降低性能,所以效率变低,而直接传结构体的地址就不会影响性能,所以在结构体传参的时候传结构体地址。
欢迎大家共同讨论,如有错误及时联系作者指出,并改正。谢谢大家!