(a) 下面结构体类型的变量的内存布局是怎样的?
typedef struct stundent_s {
int number;
char name[25];
char gender;
int chinese;
int math;
int english;
} Student;
Student s;
学生结构体变量会有两个字节的填充,填充的目的是使数据项"对齐",早期的计算机
要求数据项的地址必须是某个字节数的倍数。
(b) 如何通过结构体获取成员,如何通过指向结构体的指针获取成员?
数组最常见的操作是通过下标获取元素,结构体最常见的操作是通过成员名称获取成
员。如: s1.name 。
结构体的成员代表着内存中的一块存储空间。它可以出现在赋值语句的左边,并且可
以当作自增、自减运算符的操作数。如:
s2.chinese = 60;
s2.math++;
void print_stu_info(struct student_s* s) {
printf("%d %s %c %d %d %d\n",
(*s).number,
(*s).name,
(*s).gender,
(*s).chinese,
(*s).math,
(*s).english);
}
注意:(*s)中的小括号是不能省的,因为解引用运算符 * 的优先级是低于取成员运算
符 . 的。
为了方便使用指向结构体的指针,C语言提供了右箭头运算符 ->。上面的例子,我们
可以改写成这样
void print_stu_info(struct student_s* s) {
printf("%d %s %c %d %d %d\n",
s->number,
s->name,
s->gender,
s->chinese,
s->math,
s->english);
}
(c)(学生信息处理)有这样一种学生结构体类型,其数据成员有包括学号,性别,姓名和3门课程的成绩。实现下列功能:
-
从键盘输入5个学生的信息,将这些同学的信息采用结构体数组的方式存储起来。
-
输出每门课程最高分的学生信息。
-
输出每门课程的平均分。
-
按照总分从高到低对学生进行排序,并输出排序后的学生信息。
-
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> // 不要给指针类型起别名 typedef struct student_s { int number; char name[25]; char gender; int chinese; int math; int english; } Student; void print_stu_info(struct student_s* p) { printf("%d %s %c %d %d %d\n", p->number, p->name, p->gender, p->chinese, p->math, p->english); } int total_score(Student* p) { return p->chinese + p->english + p->math; } int cmp(Student* p1, Student* p2) { // p1 < p2 返回负值 // p1 = p2 返回0 // p1 > p2 返回正值 int total1 = total_score(p1); int total2 = total_score(p2); return total2 - total1; } void swap(Student* arr[], int i, int j) { Student* tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } void sort_students(Student* arr[], int n) { // 选择排序 for (int i = 0; i < n - 1; i++) { int minIdx = i; for (int j = i + 1; j < n; j++) { if (cmp(arr[j], arr[minIdx]) < 0) { minIdx = j; } } swap(arr, i, minIdx); } } void print_score(Student students[], int n) { // p[i] = *(p + i) int idx1 = 0; int idx2 = 0; int idx3 = 0; for (int i = 1; i < n; i++) { if (students[i].chinese > students[idx1].chinese) { idx1 = i; } if (students[i].math > students[idx2].math) { idx2 = i; } if (students[i].english > students[idx3].english) { idx3 = i; } } print_stu_info(&students[idx1]); print_stu_info(&students[idx2]); print_stu_info(&students[idx3]); } void print_average_score(Student students[], int n) { double avg1 = 0; double avg2 = 0; double avg3 = 0; for (int i = 0; i < n; i++) { avg1 += students[i].chinese; avg2 += students[i].math; avg3 += students[i].english; } printf("Average score of chinese: %.2lf\n", avg1 / n); printf("Average score of math: %.2lf\n", avg2 / n); printf("Average score of english: %.2lf\n", avg3 / n); } int main(void) { Student students[5]; for (int i = 0; i < 5; i++) { scanf("%d%s %c%d%d%d", &students[i].number, students[i].name, &students[i].gender, &students[i].chinese, &students[i].math, &students[i].english); } // 打印单科最高分学生的信息 print_score(students, 5); // 输出没门课程的平均分 print_average_score(students, 5); // 按照总分从高到低对学生进行排序,并输出排序后的学生信息。 Student* pstus[] = { students, students + 1, students + 2, students + 3, students + 4 }; sort_students(pstus, 5); for (int i = 0; i < 5; i++) { print_stu_info(pstus[i]); } return 0; }
简答题
(a) 动态内存分配函数有哪些?它们的功能是什么?
我们可以使用下面三个函数进行动态内存分配,这些函数都声明在 <stdlib.h> 头文件
中。
void* malloc(size_t size)
分配 size 个字节的内存块,不对内存块进行清零;如果无法分配指定大小的内存
块,返回空指针。
void* calloc(size_t nmemb, size_t size)
为有 nmemb 个元素的数组分配内存块,其中每个元素占 size 个字节,并且对内存
块进行清零;如果无法分配指定大小的内存块,返回空指针。
void* realloc(void *ptr, size_t size)
调整先前分配内存块的大小。如果重新分配内存大小成功,返回指向新内存块的指
针,否则返回空指针。
这三个函数当中, malloc 是常用的,也是效率最高的。
当我们调用这些函数申请堆内存空间的时候,函数是无法知道我们打算往内存中存储
什么类型数据的,所以函数返回 void* 类型的指针。 void* 类型的值是"通用"指
针,指针本质上其实就是内存地址。 void* 类型的指针可以转换成其它类型的指针,
其它类型的指针也可以转换成 void* 类型的指针。
(b) 什么叫空指针?C 语言是如何表示空指针的?
调用内存分配函数时,如果找不到足够大的内存空间,函数就会返回空指针。空指针
是"不指向任何对象的指针"——在实现上,我们通常是将它指向一个特殊的地址
(0x00000000),并且用宏 NULL 表示。
-
当函数可能返回空指针的时候,在使用指针之前,我们应该进行判断。
-
void *p = malloc(1024); if (p == NULL) { /* allocation failed; take appropriate action */
正如我们前面所说的,空指针其实就是值为 0 的指针。而在 C 语言中,0 代表假,因
此空指针为假;非 0 代表真,因此非空指针都为真。因此 if (p == NULL) 可以写成
if (!p) ;而 if (p != NULL) 也可以写成 if (p) 。
(c) 什么叫垃圾?什么是内存泄漏?如何避免内存泄漏?
malloc , calloc 和 realloc 函数都是在堆上申请内存空间的,如果频繁地调用这
些函数,那么堆上的空间总会被消耗殆尽。更为糟糕的是,如果丢失了对这些内存块
的跟踪,那么这些内存块就无法再被程序使用,也就出现了所谓的内存泄漏现象。
p = malloc(...);
q = malloc(...);
p = q;
对程序而言,不可再被访问的内存块被称为垃圾(garbage)。如果程序中留有垃圾,
这种现象被称为内存泄漏(memory leak)。一些语言提供垃圾收集器用于自动定位和
回收垃圾。但是 C 语言要求程序员自己负责垃圾的回收,它提供了 free 函数。
void free(void *ptr)
使用 free 函数很容易,只需要把指向不再需要的内存块的指针传递给 free 函数就
可以了:
p = malloc(...);
q = malloc(...);
free(p);
p = q;
这样程序就不会出现内存泄漏了。
使用 free 函数有两个要注意的事情:
1. 传递给 free 的参数必须是由内存分配函数返回的指针,否则 free 函数的行为是
未定义的。
2. 同一片内存空间不能被 free 两次,否则会出现 double-free 现象。