西邮Linux兴趣小组2020纳新试题

第一题:

运行下面的代码,输出结果是什么,请解释说明:

#include<stdio.h>
int i;
int main(int argc, char *argv[])
{
    i--;
    if (i > sizeof(i))
    {
         printf(">\n");
    }
     else
    {
        printf("<\n");
    }
    return 0;
}

输出结果为:>

说明:sizeof运算符返回一个unsigned类型,而当i和sizeof(i)比较大小时,先将int类型的i转化为unsigined int类型,而-1转化成unsigined int类型是4294967295大于sizeof(i)=4;所以该程序输出>.

第二题:

执行下面的代码段,输出结果和你预想的一样没吗?谈谈你对宏的理解:

#include<stdio.h>
#define A 2+2
#define B 3+3
#define C A*B
int main(int argc,char *argv[])
{
    printf("%d\n",C);
    return 0;
}

 该程序输出11;

在编译时,编译器会把C替换成2+2×3+3,因此计算结果是11.

宏:一些命令组织在一起,作为一个单独命令完成一个特定任务

它在某些地方与函数相似,但可省去函数调用的代价,但是代码长度会大一些。因为不管宏语句在代码中出现了多少次,每次都被完整的宏体所替代,而函数码在程序中只存在一次就可以了。

与函数的区别,是宏将代码复制到调用处,而函数是转去执行,如调用10次,则宏的代码被复制10次,而函数的代码只有一份。使用宏的速度快,但程序较大,使用函数程序较小,但相对速度要慢。

所以比较短小又使用频繁的功能适合做成宏,而相对大些的写成函数。

宏嵌套的展开规则:

1.一般的展开规律像函数的参数一样:先展开参数,再分析函数,即由内向外展开
2.当宏中有#的时候,不展开参数
3.当宏中有##的时候,先展开函数,再分析参数
4.##运算符用于将参数连接到一起,预处理过程把出现在##运算符两侧的参数合并成一个符号,注意不是字符串。

第三题:分析下面程序的输出结果:

#include<stdio.h>
int main(int argc,char*argv[])
{
    char str[]="Welcome to XiyouLinuxGroup";
    printf("%zu %zu\n",strlen(str),sizeof(str));
    return 0;
}

该程序输出26 27;

strlen函数计算字符串的长度,遇到\0则停止并返回,而sizeof是计算字符串所占内存大小,sizeof运算符算入了字符串最后一个\0而strlen遇到则\0立即返回因此strlen函数计算出的结果比sizeof运算符计算出的结果小1。

第五题:

分析以下程序,推测并验证其作用:

#include<stdio.h>
int main(int argc,char*argv[])
{
    int number;
    unsigned mask;
    mask=1u<<31;
    scanf("%d",&number);
    while(mask)
    {
        printf("%d",(number&mask)?1:0);
        mask>>=1;
    }
    return 0;
}

该程序的作用是将输入的整数number以二进制形式输出。

1U 表示 无符号整型 1,语句mask=1u<<31的作用是将1左移31位后赋值,mask的值就是1000 0000 0000 0000 0000 0000 0000 0000,

接下来读入一个数字number,循环时对number与mask进行按位与计算,如果(number&&mask)则输出1,否则输出0,每循环一次将mask右移一位并赋值,直到mask每一位全部为0推出循环。

C按位运算符:

(1)二进制反码或按位取反:~

一元运算符~把1变为0,把0变为1.   例如:

~(10010011)//表达式

(01101100)//结果值

(2)按位与:&

二元运算符&通过逐位比较两个运算对象,生成一个新值,对于每个单位,只有两个运算对象中相应的位都为1时,结果才为1,否则都为0.例如:

(10010011)&(11110001)//表达式

(10010001)//结果值

(3)按位或:|

二元运算符|,通过逐位比较两个运算对象,生成一个新值。对于每个位,如果两个运算对象中相应的位为有一个为1,结果为1.例如:

(10010011)|(11110001)//表达式

(11110011)//结果值

(4)按位异或:^

二元运算符^逐位比较两个运算对象,对于每个位,如果两个运算对象中相应的位一个为1,一个不为1,则结果为1,否则结果为0.例如:

(10010011)^(11110001)//表达式

(01100010)//结果值

移位运算符:

(1)左移:<<

左移运算符<<将其左侧运算对象每一位的值向左移动其右侧运算对象指定的位数,左侧运算对象移出左末端位的值丢失,用0填充空出的位置

(01100111)<<2//表达式

(10011100)//结果值

(2)右移:>>

右移运算符>>将其左侧运算对象每一位的值向右移动其右侧运算对象指定的位数,右侧运算对象移出右末端位的值丢失,用0填充空出的位置

(01100111)>>2 //表达式

(00011001) //结果值

第六题:

下面程序的运行结果是什么,请解释说明:

#include<stdio.h>
int main()
{
    char *str="Xiyou Linux Group";
    printf("%c\n",*str+1);
    return 0;
}

程序运行的结果是Y;

说明:printf("%c\n",*str+1);语句的作用是打印一个字符,*的优先级比+高,因此*str为X,*str+1为Y故输出Y。

C语言运算符优先级:

优先级

运算符

名称或含义

使用形式

结合方向

说明

1

[]

数组下标

数组名[常量表达式]

左到右

--

()

圆括号

(表达式)/函数名(形参表)

--

.

成员选择(对象)

对象.成员名

--

->

成员选择(指针)

对象指针->成员名

--

2

-

负号运算符

-表达式

右到左

单目运算符

~

按位取反运算符

~表达式

++

自增运算符

++变量名/变量名++

--

自减运算符

--变量名/变量名--

*

取值运算符

*指针变量

&

取地址运算符

&变量名

!

逻辑非运算符

!表达式

(类型)

强制类型转换

(数据类型)表达式

--

sizeof

长度运算符

sizeof(表达式)

--

3

/

表达式/表达式

左到右

双目运算符

*

表达式*表达式

%

余数(取模)

整型表达式%整型表达式

4

+

表达式+表达式

左到右

双目运算符

-

表达式-表达式

5

<< 

左移

变量<<表达式

左到右

双目运算符

>> 

右移

变量>>表达式

6

大于

表达式>表达式

左到右

双目运算符

>=

大于等于

表达式>=表达式

小于

表达式<表达式

<=

小于等于

表达式<=表达式

7

==

等于

表达式==表达式

左到右

双目运算符

!=

不等于

表达式!= 表达式

8

&

按位与

表达式&表达式

左到右

双目运算符

9

^

按位异或

表达式^表达式

左到右

双目运算符

10

|

按位或

表达式|表达式

左到右

双目运算符

11

&&

逻辑与

表达式&&表达式

左到右

双目运算符

12

||

逻辑或

表达式||表达式

左到右

双目运算符

13

?:

条件运算符

表达式1?

表达式2: 表达式3

右到左

三目运算符

14

=

赋值运算符

变量=表达式

右到左

--

/=

除后赋值

变量/=表达式

--

*=

乘后赋值

变量*=表达式

--

%=

取模后赋值

变量%=表达式

--

+=

加后赋值

变量+=表达式

--

-=

减后赋值

变量-=表达式

--

<<=

左移后赋值

变量<<=表达式

--

>>=

右移后赋值

变量>>=表达式

--

&=

按位与后赋值

变量&=表达式

--

^=

按位异或后赋值

变量^=表达式

--

|=

按位或后赋值

变量|=表达式

--

15

逗号运算符

表达式,表达式,…

左到右

--

第七题:

以下程序的运行结果是什么,你知道怎么判断两个浮点数是否相同吗?

#include<stdio.h>
int main()
{
    double a=3.14;
    float b=a;
    if((float)a==b){
        printf("Xiyou");
    }
    if(a!=b){
        printf("LinuxGroup\n");
    }
    return 0;
}

该程序的运行结果是XiyouLinuxGroup;

将double类型的a赋给b时有精度缺失因此a!=b但是在第一个if语句里面将a暂时转化成了float类型,因此此时b与同样有精度缺失的a相比较,二者的大小是相等的。

第八题:

运行下面的代码,解释运行结果并谈谈自己的理解。

#include<stdio.h>
int main(int argc,char*argv[])
{
    int a[6]={0x6f796958,0x694c2075,0x2078756e,0x756f7247,0x30322070,0};
    printf("%d\n",printf("%s",(char*)a));
    return 0;
}

该程序中数组a中存储的数以小端模式存储,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

什么是大端和小端

大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

举个例子,比如数字 0x12 34 56 78(4个字节)在内存中的表示形式为:
1)大端模式:
低地址 -----------------> 高地址(数字高位存于低地址)
0x12 | 0x34 | 0x56 | 0x78
可见,大端模式和字符串的存储模式类似。
2)小端模式:
低地址 ------------------> 高地址(数字高位存于低地址)
0x78 | 0x56 | 0x34 | 0x12

printf("\n%d \n\n",printf("%s",(char*)a));语句里面嵌套的那一个printf语句将a数组中的每一个元素按照小端模式表示出来以ASCII码的形式输出,最后一位ASCII码为0其表示的字符是NULL,停止输出,并返回打印的字符数20;外层的printf语句打印20;

因此该程序的输出为Xiyou Linux Group 2020

第九题:

分析下列程序的输出,解释其原因。

#include<stdio.h>
int main()
{
    int a[2][3]={
   
   {5,7},{5,2}};
    int b[2][3]={5,7,5,2};
    int c[2][2]={
   
   {5,7},{5,2}};
    int d[2][2]={5,7,5};
    printf("%d %d\n",a[1][1],b[1][1]);
    printf("%d %d\n",c[1][1],d[1][1]);
    return 0;
}

该程序的输出为:

2 0
2 0

原因:

数组a和c的赋值就是按照格式,只给前两行的每一行的前两个赋值,而数组b和d就是按照顺序给第一行赋值完转到第二行(按顺序赋值)

数组赋值时,如果赋值的个数小于数组分配的个数,会给该数组未赋值的部分自动赋值为0

第十题:

执行下面的程序段,其输出结果是什么,请依据相关知识,解析其原因。

#include<stdio.h>
int main(int argc,char*argv[])
{
    int a=1;
    printf("%d\n",*(char*)&a);
    return 0;
}

该程序的输出结果时是1;

&a得到了a的地址,(char*)强制转换为指针类型,之后又对其进行解引用,得到该地址上的对象的值,即a的值1

第十一题:

下面程序段的输出结果是什么,若取消第4行的const注释,a数组还能被修改吗?如果取消第7,8行的注释,程序还能正常运行吗,试着解释其原因。

#include<stdio.h>
int main(int argc,char*argv[])
{
    /*const*/char a[]="XiyouLinux\0";
    char *b="XiyouLinux\0";
    a[5]='\0';
    //b[5]='\0';
    printf("%s\n",a);
    //printf("%s\n",b);
    return 0;
}

该程序的运行结果是:Xiyou

如果取消第3行的注释,a会被声明为只读常量,程序不能正常运行;

b是一个字符串指针,而字符串指针类似于const 类型的数组,字符串指针指向的内容是不可修改的,用字符串指针定义的是存放在静态存储区,是常量,不可更改。

第十二题:

一个c源文件到一个可执行文件的过程中经历了一系列步骤,你了解这个过程吗,谈谈你对gcc的认识。

C编程的基本策略是,用程序把源代码文件转换成可执行文件。典型的C实现通过编译和链接两个步骤来完成这一过程。编译器把源代码转换成中间代码,连接器把中间代码和其他代码合并,生成可执行文件 。

在使用gcc编译程序时,编译过程可以细分为4个阶段:

●       预处理(Pre-Processing)

●       编译(Compiling)

●       汇编(Assembling)

●       链接(Linking)

Linux程序员可以根据自己的需要让gcc在编译的任何阶段结束,检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。与其他常用的编译器一样,gcc也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。

gcc提供了30多条警告信息和3个警告级别,使用它们有助于增强程序的稳定性和可移植性。此外,gcc还对标准的C和C++语言进行了大量的扩展,提高了程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量。

第十三题:

仔细阅读下面这个函数,你可以看出这个函数的功能吗?试着理解算法原理,并尝试优化它。

void sort(int arr[],int size)
{
    int i,j,tmp;
    for(i=0;i<size-1;i++){
        for(j=0;j<size-i-1;j++){
            if(arr[j]>arr[j+1]){
                tmp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=tmp;
            }
        }
    }
}

这段代码写的是冒泡排序;

冒泡排序是比较基础的排序算法之一,其思想是相邻的元素两两比较,较大的数下沉,较小的数冒起来,这样一趟比较下来,最大(小)值就会排列在一端

冒泡排序的思路:

1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2、每趟从第一对相邻元素开始,对每一对相邻元素作同样的工作,直到最后一对。
3、针对所有的元素重复以上的步骤,除了已排序过的元素(每趟排序后的最后一个元素),直到没有任何一对数字需要比较。

//改进后的冒泡排序
void sort(int arr[],int size)
{
    int i,j,tmp;
    for(i=0;i<size-1;i++){
        int count=0;
        for(j=0;j<size-i-1;j++){
            if(arr[j]>arr[j+1]){
                tmp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=tmp;
                count++;
            }
        }
        if(count==0){
            break;//如果一小轮完成之后没有数字的交换,则证明数组已经变得有序,可直接跳出循环,增加效率
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_61007453/article/details/121909402