智邮普创 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 字节
};
  1. 该结构体在 32 位和 64 位系统上的大小分别是多少?请解释原因。
  2. 如果希望优化该结构体的内存布局,应该如何重新排列这些字段?重新排列后的结构体在 32 位和 64 位系统上的大小分别是多少?
  3. 解释结构体对齐的原则,以及为什么需要进行结构体对齐。

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)

实现一个函数,该函数接收一个整数数组和数组大小作为参数,返回数组中所有元素的平均值。

详细要求:

  1. 函数名为 average。
  2. 函数的参数为一个整数数组 arr 和一个整数 size,表示数组大小。
  3. 函数返回一个 double 类型的值,即数组元素的平均值。
  4. 考虑整数除法的问题,计算平均值时请确保结果为小数。
  5. 编写一个简单的 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 程序,实现一个简单的温度转换器,将摄氏温度转换为华氏温度。程序应从命令行接收一个摄氏温度值,并打印对应的华氏温度值。

详细要求:

  1. 程序应从命令行读取一个浮点数,表示摄氏温度。
  2. 使用公式 华氏温度 = (摄氏温度 × 9 / 5) + 32 进行转换。
  3. 打印转换后的华氏温度,保留两位小数。
  4. 如果输入不是有效的浮点数,程序应打印错误信息并退出。
  5. 编写一个简单的 main 函数测试温度转换器。

首先就是难点:怎么判断一个"数"是不是一个正常的浮点数?

列举一下几种情况:

  1. 不是一个数字
  2. 是一个被污染的数字(非纯数字)
  3. 超出了浮点数的表示范围(溢出)

因此可以通过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;
}