- 结构体
- 意义
整数用int类型,浮点型用double,字符用char,多个同类型用数组...那么假设我要一个装学生信息的类型(姓名,年龄,学号...),也就是集多种类型于一身的类型,这就是结构体(可以类比是一个函数)
可以装数组、整型,字符串,浮点型等数据类型。
数组只能装同种类型的数据。
在实际项目中,结构体是大量存在的。研发人员常使用结构体来封装一些属性来组成新的类型。由于C语言内部程序比较简单,研发人员通常使用结构体创造新的“属性”,其目的是简化运算
封装的好处就是可以再次利用。
结构体的本质是数组,只是元素类型不同。
- 声明一个结构体类型
- 结构体组成
struct Stu
{
char name[10];//名字4个字才8字节+\0,所以够
int age;//年龄
double high;//身高
char Num[20];//学号
};
声明位置:在主函数外,所有函数都能使用;在函数内,在哪个函数,就只能在此函数使用,对外不可见
关键字:struct
结构题的名字:Stu不同的结构体 都是以名字区分,并且定义结构题变量 要用这个名字,唯一
花括号:结构体实体
结构体成员:name age num等,类型个数不限制,以及同种类型重复次数不限制。定义方式跟普通变量一样
分号结尾:结构体不加报错。结构体不加结构体成员报错。
- 不加名字结构体
struct
{
char name[10];
int age;
char Num[20];
};
不加名字使用比较受限制了
就不能用名字正常定义变量了
- 声明、定义结构体变量
- 声明两种形式
- 在声明结构体类型的时候顺便声明变量
- 方式一:struct Stu
{
char name[10];
int age;
char Num[20];
} stu1, stu2;//在结尾处声明变量
多个变量用逗号隔开
- 方式二:struct
{
char name[10];
int age;
char Num[20];
}stu1 ,stu2;
这一种声明方式是不用写名字的
- 利用结构体名字声明变量
struct Stu stu3, stu4;
- strcut Stu == int
- 关键字struct和结构体名组合成一种类型标识符,其地位如同通常的int、char等类型标识符
- 定义
即声明的时候初始化
struct Stu//声明结构体
{
char name[10];//名字4个字才8字节+\0,所以够
int age;//年龄
double high;//身高
char Num[20];//学号
};
int main(void)
{
struct Stu stu1 = {"王豪",25,1.73,"201921202094"};//声明、定义(初始化)变量
system("pause");
return 0;
}
- 原理跟数组初始化一样
- 依次初始化给对应的成员
- 初始化部分元素,其他的都给0
- 全局变量默认初始化0,局部变量随机
- 访问成员
- 结构体实例变量
实例变量:有空间的就是实例 struct Stu stu7;(类比:int a)
取成员运算符:“.“,成员变量不能直接使用
取数据:stu1.name ,stu1.age, stu1.Num
char str[10];
stu7.name 跟 str是一样一样的,只不过不能直接使用name,需要用stu7.name
struct Stu//声明结构体
{
char name[10];//名字4个字才8字节+\0,所以够
int age;//年龄
double high;//身高
char xuehao[20];//学号
};
int main(void)
{
struct Stu stu1 = { "王豪", 25, 1.73, "201921202094" };//声明、定义(初始化)变量
//结构体输出
printf("%s %d %lf %s",stu1.name,stu1.age,stu1.high,stu1.xuehao);
system("pause");
return 0;
}
- 结构体指针变量
指针变量:就是指针struct Stu *p(类比:int *p)
取成员运算符:->
int main(void)
{
struct Stu stu1 = { "王豪", 25, 1.73, "201921202094" };//声明、定义(初始化)变量
printf("%s %d %lf %s\n",stu1.name,stu1.age,stu1.high,stu1.xuehao);
struct Stu *p = &stu1;
printf("%s %d %lf %s\n", p->name,p->age,p->high,p->xuehao);//p操作的是指向的这块空间
system("pause");
return 0;
}
- 指针2种使用方式
方式一: -> p->name p->age p->Num 或者【(&stu7)->name;】都是地址访问。
方式二:. (*p).name (*p).age (*p).Num
struct Stu stu1 = { "王豪", 25, 1.73, "201921202094" };//声明、定义(初始化)变量
printf("%s %d %lf %s\n",stu1.name,stu1.age,stu1.high,stu1.xuehao);
struct Stu *p = &stu1;
printf("%s %d %lf %s\n", p->name,p->age,p->high,p->xuehao);
printf("%s %d %lf %s\n", (*p).name, (*p).age, (*p).high, (*p).xuehao);
结果都是一样的!
地址访问用箭头;空间本身(实例)访问用点.
- 赋值
- 成员单个赋值
strcpy(stu7.name, "hello C3~")//字符 数组 必须用 strcpy / 循环
stu7.age = 18;
strcpy(stu7.Num, "20180101")
- 复合文字结构
赋值和初始化不同,赋值是后期,初始化是最开始的赋值。
c99 // 2013 2015 2017 支持
- 全部元素赋值:stu7 = (strcut stu){"小华", 12, "123456"}//类似结构体变量初始化
struct Stu stu = { .age = 27 };
struct Stu stu1 = { "王豪", 25, 1.73, "201921202094" };//声明、定义(初始化)变量
stu1 = (struct Stu){ "小华", 12, 1.73,"201244155" };
printf("%s %d %lf %s\n",stu1.name,stu1.age,stu1.high,stu1.xuehao);//结果:小华 12 1.730000 201244155
- 初始化指定元素:
struct Stu stu = { .age = 27 };
- 其他类型成员
- 指针成员
struct Stu//声明结构体
{
int *p;
};
int main(void)
{
int a [6] = {1,2,3,4,5};
struct Stu stu1 = { a };
printf("%d %d\n",stu1.p[0],stu1.p[2]);//stu1.p[]和a[]使用一样
syst em("pause");
return 0;
}
- 函数成员
函 数不能 放在结构体中,所以想法:把函数地址(函数指针)放在其中,间接使用。
例子一:void fun(void)
{
printf("i am fun");
}
struct Stu//声明结构体
{
void(*p)(void);
//int *p;
};
int main(void)
{
struct Stu stu1 = { fun};//&fun或者fun是一样的,就是函数的地址
(stu1.p)();//函数调用=地址+参数
system("pause");
return 0;
system("pause");
return 0;
}
例子二:
void fun()
{
printf ("hello c3");
}
strcut Teach
{
void (*p)();
};
初始化:Struct teach te = {fun};
调用:(te.p)();
- 结构体嵌套
- 嵌套结构体名字
取嵌套成员:tea.stu9.b
初始化:struct Teach tea = {12,{b}};里面用花括号进行初始化
赋值:Teach tea;
tea.stu9.b = 12;
struct Stu//声明结构体
{
char name[10];
int age;
};
struct teach
{
char name[100];
struct Stu st;
int age;
};
int main(void)
{
struct teach te = { "包老师", { "王豪", 27 }, 25 };//不加花括号也行 因为顺序已经确 定了 加的话:比较清晰
printf("%s,%s\n", te.st.name, te.name);
//struct Stu stu1 = {};
system("pause");
return 0;
}
- 嵌套结构体结构体
也可以定义在结构体中
这种跟主函数的那种有些差别,这个在外边也可以直接使用。
struct teach
{
struct Stu//声明结构体
{
char name[10];
int age;
};
char name[100];
struct Stu te1;
int age;
};
int main(void)
{
struct teach te0 = { "包老师", { "王豪", 27 }, 25 };//不加花括号也行 因为顺序已经确定了 加的话:比较清晰
printf("%s,%s\n", te0.te1.name, te0.name);
//struct Stu stu1 = {};
system("pause");
return 0;
}
这个没有第一个清晰明白!
- 结构体数组
每一个元素都是结构体
struct teach
{
char name[100];
int age;
};
int main(void)
{
struct teach te0[3] = { { "包老师", 200 }, { "王豪", 27 }, { "豪", 29 }, };
//不加花括号也行 因为顺序自动确定,会出错,加的话:比较清晰,且每个花括号里面可以自由初始化
printf("%d,%s\n", te0[0].age ,te0[1].name);
system("pause");
return 0;
}
- 声明及初始化
struct Stu stu[3] = {{"小李",12,“1234”},{“小黄”,13, “1235”},{“小刘”, 25, “1236”}};
- 调用
stu[0].name //类似数组调用
- 赋值
法一、strcpy(stu[0].name, "asd");//利用strcpy函数
法二、stu[0] = (struct Stu){"qqq", 34, "1237"};
- 结构体大小
- 字节对齐/内存对齐
- cpu处理数据
- CPU取数据是固定的方式:4/8字节的取读
- 32位cpu一次能处理的数据是32bit位 4字节
- 64位cpu一次能处理的数据是64bit位 8字节
- 起始地址是偶数:因为从0开始,+4 +4 /+8+8
- 对齐方式
- 跨段存储
假设数据挨着存储,char c, int a;就会出现:跨字节段存储
因此:cpu要读两次,把两次的组成一个数据,这样的跨段效率就低了
因此就出现了内存对齐方式
- 对齐存储
是数据存储规则,假设我的数据按字节段存储 char c, int a;分别存在各自的字节段,每个数据cpu只需要读一次,就得到了
这种存储方式叫内存对齐
执行效率大大提高,但是会浪费一点儿空间!
以上两种方式对比总结:空间换时间,时间换空间。二者不可兼得
- 结构
- sizeof()求大小
不是简单的所有成员大小之和
要涉及到内存对齐
- 计算规则
变量排列不同,大小字节数也不同,因为对齐顺序不同了。
1、以最大类型为字节对齐宽度
2、依次填补各个成员字节
3、偶地址开始,4字节对齐
4、结尾补齐(每个类型的结尾)
矩形对齐格!
struct teach
{
double d;//8字节
int age;//4
short a;//2
char k;//1
char name[10];//这个是1字节,连续10个
};
- 上面的是最大类型8字节为模板
- 当然也可以手动设置模板
#pragma pack(1)
可以往小设置,不能设置比最大的大
但是我们一般不做设置,编译器会自动帮我们选择合适的对齐策略,了解即可。