目录
一、为什么要有结构体?
二、结构声明
三、访问结构成员
如果是结构体嵌套问题?
四、结构体变量的初始化
五、结构体变量的存储原理
六、结构体数组
七、结构体与指针
一、为什么要有结构体?
因为在实际问题中,一组数据往往有很多种不同的数据类型。例如,登记学生的信息,可能需要用到 char型的姓名,int型或 char型的学号,int型的年龄,char型的性别,float型的成绩。又例如,对于记录一本书,需要 char型的书名,char型的作者名,float型的价格。在这些情况下,使用简单的基本数据类型甚至是数组都是很困难的。而结构体则可以有效的解决这个问题,这就是其存在的意义。
结构体本质上还是一种数据类型,但它可以包括若干个“成员”,每个成员的类型可以相同也可以不同,也可以是基本数据类型或者又是一个构造类型。
结构体的优点:结构体不仅可以记录不同类型的数据,而且使得数据结构是“高内聚,低耦合”的,更利于程序的阅读理解和移植,而且结构体的存储方式可以提高CPU对内存的访问速度。
二、结构声明
struct 结构名{
成员列表
};
struct关键词表示接下来是一个结构。
例如,声明一个学生的结构:
struct Student //声明结构体 { char name[10]; //姓名 int age; //年龄 float score; //成绩 };
上述的声明描述了一个具有三个不同类型的成员的结构,但它并没有创建一个实际的数据对象。
每个成员变量都用自己的声明来描述,以分号结束。花括号之后的分号表示结构声明结束。结构声明可以放在函数外(此时为全局结构体,类似全局变量,在它之后声明的所有函数都可以使用),也可以放在函数内(此时为局部结构体,类似局部变量,只能放在该函数内使用,如果与全局结构体同名,则会暂时屏蔽全局结构体)。
要定义结构变量,则一般形式为
struct 结构体名 结构体变量名;
如
struct Student Stu1;
1.结构体变量的定义可以放在结构体的声明之后:
struct Student //声明结构体 { char name[10]; //姓名 int age; //年龄 float score; //成绩 }; struct Student Stu1;
2.结构体变量的定义也可以与结构体的声明同时:
struct Student //声明结构体 { char name[10]; //姓名 int age; //年龄 float score; //成绩 }Stu1;
三、访问结构成员
虽然结构类似一个数组,只是数组元素的数据类型是相同的,而结构中元素的数据类型是可以不同的。但结构不能像数组那样使用下标去访问其中的各个元素,而应该用结构成员运算符点“ . ”。即访问成员的一般形式是:结构体.成员名
如:
Stu1.name //表示学生Stu1的姓名
如果是结构体嵌套问题?
struct Birthday{ //声明结构体 Birthday
int year;
int month;
int day;
};
struct Student{ //声明结构体 Student
char name[10];
int age;
float score;
struct Birthday birthday; //生日
}stu1;
此时,Stu1.birthday.month表示出生的月份。
四、结构体变量的初始化
1.结构体变量的初始化可以放在定义之后:
可以对结构体成员进行逐个赋值:
struct Student Stu1; struct Student Stu2; strcpy(Stu1.name,"ZhangSan"); Stu1.age = 21; Stu1.score = 95.5;
注意:不能直接给数组名赋值,因为数组名是一个常量。
例如:
Stu1.name = "ZS";// "incompatible types in assignment of 'const char [3]' to 'char [10]'"
或者可以对结构体进行整体赋值:
Stu2 = (struct Student){"Lisi",21,90.5};
注意:此时要进行强制类型转换,因为数组赋值也是使用{},不转换的话系统无法区分。
Stu2 = {"Lisi",21,90.5}; // "no match for 'operator=' (operand types are 'Student' and '')"
2.结构体变量的初始化也可以与定义同时:
struct Student{ char name[10]; int age; float score; }Stu = {"Wangwu", 21, 91};
此时不需要强制类型转换。
根据以上几点,我们进行一个小练习:定义一个结构体类型的数组,赋值数据打印第n个学生的姓名,年龄,成绩。并且按照学生成绩进行冒泡排序(升序),若成绩相同,按照姓名降序输出。
#include#include typedef struct Student { const char* name; int age; int score; }Student; void bubblesort(Student *s, int len) { Student t; while (len--) { for(int i = 0; i < len; i++) { if(s[i].score > s[i+1].score) { t = s[i]; s[i] = s[i+1]; s[i+1] = t; } else if(s[i].score == s[i+1].score) { if(strcmp(s[i].name,s[i+1].name) < 0) { t = s[i]; s[i] = s[i+1]; s[i+1] = t; } } } } } int main() { Student brr[] = {{"xiaoming",15,154},{"zhangsan",20,164},{"liaozhang",19,164}}; int len = sizeof(brr)/sizeof(brr[0]); bubblesort(brr,len); for(int i = 0; i 输出结果为:
第1个学生: 姓名:xiaoming 年龄:15 成绩:154 第2个学生: 姓名:zhangsan 年龄:20 成绩:164 第3个学生: 姓名:liaozhang 年龄:19 成绩:164五、结构体变量的存储原理
1.结构体数据成员对齐的意义
内存是以字节为单位编号的,某些硬件平台对特定类型的数据的内存要求从特定的地址开始,如果数据的存放不符合其平台的要求,就会影响到访问效率。所以在内存中各类型的数据按照一定的规则在内存中存放,就是对齐问题。而结构体所占用的内存空间就是每个成员对齐后存放时所占用的字节数之和。
计算机系统对基本数据类型的数据在内存中存放的限制是:这些数据的起始地址的值要求是某个数K的倍数,这就是内存对齐,而这个数 K 就是该数据类型的对齐模数(alignment modulus)。这样做的目的是为了简化处理器与内存之间传输系统的设计,并且能提升读取数据的速度。
结构体对齐不仅包括其各成员的内存对齐(即相对结构体的起始位置),还包括结构体的总长度。2.结构体大小计算的方法
内存大小基本单位:字节
地址访问:cpu 内存读写不是按照1字节1字节读取。一般情况下以2、4、8的倍数的字节块进行读取。
平台读取地址 偶数地址读取 以32bit操作系统为例,cpu在读取时一个周期为32bit,如果以奇数地址进行读取,想要读取32位数据,至少要读取2个周期才能将数据读取完。
不同平台,内存对齐方式不一样。我们可以根据需要去指定对齐方式。
在说明计算原则之前,先说明一下下文的指定对齐方式。
#pragma pack(字节) //对齐方式开始标记 #pragma pack() //对齐方式结束标记平台按照某一字节进行对齐。
#pragma pack(4) typedef struct Student { int a;//4 char b; //1+1 short c;//2 char y; //1 char d;//1 short f;//2 }Student; int main() { printf("%dn",sizeof(Student)); }输出结果为:
121)变量首地址必须是Min{结构体最大基本数据类型,指定对齐方式}所占字节数的整数倍。
2)每个成员变量相对于结构体首地址的偏移量,都是Min{该成员基本类型的整数倍,指定对齐方式}。
3)结构体总大小为结构体Min{最大基本数据类型整数倍,指定对齐方式的整数倍}。
六、结构体数组结构类型作为一种数据类型,也可以像基本数据类型那样,作为数组的元素的类型。元素属于结构类型的数组成为结构型数组。
1)结构数组定义
struct 结构名 { 成员列表 } 数组名[数组长度];
如:struct Student{ //声明结构体 Student char name[10]; int age; float score; }Stu[5]; //定义一个结构结构数组Stu,共有5个元素赋值以及输出:
int main() { Student Stu[] = {{"xiaoming",15,154},{"zhangsan",20,164},{"liaozhang",19,164}}; int len = sizeof(Stu/sizeof(Stu[0]); for(int i = 0; i七、结构体与指针
当一个指针变量用来指向了一个结构变量,这个指针就成了结构指针变量。
结构指针变量中的值是所指向的结构变量的首地址。可以通过指针来访问结构变量。1)定义结构指针变量的一般形式:
struct 结构名 * 结构指针变量名;如:
struct Student *PStu; //定义了一个指针变量,它只能指向Student结构体类型的结构体变量结构指针变量的定义也可以与结构体的定义同时。而且它必须先赋值后使用。
数组名表示的是数组的首地址,可以直接赋值给数组指针。但结构变量名只是表示整个结构体变量,不表示结构体变量的首地址,所以不能直接赋值给结构指针变量,而应该使用 & 运算符把结构变量的的地址赋值给结构指针变量。即:
PStu = &Stu1;2)通过结构体指针间接访问成员值
访问一般形式:(*结构指针变量). 成员名 或 结构指针变量 -> 成员名
如:
(*PStu).name; PStu->name;注意(PStu)的小括号不能省略,因为成员符“.”优先级为1,取地址符“*”优先级为2,去掉括号就相当于*(PStu.name)了。