构造类型及结构体
1.C语言构造类型有哪些?
-
构造数据类型:构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基 本数据类型或又是一个构造类型。
-
在C语言中,构造类型有以下几种:
- 数组类型
- 结构体类型
- 共用体(联合)类型
2.什么是结构体?
- 在实际问题中,一组数据往往具有不同的数据类型。例如,在学生登记表中,姓名应为字符 型;学号可为整型或字符型;年龄应为整型;性别应为字符型;成绩可为整型或实型。显然不能 用一个数组来存放这一组数据。因为数组中各元素的类型和长度都必须一致,以便于编译系统处 理。为了解决这个问题,C语言中给出了另一种构造数据类型——“结构(structure)”或叫 “结构体”。 它相当于其它高级语言中的记录。
- “结构”是一种构造类型,它是由若干“成员”组成的。每一个成员可以是一个基本数据类型或
- 结构体是一种“构造”而成的数据类型,那么在说明和使用之前必须先定 义它,也就是构造它。如同在说明和调用函数之前要先定义函数一样。
3.为什么要有结构类型?
- 结构体可以把功能相同的数据组织起来(封装数据),存在一起,用的时候方便
- 在调用函数时,若 传递参数较多,传一个结构体相对而言简单一些。
- iOS开发中经常需要使用结构体
定义结构体
本小节知识点:
1.定义结构体的方法
- 定义一个结构的一般形式为: ``` struct 结构体名{ 类型名1 成员名1; 类型名2 成员名2; …… 类型名n 成员名n; };
- 示例
struct Student { char *name; // 姓名 int age; // 年龄 float height; // 身高 };
结构体变量的定义
本小节知识点:
-
格式:
- struct 结构体名 结构体变量名;
1.先定义结构体类型,再定义变量
- 结构体变量名为stu
struct Student {
char *name;
int age;
};
struct Student stu;
结构体变量的初始化
本小节知识点:
- 【掌握】先定义结构体变量,然后再初始化
- 【掌握】定义的同时初始化
1.先定义结构体变量,然后再初始化
- 将各成员的初值,按顺序地放在一对大括号{}中,并用逗号分隔,一一对应赋值。
struct Student {
char *name;
int age;
};
struct Student stu = {“NJ", 27};
2.定义的同时初始化
struct Student {
char *name;
int age;
} stu = {“NJ", 27};
- 注意: 只能在定义变量的同时进行初始化赋值,初始化赋值和变量的定义不能分开,下面的做法是错误的:
struct Student {
char *name;
int age;
};
struct Student stu;
stu = {“NJ", 27}; // 错误
- 可以使用另外一已经存在的结构体初始化新的结构体 ``` struct Student stu2 = stu;
- 可以通过强转的方式进行整体赋值
stu2 = (struct Student){2020,"cyx"};
```
2.定义结构体类型的同时定义变量
- 结构体变量名为stu
struct Student {
char *name;
int age;
} stu;
- 这种形式的说明的一般形式为:
struct 结构名{
成员表列
}变量名表列;
3.匿名结构体定义结构体变量
- 结构体变量名为stu
struct {
char *name;
int age;
} stu;
- 这种形式的说明的一般形式为:
struct{
成员表列
}变量名表列;
- 第三种方法与第二种方法的区别在于第三种方法中省去了结构名,而直接给出结构变量,这种结构体最大的问题是,不能再次定义新的结构体变量了。
4.如何访问结构体变量的成员?
- 一般对结构体变量的操作是以成员为单位进行的,引用的一般形式为:结构体变量名.成员名
struct Student {
char *name;
int age;
};
struct Student stu;
// 访问stu的age成员
stu.age = 27;
printf("age = %d", stu.age);
结构体内存分析
本小节知识点:
1.结构体存储原理
- 结构体类型与int\char\double一样,编译系统并不对它分配内存空间。只用通过他来定义变量的时候才会为这个变量分配存储空间
- 内存是以字节为单位编号,但一些硬件平台对某些特定类型的数据只能从某些特定地址开始, 比如从偶地址开始。若不按照适合其平台的要求对数据存放进行对齐,会影响到效率。 因此,在内存中,各类型的数据是按照一定的规则在内存中存放的,这就是对齐问题。
- 结构体占用的内存空间是每个成员占用的字节数之和(考虑对齐问题)。
2.结构体数据成员对齐的意义
- 许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的起始地址的值是 某个数k的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。
- 这种强制的要求一来简化了处理器与内存之间传输系统的设计,二来可以升读取数据的速度。比如这么一 种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能 保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只需要一次内存操作。否 则,我们就可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节 内存块上。
3.结构体变量占用存储空间大小
-
对齐模式
- 可以理解为每一次分配多少个字节存储空间,可以通过#pragma pack 指定
- 默认情况下,对齐的模数是占用存储空间最大的成员所占用的字节数
-
计算方法
- 1)将结构体内所有数据成员的长度值相加,记为sum_a;
- 2)将各数据成员为了内存对齐,按各自对齐模数而填充的字节数累加到和sum_a上,记为sum_b。 对齐模数是#pragma pack指定的数值以及该数据成员自身长度中数值较小者。该数据相对起始位 置应该是对齐模式的整数倍;
- 3)将和sum_b向结构体模数对齐,该模数是#pragma pac指定的数值和结构体内部最大的基本数据 类型成员长度中数值较小者。
结构体的长度应该是该模数的整数倍。
-
所谓“对齐在N上”,指定是“存放起始地址%N=0”
struct A
{
int a; //4
char b; //分配4个,用了1个,剩下3个
short c; //需要2个
};
printf("%zd\n",sizeof(struct A));
- 注意结构体中有数组的问题
- 使用pragma pack(指定的对其长度) 可以指定内存对齐长度
4.练习
- 分析一下结构体占用的字节数
struct A
{
int my_test_a;
char my_test_b;
double my_struct_a;
int my_struct_b;
char my_struct_c;
}
- 如果强制设置对齐模数为2 #pragma pack(2),占用多少个字节?
结构体类型的作用域
1.作用域概述
- 结构类型定义在函数内部的作用域与局部变量的作用域是相同的
- 函数外部定义的结构体类型类似全局变量
- 全局作用域:从定义的那一行开始直到本文件结束为止
2.作用域分类
- 结构体根据作用于可以分为全局结构体、局部结构体
//定义一个全局结构体,作用域到文件末尾
struct Person{
int age;
char *name;
};
void test() {
//使用全局的结构体定义结构体变量p
struct Person p = {10,"sb"};
printf("%d,%s\n",p.age,p.name);
//定义局部结构体名为Person,会屏蔽全局结构体
//局部结构体作用域,从定义开始到“}”块结束
struct Person{
int age;
};
struct Person pp;
pp.age = 50;
// pp.name = "zbz";
}
int main(int argc, const char * argv[])
{
test();
return 0;
}
结构体嵌套定义
1.结构体的嵌套方法
-
员也可以又是一个结构,即构成了嵌套的结构
Struct Date{ int month; int day; int year; } struct stu{ int num; char *name; char sex; struct Date birthday; Float score; } -
在stu中嵌套存储Date结构体内容
-
注意:
-
结构体不可以嵌套自己变量,可以嵌套指向自己这种类型的指针
struct Student { int age; struct Student stu; };
-
2.对嵌套结构体成员的访问
- 如果某个成员也是结构体变量,可以连续使用成员运算符"."访问最低一级成员
struct Date {
int year;
int month;
int day;
};
struct Student {
char *name;
struct Date birthday;
};
struct Student stu;
stu.birthday.year = 1986;
stu.birthday.month = 9;
stu.birthday.day = 10;
结构体数组
1.结构体数组的概念
- 数组的元素也可以是结构类型的。因此可以构成结构型数组。结构数组的每一个元素都是具有相同结构类型的下标结构变量。在实际应用中,经常用结构数组来表示具有相同数据结构的一个群体。如一个班的学生档案,一个车间职工的工资表等。
2.结构体数组定义
-
跟结构体变量一样,结构体数组也有3种定义方式
- 先定义结构体类型,再定义结构体数组
struct Student {
char *name;
int age;
};
struct Student stu[5]; //定义1
+ 定义结构体类型的同时定义结构体数组
struct Student {
char *name;
int age;
} stu[5]; //定义2
+ 匿名结构体定义结构体结构体数组
struct {
char *name;
int age;
} stu[5]; //定义3
3.结构体数组初始化
- 定义的同时进行初始化 ``` struct { char *name; int age; } stu[2] = { {”NJ", 27}, {"JJ", 30} };
- 先定义,后初始化,整体赋值
// 先定义,后初始化,整体赋值 s[1]=(struct stu){23,"xiaoluo"};
// 先定义,后初始化 分开赋值 s[1].age=12; strcpy(stu[1].name, "xxoo"); ```
结构体指针
1.指向结构体变量的指针
- 一个指针变量当用来指向一个结构体变量时,称之为结构体指针变量
- 结构指针变量中的值是所指向 的结构变量的首地址
- 通过结构指针即可访问该结构变量,这与数组指针和函数指针的情况是相 同的。
2.结构体指针的定义与初始化
-
结构指针变量说明的一般形式为:
struct 结构名 *结构指针变量名 -
示例
// 定义一个结构体类型
struct Student {
char *name;
int age;
};
// 定义一个结构体变量
struct Student stu = {“NJ", 27};
// 定义一个指向结构体的指针变量
struct Student *p;
// 指向结构体变量stu
p = &stu;
/*
这时候可以用3种方式访问结构体的成员
*/
// 方式1:结构体变量名.成员名
printf("name=%s, age = %d \n", stu.name, stu.age);
// 方式2:(*指针变量名).成员名
printf("name=%s, age = %d \n", (*p).name, (*p).age);
// 方式3:指针变量名->成员名
printf("name=%s, age = %d \n", p->name, p->age);
return 0; }
3.通过结构体指针访问结构体成员
-
结构指针变量,就是用来存储结构体变量地址的,可以通过它访问结构变量的各个成员。
-
可以通过以下两种方式
- (*结构指针变量).成员名
- 结构指针变量->成员名(用熟)
-
注意:
- (pstu)两侧的括号不可少,因为成员符“.”的优先级高于“ ”。 如去掉括号写作pstu.num则等效于(pstu.num),这样,意义就完全不对了。
结构体和函数
1.成员值作为函数的参数
- 结构体成员属性作为函数的参数就是值传递(成员变量是数组除外)。
2.结构体变量名作为函数的参数
- 将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变
不会影响到实参。
3.结构指针作为函数的参数
- 结构体指针作为函数的参数是地址传递时,形参的改变
会影响到实参
枚举类型基本概念
1.什么是枚举类型?
- 在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。如果把这些量说明为整型,字符型或其它类型 显然是不妥当的。
- C语言供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值, 被说明为该“枚举”类型的变量取值不能超过定义的范围。
- 应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何 基本类型。
2.枚举类型的定义
- 格式
enum 枚举名 {
枚举元素1,
枚举元素2,
……
};
// 表示一年四季
enum Season {
spring,
summer,
autumn,
Winter
};
1.枚举变量
- 先定义枚举类型,再定义枚举变量
enum Season {
spring,
summer,
autumn,
Winter
};
enum Season s;
- 定义枚举类型的同时定义枚举变量
enum Season {
spring,
summer,
autumn,
winter
} s;
- 省略枚举名称,直接定义枚举变量
enum{
spring,
summer,
autumn,
winter
} s;
4.枚举类型变量的赋值和使用
- 可以给枚举变量赋枚举常量或者整型值 ``` enum{ spring, summer, autumn, winter } s;
s = spring; // 等价于 s = 0; s = 3; // 等价于 s = winter; printf("%d", s);
---
##5.枚举使用的注意
- C语言编译器会将枚举元素(spring、summer等)作为整型常量处理,称为枚举常量。
- 枚举元素的值取决于定义时各枚举元素排列的先后顺序。默认情况下,第一个枚举元素的值为0,第二个为1,依次顺序加1。
enum Season { spring, summer, autumn, winter }; // 也就是说spring的值为0,summer的值为1,autumn的值为2,winter的值为3
- 没有指定值的枚举元素,其值为前一元素加1。也就说spring的值为0,summer的值为3,autumn的值为4,winter的值为5
- 也可以在定义枚举类型时改变枚举元素的值 enum season {spring, summer=3, autumn, winter};