Shawn摘要

1. 大小和长度竟然不是一个意思

sizeof()strlen()有什么异同之处?

他们对于不同参数的结果有什么不同?请试举例子说明。

1
2
3
4
5
6
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);
}

sizeof()是一个运算符,计算的是所占内存空间的字节数(包括最后的’\0’),strlen()是一个函数,遇到\0就会结束,返回字符串的长度,所以s的大小是16,长度是12

2. 箱子的大小和装入物品的顺序有关

test1test2都含有:1个short、1个int、1个double,那么sizeof(t1)sizeof(t2)是否相等呢?这是为什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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));
}

sizeof(t1)和sizeof(t2)的大小都是16,结构体的大小存在内存对齐,test1的大小是4+2+2+8=16,test2的大小是2+2+4+8=16

结构体大小计算遵循以下原则:

1.第一个成员在与结构体变量偏移量为0的地址处.(即结构体的首地址处,即对齐到0处)

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

3.结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4.如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

3. 哦,又是函数

想必在高数老师的教导下大家十分熟悉函数这个概念。那么你了解计算机程序设计中的函数吗?请编写一个func函数,用来输出二维数组arr中每个元素的值。

1
2
3
4
5
6
7
8
9
10
/*在这里补全func函数的定义*/void func(int arr[][13])
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
void func(int arr[][13])
{
for(int i=0;i<10;i++)
{
for(int j=0;j<13;j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
}

4.就不能换个变量名吗?

  • 请结合下面的程序,简要谈谈传值传址的区别。
  • 简要谈谈你对C语言中变量的生命周期的认识。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int ver = 123;
void func1(int ver) {
ver++;
printf("ver = %d\n", ver);
}
void func2(int *pr) {//传入ver的地址给指针pr
*pr = 1234;//修改pr指向地址中的值
printf("*pr = %d\n", *pr);
pr = 5678;//直接把int型常量赋值给int*型指针
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);
}

传值到函数结束,释放内存空间,并不能修改实际变量的值,转址由于传的是地址,可以通过地址修改变量的值

局部变量的生命周期是从局部变量创建到函数结束,全局变量,静态局部变量,静态全局变量的生命周期都是从程序创建到程序销毁

5. 套娃真好玩!

请说明下面的程序是如何完成求和的?

1
2
unsigned sum(unsigned n) { return n ? sum(n - 1) + n : 0; }
int main(void) { printf("%u\n", sum(100)); }

表达式?结果1:结果2

表达式为true返回结果1,否则返回结果2

sum(100)返回的结果是sum(99)+100=100+99+sum(98),最终到sum(2)=sum(1)+2=sum(0)+1+2=0+1+2

通过递归对1-100求和,结果是5050

6. 算不对的算术

1
2
3
4
5
6
7
8
9
10
11
12
13
void func(void) {
short a = -2;
unsigned int b = 1;
b += a;
int c = -1;
unsigned short d = c * 256;
c <<= 4;//c的补码左移4位
int e = 2;
e = ~e | 6; /*优先级:取反 > 左移 > 按位与 > 按位异或 > 按位或*/ //取反 或运算(两位只要有一个为1,其值为1)
d = (d & 0xff) + 0x2022;//与运算 (两位同时为1,结果才为1)
printf("a=0x%hx\tb=0x%x\td=0x%hx\te=0x%x\n", a, b, d, e);//(hx=short x) 16进制输出
printf("c=Ox%hhx\t\n", (signed char)c);//(hhx=short short x)
}

7. 指针和数组的恩怨情仇

8. 移形换位之术

下面有abc三个变量和4个相似的函数。

  • 你能说出使用这三个变量的值或地址作为参数分别调用这5个函数,在语法上是否正确吗?
  • 请找出下面的代码中的错误。
  • const intint const是否有区别?如果有区别,请谈谈他们的区别。
  • const int *int const *是否有区别?如果有区别,请谈谈他们的区别。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}

在语法上是正确的

func2中n的类型是const int *是常量指针,不能通过指针改变变量的值,所以 *n+=1是错的func3中n的类型是int *const是指针常量,不能再指向其他的地址,所以n=&a是错的,func4中n的类型是const int *const既不能通过指针改变变量的值,也不能再指向其他的地址

const int和int const没有区别,const int*和int const *也没有区别

区分常量指针和指针常量的关键就在于星号的位置,我们以星号为分界线,如果const在星号的左边,则为常量指针,如果const在星号的右边则为指针常量

9. 听说翻转字母大小写不影响英文的阅读?

请编写convert函数用来将作为参数的字符串中的大写字母转换为小写字母,将小写字母转换为大写字母。返回转换完成得到的新字符串。

1
2
3
4
5
6
char *convert(const char *s);
int main(void) {
char *str = "XiyouLinux Group 2022";
char *temp = convert(str);
puts(temp);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
char* convert(const char* s);
int main(void)
{
//char* str = "XiyouLinux Group 2022";//指针指向字符串常量区的地址
char str[] = "XiyouLinux Group 2022";
char* temp = convert(str);
puts(temp);
return 0;
}

//char* convert(const char* s)
//{
// char* ret = s;
// for (int i = 0; ret[i] != '\0'; i++)
// {
// if (ret[i] <= 'Z' && ret[i] >= 'A')
// {
// ret[i] += 32;
// }
// else if(ret[i] <= 'z' && ret[i] >= 'a')
// {
// ret[i] -= 32;
// }
// }
// return s;//s是首地址,变的是i,ret没有变
//}

char* convert(const char* s)
{
char* ret = s;
while (*ret != '\0')
{
if (*ret >= 'A' && *ret <= 'Z')
{
*ret += 32;
}
else if (*ret >= 'a' && *ret <= 'z')
{
*ret -= 32;
}
ret++;
}
return s;//ret变了,指向'\0',s还是首地址
}

10. 交换礼物的方式

  • 请判断下面的三种Swap的正误,分别分析他们的优缺点。
  • 你知道这里的do {...} while(0)的作用吗?
  • 你还有其他的方式实现Swap功能吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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;
}

前两种是正确的,第三种只是传递了a,b变量的值而没有传递地址,函数结束后会释放掉a,b的内存,a,b的值并没有改变

用do{…}while(0);包裹住要操作的#define,无论你外面怎么操作,都不会影响#define的操作

还可以通过以下方式实现swap功能

1
2
3
4
5
6
void swap(int* a,int* b)
{
int temp=*a;
*a=*b;
*b=temp;
}

11. 据说有个东西叫参数

你知道argcargv的含义吗?请解释下面的程序。你能在不使用argc的前提下,完成对argv的遍历吗?

1
2
3
4
5
int main(int argc, char *argv[]) {
printf("argc = %d\n", argc);
for (int i = 0; i < argc; i++)
printf("%s\n", argv[i]);
}

argc即arguments count表示传入main函数的参数个数

argv即arguments value/vector参数值,表示传入main函数的参数序列或指针,并且第一个参数argv[0]一定是程序的名称

1
2
3
4
5
6
7
8
9
int main(int argc,char* argv[])
{
int i=0;
while(argv[i]!=NULL)
{
printf("%s\n",argv[i]);
i++;
}
}

12. 人去楼空

这段代码有是否存在错误?谈一谈静态变量与其他变量的异同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}

静态变量只会初始化一次,可以多次赋值,在数据区进行存储

作用域:只能在函数内部使用

生命周期:从程序创建到程序销毁

func3有错误修改如下

1
2
3
4
5
int *func3(void){
int* n;
*n=4;//给指针指向的随机内存空间赋值为4
return n;//不能返回局部变量的地址(作用完就被释放了,相当于返回一个野指针)
}

13. 奇怪的输出

1
2
3
4
5
6
int main(void) {
int data[] = {0x636c6557, 0x20656d6f, 0x78206f74,
0x756f7969, 0x6e694c20, 0x67207875,
0x70756f72, 0x32303220, 0x00000a31};
puts((const char*)data);
}

int转化为char,636c6557对应的二进制0110 0011 0110 1100 0110 0101 0101 0111最后的0101 0111=87刚好是W以此类推,输出Welcome to xiyou Linux group 2021

14. 请谈谈对从「C语言文件到可执行文件」的过程的理解

(1)预处理阶段。预处理器(cpp)根据字符#开头的命令,修改原始的C程序。

(2)编译阶段。将c语言文件从高级语言转为汇编语言。

(3)汇编阶段。将汇编语言转化为二进制语言。

(4)链接阶段。将使用的头文件与本文件链接起来。

15. (选做) 堆和栈

你了解程序中的栈和堆吗?它们在使用上有什么区别呢?请简要说明。

栈:栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量、函数返回地址、寄存器内容等。函数中定义的局部变量按照先后定义的顺序依次压入栈中(从高地址开始放),栈中存储的数据的生命周期随着函数的执行完成而结束。栈的效率比较高。

堆:由开发人员分配和释放(由malloc()函数申请,free()函数释放)

16. (选做) 多文件

一个程序在不使用任何头文件的情况下,如何使用另一个文件中的函数。

17. (选做) GNU/Linux与文件

  • 你知道如何在 GNU/Linux下如何使用命令行创建文件与文
    件夹吗?
  • 你知道GNU/Linux下的命令ls 的每一列的含义吗?
  • 你知道GNU/Linux下文件的访问时间、修改时间、创建时间如何查看吗?并简单说说他们的区别。

恭喜你做完了整套面试题,快来参加西邮Linux兴趣小组的面试吧!

西邮 Linux兴趣小组面试时间:
2021年10月25日至2021年10月31日晚8点。
听说面试来的早一点更能获得学长学姐的好感哦。

我们在FZ103等你!