刚开始写面试题的时候,提不上来兴趣,感觉应该没多难,一会就能写完 , 但是看了 面试题之后 ,才发现自己好多东西都没有理解透彻,这才静下心来慢慢写面试题,写完感觉收获特别大 ,面试题里的每一道题往深里研究都能搞一个下午,面试题的质量还是很高的.
1 . 分析一下代码段,并解释输出结果和原因
#include<stdio.h>
int main(int argc,char *argv[])
{
int nums[3][3] = {
1,2,3,4,5,6,7,8,9};
printf("%d\n",nums[1][-2]);
printf("%d\n",(-1)[nums][5]);
printf("%d\n",-1[nums][5]);
return 0;
}
输出结果 : 2,3,-9
数组操作 可以转换为 指针操作更容易理解.
第一个输出可以理解为 * ( * (nums + 1) - 2)
nums 是一个二级指针,它的的单个元素 是一维数组,所以它 + 1 是 + 一 个 一维数组的字节(3 个int)
而 *(nums+1) 则是一级指针 ,它的单个元素 为 一个 int ,所以 它 -2 是 减 2 个 int 字节,所以最终就是 + 1个 int 也就是 a[1]
第二个 (-1)[nums][5] = nums[-1][5] 之后原理同上
第三个 -1[nums][5] = -(1)[nums][5] = - nums[1][5] 之后原理 同上
2. 分析一下代码段,并解释输出结果和原因
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int a[3][2] = {
2,0,1,8};
char *str = (char *)malloc(sizeof(char)*20);
strcpy(str,"\0101\\xb2");
printf("%zu\n",sizeof(a));
printf("%zu %d\n",sizeof(a[1][1] = 0),a[1][1]);
printf("%d\n",a[1][1]);
return 0;
}
输出结果 : 24 ,4 , 8 , 8
这里的%zu 没错 ,在linux下,%zu 是 size_t 的输出类型
这里有个亮点,是a[1][1] = 8; sizeof的 括号里边的是 表达式,在表达式中取到类型名之后,表达式在运行过程中就不再进行运算,也就不会改变 a[1][1] 的值.
3. 谈一谈 static 的 关键字
static 变量 只完成一次初始化,如果未被初始化,则系统会给 这个变量在 bss 分配一块空间。并且初始化为 0.
static 修饰的变量 在整个程序运行阶段,只被初始化一次,直到程序结束,这块空间才会被释放.
static 修饰函数时, 这个函数就只能被本函数调用,其他外部文件 是不能 够调用 这个函数的.
4.一个 c 语言程序从源代码到形成可执行文件的过程,请解释在每个阶段分别完成了什么工作 ?
从 .c 文件首先进行预处理 生成 .i 文件,.i 文件在经过 编译 生成 .s 文件,.s 文件经过 汇编 生成 .o文件,最后.o 文件生成 连接文件 a.out
5.根据所给代码,说明const关键字的用法
#include<stdio.h>
int main()
{
char y[] = "xiyoulinuxgroup",x[] = "2018"
char *const p1 = y; //(1)
const char * p2 = y; //(2)
return 0;
}
(1) const 修饰的是 p1 ,则 p1 这个指针变量不能修改
(2) const 修饰的是 *p2 , *p2 所指向的变量不能修改
6.说明下面的两个结构体所占的字节相等吗 ?
struct icd
{
int a;
char b;
double c;
}st1;
struct cdi
{
char a;
double b;
int c;
}st2;
结果当然是不想等的.
sizeof(st1) = 16
sizeof(st2) = 24
这里有一个结构体字节对齐的问题.
这里引用一下大佬的 讲解 结构体字节对齐问题
7.分析一下代码段,解释输出的结果.
#define YEAR 2018
#define LEVELONE(x) "xiyoulinux"#x"\n"
#define LEVELTWO(x) LEVELONE(x)
#define MULTIPLY(x,y) x*y
int main(int argc, char *argv[])
{
int x = MULTIPLY(1+2,3);
printf("%d\n",x);
printf(LEVELONE(YEAR));
printf(LEVETWO(YEAR));
}
输出结果 : 7
xiyoulinuxYEAR
xiyoulinux2018
7 的原因是: 1 + 2 * 3 直接将1 + 2 替换为 x ,将 3 替换为 y
第二 个结果 是 因为第二个 宏中有 个 # , # 在宏定义中 能将后面的参数字符串化
第三个结果 就是正常的宏替换.
8.谈谈你对main 函数的理解,从参数,返回值等角度分析, int main(int argc ,char *argv[]) {}
返回值 : 如果 main 函数的 返回值为 0 就代表程序正常结束 ,返回的其他值(不同的系统返回值可能不同)则代表程序异常退出.
参数 : argc 是命令的 个数, argv 代表 参数,可以通过argv[i] 引用
9.解释下面程序的输出结果
#include<stdio.h>
#include<string.h>
int main( )
{
int c;
memcpy(&c,"linux",4);
printf( "%d\n",c);
return 0;
}
输出结果 : 1970170220
第 6 行 是将 linux 中的 4 个字节复制给 c ;
二进制 : 1110101011011100110100101101100
十进制 : 1970170220
因为现在的 电脑大多都是 小端序, 所以 将 unil 复制过去,通过 二进制存进 c 中 ,最后在以 十进制输出.
10.解释程序的运行结果
#include<cstdio>
#include<cstring>
void func(char *a)
{
printf( "%lu\n",sizeof(a));
printf( "%lu\n",strlen(a));
}
int main( )
{
char a[] = "hello world";
func(a);
printf( "%lu\n",sizeof(a));
printf( "%lu\n",strlen(a));
return 0;
}
8 字符类型 的指针 ,我的电脑是 8 个字节
11 长度 为11 个字符
12 因为字符串最后会 补上一个 /0 ,所以字节为 12;
11 和 上面的 11 一样
%lu 无符号长整型
%u 以无符号十进制形式输出
%x 以无符号 16 进制形式输出
%o 无符号 8 进制输出
11.解释该函数的输出结果
#include<cstdio>
void func(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts(">6") : puts("<6");
}
int main( )
{
func();
}
输出 : >6
为什么是 > 6 呢? 刚开始的时候特别纳闷. 想了半天才发现 a 是无符号的.
无符号 和 有符号 相加的时候 ,有符号 会自动转化为无符号 之后在进行运算.
而 -20 用补码存的,转化为 无符号的 取反 + 1, 会变成一个 特别大的整数,所以a + b > 6
12 .解释程序的运行结果
#include<cstdio>
#define f(a,b) a##b
#define g(a) #a
#define h(a) g(a)
int main( )
{
printf("%s\n",h(f(1,2)));
printf( "%s\n",g(f(1,2)));
return 0;
}
输出结果 : 12
f(1,2)
#,@ 单字符化操作符
##是字符连接符号,将 2 个参数链接到一块
13.分析下列程序的输出
#include<stdio.h>
int main( )
{
int t = 4;
printf( "%lu\n",sizeof(t--));
printf( "%d\n",t);
printf( "%lu\n",sizeof("ab c\nt\012\xa1*2"));
return 0;
}
4
4
11
第二个 4 上面已经讲过了,这就不浪费时间了
/012 8进制 为 1 个字节
/xa 16进制 为 1 个字节
14 .解释下面代码的输出
#include<stdio.h>
int main( )
{
int a = 10,b = 20,c = 30;
printf("%d %d\n",b = b*c,c = c*2);
printf( "%d\n",printf( "%d",a+b+c));
return 0;
}
输出结果:
1200 60
12704
printf 是一个入栈的过程,先进后出,也就是说 ,printf 的操作符是 从右往左开始算
先算 c = c * 2,然后再算 b = b*c
15 .对比下面程序在 linux 和 windows 上的输出结果,并思考原因
#include<stdio.h>
int main( )
{
while(1)
{
fprintf(stdout,"Group");
fprintf(stderr,"Xiyoulinux");
getchar( );
}
return 0;
}
输出结果:
linux : xiyoulinuxGroup
win : Groupxiyoulinux
因为在 linux 下 stderr 不带行缓冲 ,stdout 是 带行缓冲 ,遇到 \n 才开始从 缓冲区 读取数据
win下 stderr 是 不带行缓冲,stdout 是 不带行缓冲. 运行到这一条代码,直接从缓冲区读取数据.
16 .请说出 a 在不同平台的值,并思考此种方式的优点
#ifdef_linux_
int a = 1;
#elif _WIN32
int a = 2;
#elif _APPLE_
int a = 3;
#else
int a = 4;
#endif
输出结果 :
linux : 1
WIN32 : 2
APPLE : 3
其他 : 4
优点 :防止在多平台编译下,出现相互影响的状况
13 .观察程序的运行结果,说明出现这种情况的原因
#include<stdio.h>
#include<string.h>
int main( )
{
char str[512];
int i;
for(i = 0;i < 512;++i)
{
str[i] = -1 - i;
}
printf("%lu\n",strlen(str));
}
输出结果: 255
-128 - 1 ------ 11111111 - 1 = 01111111 = 127
一直减至 -128 再减为 0
strlen 遇到 0 就停止计算
所以输出为 255
14.分析以下函数,给出f(2018)的值,推测并验证函数的作用
int f(unsigned int num)
{
unsigned int i;
printf( "%d\n",num);
for(i = 0;num;i++) num &= (num-1);
return i;
}
输出结果: i = 7
& 运算符,两个数对应的二进制位 都为 1 结果为 1 ,否则 为 0
第一次循环 num = 2018 & 2017 等价于 11111100010 & 11111100001 结果为 11111100000
即 num = 2016
第二次循环 num = 2016 & 2015 等价于 11111100000 & 11111011111 结果为 11111000000
即 num = 1984
依次类推 直到 num 为0时退出循环