前言:截止现在,我们C语言的大部分基础内容就已经讲完了,但是,光掌握基础只是还是不够的,所以小编会将自己写过的一些题整理起来,不定期更新刷题博客,希望能扩展大家的思路。
话不多说,现在就开始我们的刷题博客吧!!
选择题
题目1
请问下列表达式哪些会被编译器禁止【多选】()
int a = 248,
b = 4;
int const *c = 21;
const int *d = &a;
int *const e = &b;
int const * const f = &a;
A: *c = 32; B: *d = 43 C: e=&a D: f=0x321f
解释:如果 const 位于 * 的左侧,则 const 就是用来修饰指针所指向的变量,即指针指向为常量;*c和*d不能变。如果 const 位于 * 的右侧,则 const 就是修饰指针本身,即指针本身是常量;e和f不能变。
题目2
以下程序的输出结果为( )
#include <stdio.h>
int i;
void prt()
{
for (i = 5; i < 8; i++)
printf("%c", '*');
printf("\t");
}
int main()
{
for (i = 5; i <= 8; i++)
prt();
return 0;
}
解释:全局变量i,在main()中修改为5,第一次在prt()中执行循环输出三次'*',i被修改为8,回到main()中第二次调用prt()时,i<8为假,循环结束没输出,执行一次print("\t"),再次回到主函数后i++变为9,i<=8为假,循环结束;
题目3
下面代码段的输出是( )
int main()
{
int a=3;
printf("%d\n",(a+=a-=a*a));
return 0;
}
A: -6 B: 12 C: 0 D: -12
解释:a+=a-=a*a等价于a=a+(a=a-a*a),即先计算a=a-a*a,所以此时a的值为3-3*3=-6,再计算-6+(-6)=-12赋值给a,所以a的值为-12,也就是整个表达式的值,所以应选择D
题目4
关于表达式求值说法不正确的是:( )
A.表达式求值先看是否存在整形提升或算术转换,再进行计算
B.表达式真正计算的时候先看相邻操作符的优先级再决定先算谁
C.相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序
D.只要有了优先级和结合性,表达式就能求出唯一值
解释:
A. 表达式求值先看是否存在整形提升或算术转换,再进行计算
正确。C语言表达式求值确实先进行类型转换:
整型提升:小于int的类型提升为int
算术转换:按照类型转换规则进行转换
B. 表达式真正计算的时候先看相邻操作符的优先级再决定先算谁
正确 。优先级决定了操作符的执行顺序,例如
*
优先于+
。C. 相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序
正确。当优先级相同时,结合性决定计算顺序:
左结合:从左到右计算(如
a + b + c
)右结合:从右到左计算(如
a = b = c
)D. 只要有了优先级和结合性,表达式就能求出唯一值
**不正确。**比如:
未定义行为:有些表达式的结果是不确定的
int i = 0; int j = i++ + i++; // 未定义行为
函数调用顺序:函数参数的求值顺序未定义
printf("%d %d", i++, i++); // 输出结果不确定
题目5
下面代码的结果是:
#include <stdio.h>
int i;
int main()
{
i--;
if (i > sizeof(i))
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
A.>
B.<
C.不输出
D.程序有问题
解释:
C语言中,0为假,非0即为真。
全局变量,没有给初始值时,编译其会默认将其初始化为0。
i的初始值为0,i--结果-1,i为整形,sizeof(i)求i类型的大小,结果是4,按照此分析来看,结果应该选择B,但是sizeof的返回值类型实际为无符号整形,因此编译器会自动将左侧i自动转换为无符号整形的数据,-1对应的无符号整形是一个非常大的数字,超过4,故实际应该选择A
题目6
下面哪些描述是正确的?
A.内存中每个bit位都有唯一的地址
B.内存中每个字节都有地址,地址可以唯一标识一个内存单元的
C.C语言中地址就是指针,指针就是地址
D.C语言中只要有了地址就可以随意读写内存空间。
解释:
A. 错误 。内存是按字节编址的,每个字节有一个唯一地址,而不是每个bit位。一个字节通常包含8个bit位,这些bit共享同一个地址。
B. 正确。这是内存寻址的基本原理,每个字节都有一个唯一的地址,通过地址可以定位到具体的内存单元。
C. 正确。在C语言中,指针就是存储地址的变量,地址就是指针的值。两者本质上是同一个概念。
D.错误。这是严重的安全错误:
访问权限:有些内存区域是只读的(如代码段)
非法访问:访问未分配的内存会导致段错误
内存保护:操作系统有内存保护机制
const限定:const指针指向的内容不能修改
编程题
题目1:计算一个数的每位之和(递归实现)
描述
写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和
例如,调用DigitSum(1729),则应该返回1+7+2+9,它的和是19
输入:1729,输出:19
思路:递归就是将大问题拆解为小问题的过程,上面的题目我们应该如何把大问题拆解为小问题呢?我们可以这样来看:计算DigitSum(1729)是计算1729的每一位之和,而计算1729的每一位之和又可以转换为计算172的每一位之和加上9,即DigitSum(1729)=DigitSum(172)+9,172可以通过1729/10得到,9可以通过1729%10得到;而计算172的每一位之和可以转换为计算17的每一位之和加上2得到,即DigitSum(172)=DigitSum(17)+2......以此类推
那么递归的限制条件是什么?如果给定的数是个位数,那我们就无需将它再进行拆分,直接把这个数返回即可
结合上述思路,我们可以用递归写出代码:
int DigitSum(int n)
{
//如果是个位数,直接返回
if (n <= 9)
{
return n;
}
return n % 10 + DigitSum(n / 10);
}
题目2:打印一个数的每一位
描述
递归方式实现打印一个整数的每一位,比如,输入1234,输出1 2 3 4
思路:与上面的题一样,还是采用大问题拆解为小问题的思想,假设我们要打印1234的每一位,可以拆解为先打印123的每一位,再打印4,即Print(1234)=Print(123)+printf("%d",4);要打印123的每一位,我们可以先打印12的每一位,再打印3,即Print(123)=Print(12)+printf("%d",3);以此类推......
那么递归的限制条件是什么呢?如果只有一位数,那么我们直接将这一位数打印出来即可。
void Print(int n)
{
if (n >= 10)
Print(n / 10);
printf("%d ", n % 10);
}
题目3:记负均正_牛客题霸_牛客网
思路:这道题比较简单,直接通过 scanf 输入数据,统计负数个数,以及正数个数,并且在统计正数个数的过程中求取正数总和,最后计算得出平均数即可。需要注意的是所有数字中0是不统计在内的。
#include <stdio.h>
#include<stdlib.h>
int main()
{
int n;
scanf("%d", &n);
int* arr = (int*)malloc(sizeof(int) * n);
long double sum = 0;
int count1 = 0, count2 = 0;
for (int i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
if (arr[i] < 0)
{
count1++;
}
if (arr[i] > 0)
{
count2++;
sum += arr[i];
}
}
//要注意正数的个数为0时不能进行求平均值
if(count2)
{
long double ave = sum / count2;
printf("%d %.11llf", count1, ave);
}
else {
printf("%d %d",count1,0);
}
return 0;
}
题目4:旋转数组的最小数字_牛客题霸_牛客网
思路:这道题比较好玩,如果只是要让我们求出数组中的最小值那倒还挺简单,但是由于旋转之后的数组不一定是有序的,所以我们可能直接就想到遍历整个数组取找到最小值,这样的话,时间复杂度就是O(n),但是,题目要求的时间复杂度是O(logN),所以我们不能直接遍历数组,那有什么其他思路呢?
在C语言中,我们接触过的有关数组的时间复杂度为O(logN)的算法就是二分查找了,二分查找算法的核心思想就是将查找范围不断地减半,从而提高效率。
那么这一题,我们能否也利用二分的思想呢?
如果我们要利用二分的思想,可以定义一个查找的有效区间,不妨就设为left和right,那我们就需要保证最小值总是要在left和right之间的。
我们再来观察一下所给序列有何特点?由于所给的数组是通过非降序的数组通过旋转得到的,所以一定存在一个分界线,在这个分界线之前和之后,两个序列都是从小到大有序的。
我们可以举例说明:
假设所给序列是{3,4,5,1,2},初始中间位置mid指向的值是5,而右端点right所指向的值是2,5>2,说明此时最小值一定在中间位置的右序列中,由此我们就可以缩小查找范围,使left=mid+1
假设所给的序列是{5,1,2,3,4},初始中间位置mid指向的值是2,而右端点right所指向的值是4,4>2,说明此时从中间位置到右端点的所有数据一定是有序排列的,那么最小值一定在中间位置或者中间位置的左边,那我们就有可以缩小查找范围,使right=mid
最后一种情况,中间位置指向的元素与右端点指向的元素相同,比如{1,0,1,1,1,1,1}和{1,1,1,1,1,0,1},那么此时上面的两种缩小查找范围的方法就已经不适用了,这也是查找效率最低的情况,此时我们不能直接将查找范围减半,因为要查找的元素可能在中间位置的左序列中,也可能在中间位置的右序列中,那我们只能进行逐个排查,我们已知的是中间位置指向的元素与右端点指向的元素相同,那此时我们就可以果断地舍弃右端点,直接让right--,然后再继续按照逻辑查找即可
分析了这么多,我们来写一下代码吧:
int minNumberInRotateArray(int* nums, int numsSize) {
int left=0,right=numsSize-1;
while(left<right)//当跳出循环时,left和right指向相等,此时[left,right]范围内只有一个元素,这个元素就是我们要找的最小值
{
int mid=left+(right-left)/2;
if(nums[mid]>nums[right])//此时所查找的元素一定在中间位置的有序列中
left=mid+1;
else if(nums[mid]<nums[right])
right=mid;
else
right--;
}
return nums[left];
}
这一小节的内容就到这里咯,小伙伴们也要自己好好练习一下哦!!!