智邮普创 2024一面
此WriteUp不是官方题解
0x01|包含头文件(难度:1)
请简述
#include <stdio.h>和#include "stdio.h"这两种写法的区别。
-
用
<>包裹的首先从系统标准目录中寻找,其次再考虑其他(用于引用标准库和系统头文件) -
用
""包裹的首先从当前源文件所在目录(或是指向的目录)查找,其次再去系统目录找(用于引入用户自己定义的头文件)
索引:《C Primer Plus》P531
0x03|2B 的运算符(难度:2)
分析如下代码并给出合理解释:
#include <stdio.h> int main() { int a = 1; int b = 1; if (a+++b)//no.1 if (a*!~b)//no.2 b++; else a++; printf("%d %d %d\n", a, b, a++-+-+-+-++b);//no.3 return 0; }
no.1 |C 语言采用"最长匹配原则"(maximal munch rule):编译器从左到右扫描时,尽可能把最长的合法符号拼成一个 token。因此应该被机器读成(a++)+b
no.2 |在单目操作符中,&,|,^,<<,>>都是左结合(从左到右),而~是右结合(从右到左)的,而位运算符的优先级是低于关系运算符的(<,>,==)
这里的!和~都是右结合的,都作用与b,先是~b=-2,是一个非零的数,于是被!判断为真值,取反返回0,因此b=1,a=3
no.3 |一个printf()的输出语句,首先需要知道:
通常情况下,printf函数的参数计算顺序是从右到左
因此,首先将最右侧的算式计算出来,这么长一串,拆开来看:
(a++) - (+ (- (+ (- (+ (- (++b)))))))
//只有第一个-号是双目运算符,其他全都是单目运算符;以此类推,得到上述形式
总结:
所有的单目运算符和三目运算符和赋值都是右结合的
在左结合中:
++ -->* / %>+ ->>> <<>< > =>== !=>&>^>|>&&(逻辑与)>||(逻辑或)>,
所以答案是:
4 2 5
0x04|静态变量和全局变量的区别(难度:2)
请简述在 C 语言中静态变量与全局变量之间的主要区别。
对于静态变量来说,并不是值不变,而是在内存中的位置不变
只要一个变量是定义在函数之外(全局变量),或是用static修饰的局部变量,都是静态变量.这些变量离开各自所在的函数后不会销毁,具有块作用域,无链接.
0x05|静态变量(难度:2)
请测试以下代码,并讲解输出结果产生的原因:
#include <stdio.h> void func() { static int count = 0; int local = 0; count++; local++; printf("count = %d, local = %d\n", count, local); } int main() { for (int i = 0; i < 3; i++) { func(); } return 0; } // output // count = 1, local = 1 // count = 2, local = 1 // count = 3, local = 1
在《C Primer Plus》P381 页的程序loc_stat.c与此类似,不作讲解,附上源码:
#include <stdio.h>
void trystat(void);
int main(void)
{
int count;
for (count = 1; count <= 3; count++)
{
printf("Here comes iteration %d:\n", count);
trystat();
}
return 0;
}
void trystat(void)
{
int fade = 1; // 自动变量:每次调用都重新初始化为 1
static int stay = 1; // 静态局部变量:只在编译时初始化一次
printf("fade = %d and stay = %d\n", fade++, stay++);
}
0x06|结构体内存对齐(难度:3)
根据以下 C 语言结构体定义回答问题。在 GCC 编译器默认配置下,不考虑优化及
#pragma pack等影响内存对齐的指令。struct MyStruct { char a; // 1 字节 int b; // 4 字节 char c; // 1 字节 double d; // 8 字节 short e; // 2 字节 };
- 该结构体在 32 位和 64 位系统上的大小分别是多少?请解释原因。
- 如果希望优化该结构体的内存布局,应该如何重新排列这些字段?重新排列后的结构体在 32 位和 64 位系统上的大小分别是多少?
- 解释结构体对齐的原则,以及为什么需要进行结构体对齐。
0x07|指针(难度:4)
请测试以下代码,分析程序错误的原因并修正错误:
#include <stdio.h> #include <stdlib.h> void process(int *ptr, int size) { // 动态分配一个整型数组,大小为 size int *array = (int *)malloc(size * sizeof(int)); // 初始化数组 for (int i = 0; i < size; i++) array[i] = i * i; // 将 ptr 指向动态分配的数组 ptr = array; } int main() { int *ptr = NULL; int size = 5; process(ptr, size); // 打印 ptr 指向的数组元素 for (int i = 0; i < size; i++) printf("%d ", ptr[i]); free(ptr); return 0; }
分析程序的意图,是要完整地打印一个大小为5的数组,因此需要将ptr指向的地址换成函数molloc()在process()中分配好的数组array.但这里函数process()的类型错了,类型应该是一个二维指针,否则,在后面process()传入的值就是NULL而不是p指向的地址.
同时也需要更改传入p为p的地址,即&p,在分配ptr和数组时也要将数组的(首位)地址递给ptr对应的*p(p指向的地址)
总共修改三处,修改完如下:
#include <stdio.h>
#include <stdlib.h>
void process(int** ptr, int size)//1
{
// 动态分配一个整型数组,大小为 size
int* array = (int*)malloc(size * sizeof(int));
// 初始化数组
for (int i = 0; i < size; i++)
array[i] = i * i;
// 将 ptr 指向动态分配的数组
*ptr = array;//2
}
int main()
{
int* ptr = NULL;
int size = 5;
process(&ptr, size);
// 打印 ptr 指向的数组元素
for (int i = 0; i < size; i++)
printf("%d ", ptr[i]);
free(ptr);
return 0;
}
运行一下,输出:
0 1 4 9 16
0x08|求平均数(难度:1)
实现一个函数,该函数接收一个整数数组和数组大小作为参数,返回数组中所有元素的平均值。
详细要求:
- 函数名为
average。- 函数的参数为一个整数数组
arr和一个整数size,表示数组大小。- 函数返回一个
double类型的值,即数组元素的平均值。- 考虑整数除法的问题,计算平均值时请确保结果为小数。
- 编写一个简单的
main函数测试average函数。
#include<stdio.h>
double sum = 0;
double averange(int arr[], int size)
{
int i = 0;
for (i = 0;i < size;i++)
{
sum += (double)arr[i] / size;
}
return sum;
}
int main(void)
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("arr数组的平均值是:%.2f", averange(arr,sizeof(arr)/sizeof(arr[0])));
return 0;
}
0x09|温度转换器(难度:2)
编写一个 C 程序,实现一个简单的温度转换器,将摄氏温度转换为华氏温度。程序应从命令行接收一个摄氏温度值,并打印对应的华氏温度值。
详细要求:
- 程序应从命令行读取一个浮点数,表示摄氏温度。
- 使用公式
华氏温度 = (摄氏温度 × 9 / 5) + 32进行转换。- 打印转换后的华氏温度,保留两位小数。
- 如果输入不是有效的浮点数,程序应打印错误信息并退出。
- 编写一个简单的
main函数测试温度转换器。
首先就是难点:怎么判断一个"数"是不是一个正常的浮点数?
列举一下几种情况:
- 不是一个数字
- 是一个被污染的数字(非纯数字)
- 超出了浮点数的表示范围(溢出)
因此可以通过strtod()函数自动得到读取的最后一个字符,判断它是一个'\0'还是根本没有读取,或是系统内部的errno已经报错溢出,如下:
char* end;//设置指针end
double c = strtod(argv[1],&end)//从argv[1]指向的第一个字符开始读取,直到读取到第一个非法的字符
然后是题目中要求从命令行读取浮点数,说明要在.exe文件运行时传入参数argv[1],因此需要从main()函数入手:
int main(int *argc,char*argv[])
这里的argc读取以空格为界的字符串,argv[]指针数组存放每一组字符串.其中合法的存放应该时argv[0]是.exe文件所在的地址,而argv[1]是想要转换的摄氏温度的地址.
同时还要想到argc的计数不为2的情况,也要截断函数进行提醒.于是源码如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char* argv[])
{
if (argc != 2) {
printf("用法:%s <摄氏温度>", argv[0]);
return 1;
}
errno = 0;
char* end;
double c = strtod(argv[1], &end);
if (end == argv[1] || *end != '\0' || errno == ERANGE ) {
printf("错误: \"%s\" 不是有效的浮点数\n", argv[1]);
return 1;
}
printf("%.2f\n", c * 9.0 / 5.0 + 32.0);
return 0;
}