C基础结构体
Posted mChenys
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C基础结构体相关的知识,希望对你有一定的参考价值。
目录
- 一、结构体struct的定义和.操作符的使用
- 二、 结构体的创建和初始化
- 三、结构体的内存对齐模式
- 四、指定结构体元素的位字段(bit)
- 五、结构数组
- 六、结构体嵌套
- 七、结构体的赋值
- 八、箭头操作符 ->的使用
- 九、指向结构体数组的指针
- 十、结构体中的数组成员和指针成员
- 十一、在堆中创建结构体
- 十二、结构体作为函数的参数
- 十三、案例-使用结构体动态接收控制台输入参数
- 十四、思考:结构体的成员到底在栈还是堆? 结构体的成员可以在结构体中初始化吗?
- 十五、如何解决数组名不能当做左值进行赋值的问题
- 十六、结构体内存申请与赋值操作
- 十七、声明结构体的时候确定结构体的名字
- 十八、结构体使用=号赋值的隐患
- 十九、结构体嵌套一级指针的内存管理
- 二十、结构体内嵌套二级指针的内存管理
- 二十一、结构体嵌套结构体的赋值
- 二十二、结构体嵌套结构体的指针偏移取值操作
- 二十三、结构体的对齐模式
一、结构体struct的定义和.操作符的使用
#include <stdio.h>
#include <string.h>
// 定义一个结构体,相当于扩展的数据类型
struct student
char name[100];
int age;
; // 注意这里的;号不能少
int main()
// 定义一个student类型的结构体变量,变量名是st,存在栈内存中
struct student st; // 注意struct关键字不能少
// 通过.操作符给结构体的变量赋值
st.age = 20;
strcpy(st.name, "刘德华");
printf("name=%s,age=%d\\n", st.name, st.age); // name=刘德华,age=20
return 0;
二、 结构体的创建和初始化
struct数据类型同样支持在创建的时候进行初始化,这里介绍4种初始化方式。
#include <stdio.h>
#include <string.h>
// 定义一个结构体,相当于扩展的数据类型
struct student
char name[100];
int age;
; // 注意这里的;号不能少
int main()
// 创建结构体并初始化
// 方式1
struct student st1 = "周星驰", 40;
printf("方式一:name=%s,age=%d\\n", st1.name, st1.age);
// 方式2
struct student st2 = "周杰伦"; // 仅初始化名字
printf("方式二:name=%s,age=%d\\n", st2.name, st2.age);
// 方式3
struct student st3 = 0; // 重置所有变量
printf("方式三:name=%s,age=%d\\n", st3.name, st3.age);
// 方式4
struct student st4 = .age = 30, .name = "张学友"; // 通过.操作符,可以不用按照变量在结构体中的定义顺序来赋值
printf("方式四:name=%s,age=%d\\n", st4.name, st4.age);
return 0;
结果如下:
方式一:name=周星驰,age=40
方式二:name=周杰伦,age=0
方式三:name=,age=0
方式四:name=张学友,age=30
三、结构体的内存对齐模式
编译器在编译一个结构体的时候采用内存对齐模式
#include <stdio.h>
// 定义一个结构体,相当于扩展的数据类型
struct man
char a;
int b;
;
int main()
struct man m;
printf("%lu\\n", sizeof(m)) ; // 结果是 8
一个结构体变量的成员总是以最大的那个元素作为对齐单位的,也就是说上面man的结构体中虽然int+char总共才5个字节, 但是对齐后会以int的大小为最小单位来分配内存,那就是分配8个字节, 这样当新增变量的时候会判断结构体中空闲的空间是否能放下,如果能放下则不会去申请内存,如果放不下则又会继续申请n*4个字节,n的大小由变量个数和类型决定(假设新增的变量的数据类型长度都是小于4个字节的情况下); 如果结构体成员出现数组,对齐的时候也是看数组的类型和其他数据类型做比较,找最大的数据类型作为对齐的最小单位。
注意:C语言中结构体的内存对齐还会受到结构体中变量的定义顺序的影响.
#include<stdio.h>
struct A
char a;
short b;
char c;
int d;
;
int main()
struct A a = 1,2,3,4;
printf("%lu\\n",sizeof(a)); //结果是12,而不是8, 这是因为short的摆放位置导致的
return 0;
用一张图来说明上面输出结果是12而不是8的原因
所以上面的代码可以优化一下
struct A
char a;
char c;
short b; //很简单,只需要把short移动一下, 把char c移上去即可
int d;
;
这个时候内存对齐的图示就变成这样了.
看图就知道了,这样就可以只需要分配8个字节的内存就可以了.
规律:结构体内存的对齐规律总是以2个倍数进行对齐的,如下图所示:
3.1结构体强转其他类型
如果一个结构体中所有成员都是一种类型,那么这个结构体变量在内存中就基本和一个数组类似,因为都是内存连续的一块空间。
#include<stdio.h>
struct A
char a[10];
char b;
;
int main()
struct A a = 0;
printf("%u\\n",sizeof(a)); //11
//如果一个结构体中所有成员都是一种类型,那么这个结构体变量在内存中就基本和一个数组类似
char *s = (char*) &a; //强制转成char *类型,就可以把他看成char[] 了
//通过数组赋值的方式对结构体的变量赋值
s[0] = 'a';
s[1] = 'b';
s[2] = 'c';
printf("a.a=%s,a.b=%c\\n",a.a,a.b); //a.a=abc, a.b还是默认值, 因为结构体的前10个字节都是变量a的空间
printf("%p,%p\\n",&a,a.a); //结构体变量的地址就是这个结构体首元素的地址
return 0;
四、指定结构体元素的位字段(bit)
定义一个结构体的时候可以指定具体的元素的位长(bit)
struct test
char a : 2;// 指定元素为2位长(2bit),不是2个字节长 ,注意是 : 号
;
结构体中使用位字段是有特殊用途的,比如一个char 有8个bit, 最多可以当做8个布尔变量来使用, 因为一个bit要么是0,要么是1。
#include<stdio.h>
struct A
unsigned char a1 : 1;
unsigned char a2 : 1;
unsigned char a3 : 1;
unsigned char a4 : 1;
unsigned char a5 : 1;
unsigned char a6 : 1;
unsigned char a7 : 1;
unsigned char a8 : 1;
;
struct B
unsigned char a:1;
int b;
;
struct C
unsigned char a:1;
int b[10];
;
int main()
// 结果是1, 因为8个bit 刚好用1个字节就可以搞定了. 虽然结构体A中定义了8个char 关键字,
// 但是每个变量都是用:1 表示只需要char中的1个bit
printf("%u\\n",sizeof(struct A));
// 结果是8, 因为内存对齐是以int为最小单位分配的,所以分配了2个int的大小就是8个字节.
printf("%u\\n",sizeof(struct B));
// 结果是44,因为变量b需要10个int, 变量a以int位最小单位对齐,也需要1个int, 所以总共 11个int, 也就是44个字节
printf("%u\\n",sizeof(struct C));
return 0;
五、结构数组
结构体也是变量,所以也可以存在数组的形式
#include<stdio.h>
struct student //定义一个结构体
char name[20];
unsigned char age;
int sex;
;
int main()
// 定义固定大小的student结构体数组, 数据类型是struct student
// struct student st[3] = "刘德华",30,1,"小明",40,0,"hello",20,0;
// 定义student结构体数组并初始化值 ,[]不填数字,说明数组是动态的, 可以初始化任意个数
struct student st[] = "abc",20,1,"hello",10,2;
int i;
for(i =0 ;i < sizeof(st)/sizeof(st[0]);i++)
//循环取出数组中的元素, 元素的数据类型是 struct student
struct student s = st[i];
printf("name=%s,age=%d,sex=%d\\n",s.name,s.age,s.sex);
return 0;
5.1 结构体数组的冒泡排序
#include <stdio.h>
struct student
char name[10];
unsigned char age;
;
int main()
//定义一个student结构体数组
struct student st[] = "a", 5, "b", 10, "c", 8, "d", 2;
int i, j;
int size = sizeof(st) / sizeof(st[0]); // 获取数组元素个数
//冒泡排序
for (i = 0; i < size; i++)
for (j = 0; j < size - i - 1; j++)
if (st[j].age > st[j + 1].age) // 对比年龄
// 定义临时变量, 注意类型是struct student
struct student temp = st[j];
st[j] = st[j + 1];
st[j + 1] = temp;
i = 0;
for (i = 0; i < size; i++)
printf("name=%s,age=%d\\n", st[i].name, st[i].age);
return 0;
结果如下:
name=d,age=2
name=a,age=5
name=c,age=8
name=b,age=10
六、结构体嵌套
结构体是允许被嵌套的, 嵌套后的结构体在内存对齐的时候会单独看成一个整体,和它同级的变量不会存放到这个结构体的内存区域中.
#include <stdio.h>
struct A
int a1;
char a2;
;
struct B
struct A a1; // 结构体A会单独一个整体
char a2;
int a3;
;
int main()
struct B b; // 创建结构体B
b.a1.a1 = 10; // 给结构体B中的A的变量a1赋值
printf("%lu\\n", sizeof(b)); // 长度是16
printf("a.a1=%d\\n", b.a1.a1); // a.a1=10
return 0;
长度是16,因为结构体B中的 a2变量不会跑去结构体A的内存区域, 因为结构体A是单独一个整体,所以结构体B中的a2会单独申请一个int 的最小单元大小, 如下图示:
七、结构体的赋值
结构体本质也是变量, 所以是可以直接用=号赋值,当然前提是必须同类型的结构体之间才可以直接赋值.
#include <stdio.h>
struct Student
char name[10];
int age;
;
int main()
struct Student s1 = "abc", 20;
struct Student s2 = s1; // 结构体的变量赋值就是内存拷贝
printf("%s,%d\\n", s2.name, s2.age); // abc,20
return 0;
八、箭头操作符 ->的使用
当指针指向结构体时,结构体的成员赋值可以使用箭头符号->
#include <stdio.h>
#include <string.h>
struct student // 定义一个student结构体
char name[10];
unsigned char age;
;
int main()
// 创建一个student结构体
struct student s = "hello", 20;
// 定义student结构体的指针,指向student结构体的地址
struct student *p = &s;
// 有2种方式对结构体的变量赋值
// 方式1 , 这种方式就是前面学的, 通过*p先获到student结构体 ,然后再操作结构体内的变量
strcpy((*p).name, "hi");
(*p).age = 50;
// 方式2, 使用箭头操作符, 推荐使用
strcpy(p->name, "hi");
p->age = 50;
return 0;
九、指向结构体数组的指针
指向结构体数组和指向普通数组的指针用法一样,把指针变量名当做数组名来使用即可.
#include <stdio.h>
#include <string.h>
struct student // 定义一个student结构体
char name[10];
unsigned char age;
;
int main()
// 创建一个student结构体数组
struct student st[3] = "张三", 0, "李四", 0, "王五", 0;
// 数组名就是首元素的地址,可以用指针来接收
struct student *p = st;
p->age = 100; // 操作首元素的结构体student的age
p++; // 指针++后执行数组的第二个元素
p->age = 20; // 再次对age赋值,此时操作的是数组中的第二个元素了
p--; // 重新复位指针的位置
// 遍历数组, 操作指针即可
for (int i = 0; i < 3; i++)
// 操作指针和操作数组名一样
printf("操作指针--->%s,%d\\n", p[i].name, p[i].age);
printf("操作数组名--->%s,%d\\n", st[i].name, st[i].age);
return 0;
结果如下:
操作指针--->张三,100
操作数组名--->张三,100
操作指针--->李四,20
操作数组名--->李四,20
操作指针--->王五,0
操作数组名--->王五,0
十、结构体中的数组成员和指针成员
一个结构中可以有数组成员,也可以有指针成员,如果要对结构体内的指针变量赋值的时候就需要提前为指针成员分配内存。
struct man1
char name[100]; // 数组成员
;
struct man2
char *name; // 指针成员
;
上面2个结构体的name变量的含义是不一样的,char name[100];是一个数组,变量名name是有内存地址的,就是数组首元素的地址, 而 char *name;是一个指针,没有赋值的话是NULL,也就是没有指向任何的地址,是不能直接使用的。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct man1
char name[100];
;
struct man2
char *name;
;
int main()
struct man1 m1 = 0;
struct man2 m2 = 0;
printf("man1.name=%p,man2.name=%p\\n", m1.name, m2.name); // man1.name=0x16b43aff4,man2.name=0x0
strcpy(m1.name, "hello"); //数组可以直接使用strcpy.
// strcpy(m2.name,"hello");//这个会报错,因为man2的name变量是指针,使用前必须先指向一个内存地址, 否则就是空指针
// 先给指针指向一个地址,从堆中申请20个字节大小
m2.name = (char *)calloc(20, sizeof(char));
strcpy(m2.name, "hello world"); //char *使用strcpy必须先初始化指针
printf("man1.name=%s,man2.name=%s\\n", m1.name, m2.name); // man1.name=hello,man2.name=hello world
// 释放堆内存
free(m2.name);
return 0;
说白了就是结构体的char *
不能直接传递给strcpy使用,必须先指向一个地址, 也就是需要先初始化, 或者在创建结构体的时候就显示赋值, 这种操作等同于char * c = "hello";
就是指向一个字符串常量的地址。