XiyouMobile2024纳新一面题解
文章目录
1.Hello,3G!(5分)
说说下述代码的输出结果,以及为什么会有这样的输出? strlen和sizeof的区别是什么?
#include <stdio.h> #include <string.h> int main() { char str1[] = "Hello, 3G!"; char str2[20] = "Hello"; char str3[20] = {'H', 'e', 'l', 'l', 'o'}; printf("strlen(str1) = %zu\n", strlen(str1)); printf("sizeof(str1) = %zu\n", sizeof(str1)); printf("strlen(str2) = %zu\n", strlen(str2)); printf("sizeof(str2) = %zu\n", sizeof(str2)); printf("strlen(str3) = %zu\n", strlen(str3)); printf("sizeof(str3) = %zu\n", sizeof(str3)); return 0; }
输出:
strlen(str1) = 10 sizeof(str1) = 11 strlen(str2) = 5 sizeof(str2) = 20 strlen(str3) = 5 sizeof(str3) = 20
分析:
char str1[] = "Hello, 3G!";
- 初始化了一个包含
Hello, 3G!
的字符数组,这个字符串有10个字符,C语言中,字符串以空字符‘\0’
结尾,所以这个数组的大小为11strlen(str1)
计算字符串长度,不包括结尾的空字符,所以输出结果为10
sizeof(str1)
计算这个数组所占字节数,输出结果为11
char str2[20] = "Hello";
- 定义了一个大小为20的字符数组,并初始化为
"Hello"
,这个字符串有5个字符,再加上结尾的空字符‘\0’
,一共有6个字符strlen(str2)
计算字符串长度,不包括结尾的空字符,所以输出结果为5
sizeof(str1)
计算这个数组所占字节数,输出结果为20
char str3[20] = {'H', 'e', 'l', 'l', 'o'};
- 定义了一个大小为20的字符数组,并初始化了5个字符,未完全初始化,该数组其它元素默认初始化为
0
strlen(str2)
计算字符串长度,不包括结尾的空字符,所以输出结果为5
sizeof(str1)
计算这个数组所占字节数,输出结果为20
strlen和sizeof的区别
- sizeof是一个**计算数据类型所占空间大小(字节数)**的单目运算符,在计算字符串的空间大小时,包含结尾的空字符
‘\0’
- strlen是一个计算字符串长度的函数,使用时需要引用头文件
#include <string.h>
,在计算字符串长度时,不包含结尾的空字符‘\0’
2.怎么停不下来?!(5分)
讲讲程序会输出什么吧!运用相关知识解释原因。
#include <stdio.h> int main() { unsigned char i = 5; while (i >= 0) { printf("%d ", i); i--; } return 0; }
输出:
无限循环打印 i 的值,i 的值从5递减到0,然后到255,继续递减,无限循环
分析:
unsigned char i = 5;
定义了一个无符号字符型变量i,初始化为5
。由于无符号字符型变量取值范围是0~255
,当i递减到0
后,继续递减,此时i的值变为255
,然后继续递减,所以while (i >= 0)
这个条件会一直为真,该循环为无限循环
相关知识:
- 任何存储与计算机的数据,其本质都是以二进制码存储
unsigned char
类型的变量占一个字节(即8位),因此它能表示的数值范围是0(00000000)~255(11111111)
- 在计算机系统中,数值一律用补码来表示和存储。
- 正数原码=补码,负数补码={原码符号位不变} + {数值位按位取反后+1}
- 由于计算机只有加法运算器,当i递减到0后,继续递减即
0(00000000)+(-1)(11111111)= 255(11111111)
3.宏定义与宏函数(10分)
这段函数会输出什么?为什么会有这样的输出?
#include <stdio.h> #define A "Welcome to join 3G!\n" #define B "Hello!" #define C(x) ((x)+4) #define N 4 #define Y(n) ((N+2)*n) int main() { int z = 2 * (N + Y(5 + 1)); printf("%d\n", z); printf("%s %d%d", B, printf(A), C(printf(A))); }
输出:
70 Welcome to join 3G! Welcome to join 3G! Hello! 2024
分析:
#define A "Welcome to join 3G!\n"
:定义了一个字符串常量A
,代表"Welcome to join 3G!\n"
。#define B "Hello!"
:定义了一个字符串常量B
,代表"Hello!"
。#define C(x) ((x)+4)
:定义了一个带参数的宏C
,它接受一个参数x
,并返回(x)+4
的结果。#define N 4
:定义了一个常量N
,值为4
。#define Y(n) ((N+2)*n)
:定义了一个带参数的宏Y
,它接受一个参数n
,并返回((N + 2) * n)
的结果。int z = 2 * (N + Y(5 + 1));
- 首先计算
Y(5 + 1)
,即((N + 2) * 5 + 1)
,其中N
为4
,所以((4 + 2) * 5 + 1) = 31
。- 然后计算
N + Y(5 + 1)
,即4 + 31 = 35
。- 最后计算
2 * (N + Y(5 + 1))
,即2 * 35 = 80
,并将结果赋值给变量z
。printf("%d\n", z);
输出变量z
的值,即70
。printf("%s %d%d", B, printf(A), C(printf(A)));
printf(A)
会输出字符串"Welcome to join 3G!\n"
,同时返回输出的字符数,即20
。printf("%s %d%d", B, printf(A), C(printf(A)))
首先输出字符串"Hello!"
,然后输出printf(A)
的返回值20
,接着输出C(printf(A))
的值,即24
。
宏定义与宏函数:
- 预处理器不做计算,不对表达式求值,它只进行替换(在编译前进行)
4.真假小a(10分)
说说下述代码的运行结果,并尝试分析解释这段代码
#include <stdio.h> int a = 10; void func() { int a = 20, c = 40; static int b = 30; b++; c++; printf("a = %d, b = %d, c = %d\n", a, b, c); } int main() { func(); func(); printf("a = %d\n", a); return 0; }
运行结果:
a = 20, b = 31, c = 41 a = 20, b = 32, c = 41 a = 10
分析:
int a = 10;
定义了一个全局变量a
,其作用域是整个程序。void func()
int a = 20, c = 40;
在func
函数内部,定义了局部变量a
和c
,它们的作用域仅限于func
函数内部。static int b = 30;
定义了静态局部变量b
,它的作用域也在func
函数内部,但它在程序的整个生命周期内都存在,并且只在第一次进入函数时初始化。main
函数中第一次调用func
func
函数内部,局部变量a
的值为20
,静态局部变量b
的初始值为30
,然后b
自增为31
,局部变量c
的值为40
,然后c
自增为41
。- 输出
"a = 20, b = 31, c = 41"
。main
函数中第二次调用func
- 局部变量
a
的值仍然为20
,静态局部变量b
的值为上一次调用结束后的31
,然后b
自增为32
,局部变量c
的值重新初始化为40
,然后c
自增为41
。- 输出
"a = 20, b = 32, c = 41"
。printf("a = %d\n", a);
输出全局变量"a = 10"
。
相关知识:
- 全局变量和局部变量的变量名相同时,遵循局部优先原则
- static修饰局部变量(静态局部变量)
- 在函数中声明变量时, static 关键字指定变量只初始化一次,并在之后调用该函数时保留其状态。
- 改变局部变量的存储位置(局部变量存储在栈区,静态局部变量存储在静态区),不改变作用域,只改变生命周期(局部变量进作用域创建,出作用域销毁;而静态局部变量直到程序结束才销毁)
5.这啥啊,一堆符号(10分)
以下程序的运行结果是什么,你知道运算符的优先级吗?
#include <stdio.h> int count =-1; int calculate(int num) { while (1) { switch (num) { case 0: num = (num ^ 10) + 10; case 31: num = (num | 0); break; case 9: num += 6; break; case 20: num = num >> 1; num-= 7; break; case 3: count += num; num = (num ^ 7) + 5; break; case 15: num += count++; count = count << 3; break; case 10: return (1 << ++num)- count; default: num = (num & 1) ? num + 1 : num- 4; break; } } } int main() { int start = 0; int y = calculate(start); printf("answer == %d\n", y); printf("%d\n", count); return 0; }
运行结果:
answer == 2024 24
分析:
int count =-1;
初始化全局变量count=-1
int start = 0;
初始化start=0
int y = calculate(start);
- 调用函数
calculate
,并将返回值赋给y
- 首先,执行
case 0
:num = (num ^ 10) + 10;
得到num=20- 由于没有
break;
继续执行下一条case 3
:将 num 与 0 进行按位或运算,得到num=20case 20
:先将num
右移一位,然后再减去7
,num=3case 3
:将全局变量count
加上num
,然后将num
先进行异或运算(num ^ 7)
,再加上5
,count=2,num=9case 9
:num
加6,num=15case 15
:将num
加上count
,然后将count
进行左移三位操作,count=24,num=17default
:如果num
与1
进行按位与运算结果为真(即num
为奇数),则将num
加1
;否则将num
减4
。连续执行三次,num=10case 10
: 返回(1 << ++num)- count
,即先将num
自增1
,然后进行左移一位运算,再减去count
。返回值为2024y=2024,count=24
相关知识:
按位与:&
- 两个都为1,结果为1,其余情况为0
按位或:|
- 两个都为0,结果为0,其余情况为1
按位异或:^
- 一个为1,一个为0,结果为1,其余情况为0
左移:<<
- 左移n位相当于乘以2的n次方
右移:>>
- 右移n位相当于除以2的n次方
6.指针数组,数组指针(10分)
请结合以下代码讲述指针数组和数组指针的含义与区别
#include <stdio.h> int main() { //简述这段代码的输出结果,并解释原因 int values[] = {10, 20, 30, 40, 50}; int *ptrArr[5] = {0}; int (*arrPtr)[5] = &values; for (int i = 0; i < 5; i++) { ptrArr[i] = &values[i]; } for (int i = 0; i < 5; i++) { printf("%d ", *ptrArr[i]); } printf("\n"); for (int i = 0; i < 5; i++) { printf("%d ", (*arrPtr)[i]); } return 0; }
输出结果:
10 20 30 40 50 10 20 30 40 50
分析:
int values[] = {10, 20, 30, 40, 50};
- 定义了一个整数数组
values
int *ptrArr[5] = {0};
- 定义了一个指针数组
ptrArr
,并初始化为0int (*arrPtr)[5] = &values;
- 定义了一个指向数组的指针
arrPtr
- 第一个循环:
- 将指针数组
ptrArr
每个元素指向values
数组的相应元素- 第二个循环:
- 遍历指针数组
ptrArr
,并输出每个指针所指向的整数。由于在第一个循环中,将ptrArr
的每个元素分别指向了values
数组的相应元素,所以这个循环会输出values
数组的元素,即10 20 30 40 50
。- 第三个循环:
- 这里使用指向数组的指针
arrPtr
来访问values
数组的元素。arrPtr
指向了整个values
数组,(*arrPtr)
解引用后得到的是values
数组本身,所以(*arrPtr)[i]
可以用来访问values
数组的各个元素。同样,这个循环也会输出values
数组的元素,即10 20 30 40 50
。
指针数组和数组指针的含义与区别
含义:
- 指针数组:本质是一个数组,该数组中的每个元素都是一个指针。
- 数组指针:本质是一个指针,指向了一个数组。
区别:
指针数组是一个包含若干个指针的数组,p是数组名,当执行p+1时,则p会指向数组中的下一个元素。
数组指针也称行指针,也就是说,当指针p执行p+1时,指针会指向数组的下一行
7.怎么还是指针!(10分)
简述这段代码的输出结果,并解释原因
#include <stdio.h> int main(){ int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} }; int* ptr1 = (int*)(&arr + 1); int* ptr2 = (int*)(*(arr + 1)); printf("*(ptr1- 1) = %d\n", *(ptr1- 1)); printf("*(ptr2 + 7) = %d\n", *(ptr2 + 7)); printf("*(arr[0] + sizeof(int) * 11) = %d\n", *(arr[0] + 11)); printf("*(arr[2] + 3) = %d\n", *(arr[2] + 3)); printf(" *(*(arr+2) + 3) = %d\n", *(*(arr + 2) + 3)); printf("*(*(&arr[1] + 1) + 3) = %d\n", *(*(&arr[1] + 1) + 3)); return 0; }
输出结果:
*(ptr1- 1) = 12 *(ptr2 + 7) = 12 *(arr[0] + sizeof(int) * 11) = 12 *(arr[2] + 3) = 12 *(*(arr+2) + 3) = 12 *(*(&arr[1] + 1) + 3) = 12
分析:
int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
:
- 定义了一个
3×4
的二维整数数组arr
,并初始化了其元素。int* ptr1 = (int*)(&arr + 1);
:
&arr
是一个指向整个二维数组的指针。当对其进行&arr + 1
操作时,指针会移动到整个二维数组之后的位置。然后将其强制转换为int*
类型的指针ptr1
。int* ptr2 = (int*)(*(arr + 1));
:
arr
是一个指向一维数组(包含 4 个整数)的指针数组。arr + 1
指向第二行的一维数组,*(arr + 1)
解引用后得到第二行的一维数组的首地址,然后将其强制转换为int*
类型的指针ptr2
。printf("*(ptr1 - 1) = %d\n", *(ptr1 - 1));
:
ptr1
指向二维数组之后的位置,ptr1 - 1
则指向二维数组的最后一个元素,即12
。所以输出12
。printf("*(ptr2 + 7) = %d\n", *(ptr2 + 7));
:
ptr2
指向第二行的一维数组的首地址,ptr2 + 7
指向第二行一维数组的第8
个元素(从0
开始计数),也就是第三行的最后一个元素12
。所以输出12
。printf("*(arr[0] + sizeof(int) * 11) = %d\n", *(arr[0] + 11));
:
arr[0]
是第一行一维数组的首地址,arr[0] + sizeof(int) * 11
指向第一行一维数组的第12
个元素(因为sizeof(int)
是4
字节,乘以11
就是向后移动44
字节,即第12
个元素),也就是第三行的最后一个元素12
。所以输出12
。printf("*(arr[2] + 3) = %d\n", *(arr[2] + 3));
:
arr[2]
是第三行一维数组的首地址,arr[2] + 3
指向第三行一维数组的第4
个元素,即12
。所以输出12
。printf(" *(*(arr+2) + 3) = %d\n", *(*(arr + 2) + 3));
:
arr + 2
指向第三行的一维数组,*(arr + 2)
解引用后得到第三行一维数组的首地址,*(arr + 2) + 3
指向第三行一维数组的第4
个元素,即12
。所以输出12
。printf("*(*(&arr[1] + 1) + 3) = %d\n", *(*(&arr[1] + 1) + 3));
:
&arr[1]
是第二行一维数组的地址,&arr[1] + 1
指向第三行一维数组的地址,*(&arr[1] + 1)
解引用后得到第三行一维数组的首地址,*(&arr[1] + 1) + 3
指向第三行一维数组的第4
个元素,即12
。所以输出12
。
8.结构体与联合体(10分)
题目:给出以下代码块,描述输出结果以及原因
#include <stdio.h> #define INT_PTR int* #define CHAR_PTR char* typedef int* int_ptr; typedef char* char_ptr; struct MyData { int i; char c; INT_PTR iptr1,iptr2; int_ptr iptr3,iptr4; double d; }; union MyUnion { int i; char c; CHAR_PTR cptr1,cptr2; char_ptr cptr3,cptr4; float d; }; int main() { printf("Size of MyData: %zu\n", sizeof(struct MyData)); printf("Size of MyUnion: %zu\n", sizeof(union MyUnion)); return 0; }
输出结果(X64):
Size of MyData: 48 Size of MyUnion: 8
分析:
- 先定义了两个宏
INT_PTR
和CHAR_PTR
,分别代表int*
和char*
。同时使用typedef
定义了int_ptr
和char_ptr
类型别名,分别对应int*
和char*
。struct MyData
INT_PTR iptr1,iptr2;
定义了一个整数指针iptr1
和一个整数iptr2
int_ptr iptr3,iptr4;
定义了两个整数指针iptr3
和iptr4
- 这个结构体包含了两个整数
i
、iptr2
(占2*4=8个字节),一个字符c
(占1个字节),三个整数指针iptr1
、iptr3
、iptr4
(占3*8=24个字节),一个双精度浮点数d
(占8个字节)(一共41个字节)- 由于结构体内存对齐,这个结构体实际大小为48(稍后解释)
union MyUnion
CHAR_PTR cptr1,cptr2;
定义了一个字符指针cptr1
和一个字符cptr2
char_ptr cptr3,cptr4;
定义了两个字符指针cptr3
和cptr4
- 这个联合体包含了两个字符
c
、cptr2
,一个整数i
,三个字符指针cptr1
、cptr3
、cptr4
,一个单精度浮点数d
(其中字符指针所占字节数最大为8)- 所以这个联合体大小为8
sizeof
分别求这个结构体和联合体大小(所占字节数)
相关知识
1.
define
和typedef
define
是预处理指令(宏定义),只进行简单的替换;typedef
是一个关键字,对已存在的数据类型取别名INT_PTR iptr1,iptr2;
等价于int *iptr1,iptr2;
所以iptr1
是整数指针,而iptr2
是整数int_ptr iptr3,iptr4;
则是定义了两个int_ptr
类型的变量,即int *
类型2.结构体
struct
和联合体union
- 结构体各成员拥有自己的内存,各自使用互不干涉,同时存在,遵循内存对齐原则。一个结构体变量的总长度等于所有成员长度之和。
- 联合体各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权,各变量共用一个内存首地址。一个联合体变量的总长度至少能容纳最大的成员变量,且要满足是所有成员变量类型大小的整数倍。
3.结构体内存对齐
规则
第一个成员在与结构体变量偏移量为0的地址处。(即结构体的首地址处,即对齐到处)
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
对齐数 = 该结构体成员变量自身的大小与编译器默认的一个对齐数的较小值。(注:VS中的默认对齐数为8,不是所有编译器都有默认对齐数,当编译器没有默认对齐数的时候,成员变量的大小就是该成员的对齐数。)
对于该题的结构体
struct MyData
,其对齐方式如下图所示从图中可以直观地看到这个结构体一共占48个字节
9.好一个递归,一递起来就发狠了!忘情了!…(10分)
#include <stdio.h> int function(int n){ if (n < 5) { return n; }else { return function(n-1) + function(n-3); } } int main(){ int n=10; printf("%d\n", function(n)); return 0; }
输出结果:
41
分析:
function(10)
- 因为
10 >= 5
,所以返回function(9) + function(7)
。function(9)
- 因为
9 >= 5
,所以返回function(8) + function(6)
。function(8)
- 因为
8 >= 5
,所以返回function(7) + function(5)
。function(7)
- 因为
7 >= 5
,所以返回function(6) + function(4)
。function(6)
- 因为
6 >= 5
,所以返回function(5) + function(3)
。function(5)
- 因为
6 >= 5
,所以返回function(4) + function(2)
。function(4)
- 因为
4 < 5
,所以直接返回4
。function(3)
- 因为
3 < 5
,所以直接返回3
。function(2)
- 因为
2 < 5
,所以直接返回2
。- 计算
-function(5)
=function(4) + function(2)
=4 + 2 = 6
。
-function(6)
=function(5) + function(3)
=6 + 3 = 9
。
-function(7)
=function(6) + function(4)
=9 + 4 = 13
。
-function(8)
=function(7) + function(5)
=13 + 6 = 19
。
-function(9)
=function(8) + function(6)
=19 + 9 = 28
。
-function(10)
=function(9) + function(7)
=28 + 13 = 41
。
递归
1. 什么是递归
- 递归是指程序调用自身的编程思想,即一个函数调用本身
2. 递归三要素
- 第一要素:递归的定义
- 接什么参数,返回什么值,代表什么含义
- 第二要素:递归的拆解
- 确定单层递归的逻辑(把大问题拆解成小问题)
- 第三要素:递归的出口
- 确定终止条件(到什么时候应该结束)
3. 递归的优缺点
- 优点:
- 结构清晰、可读性强
- 缺点:
- 运行效率较低、耗费计算时间和占用存储空间多
- 可能导致栈溢出
10.对小球进行持续殴打,直到他承认自己是次品。(10分)
8 个小球,有一个次品,不知次品小球的轻重。请你用一台无砝码天平称,找出这个次品小球,最少需要几次能 秤出来?怎样秤能保证秤的次数最少?
最少需要两次(运气爆棚)
- 1.随便选俩球称量,记为A、B,刚好不平衡
- 2.再用A(B)和剩余6个球中任意一个称量
- 平衡,则次品是B(A)
- 不平衡,则次品是A(B)
正常情况保证称的次数最少(3次)
- 1.将8个球分成四组,每组俩个(记为12,34,56,78),先用12和34称量
- 不平衡(次品在1234中)
- 2.用1和2称量
- 不平衡(次品在12中)
- 3.用1(2)和其他6个球任意一个称量
- 平衡(次品是2(1))
- 不平衡(次品是1(2))
- 平衡(次品在34中)
- 3.用3(4)和其他6个球任意一个称量
- 平衡(次品是4(3))
- 不平衡(次品是3(4))
- 2.2平衡(次品在5678中)
- 2.用5和6称量
- 不平衡(次品在56中)
- 3.用5(6)和其他6个球任意一个称量
- 平衡(次品是6(5))
- 不平衡(次品是5(6))
- 平衡(次品在78中)
- 3.用7(8)和其他6个球任意一个称量
- 平衡(次品是8(7))
- 不平衡(次品是7(8))
11.排序算法(10分)
简单说说你所知道的排序算法。选择一种你较为了解的排序方法,现场说说原理并使用代码实现。
冒泡排序
- 原理
- 从序列的第一个元素开始,比较相邻的两个元素,如果它们的顺序错误(如:从小到大排序时,前一个元素大于后一个元素),则交换它们的位置。继续比较下一对相邻元素,执行相同的操作,直到序列的末尾。
- 分析
- 核心是多趟排序
- 以升序排序为例,假设有n个数。
- 第一趟排序目的是找到整个数组中最大的数并把它排在最后端;
- 第二趟排序目的是在剩下的n-1个数找出最大的(即整个数组中第二大的数)并把它放在倒数第二位
- …….
- 这样一轮一轮的比较,直到只剩下一个数时(完成了n-1趟的排序)这个排序就完成了,从而实现从小到大的排序。
- 实现
void bubble_sort(int arr[], int n) { for(int i=0;i<n-1;i++) { for(int j=0;j<n-i-1;j++) { if(arr[j]>arr[j+1]){ int temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } } }
- 优化
void bubble_sort(int arr[], int n) { for(int i=0;i<n-1;i++) {// int flag=1;//用于标记这一趟排序是否发生了交换 for(int j=0;j<n-i-1;j++) {// if(arr[j]>arr[j+1]){ flag=0;//如果发生了交换,将flag置为0 int temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } if(flag) {//如果这一趟排序没有发生交换,说明数组已经有序,可以提前结束排序 break; } } }