结构体的定义与使用
在学习语言的时候,做的最多的就是学生成绩管理系统。里面包含的数据类型丰富、拥有基本的CRUD操作,是一个比较经典的例子。那么,我们结构体的学习,也将使用这个例子。
结构体的定义
由学生成绩管理系统的例子不难看出,学生的诸多属性(学号、姓名、年龄、成绩等)构成了学生这个结构体。结构体定义需要用到关键字struct,定义规则如下:
struct 结构体名称 {
// 成员列表
}; // 注意:末尾有;
如,一个学生结构体:
struct Student {
char *studentId; // 学生学号
char *name; // 学生姓名
int age; // 学生年龄
double score; // 学生成绩
};
因此,如果我们对每一个Student结构体变量进行初始化,都会有学号、姓名、年龄、成绩这几个成员。
结构体类型变量
定义结构体变量定义的方式有很多种:
1. 定义结构体后,定义变量(最优)
struct Student {
char *studentId;
char *name;
int age;
double score;
};
struct Student stuA, stuB; // 定义结构体变量stuA、stuB
2. 在定义结构体时,定义变量
struct Student {
char *studentId;
char *name;
int age;
double score;
} stuA, stuB; // 定义结构体变量stuA、stuB
3. 直接定义变量
struct {
char *studentId;
char *name;
int age;
double score;
} stuA, stuB; // 定义结构体变量stuA、stuB
结构体变量初始化
结构体只能在定义的时候,能为整个结构体变量进行赋值。
struct Student {
char *studentId;
char *name;
int age;
double score;
};
struct Student stuA = {"11310110", "Karen", 15, 100};
// 下面这种写法是错误的,定义与整体初始化不能分开
struct Student stuB;
stuB = {"11310110", "Karen", 15, 100};
结构体成员的访问
在实际操作中,不可能定义一个结构体变量就能整体初始化。这时,我们只能单个对结构体的成员进行访问,如:
struct Student {
char *studentId;
char *name;
int age;
double score;
};
struct Student stuA;
stuA.studentId = "11310110";
stuA.name = "Karen";
stuA.age = 15;
stuA.score = 100;
// 输出
printf("学号是:%s\n", stuA.studentId);
printf("姓名是:%s\n", stuA.name);
printf("年龄是:%d\n", stuA.age);
printf("成绩是:%lf\n", stuA.score);
// 整体赋值:将stuA的数据整体赋值给stuB
struct Student stuB = stuA;
结构体变量所占内存大小
在结构体内存的计算上,有一个自动对齐原则,如,Student这个结构体中,所占内存最大的成员是char *或double类型,那么结构体所占的内存大小 = 成员个数 * 成员中占用最大的字节数。
如上面的学生结构体 = 4 * sizeof(char *) = 32
结构体数组
和之前学习数组的目的一样,在学生管理系统中,不可能一个一个学生去定义并初始化,所以引入了数组的概念。
结构体数组的定义
与结构体变量定义一样,结构体数组的定义也有三种方式:
1. 定义结构体后,定义数组(最优)
struct Student {
char *studentId;
char *name;
int age;
double score;
};
struct Student stu[10]; // 定义一个长度为10的结构体数组
2. 在定义结构体时,定义数组
struct Student {
char *studentId;
char *name;
int age;
double score;
} stu[10]; // 定义一个长度为10的结构体数组
3. 直接定义数组
struct {
char *studentId;
char *name;
int age;
double score;
} stu[10]; // 定义一个长度为10的结构体数组
结构体数组的赋值与访问
struct Student students[10];
for (int i = 0; i < 10; i ++) {
students[i].age = i;
}
for (int i = 0; i < 10; i ++) {
printf("第%d个学生的年龄 = %d\n", i + 1, students[i].age);
结构体指针
指向结构体变量的指针
与指向基本类型的指针类似,结构体指针指向的是一个结构体变量的地址。定义方式为:
struct 结构体名称 *指针变量名;
如,定义一个指向学生结构体的指针:
struct Student {
char *studentId;
char *name;
int age;
double score;
};
struct Student *pStu; // 定义结构体指针
结构体指针的赋值与访问
赋值
struct Student {
char *studentId;
char *name;
int age;
double score;
};
struct Student *pStu; // 定义结构体指针
// 赋值
struct Student stu = {"11310110", "Karen", 15, 100}; // 定义结构体变量
pStu = &stu; // 将结构体变量stu的首地址给指针pStu
访问
结构体指针访问成员的方式有两种:
1. 先取*然后访问
printf("学生%s的年龄为%d\n", (*pStu).name, (*pStu).age);
// 输出:学生Karen的年龄为15
2. 使用->访问
printf("学生%s的年龄为%d\n", pStu->name, pStu->age);
// 输出:学生Karen的年龄为15
结构体的嵌套
比如,现在有这样一个需求:
定义一个人的结构体,有姓名、出生日期等成员,应该怎么定义?
或许可以这样定义:
struct Person {
char *name;
int year;
int month;
int day;
};
这样也可以,但是我认为这个设计不好,年月日应该是日期的成员,怎么会是一个人的成员呢!所以,有了以下定义:
struct Date {
int year;
int month;
int day;
};
struct Person {
char *name;
struct Date birth;
};
结构体嵌套的赋值与访问
// 定义结构体变量
struct Person me;
me.name = "Karen";
me.birth.year = 2000;
me.birth.month = 1;
me.birth.day = 1;
预习
1. malloc 动态内存管理
在头文件 <stdlib.h> 中定义 |
void* malloc( size_t size ); |
分配size个字节的未初始化的存储空间。
如果分配成功,返回指向分配内存块最低(第一个)字节的指针,且其对所有的目标类型都进行适当地对齐。
如果size为零,其行为由实现定义(可能返回空指针,也可能返回某个不能用来访问存储空间的非空指针)。
参数
size |
- |
要分配的字节数 |
返回值
指向新分配的内存开始位置的指针,或者在错误发生时为空指针。该指针必须用free()释放。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p1 = malloc(4*sizeof(int)); // allocates enough for an array of 4 int
int *p2 = malloc(sizeof(int[4])); // same, naming the type directly
int *p3 = malloc(4*sizeof *p3); // same, without repeating the type name
if(p1) {
for(int n=0; n<4; ++n) // populate the array
p1[n] = n*n;
for(int n=0; n<4; ++n) // print it back out
printf("p1[%d] == %d\n", n, p1[n]);
}
free(p1);
free(p2);
free(p3);
}
2. 栈
Stack is a LIFO (last in first out) data structure. 栈先进后出
3. 队列
Queue is a FIFO (first in first out) data structure.队列先进先出,。
队列实现了先入先出的语义 (FIFO) 。队列也可以使用数组和链表来实现:
图 4.1 队列
队列只允许在队尾添加数据,在队头删除数据。但是可以查看队头和队尾的数据。还有一种是双端队列,在两端都可以插入和删除:
4. 链表
5.memcpy
明天要问到:
1. 栈和队列的特点
2. 链表的特点
3. 链表与数组的区别(优劣势)
4. 链表需要用到的知识点(指针、结构体
1、定义一个结构体,描述日期的年月日;
1)提示用户输入日期,计算该日在本年中为第几天?
2)求今年任意2天的天数差?
#include <stdio.h>
#include <stdlib.h>
enum Year getLeapYear(int year);
#pragma mark - Globle Variables
struct Date {
int year;
int month;
int day;
} ;
enum Year {leapYear = 0, commonYear};
int totalDay;
//判断用户输入的是否为闰年
enum Year getLeapYear(int year) {
if ((year % 4 == 0 && year % 100 != 0 ) || year % 400 == 0) {
return leapYear;
}
return commonYear;
}
//取得当前日期天数(返回值 + 用户输入的day = 总的天数)
int getDaysOfYear(int month) {
int sumDays = 0;
switch (month-1) {
case 12: sumDays += 31;
case 11: sumDays += 30;
case 10: sumDays += 31;
case 9: sumDays += 30;
case 8: sumDays += 31;
case 7: sumDays += 31;
case 6: sumDays += 30;
case 5: sumDays += 31;
case 4: sumDays += 30;
case 3: sumDays += 31;
case 2: sumDays += 28;
case 1: sumDays += 31;break;
default: break;
}
return sumDays;
}
int main(int argc, const char * argv[]) {
// 提示用户输入日期,计算该日在本年中为第几天?
int totalDay = 0;
struct Date userDate;
printf("Please input year/month/date (year month date):\n");
scanf("%d%d%d", &userDate.year, &userDate.month, &userDate.day);
enum Year isYear = getLeapYear(userDate.year);
if (isYear == commonYear) {
totalDay = getDaysOfYear(userDate.month);
} else {
totalDay = getDaysOfYear(userDate.month) + 1;
}
totalDay += userDate.day;
printf("%d/%d is %d days of year %d\n\n",userDate.month, userDate.day, totalDay ,userDate.year);
//求今年任意2天的天数差?
int difference = 0;
struct Date date1, date2;
date1.year = 2015;
date2.year = 2015;
printf("Input a date (month date): \n");
scanf("%d%d", &date1.month, &date1.day);
printf("Input another date (month date): \n");
scanf("%d%d", &date2.month, &date2.day);
difference = getDaysOfYear(date1.month) + date1.day - getDaysOfYear(date2.month) - date2.day;
difference = abs(difference);
printf("Difference of these two dates = %d \n", difference);
return 0;
}
2、某班有5个学生,三门课。分别编写实现以下要求:
(1) 写一个函数,输出一名学生的所有信息
(2) 求各门课的平均分;
(3) 找出有两门以上不及格的学生,并输出其学号和不及格课程的成绩;
(4) 找出三门课平均成绩在85-90分的学生,并输出其学号和姓名
注:结构体元素有:num,name,score[3]
#include <stdio.h>
typedef struct Student Stu;
struct Student {
char *num;
char *name;
float score[3];
} ;
//输出一名学生的所有信息
void printStudent(Stu *studt) {
puts(studt -> name);
puts(studt -> num);
printf("Math = %.1f, English = %.1f, Chinese = %.1f\n",
studt -> score[0], studt -> score[1], studt ->score[2]);
}
//求各门课的平均分
void getAverageScore(Stu stu[], int length) {
for (int i = 0; i < 3; i++) {
float sumScore[3] = {0.0};
for (int j = 0; j < length; j++) {
sumScore[i] += stu[j].score[i];
}
printf("Average score = %.2f\n", sumScore[i] / length);
}
}
//找出有两门以上不及格的学生,并输出其学号和不及格课程的成绩;
void noPassStudent(Stu stu[], int length) {
for (int i = 0; i < length; i++) {
int count = 0;
for (int j = 0; j < 3; j++) {
if (stu[i].score[j] < 60) {
count++;
}
}
if (count > 1) {
printf("%s ", stu[i].num);
puts(stu[i].name);
//输出不及格的成绩
for (int k = 0; k < 3; k++) {
if (stu[i].score[k] < 60) {
printf("%.2lf\n", stu[i].score[k]);
}
}
}
printf("\n");
}
}
//找出三门课平均成绩在85-90分的学生,并输出其学号和姓名
void getGoodStudent(Stu stu[], int length) {
for (int i = 0; i < length; i++) {
float avgScoreOfStudent = 0;
float sumScoreOfStudent = 0;
for (int j = 0; j < 3; j++) {
sumScoreOfStudent += stu[i].score[j];
}
avgScoreOfStudent = sumScoreOfStudent / 3;
if (avgScoreOfStudent > 85 && avgScoreOfStudent < 90) {
puts(stu[i].num);
puts(stu[i].name);
}
}
}
int main (int argc, const char * argv[]) {
Stu studentsOfClass[5] = {{"20131003", "Jack", 90, 49, 80},
{"20131252", "Mike", 50, 49, 60},
{"20131021", "Lucus", 72, 93, 85},
{"20131325", "David", 85, 88, 87},
{"20131124", "Sam", 72, 53, 55}};
Stu *pointer = studentsOfClass;
//输出每一名学生的所有信息
for (int i = 0; i < 5; i++) {
printStudent(pointer);
pointer ++;
}
getAverageScore(studentsOfClass, 5);
noPassStudent(studentsOfClass, 5);
getGoodStudent(studentsOfClass, 5);
return 0;
}