常见面试题7.9

(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 现象。

  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值