目录
1. 结构体类型的声明
1.2特殊的声明方法
1.3结构体的自引用
1.4 typedef 对结构体命名
2. 结构体变量的创建和初始化
3. 结构成员访问操作符
4. 结构体内存对⻬
4.1下面给大伙4个练习题,有自我解析
4.2为什么存在内存对齐
4.3修改默认对齐数
5. 结构体传参
1. 结构体类型的声明
结构体就是一些值的集合,然后将这些值成为成员变量。成员变量可以是不同的类型的变量
可以用结构体表示一个学生的数据。(其他物品的数据也可以表示),下面是一个描述学生的结构体。
struct stu
{
char name[10];
int age;
int h;
}s;
这个结构体的名字叫做stu 类型是struct stu 里面有三个成员变量。并且在下面还创建了一个结构体变量 s 。当然在其他函数下也可以创建变量,int i = 0,创建的是一个整型。
那结构体 的创建就是这样 struct stu yang = {0};下面是对结构体变量进行赋值。
int main()
{
struct stu s = { "yangjianglong",20,172 };
printf("%s %d %d",s.name, s.age, s.h);
return 0;
}
1.2特殊的声明方法
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
上面这两个代码省去了结构体的名字,上面原始的声明方法的结构体名字就是stu。而下面这两个代码省去了 结构体名字,这样也是可以的,但是这样的结构体就是一次性的,所以你必须在后面就把需要的变量添加上去。就像第一串的 x 变量和 后面的 a【20】和 *p 变量。
虽然这两个变量无名字但是他们的类型还是不同的。
1.3结构体的自引用
对于结构体的自引用来说必须用指针,而不能直接赋值变量。下面给到解析
如果这样写一个结构体自引用的话,首先他会无限循环下去,然后结构体的大小不能计算。因为这个结构体无限引用无法知道大小。 sizeof(struct Node)是无限大的,会报错。
但是如果你用指针自引用的话就可以避免这个问题
因为指针的大小在32和64位系统上是固定的为 4个或8个字节。 所以这里的在结构体里的自引用是能求出大小的。所以不会出现错误。
1.4 typedef 对结构体命名
typedef struct stu
{
int data;
}Node;
typedef 可以用来定义一个类型,这里就是将结构体 struct stu 重命名为Node。下次使用时 就用 Node 作为类型名即可,就像 Node s = {0};。
思考下面这串代码错在哪里?
typedef struct
{
int data;
Node* next;
}Node;
这个代码采用了 匿名结构体的形式初始化,但Node 是在整个结构体赋值结束后,才有的名称,所以上面的 Node* next 理论上并不存在。这样是不行的。
解决⽅案如下:定义结构体不要使⽤匿名结构体了
typedef struct Node
{
int data;
struct Node* next;
}Node;
2. 结构体变量的创建和初始化
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = { x, y };
第一步定义一个结构体,p1 是 在结构体后直接 创建变量,p2 是创建结构体变量但不赋值,p3是 创建结构体变量 并且赋值。
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = { x, y };
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = { "zhangsan", 20 };//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化
struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化
先创建一个 stu 结构体,s 变量是创建变量 并且赋值为 “zhangsan” ,20.
第二个是Node 结构体,n1 是直接在结构体后嵌套初始化,这样就不用在写一遍结构体类型
n2 是在结构体定义好了后,用类型名 创建一个 变量 并对其赋值。data 赋值为 20,在其中在调用 point 定义一个变量 p 其中在赋值 x y 为 5,6. 给next 循环指针赋值为 NULL 。
3. 结构成员访问操作符
结构体的操作符有两个,第一个是 . 第二个是 ->,第一个是一个点,第二个是一个箭头(减号加大于符号)。
举例如下:
#include <stdio.h>
#include <string.h>
struct Stu
{
char name[15];//名字
int age; //年龄
};
void print_stu(struct Stu s)
{
printf("%s %d\n", s.name, s.age);
}
void set_stu(struct Stu* ps)
{
strcpy(ps->name, "李四");
ps->age = 28;
}
int main()
{
struct Stu s = { "张三", 20 };
print_stu(s);
set_stu(&s);
print_stu(s);
return 0;
}
这个代码有 一个结构体定义,两个小函数。
定义一个 stu 结构体 添加两个成员变量。第一个变量作为名字,第二个作为年龄。
第一个函数则是用来打印 定义的变量,第一次打印s.name则是 在main 函数里定义的“张三”。s.age 是在main 函数里定义的 20.
第二个函数用来 改变 s 变量 里的内容。
4. 结构体内存对⻬
这是一个热门的考点
结构体的对齐规则:
1. 结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。- VS 中默认的值为 8- Linux中没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
4.1下面给大伙4个练习题,有自我解析
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
这个代码打印出来后,结果是12.怎么计算的呢。如果按我们正常计算字节数的话上面3个变量的字节加起来就 6 个字节啊?
12是怎么算的?
首先根据对齐规则 char 的字节 为 1 VS的默认对齐字节为8. 所以 char c1先占了 第一个字节。
但 第二个 int i 变量 的字节大小为 4 VS为 8 。所以对齐数为4. 因为对齐数为4,由对齐规则第二点得知 成员变量 要对齐到 整数倍的地址。所以int 的地址应该是从第4个 字节开始。
然后在占用4个字节所以现在 到了 第 8个字节。(与第一个char之间的三个字节算浪费了)
第三个char 是1个 对齐字节 所以不管什么整数都是他的整数。所以他直接占用第九字节即可
那不应该到这里就结束了嘛。但根据对齐规则第三点。结构体的大小必须是 所有成员变量的最大对齐数的整数倍。因为 int 的为最大对齐数 所以要是 4 的整数倍,现在已经占了 9个字节的位置,所以再给 2个字节到达 第12个字节即可。所以 大小为 12字节。
接下来给三个练习题,前面两个和上面这题没多大区别,重点是知道每个类型的字节大小。
最后一题需要知道嵌套结构体的字节大小。(会有自我理解在下面。)
struct S2
{
char c1;
char c2;
int i;
};
//练习3
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
S4的讲解在这!!
对于一个结构体里面在嵌套一个结构体变量这样的结构体大小是这样算的
首先你得先将 S3展开 就是将 S3里的全部变量拿出来,s3 就不是一个整体了,变成下面这样
struct S4
{
char c1;
double d;
char c;
int i;
double d;
};
这样计算出来就为32了。(不懂的uu可以私信我。)
4.2为什么存在内存对齐
1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。2. 性能原因:数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要 作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地对⻬成8的倍数,那么就可以 ⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两 个8字节内存块中。 总体来说:结构体的内存对⻬是空间来换取时间的做法。那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到:让占⽤空间⼩的成员尽量集中在⼀起
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
经过计算肯定是s2的内存占用较少,所以将空间小的成员集中可以节省空间。
4.3修改默认对齐数
默认对齐数是可以修改的,但要用到 # pragma这个预处理指令。实例看下面:
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对⻬数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S));
return 0;
}
5. 结构体传参
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 print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
上面两种结构体传参都是对的,但我们尽可能的使用 指针传参,为什么呢?
可以想象如果 你传的是一个结构体,如果那个结构体的体量很大,字节数很多,那么在传的时候,进行压栈,就会花费更多的空间和时间。
但如果你只是一个指针,你的字节数只会为4或者8. 那需要的时间 和空间都会相对较小。