定义结构体如下
typedef struct MyPoint {
int x;
int y;
} MyPoint;
一:结构体变量的初始化
通常不能直接用结构体自身对自己进行赋值, 需通过结构体的变量来进行初始化
如:
1.
MyPoint p;
p.x = 5;
p.y = 6;
这是声明和赋值分离
2.
也可声明的同时进行赋值:
MyPoint p = {5, 6};
3.
当然也可以声明的同时, 不完整赋值
MyPoint p1 = {5};
如此时只给了一个值, 按照顺序, 这个值是传给结构体中第一个成员x, 第二个成员y没有赋值, 默认为0
4.
C语言结构体不允许跳过某一个值, 对其下一个成员进行赋值
如:
MyPoint p2 = {, 5};
这是不允许的. 由于结构体空间是至上而下的, 在赋值的时候编译器会从上往下访问成员变量的地址, 首先访问到成员x的地址, 对其赋值, 再找到成员y的地址, 对其赋值.这意味着 这个语句给第一个成员赋上空值, 第二个成员赋值为5. 而空值是无法确定该值的地址, 也就是C语言找不到这个空值的地址, 也就是无法知道具体给x赋上了什么值, 导致编译失败
那么要怎么做才能跳过第一个值, 直接对后面的值赋值呢?
可以通过访问成员变量的地址, 给其赋值
如:
MyPoint p2 = {.y = 5};
这么做是直接跳过x的地址, 直接访问到成员y的地址, 对其赋值
同理, 要给全部成员赋值也可以这么写:
MyPoint p2 = {.x = 3, .y = 5};
由于本方法是直接让系统访问到相应成员的空间对其赋值, 所以用本方法可以无视赋值顺序, 先给第二个成员赋值 再给第一个成员赋值结果也是一样, 是可以通过编译的
5.
可以通过强制转换数据类型对其进行编译
如:
MyPoint p3 = (MyPoint){3, 5};
可以理解为 数据{3, 5} 是在结构体MyPoint 声明的空间进行的赋值 注意这里的强转名和变量的类型要匹配 不能再定义另一个成员和MyPoint 完全相同的结构体作为强转名, 这会导致类型不匹配的问题不通过编译
附: 通常同类型的强转不会失败, 但是却常常是多此一举的. 因为不论进不进行强转 系统都会通过被复制的变量的类型开辟本类型的空间, 再声明是否是本类型的数据就显得没有意义了, 所以通常会缺省(MyPoint)
二.结构体数组的初始化
首先得明白两个C语言规则
1.首先得明确一点, 在数组的初始化中是不能进行声明的
int a[5] = {int b = 10, int c = 11};
比如这就是不允许的 同理, 结构体数组也是一样
原因是 : 因为声明意味着新开辟一个空间, 而赋值是已经给定好了一块空间, 在这块空间进行书写. 由于地址是常量, 在赋值过程声明, 相当于在一块定值大小空间里再开辟一块新空间,相当于要改变原地址的值, 这是在C语言中不允许的.
2.赋值是传值过程, 比如说int a = 5; int b = a; 这时a 和 b均有空间, 赋值语句 b = a 意思是将 a空间的值 赋值一份 再传到 b的空间里, a, b的空间依然还存在
明确了这2点后进行接下来的讨论结构数组的初始化.
1.
由于不能在数组里面不能在声明一个新的变量,所以:
MyPoint po[2] = {MyPoint po1, MyPoint po2};
这么写是不允许的. 那怎么才能将结构体变量放到结构体数组中呢
根据规则2, 可以先定义结构体变量, 然后将结构体变量的值传入数组中
MyPoint p1 = {5, 3}, p2 = {5};
MyPoint po[2] = {p1, p2};
这里暂且称p1, p2是数组po的数据源
这样就让数组po复制保存了 p1, p2的值了, 可以通过数组的操作方式, 来对这个数组进行处理
注意我用的是复制这个词. 因为对数组po进行数值操作, 数据源的值是不会跟着改变的.
比如: 接上代码继续写如下
po[0].x = 10;
printf("%d, %d",po[0].x, p1.x);
让数组po第0个元素(p1)的成员x 变成10, 再访问数据来源的结构体变量p1的成员x
发现打印结果为
10, 5
也就是说改变了数组里元素的值, 数据源的值是不会随着改变的
有兴趣的读者可以继续实验, 这里就不跑题了 继续讨论结构体数组的初始化方法
2.
如果不想在结构体数组外再多余开辟空间, 可以直接在数组空间内赋值
MyPoint po1[3] = {{4, 2},{5, 5},{6, 3}};
注意的是, 在数组内赋值的规则同结构体变量的赋值规则, 只是在数组内不能再声明, 只能通过数组的下标获得对应元素
3.
同结构体的赋值, 若我定义了一个元素个数为5 的数组, 要怎么跳过前边的元素, 对后面的某个元素赋值呢?
比如:
MyPoint po2[5] = {,p1,,,p2};
定义一个元素个数为5的po2数组, 直接对 下标为1, 4的元素进行赋值
根据前文的道理, 这是不允许的, 得通过直接找到并访问要赋值的元素地址进行赋值才可以打破编译器的顺序赋值的顺序进行想要的赋值
那怎么直接访问下标为1, 4的元素地址呢?
MyPoint po2[5] = {[1] = p1, [4] = p2};
可以直接通过下标来获得对应下标的地址, 进行数值的赋值
附: 结构体的空间开辟规则
如定义一个结构体
struct P {
int x[10];
int y;
char c;
double z;
};
它的空间大小是多少呢?
我们来计算一下:
数组x是 int型数组,共8个元素 int是4个字节, 所以共占4*10 = 40个字节空间
变量y是int型 只有一个 占4个字节空间
变量c是char型 只有一个 只占1个字节空间
变量z是double型 只有一个, 占8个字节空间
一共是40 + 4 + 1 + 8 = 53 , 所以一个声明一个结构体变量, 会开辟大小为53的空间, 而实际上呢?
用sizeof()函数来看一下:
printf("%lu",sizeof(struct P));
结果为56.并不是53
这里需要了解到结构体的自动字节对齐
是什么意思呢?
编译器会先注意到结构体struct P内的成员 单个元素占字节空间最大的是8
所以编译器会以8个字节为单位开辟空间 顺序是这样的
先开辟了8个空间, 然后将数组x放入, 放了a[0], a[1]以后空间已满, 再放a[2]时就溢出了, 这时候就会再开辟8个空间, 如此遇满再开一个单位空间 直到结构体内的所有成员保存完毕
#pragma pack(1)
在
结构体声明的文件里 import语句下
加上本句, 可以取消结构体的自动字节对齐功能 这时候再查看结构体的空间 就会得到53了