西邮Linux兴趣小组2019-2021年纳新面试题解析

目录

2019年

2020年

2021年


2019年


1、

结果:无限打印“ = ”

大家都知道在码长为16位的情况下,int 的取值范围为-32768~32767,而unsinged int即无符号整形的范围为0~65535

那么问题来了哈,unsigned int类型如果遇到的赋值为负数,它会怎么处理呢?

答案是它会以补码的形式转换表示。

回归到本题:当for循环最后一次将unsigned int类型的i自减为-1时,此时i将会变成其十进制补码:65535。而跳脱for循环的条件是i<0,但每每i想要成为负数的那一刻,啪,直接给转为正数补码了,所以无法结束这个循环,也就只能一直打印“ = ”了。


2、

 (1)主要引用了一个中间变量c,可看作它是一个空杯子,相当于一个交换媒介。先把a的值暂存于“空杯子"c中,再把b的值赋给a,最后将”空杯子“c(也就是a)的值赋给b,即完成了数值交换的操作。

(2)这就是一个数学上的简单交换障眼术。

(3)使用异或进行解决。对于两个值来说,异或结果为:同0异1

例如两个整数a=10100001,b=00000110,可通过下列语句实现交换:

  a = a^b;   //a=10100111

  b = b^a;   //b=10100001

  a = a^b;   //a=00000110

于是a、b就这样交换了数值。


3、

 结果:输出结果不一致,每调用一次函数,a值就递增1,而b值始终为1。

这道题主要涉及局部变量和static关键字的作用。我们在程序中多次调用的输出如下:

首先我们知道a、b都定义在函数体内,为局部变量。而局部变量一旦离开了定义它的代码块时,声明周期结束,变量就会被销毁,无法再继续使用(所以每调用一次函数结束时,b都会被清零,b固然每次都为1)

那凭什么a每次都能递增?那是因为此时对a设定了“static”—a就变成了局部静态变量

局部静态变量

作用域同样为局部作用域,同样也是当定义它的函数结束时,作用域结束。但是!当局部静态变量离开作用域后,其本身数据并没有被销毁,而是留在了内存当中。直到该函数再次被调用,此值就会在原值的基础上做改变

也就是说,每调用一次函数对a实现++时,最后的结果都被保存在了内存中没被清零,因此a呈递增状态。 ( static就相当于局部变量值的“保护伞”啦!)


4、

结果:Xiyou Linux Group2019

主要考察printf函数的返回值

printf函数返回其成功打印字符的个数包括空格、换行符'\n'本题的输出自右往左,第一个输出没有内容,因此'%d'处的值为0;接着第二个函数输出为'Xiyou Linux Group20';第三处使用第二处打印的返回值(打印字符个数)为19。最后连起来即为输出结果。


5、

 结果:-1 0

(a是啥!a没声明也没初始化又不能当作常量赋值,就导致编译错误了,小问题这应该是题目不小心打错了)修改后代码如下:

本题和第一题类似,都是考察数据类型的范围越界问题

  • unsigned char(无符号型)的范围为0~255
  • signed char即 char(有符号型)的范围为-128~127。

那么为什么这里的255输出为-1呢?是不是char类型的问题?那接下来我们试试“255”对于int类型和char类型的输出分别是多少

int main(int argc,char *argv[])
{
	char ch = 255;
	int sh = 255;
	printf("ch = %d\nsh = %d\n",ch,sh);
}

结果输出为:

我们看到int类正常输出255,而char类输出却为-1

首先在计算机中,存储数据采用补码的形式存储

所以当255作为int类型来声明时,因为没有超出int类型的有效范围,并且作为正数的255的二进制原反补码都相同,即都为1111 1111(255),因此以int类型来定义时其输出值为255;

再来看看,当255以char类型来声明时,因为有符号char型范围是-128~127:

完全可以将其范围看作是一个头接尾的圆圈,一个圈所容纳的数据数量恰好为256(127+128还有0的存在再+1),也就代表着我们可以把255当作是这个圈内的第255个元素,也就是-1了。

用式子表达就是:在255中,当到127+1越界后变为-128(即跳到负数表达范围内),-128再与((255-127)-1)相加=(-128+127)=-1。


6、

结果为:x=-1, y=4, t=-1 // x=0, y=5, t=1

本题对于x和y的输出很简单,就是普通的加操作。重点在于t的两个输出,这其中涉及的两个运算符是:‘ | ’和‘ || ’

运算符|

指的是对两数的二进制补码进行按位或运算,有1则1,注意最后的结果也为补码形式。(‘ & ’有0则0)

逻辑运算符||

如果两个数中有一个为非零,则为真(1)。(‘ && ’两个数都为非零才为真)

同时注意进行运算时,x先++再(运算/判断),y先(运算/判断)再++

也就是说当执行代码段第三行时,x值为-1、y值为3,此时进行按位或运算。-1的二进制补码表示为1111 1111,谁碰上它进行或运算每位都必为1,最终结果都必为-1(1111 1111 ),因此第一个t的输出为-1。

同理,当执行代码段第五行时,x的值为0、y值为4,此时进行逻辑或判断。判断开始时已判断x为正,因此逻辑判断为真返回1,所以第二个t的输出为1。


7、

瞧这个大坑,你是不是看到“输出为4”的坑就呼呼往里头栽?(反正我是)

看看人家输出:

 人家输出的是3。

不要以为这里面的 X*X =(1+1)*(1+1)= 4,这是人脑思维。计算机在#define宏定义的时候接受的是X = a*b,那它在后续计算的时候只会傻愣往里头代 a*b 而不是(a*b),所以此时X*X = 1+1*1+1=3。

你的初衷如果是想要它输出4,那你就要把宏定义改为:#define X (a+b),手动加括号。


8、

输出结果为:

  1. 定义int型变量val,将val赋值为2018
  2. 定义int*类型指针pi,并让pi指向‘2019’所在地址
  3. 把val的地址赋给pi
  4. 通过pi指针,将0赋值给val(通过指针pi修改了val的值

9、

 输出结果:

*q = p使得p和q同时指向了malloc出来的这块空间,q里存放着p的地址。所以当输入‘Xiyou’时,‘Xiyou’存放在这块内存中;但当‘Linux’输入后,内容刷新,覆盖了前面的‘Xiyou’,因此输出结果为两个Linux。


10、

输出的结果为:

第一个打印的两个值相同。a用%p打印表示数组的首地址,&a表示数组首元素的地址。

第二个打印的值都发生了变化。a+1数组下一元素的首地址,即a[1]的首地址。因为为int类型,所以增加了4个字节&a+1下一个数组的首地址


11、

实现代码如下:

#include <stdio.h>
int feib(int n)
{
	if (n==1||n==2)
		return 1;
	else{
		return feib(n-1)+feib(n-2);
	}
}
int main()
{
	int a;
	scanf("%d", &a);
    printf("斐波那契数列第%d项的值为:%d\n", a, feib(a));
	return 0;
}

 12、

上述代码为冒泡排序

冒泡排序顾名思义就是通过元素的两两比较,判断是否符合要求,如不符合就交换位置来达到排序的目的。就像它的名字,在交换过程中,各个数值类似水冒泡,小(大)的元素经过不断的交换由水底慢慢的浮到水的顶端。通过重复的循环访问数组,直到没有可以交换的元素,那么整个排序就已经完成了。

冒泡的三种优化:

(1)泡泡到顶以后再也不进行扫描:减少无效扫描

#include <stdio.h>
void bubble_sort(int arr[],int sz)
{
	int i = 0;
	int t;
	for (i = 0; i < sz-1; i++)//进行元素个数减一次排序
	{
		int j;
		for (j = 0; j <sz-1-i; j++)//每次排序次数比较次数减少i个,也就是不再对已经上浮到位的元素进行比较
		{
		if (arr[j] > arr[j + 1])
			{
				t = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = t;
			}
			else
				continue;
		}
	}
}
int main()
{
	int i=0;
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };//排为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);//冒泡排序函数
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", arr[i]);
	}	
	return 0;
}

(2)如果检测到一整轮没有发生交换,则直接跳出结束排序。

#include <stdio.h>
void bubble_sort(int arr[],int sz)
{
	int i = 0;
	int t;
	for (i = 0; i < sz-1; i++)//进行元素个数减一次排序
	{
		int flag = 1;//定义一个整型变量一会用来判断if语句
		int j;
		for (j = 0; j <sz-1-i; j++)//冒泡双重for嵌套循环
		{
		if (arr[j] > arr[j + 1])
			{
				t = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = t;
				flag = 0;//程序如果没来这,说明这一次扫描没有发生过换位,所以可以直接结束排序
			}
			else
				continue;
		}
		if (flag == 1)
		{
			break;
		}
	}
}
int main()
{
	int i=0;
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };//排为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);//冒泡排序函数
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", arr[i]);
	}	
	return 0;
}

(3)冒泡的同时把“水”沉下去:上浮+下沉的双向排序(鸡尾酒排序)

鸡尾酒排序:冒泡排序的每一轮都是从左至右来比较元素,进行单向的位置交换。那么鸡尾酒排序做了怎样的优化呢?鸡尾酒排序的元素和交换过程是双向的。

下面举一个例子:有数组[2, 3, 4, 5, 6, 7, 8, 1],希望对其进行从小到大的排序。如果按照冒泡排序的思想去做的话,元素2, 3, 4, 5, 6, 7, 8已经是有序的了,只有元素1的位置不对,却还要进行7轮排序!鸡尾酒排序正是要解决这个问题的。

鸡尾酒排序过程:
第1轮:和冒泡排序一样,从左至右依次比较并交换;
第2轮:反过来,从右至左依次比较并交换;
第3轮:同第1轮...
第4轮:同第2轮...
直到某一轮排序中没有元素进行交换,证明已经有序,排序结束。这就是鸡尾酒排序的思路。排序过程就像钟摆一样,从左至右,从右至左,循环往复直至有序。

#include <stdio.h>
void bubble_sort_down(int arr[], int sz)//冒泡排序的双向扫描
{
	int i = 0;
	int t;
	int m;
	for (i = 0; i < (sz)/2; i++)//进行元素个数减一次排序,双向排序中可以让排序次数减半
	{
		int flag = 1;//定义一个整型变量一会用来判断if语句
		int j;
		int k;
		for (j = 0; j < sz - 1 - i; j++)//每次排序次数比较次数减少i个,也就是不再对已经上浮到位的元素进行比较
		{
			if (arr[j] > arr[j + 1])
			{
				t = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = t;
				flag = 0;//程序如果没来这,说明这一次扫描没有发生过换位,所以可以直接结束排序
			}
		}
		for (k = sz -1-i; k > 0; k--)//从小到大排
		{
			if (arr[k] < arr[k - 1])
			{
				m = arr[k];
				arr[k] = arr[k - 1];
				arr[k - 1] = m;
				flag = 0;
			}
		}
		if (flag == 1)
			{
				break;
			}
	}
}
int main()
{
	int i = 0;
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };//排为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort_down(arr, sz);//冒泡排序函数
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", arr[i]);
	}
	return 0;
}

其他排序方法还有选择排序、快速排序、直接插入排序、希尔排序、归并排序、基数排序和堆排序。


13、

  • 大端模式:指高位字节放在内存的低地址端,低位字节放在内存的高地址端。这样的存储模式有点类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

低地址 --------------------> 高地址
0x12  |  0x34  |  0x56  |  0x78

  • 小端模式:指低位字节放在内存的低地址端,高位字节放在内存的高地址端。这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

低地址 --------------------> 高地址
0x78  |  0x56  |  0x34  |  0x12

采用大端方式进行数据存放符合人类的正常思维

采用小端方式进行数据存放利于计算机处理

下面这段代码可以用来测试一下你的编译器是大端模式还是小端模式:

#include <stdio.h>
int main()
{
   short int x;
   char x0,x1;
   x=0x1122;

   x0=*((char*)&x); //低地址单元 ,或者((char*)&x)[0];
   x1=*((char*)&x + 1); //高地址单元,或者((char*)&x)[1];

   printf("x0=%x\nx1=%x\n",x0,x1);
}

若x0=0x11,则是大端;若x0=0x22,则是小端。

我的是小端模式 。


 14、

详见博客:

初探Linux操作系统与文件_Yan__Ran的博客-CSDN博客



2020年


1、

 结果:>

上面的代码没有对全局变量i进行初始化,因此编译器自动默认其值为0。经过i--的操作i变为-1,同时sizeof(i)的值为int类型所占字节数为4。

但要注意一点:sizeof返回值为无符号数,而i为有符号数当有符号数遇到无符号数时将会被强制转换为无符号数,那么i将会变成一个非常大的数。


2、

结果:11

这题和上面那套2019年纳新题的第7题一个道理,#define宏定义直接替换C为A*B=2+2*3+3=11,不多描述。


3、

结果:26 27

主要考察strlen函数和sizeof关键字的输出。strlen函数主要用于计算字符长度,而sizeof主要用于计算所占内存数两值相差1是因为str是一个字符串,在字符串末端会有一个‘ \0 ’,sizeof关键字会计算它。


4、

 多次执行该函数,结果为:

本题与2019年面试题第3题同理,涉及static设置局部静态变量保留上一次执行的值的知识点,不多描述。


5、

输出测试:

可以看得出来这串代码的作用是将十进制数转化为32位的二进制数。下面我们来看看这串代码:

这里面涉及两个运算符:左移运算符<<和右移运算符>>

  • << 左移运算符

V << N,表示数值V左移N位

右边空出的位用0填补,高位左移溢出则舍弃。

例如:3的二进制是0000 0011。如果我们把这个数值往左移动2位,即3<<2,那么就得到0000 1100,此时表示的十进制是12。

数值N往左移动X位,得到是数值是N * (2^X),即N乘以2的X次方。

  • >> 右移运算符

V >> N,表示数值V右移N位

左边空出的高位用0或者1补(正数用0补,负数用1补),低位右移溢出则舍弃。

例如:8的二进制是 0000 1000。如果把它往右移动2位,即8>>2,那么低字节的2位移出,高位补0,就得到0000 0010,就是2的数值。

数值N往右移动X位,得到是数值是N / (2^X),即N除以2的X次方。

下面我们来看一下题中的代码:

#include <stdio.h>
int main(int argc, char *argv[])
{
     int number;
     unsigned mask;
     mask = 1u  <<  31;  //1u表示将1转化为unsigned int即无符号整型
     scanf("%d", &number);
     while(mask)  //当mask不为0时进入循环
     {
        printf("%d",(number & mask) ? 1 : 0);  //number和mask按位与运算(有0则为0)
        mask>>=1;
     }
     return 0;
}

首先第五行操作先将1转化为无符号整型并将其32位编码向左移31位,即最终得到1000..00(32位),并将其赋值给mask。接下来作while循环,当mask不为0时进入循环。接下来作运算符判断:

(number & mask) ? 1 : 0
 mask>>=1;

这是代码的核心部分。number和mask作与运算 ,并且每进行一次运算就对mask做一次右移操作(使得1移动)。这套操作主要是想要通过与运算(都为1时才为1)以及对1右移实现对输入数字二进制编码的遍历,从而一个一个输出对应的0或1,输出的值就是number所对应的32位二进制编码了


6、

结果:Y

*str指向字符串的首字母X,而X+1则表示为X的下一个字母即Y。


7、

结果:XiyouLinuxGroup

  • 判断浮点数是否相同:

判断两个浮点数时需要注意两者类型的统一。像"float"和"double"虽然都表示浮点数,但两者的精度不同。double精度高,有效16位,而float的精度为7位。(但double消耗内存是float的两倍,所以double的运算速度比float慢得多。)

第二行中的float b=a看似将a和b等价了起来,但因为精度不同导致了a精度的缺失。

第一个if判断中"(float)a"的操作对a进行了暂时的强制转换所以a与b能相等;反观第二个if判断,没有对a、b中任意一个值进行强制转换类型,所以类型不相同,成立第二个if的判断条件打印其内容。


8、

结果:Xiyou Linux Group 2020  

数组a中表示的是ASCII码的值,打印出来为Xiyou Linux Group 20。其中最后一个元素0被读取为NULL,使该字符串的输出停止。最外层打印读取有效字符数为20。


9、

结果:2 0 2 0

考察多维数组的补位问题。来看看前两个数组的结构:

 c、d数组同理,最后即得输出2 0 2 0。


10、

 结果:1

属实是套中套中套了。首先一个指针指向了一个char类型指针,而这个char类型指针又指向a的地址。所以返回去究其根本指向的还是a,所以最后打印a的值1。


11、

取消第三行const注释后,a将会被定义为只读常量不可修改

取消三个注释后,程序运行显示:

不能对只读常量赋值

那我们现在把第五行代码注释掉再执行,程序显示:

出现段错误,显然第六行的存在也不合理。 段错误究其根本就是访问了非法内存如果你想要将字符串b中的第6个字符替换为\0的话,可以这样修改:

#include<stdio.h>
int main(int argc,char*argv[])
{
    const char a[]="xiyoulinux\0";
    char b[20]="xiyoulinux\0"; //修改指针为数组
    b[5]='\0';  //替换
    printf("%s\n",a);
    printf("%s\n",b);
    return 0;
}

输出结果:

原代码最终注释掉两行结果正常输出两遍Xiyou Linux Group


12、

C源文件到可执行文件共经历了4个过程:预处理、编译、汇编、链接

  • 预处理

预处理阶段编译器主要作加载头文件、宏替换、条件编译的作用。一般处理带“#”的语句

我们可以通过gcc的-E进行查看:

root@gougou:/home/yanran# gcc -E main.c -o main.i

编译器将main.c预处理结果输出 main.i 文件。

  • 编译

在编译过程中,编译器主要作语法检查和词法分析。我们可以通过-S来进行查看,该选项预处理之后的结果翻译成汇编代码

root@gougou:/home/yanran# gcc -S main.i

编译器将预处理输出文件main.i 文件编译成main.s 文件。

  • 汇编

在汇编过程中,编译器把汇编代码转化为机器代码。我们可以通过-c来进行查看:

root@gougou:/home/yanran# gcc –c main.s

编译器将main.s 文件转化为main.o 文件。

  • 链接

链接就是将目标文件、启动代码、库文件链接成可执行文件的过程,这个文件可被加载或拷贝到存储器执行。我们可以通过gcc main.o查看文件,如下所示:

root@gougou:/home/yanran# gcc –o main.o

编译器将输出文件main.o 链接成最终可执行文件a.out。


13、

冒泡排序的三种优化方法,参考2019年面试题的第12题。


14、

(1) 查看工作路径:pwd+文件名

(2) 查看文件:ls

(3)创建目录

  • 在当前目录下创建一个新目录:mkdir+新目录名          
  • 在指定目录下创建一个名为aaa的目录:mkdir+/目录名/新目录名
  • 创建文件:touch+新文件名

 (4) 拷贝目录:cp -r +目录名称+拷贝的目标位置



2021年


结果:16 12

sizeof和strlen的异同

sizeof关键字计算所占内存字节长度

strlen函数计算字符串字符长度

strlen函数遇\0停止,而sizeof计算\0。

都可以计算字符串数组中元素的个数

值得注意的是代码中字符串的结尾还有一个隐藏的\0,所以sizeof值为16而非15。


结果:相等,都为16

主要考察结构体内存对齐的知识点。

  • 第一个成员在结构体变量偏移量为0的地址处。
  • 其他成员变量要对齐到对齐数整数倍的地址处。

对齐数 = 编译器默认的一个对齐数与该成员大小中的较小值。(vs中默认值是8 Linux默认值为4。)

  • 结构体总大小为最大对齐数的整数倍

由此可见,test1三个元素内存分别为4、2、8,其中2补齐2变为4,所以结果为4+4+8=16。test2三个元素内存分别为2、4、8,同样2补齐变为4,结果同样为16。


 

二维数组作为函数参数来传递的三种方法:

1、行参给出第二维的长度

void func(int n,char arr[][13])
{
     int i;
     for(i=0;i<n;i++)
     {
          printf("%s\n",i,arr[i]);
     }
}//下面两个的函数体内部结构和这个一样,后面的不展开写了

 2、形参声明为指向数组的指针

void func(int n,char(*arr)[13])

 3、形参声明为指针的指针

void func(int n,char **arr)

 

  • 传值和传址的区别

传值:传值相当于单纯把值传给别人,相当于复制粘贴的操作,传值不会改变传值方的值

传址:传地址相当于将地址赋给了对方,也就是说两个参数此时就是绑在一起的蚂蚱,它们指向同一片地址。传址后两个参数只要有一个发生改变,另一个参数就会随之改变

  • 变量的生命周期

变量类型主要有全局变量、局部变量和静态变量。

局部变量:定义在函数体内部的变量,作用域仅限于函数体内部。离开函数体就会无效,再调用就是出错。

全局变量:所有的函数外部定义的变量,它的作用域是整个程序,也就是所有的源文件,包括.c和.h文件。

静态变量(static:变量在运行区间结束后内存不释放地址不变

对上面代码中的各个函数区域变量的解析

#include <stdio.h>
int ver=123;  //全局变量
void func1(int ver) //只在func1函数中有效
{
    ver++;
    printf("ver=%d\n",ver);//1026 出了这个函数数据就被灭掉
}
 
void func2(int*pr)//传局部变量ver的地址给指针pr
{
    *pr=1234;//在此函数内修改pr指向地址中的值(ver)
    printf("*pr=%d\n",*pr);//1234 修改成功 并且会影响主函数中ver的值(因为是传的地址)
    pr=5678;//直接把int型常量赋给地址会出现警告
    printf("ver=%d\n",ver);//123 此时ver的值为全局变量ver的值。因为在func2函数中的参数是指针,没有int类型常量的定义所以从全局中调变量值。
}
 
int main()
{
    int a=0;//局部变量,只在主函数体内有效
    int ver=1025;//同上
    for(int a=3;a<4;a++)  //for循环内a的初定义
    {
        static int a=5;//静态局部变量a,一次循环结束后的值会保留至下一循环迭代使用。仅在此循环中有效
        printf("a=%d\n",a);//5
        a=ver;
        printf("a=%d\n",a);//1025
        func1(ver); //传入的是主函数内定义的局部变量ver
        int ver=7;//仅在此循环中有效的局部变量ver
        printf("ver=%d\n",ver);//7
        func2(&ver);//传局部变量ver的地址给指针pr
        //如果在这里插入printf("%d",ver);输出为:ver = 1234,因为在func2函数中通过修改*pr指针修改了ver的值
    }
    //出了这个for循环,循环内定义的a和ver空间被释放,其值1025和1234被清空。
    printf("a=%d\tver=%d\n",a,ver);//0 1025 就是在主函数内刚开始定义和初始化的值
    return 0;
}

 输出如下:

结果:5050

表达式?结果一:结果二

表达式若为真(1),调用结果一;若为假(0),则调用结果二。

递归中的“归”是如何实现的

递归中必须存在一个临界点。比如最内层被调用的函数被赋给了一个确切的返回值,这个确切的返回值就是递归的临界点。接受到确切的值后最内层函数的值确定,然后将结果返回给上一层函数,然后一层层结束,一层层返回,这就是“归”。

引用一篇博客中的例子:

我们再回归到这道题中,来看看函数体:

unsigned sum(unsigned n)
{
    return n ? sum(n-1) + n : 0;
}

函数自己调用自己,一看就是老递归了。 

从n = 100开始递减,当n不为0时,函数不停返回sum(n-1)调用自己;

当n减到1时,返回sum(0)+1,而sum(0)通过运算符判断返回0临界点),sum(0)=0再返回给sum(1)...就这样逐层返回最后0+1+2+...+100=5050。


代码分析:

#include<stdio.h>
void func(void);
int main()
{
    func();
    return 0;
}

void func(void)
{
    short a=-2; //a十进制-2,二进制1111 1111 1111 1110,十六进制fffe
    unsigned int b=1;
    b+=a; //b十进制-1,二进制1111 1111 1111 1111 1111 1111 1111 1111,十六进制ffffffff
    int c=-1;
    unsigned short d=c*256;
    c<<=4; //c的补码右移4位变为1111 1111 1111 1111 1111 1111 1111 0000,十进制-16,十六进制fffffff0
    int e=2;
    e=~e|6; //~取反后进行或运算(两个只要有一个为1就为1)得e二进制为1111 1111 1111 1111 1111 1111 1111 1111,十六进制ffffffff
    d=(d&0xff)+0x2022; //与运算(都为1才是1)结果为0+0x2022 =0x2022
    printf("a=0x%hx\t b=0x%x\t d=0x%hx\t e=0x%x\n",a,b,d,e);//十六进制输出(%hx表示输出short类型值)
    printf("c=0x%hhx\t\n",(signed char) c);//(%hhx表示输出short short类型值)
}

程序输出:


 结果:10 4 9

首先a是一个三维数组,b是一个指向一维数组的数组指针。

++b操作如下(框内为数组a[3][3]):

此时对b[1][1]赋值10实际上是将a[2][1]赋为了10,即7替换成了10

int*ptr=(int*)(&a+1)指向整个a数组的下一个数组

这里插播一个:

对于数组:a和&a的区别

  • a代表数组首元素的地址
  • &a代表整个数组的地址

这俩的数据类型也不同:a是首元素的类型,&a是数组的类型

在最后打印中:

**(a+1):对a数组一维+1得到第二维,然后再对第二维处首元素取值得到4

*(ptr-1)整个数组的下一个数组-1,得到上一个数组a中的最后一个元素9


先回答后两个问题:

  1. const int 和 int const 没有区别,它们都表示int类型的变量不能修改;
  2. const int* 和int const* 也没有区别,它们都表示指针指向的值不能修改。

下面是错误代码分析(func2、func3、func4有错):

主要涉及‘对于指针来说,const作用位置所对应的不可改变位置

void func2(const int *n)//const作用于*n:不可修改指针n指向的值
{
    *n+=1;//向只读形参赋值,错误
    n=&a;
}
void func3(int*const n)//const作用于n:不可修改指针n指向的地址
{
    *n+=1;
    n=&a;//向只读位置赋值,错误
}
void func4(const int *const n)//const作用于*n和n:地址和值都不能修改
{
    *n+=1;//向只读形参赋值,错误
    n=&a;//向只读位置赋值,错误
}

如果没有限制,则可以使用大小写字母的ASCII相差32的性质来解决大小写反转问题:

#include<stdio.h>
int main()
{
	int I=2;   //随便赋个值使I不为零
	while (I)  //当括号内表达式不为零时实现后面的循环
	{
		char b;
		scanf("%c", &b);
		if (b >= 'A' && b <= 'Z')
		{
			b = b + 32;
			printf("%c", b);
		}
		else if (b >= 'a' && b <= 'z')
		{
			b = b - 32;
			printf("%c", b);
		}
	}
	return 0;
}

不过该题目要求使用以指针为形参的函数来解决,并且是特定字符串,最后还要返回反转后的字符串

代码实现如下:

char *convert(const char *s)
{
    char *k=(char*)malloc(N);//在函数中分配一段空间并且让指针k指向这段字符串的位置
    char *t=s;//指针t用来保存原字符串s的位置
    char *q=k;//再用一个指针保留新分配的内存地址
    memset(k,0,N);//将新分配的内存空间中的数全部置为0
    char str[N];//用一个数组来存放转换后的字符串

    for(;*s!='\0';*s++,k++)//通过指针的移动将数据储存在新地址上
    {
        if(*s>='a'&&*s<='z')
            *k=toupper(*s);  //小写转大写
        else if(*s>='A'&&*s<='Z')
            *k=tolower(*s);  //大写转小写
        else
            *k=*s;
    }
    t=q;//让原指向s的指针指向新地址
    return t;
}

  • 前两种方法正确,最后一种错误。

Swap1:直接从原函数中传递参数,比较方便

Swap2:在参数中创建交换变量t,作用时间短且占用内存空间小

Swap3:错误!只传递了a,b变量的值,并没有传地址,所以仅在函数内部改变了a,b的值。而函数作用完后就释放掉了函数中变量的内存,a,b的值依旧没有改变

  • do{...}while(0)的作用:宏定义中实现局部作用域

我们知道宏定义只是做一个标识符和字符串的替换,尽管宏定义的形式可以类似于函数,但它实际并不具备与函数类似的局部作用域。当然,我们可以通过在宏定义中使用大括号的形式,如:#define swap(x){...} 来实现局部作用域,但也会带来新的麻烦:

#define swap(a, b){a = a+b; b = a-b; a = a-b;}

int main()
{
    int a = 1, b = 2;
    if(1)
        swap(a,b);
    else
        a = b = 0;
    return 0;
}

上面的代码乍看并没有问题,但要想到一点:宏定义会在预编译后被替换

下面是替换后的代码:

int main()
{
    int a = 1, b = 2;
    if(1)
    {
        a = a+b; 
        b = a-b; 
        a = a-b;
    };
    else
        a = b = 0;
    return 0;
}

这下问题就明显了:在if后的代码块后面多出了一个 ;,这会引发编译错误。

使用该宏定义时不在后面加;可以解决这个问题,但这显然不符合我们的编码习惯。

在宏定义中使用do…while(0)可以解决这个问题:

#define swap(a, b) do{\
            a = a+b;\ 
            b = a-b;\
            a = a-b;\
        }while(0)

int main()
{
    int a = 1, b = 2;
    if(1)
        swap(a,b);
    else
        a = b = 0;
    return 0;
}

像上面的代码那样,我们可以放心地在宏定义后面使用分号,就不会造成问题啦。

  • 实现swap功能的其他方法,可参考2019年面试题的第2题。

 

argc为参数个数,argv为指针数组的首元素(指针)。

要想在不使用argc的前提下完成对argv的遍历,只能从argv自身出发。当argv指针不为空时即可完成遍历:

#include<stdio.h>
int main(int argc, char*argv[])
{
     int i=0;
     while(argv[i]!=NULL)
          printf("%s\n",argv[i++]);
     return 0;
}

int *func1(void)
{
    static int n=0;//静态变量的生命周期是从调用它开始到本函数结束
    n=1;
    return &n;//只能返回指针值也就是n的地址(此处可返回地址是因为静态变量的内存不会被释放,值会一直保存且地址一直存在)
}

int *func2(void)
{
    int *p=(int*)malloc(sizeof(int)); //手动给指针p分配内存
    *p=3;
    return p;//返回首地址
}

/*func3错误*/
int *func3(void)
{
    int n;//没初始化
    return &n;//不可返回局部变量的地址(&n),因为它作用完就被释放了,相当于返回一个野指针
}

结果:Welcome to xiyou linux group 2021

涉及大小端问题,详情可参考2019年第13题和2020年第8题。


详情参考2020年面试题第12题 。


后面会单开一篇博客讨论这个。


建立头文件(.h)是为了声明c文件(.c)中的函数,以及包括宏定义。

首先要建一个.h文件:

打开.h文件进行建立编辑。建立头文件是有一定步骤的,要用到#ifndef ...、#define ...、#endif,这是为了避免重复定义。

#ifndef后面要写的是头文件名称的大写。例如:ceshi.h要写成__CESHI_H__。前面与后面是两个下划线。然后在define与endif中间声明函数名,记得写冒号。

然后在.c文件中写上include这个头文件,就可以调用了。

 最后编译运行就完成了。


详情参考2020年面试题第14题,以及我的另一篇博客:初探Linux操作系统与文件_Yan__Ran的博客-CSDN博客


此处应有表情包(自行脑补

猜你喜欢

转载自blog.csdn.net/Yan__Ran/article/details/121733129
今日推荐