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