第一题:
运行下面的代码,输出结果是什么,请解释说明:
#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 | 0x12printf("\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;//如果一小轮完成之后没有数字的交换,则证明数组已经变得有序,可直接跳出循环,增加效率 } } }
文章评论