2021面试题题解
1. 大小和长度竟然不是一个意思
sizeof()
和strlen()
有什么异同之处?
int main(void) {
char s[] = "I love Linux\0\0\0";
int a = sizeof(s);
int b = strlen(s);
printf("%d %d\n", a, b);
}
Solution
- 运行结果:16 12
- sizeof与strlen函数
- 共同点:都是以字节为单位返回大小
- 区别 :strlen是函数,而sizeof是算符
- strlen:返回字符串的大小,所以以\0为截止
- sizeof:返回对象所占内存的大小
2. 箱子的大小和装入物品的顺序有关
test1
和test2
都含有:1个short
、1个int
、1个double
,那么sizeof(t1)
和sizeof(t2)
是否相等呢?这是为什么呢?
struct test1 {
int a;
short b;
double c;
};
struct test2 {
short b;
int a;
double c;
};
int main(void) {
struct test1 t1;
struct test2 t2;
printf("sizeof (t1) : %d\n", sizeof(t1));
printf("sizeof(t2): %d\n", sizeof(t2));
}
solution
- 运行结果:sizeof (t1) : 16,sizeof(t2): 16
-在struct中,地址必须是从这个数据类型大小的整数倍开始,且内存大小为每个数据内存的加和。
3. 哦,又是函数
想必在高数老师的教导下大家十分熟悉函数这个概念。那么你了解计算机程序设计中的函数吗?请编写一个
func
函数,用来输出二维数组arr
中每个元素的值。
/*在这里补全func函数的定义*/
int main(void) {
int arr[10][13];
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 13; j++) {
arr[i][j] = rand();
}
}
func(arr);
}
Solution:
解析如下:
void func(int *prt)
{
int i,j;
for(i=0;i<10;i++)
{
for(j=0;j<13;j++)
{
printf("%d",prt[i][j]);
}
}
}
4.就不能换个变量名吗?
- 请结合下面的程序,简要谈谈
传值
和传址
的区别。- 简要谈谈你对C语言中变量的生命周期的认识。
int ver = 123;
void func1(int ver) {
ver++;
printf("ver = %d\n", ver);
}
void func2(int *pr) {
*pr = 1234;
printf("*pr = %d\n", *pr);
pr = 5678;
printf("ver = %d\n", ver);
}
int main() {
int a = 0;
int ver = 1025;
for (int a = 3; a < 4; a++)
{
static int a = 5;
printf("a = %d\n", a);
a = ver;
func1(ver);
int ver = 7;
printf("ver = %d\n", ver);
func2(&ver);
}
printf("a = %d\tver = %d\n", a, ver);
}
solution
- 运行结果:
- a = 5
- ver = 1026
- ver = 7
- *pr = 1234
- ver = 123
- a = 0
- ver = 1025
原因:
- 局部变量的有效范围是局限于函数内部的,形参也是局部变量局部变量的改变无法影响到主调函数
- 所以可以通过指针直接访问指向对象的地址,从而改变该对象的值
-生命周期 :
- ①把局部变量称为自动变量,即函数被调用时,系统自动为其局部变量分配存储单元;一旦该函数调用结束(不一定是整个程序运行结束),所有分配给局部变量的单元由系统自动回收
- ②动态存储区是使用堆栈来管理的,适合函数动态分配与回收存储单元。而静态存储区相对固定,它用于存放全局变量和静态变量
5. 套娃真好玩!
请说明下面的程序是如何完成求和的?
unsigned sum(unsigned n)
{
return n ? sum(n - 1) + n : 0;
}
int main(void)
{
printf("%u\n", sum(100));
return 0;
}
Solution
- 运行结果:5050
- ①return的作用:
- 终止一个函数的执行,结束当前代码块中return后的语句,从当前函数退出
- 返回调用函数,而且把表达式的值作为函数的结果
- ②三目运算符的作用:
- 对于条件表达式b ? x : y
- 先对b进行判断。如果b的值为true(非0),计算x的值,运算结果为x的值;否则,计算y的值,运算结果为y的值
- ③函数的递归:
- 函数内部不断的调用自己,直到return 0 结束
6. 算不对的算术
void func(void) {
short a = -2;
unsigned int b = 1;
b += a;
int c = -1;
unsigned short d = c * 256;
c <<= 4;
int e = 2;
e = ~e | 6;
d = (d & 0xff) + 0x2022;
printf("a=0x%hx\tb=0x%x\td=0x%hx\te=0x%x\n", a, b, d, e);
printf("c=Ox%hhx\t\n", (signed char)c);
}
Solution
- 运行结果:
- a=0xfffe
- b=0xffffffff
- d=0x2022
- e=0xffffffff
- c=Oxf0
符号 | 描述 | 运算规则 | 实例 |
---|---|---|---|
& | 与 | 两个都为1,结果为1 | 0001 & 0001 = 1 |
| | 或 | 两个位都为0时,结果才为0。 | 0000|0001=0001 |
^ | 异或 | 两个位相同为0,相异为1 | 0001 ∧ 0001 = 0000 |
~ | 取反 | 0变1,1变0。 | ∼ 0 = 1 , ∼ 1 = 0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0。 | 0001 < < k = 0100 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,右移补1 11。 | 0100 > > k = 0001 |
7. 指针和数组的恩怨情仇
int main(void) {
int a[3][3] = {
{
1, 2, 3}, {
4, 5, 6}, {
7, 8, 9}};
int(*b)[3] = a;
++b;
b[1][1] = 10;
int *ptr = (int *)(&a + 1);
printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));
}
- Solution
- 为了更直观的感受指针与数组运算的过程,我们做一下处理:
#include<stdio.h>
int main(void) {
int a[3][3] = {
{
1, 2, 3}, {
4, 5, 6}, {
7, 8, 9}};
int(*b)[3] = a;
++b;
printf("%d\n",**b);
printf("%d\n",b[0][1]);
b[1][1] = 10;
int *ptr = (int *)(&a + 1);
printf("%p\n",a);
printf("%p\n",a+1);
printf("%p\n",&a);
printf("%p\n",&a+1);
printf("%p\n",ptr);
printf("%p\n",ptr-1);
printf("%p\n",&a[2][2]);
printf("%lu %lu\n",sizeof(a),sizeof(ptr));
printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));
运行结果如下:
- a+1 =0x7fffffffdccc ①
- &a =0x7fffffffdcc0 ②
- &a+1 =0x7fffffffdce4 ③
- ptr =0x7fffffffdce4 ④
- ptr-1 =0x7fffffffdce0 ⑤
- &a[2][2]=0x7fffffffdce0 ⑥
- 36 8
- 10 4 9
分析:
名称 | 代表 | 所占存储空间 |
---|---|---|
&a | 以整个数组为一个存储单元 | (3*4)*3=36字节 |
a | 以一个a[x]为一个存储单元,a代表&a[0] | 3*4=12字节 |
a[x] | 以一个a[x][y]为一个存储单元,a[0]代表&a[0][0] | 4字节 |
- 再来看看我们的指针ptr
- int *ptr = (int *)(&a + 1)
- 这里为什么要用(int *)强制转换呢?
- 看看上面的解析就知道&a的大小为36字节!,ptr为一个一维指针(一般都为8字节),字节数都不相同,肯定要转换呀!!
- 接下来就是ptr与ptr+k
- 注意:指针的加减运算是增加k个存储单元,所以移动量应该是 4(int型)*k,而不是指针所占的8字节
8. 移形换位之术
下面有
a
、b
、c
三个变量和4个相似的函数。
- 你能说出使用这三个变量的值或地址作为参数分别调用这5个函数,在语法上是否正确吗?
- 请找出下面的代码中的错误。
const int
和int const
是否有区别?如果有区别,请谈谈他们的区别。const int *
和int const *
是否有区别?如果有区别,请谈谈他们的区别。
int a = 1;
int const b = 2;
const int c = 3;
void funco(int n) {
n += 1;
n = a;
}
void func1(int *n) {
*n += 1;
n = &a;
}
void func2(const int *n) {
*n += 1;
n = &a;
}
void func3(int *const n) {
*n += 1;
n = &a;
}
void func4(const int *const n) {
*n += 1;
n = &a;
}
Solution
- ①const int 与int const是一样的,指针指向的内容是一个常量,不能改变
- ②const int*->指针指向的值不能改变
- int const* ->指针指向的值不变
- int *const ->指针指向的地址不变
9. 听说翻转字母大小写不影响英文的阅读?
请编写
convert
函数用来将作为参数的字符串中的大写字母转换为小写字母,将小写字母转换为大写字母。返回转换完成得到的新字符串。
char *convert(const char *s);
int main(void) {
char *str = "XiyouLinux Group 2022";
char *temp = convert(str);
puts(temp);
}
solution
char *converAndMerge(char (*words)[20])
{
char *NEW;
char *OK;
NEW=(char *)malloc(40);
NEW=strcat(words[0],words[1]);
OK=NEW;
while(*NEW)
{
if(islower(*NEW))
*NEW=toupper(*NEW);
else if(isupper(*NEW))
*NEW=tolower(*NEW);
NEW++;
}
return OK;
}
10. 交换礼物的方式
- 请判断下面的三种
Swap
的正误,分别分析他们的优缺点。- 你知道这里的
do {...} while(0)
的作用吗?- 你还有其他的方式实现
Swap
功能吗?
#define Swap1(a, b, t) \
do {
\
t = a; \
a = b; \
b = t; \
} while (0)
#define Swap2(a, b) \
do {
\
int t = a; \
a = b; \
b = t; \
} while (0)
void Swap3(int a, int b) {
int t = a;
a = b;
b = t;
}
solution
- do {…} while(0)循环使宏展开中对个语句为一个整体,而且只执行一次
- Swap3中应该用指针
11. 据说有个东西叫参数
你知道
argc
和argv
的含义吗?请解释下面的程序。你能在不使用argc
的前提下,完成对argv
的遍历吗?
int main(int argc, char *argv[]) {
printf("argc = %d\n", argc);
for (int i = 0; i < argc; i++)
printf("%s\n", argv[i]);
}
11.Solution
- 运行结果:argc = 1
- /home/yuanxioatian/公共的/code/no-name
- argc : 命令行传入参数的总个数
- argv : *argv[]是一个指针数组,里面存放的指针指向所有的命令行参数,argv[0]指向程序的全局路径,argv[1]指向在命令行中执行程序名后的第一个字符串,argv[2]指向第二个。
12. 人去楼空
这段代码有是否存在错误?谈一谈静态变量与其他变量的异同。
int *func1(void) {
static int n = 0;
n = 1;
return &n;
}
int *func2(void) {
int *p = (int *)malloc(sizeof(int));
*p = 3;
return p;
}
int *func3(void) {
int n = 4;
return &n;
}
int main(void) {
*func1() = 4;
*func2() = 5;
*func3() = 6;
}
- ①全局变量 :
- 存储位置 : 存放在静态存储区中。因此他们的生存周期是固定的,存在于程序的整个运行过程中。
- 全局变量的作用范围 : 一般是从定义位置开始到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。
- ②局部变量 :
- 局部变量的有效范围是局限于函数内部的,形参也是局部变量局部变量的改变无法影响到主调函数
-生命周期 :
- ①把局部变量称为自动变量,即函数被调用时,系统自动为其局部变量分配存储单元;一旦该函数调用结束(不一定是整个程序运行结束),所有分配给局部变量的单元由系统自动回收
- ②动态存储区是使用堆栈来管理的,适合函数动态分配与回收存储单元。而静态存储区相对固定,它用于存放全局变量和静态变量
- 自动变量若没有赋初值,那么其存储单元是随机值。而静态局部变量如果定义时没有赋初值,系统会自动赋0
- -
13. 奇怪的输出
int main(void) {
int data[] = {
0x636c6557, 0x20656d6f, 0x78206f74,
0x756f7969, 0x6e694c20, 0x67207875,
0x70756f72, 0x32303220, 0x00000a31};
puts((const char*)data);
}
12.Solution
运行结果:
Welcome to Xiyou Linux Group 2021
LE
对于一段16进制的解析:
- 对于一个,如(0X636c6557)16进制数,在计算机中以二进制来储存:0110 0011 0110 1100 0110 0101 0101 0111
二进制代码 | ASCII编码 | 字符 |
---|---|---|
01100011 | 99 | c |
01101100 | 108 | l |
01101010 | 101 | e |
01010111 | 87 | W |
- 其次在计算机中对于读取一段数据采用小端序读取
- 就是从后往前的读取
- -
14. 请谈谈对从「C语言文件到可执行文件」的过程的理解
- 1.预处理
- 预处理负责对源代码进行文本处理。主要处理代码中以字符#开头的命令。
- 有以下几个处理的地方:
- (一)解析所有的条件预处理指令。
- (二)解析定义的宏,将代码中使用的宏进行替换。
- (三)删除注释
- (四)解析 #include,将引入的头文件拷贝到当前命令位置
- 完成这些后,以从c为后缀的文本文件生成了以.i为后缀的文本文件。
- 2.编译
- 由.i的文本文件生成.s的文本文件。此文件为以汇编语句完成的代码
- 3.汇编
- 经过汇编阶段把汇编语句转换为机器指令,由.s的文本文件生成.o的二进制文件。
- 4.链接
- 链接器将所有二进制的目标文件和系统组件组合成一个可执行文件(.exe)
- 汇编结束后每个源文件都会生成一个.o文件,这里的目标文件就是这些.o文件
15. (选做) 堆和栈
你了解程序中的栈和堆吗?它们在使用上有什么区别呢?请简要说明。
- (1)程序内存布局场景下,堆与栈表示两种内存管理方式:
- 栈:
- 栈由操作系统自动分配释放
- 函数中定义的局部变量按照先后定义的顺序依次压入栈中,也就是说相邻变量的地址之间不会存在其它变量
- 后定义的变量地址低于先定义的变量
- 堆:
- 堆的申请和释放工作由程序员控制
- 生长方向不同。堆的生长方向向上,内存地址由低到高
- (2) 数据结构场景下,堆与栈表示两种常用的数据结构
- 栈:
- 栈是一种线性表
- 仅允许在表的一端进行插入和删除操作,这一端被称为栈顶(Top),相对地,把另一端称为栈底(Bottom)。
- 运算使栈拥有“先进后出”的特性
- 堆:
- 堆是一种常用的树形结构,是一种特殊的完全二叉树
- 当且仅当满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆
16. 多文件
一个程序在不使用任何头文件的情况下,如何使用另一个文件中的函数。
17. GNU/Linux
与文件
- 你知道如何在
GNU/Linux
下如何使用命令行创建文件与文
件夹吗?
touch命令- 你知道
GNU/Linux
下的命令ls 的每一列的含义吗?- 你知道
GNU/Linux
下文件的访问时间、修改时间、创建时间如何查看吗?并简单说说他们的区别。