C语言强化训练(2)
前言:截止现在,我们C语言的大部分基础内容就已经讲完了,但是,光掌握基础只是还是不够的,所以小编会将自己写过的一些题整理起来,不定期更新刷题博客,希望能扩展大家的思路。
话不多说,现在就开始我们的第一篇刷题博客吧!!
选择题
题目1
在C语言中,以下哪个选项描述了变长数组(Variable Length Array,VLA)的特点?
A.变长数组的大小在编译时确定,不能改变。
B.变长数组的大小可能在运行时确定,比如使用变量来指定数组大小,一旦确定大小后,它的大小是固定的,无法改变。
C.一旦确定大小后,它的大小是固定的,无法改变
D.变长数组只能用于存储字符类型的数据。
解释:
变长数组( variable-length array),C语言术语,也简称VLA。是指用 整型变量或表达式声明或定义的数组 ,而不是说数组的长度会随时变化,变长数组在其生存期内的长度同样是固定的 。
代码示例:
int n; scanf ("%d", &n); int array[n];注意上述语法在C99之前是不支持的。
A选项错误:编译时无法确定,编译时候编译器不知道n是什么值,n的值要等到程序运行起来后,用户输入之后n的值确定了,才能确定数组的大小
B选项:说法不严谨,一定是在运行时确定大小的,而不是可能
C选项正确
D选项错误:存储什么类型数据,看定义时候给数组名前放什么类型,比如int a[n]就是存放int类型 short a[n]就是存在short类型
题目2
以下程序段的输出结果是( )
#include<stdio.h>
int main()
{
char s[] = "\\123456\123456\t";
printf("%d\n", strlen(s));
return 0;
}
A: 12 B: 13 C: 16 D: 以上都不对
解释:这里考查转义字符,注意:\\ 表示字符'\',\123表示字符'{',\t表示制表符,这些都是一个字符
所以答案选A
题目3
若有以下程序,则运行后的输出结果是()
#
include <stdio.h>
#define N 2
#define M N + 1
#define NUM (M + 1) * M / 2
int main()
{
printf("%d\n", NUM);
return 0;
}
A: 4 B: 8 C: 9 D: 6
解释:
M,N是定义的宏,宏在编译过程中会被直接替换,,替换后NUM的样子是(2+1+1)*2+1/2,计算得8,所以选B
题目4
如下函数的 f(1) 的值为( )
int f(int n)
{
static int i = 1;
if(n >= 5)
return n;
n = n + i;
i++;
return f(n);
}
A: 5 B: 6 C: 7 D: 8
解释:注意哦,这里的i是静态变量,存储在静态区,这也就意味着当函数作用域销毁时,或走出f函数的作用域时,i的内存并不会被销毁,存储的值并不会被改变。
初始,n=1,执行n=n+i,n变成2,执行i++,i变成2,之后再执行return f(n),开始递归
n=2,执行n=n+i,n变成4,执行i++,i变成3,之后再执行return f(n),
n=4,执行n=n+i,n变成7,执行i++,i变成4,之后再执行return f(n),
n=7,直接将7返回,函数栈桢销毁
……
以此类推,直到回到n=1时的函数栈桢,此时f(n)=7,直接将它返回即可,所以结果就是选C
题目5
下面3段程序代码的效果一样吗()
int b;
(1)const int *a = &b;
(2)int const *a = &b;
(3)int *const a = &b;
A: (2)=(3) B: (1)=(2) C: 都不一样 D: 都一样
解释:
正确答案:Bconst在*的左边,则指针指向的变量的值不可直接通过指针改变(可以通过其他途径改变);在*的右边,则指针的指向不可变。简记为"左定值,右定向",(1)和(2)const都在*的左边,(3)中const在*的右边,所以应该选择B。
题目6
对于下面的说法,正确的是()
A: 对于 struct X{short s;int i;char c;},sizeof(X)等于sizeof(s) + sizeof(i) + sizeof(c)
B: 对于某个double变量 a,可以使用 a == 0.0 来判断其是否为零
C: 初始化方式 char a[14] = "Hello, world!"; 和char a[14]; a = "Hello, world!";的效果相同
D: 以上说法都不对
解释:
正确答案:D
选项A:没有考虑内存对齐(对于内存对齐的问题可以看博客:自定义类型:结构体_结构体的定义声明和调用csdn-CSDN博客)
选项B:考察double类型的比较,由于浮点数存在误差,不能直接判断两个数是否相等,通常采用比较两数之差的绝对值是否小于一个很小的数字(具体的可自己设定这样一个数,作为误差)来确定是否相等,比如:
#include <math.h> #include <stdio.h>//double fabs (double x):fabs()函数,将浮点类型转换为他的绝对值// 定义容差值 #define FLOAT_EPSILON 1e-6f #define DOUBLE_EPSILON 1e-12// 判断float是否为0 int is_float_zero(float f) {return fabs(f) < FLOAT_EPSILON; }// 判断double是否为0 int is_double_zero(double d) {return fabs(d) < DOUBLE_EPSILON; }int main() {float num1 = 0.000001f; // 非常接近0float num2 = 0.00001f; // 大于epsilonif (is_float_zero(num1)) {printf("num1 可以认为是0\n"); // 会输出} else {printf("num1 不是0\n");}if (is_float_zero(num2)) {printf("num2 可以认为是0\n");} else {printf("num2 不是0\n"); // 会输出}return 0; }
选项C:a为数组首地址,为常量不能改变它的值,所以错误
综上,选D
题目7
关于return语句说法正确的是哪个?
A.函数中必须有return语句
B.在函数中return语句必须返回值,不能使用return;
C.return语句执行后,return语句后边还有代码则不在执行。
D.return可以返回函数中定义的数组
解释:
A: 函数中可以没return语句,只有需要返回值,或者提前结束函数的是才使用retutrn
B:函数中需要提前结束,但是函数的返回类型是void,不需要返回任何值的时候,就使用return;
C: 正确
D: 函数中创建的数组,只能在函数内部使用,函数中返回数组,其实是将数组的地址返回给主调函数,等函数返回后,数组空间还给操作系统了,主调函数得到的地址就是野指针了。
编程题
题目1
描述
编写程序数一下 1到 100 的所有整数中出现多少个数字9
思路:1~100中数字9的出现的位置无非就是个位数和十位数,个位数上的值我们可以通过对10进行模除得到,十位数上的值我们可以通过对10进行整除得到,再将得到的值与9进行比较即可
#include <stdio.h>int main()
{int i = 0;int count = 0;for(i=1; i<=100; i++){if(i%10==9)count++;if(i/10==9)count++;}printf("%d\n", count);return 0;
}
题目2:求两个数的最大公约数
描述
给定两个数,求这两个数的最大公约数
例如:
输入:20 40
输出:20
思路:辗转相除法。辗转相除法的本质是 “两数的最大公约数,等于较小数与两数相除余数的最大公约数”,具体定理可表述为:
对于任意两个正整数 a 和 b(假设 a > b),若 a%b==r则有:
gcd(a, b) = gcd(b, r))。辗转相除法的核心是通过 “求余” 操作不断将原问题(求 a 和 b 的 GCD)转化为更小规模的问题(求 b 和 r 的 GCD),直到余数 r = 0 为止。此时,非零的除数就是原两数的最大公约数。
简单来说,就是 “大的数除以小的数,取余数;再用小的数除以余数,再取余数;重复此过程,直到余数为 0,最后一个非零余数就是最大公约数”。
这个过程可以通过递归或循环实现。
利用循环:
//求两个数的最大公约数
int GCD(int a, int b)
{//求出两个数的最小值和最大值int big = a, small = b;if (b > a){small = a;big = b;}if (small == 0)return big;//求余int r = big % small;while (r){big = small;small = r;r = big % small;}return small;
}
#include<stdio.h>
int main()
{int r = GCD(54,72);printf("%d", r);return 0;
}
利用递归
//求两个数的最大公约数
int GCD_1(int a, int b)
{//求出两个数的最小值和最大值int big = a, small = b;if (b > a){small = a;big = b;}if (small == 0)return big;int r = big % small;if (r == 0){return small;}return GCD_1(small, r);
}#include<stdio.h>
int main()
{int r = GCD_1(20,4);printf("%d", r);return 0;
}
题目3:尼科彻斯定理_牛客题霸_牛客网
描述
尼科彻斯定理,又称为斐波那契数列定理,指的是对于任意正整数 nn,存在一个由连续奇数组成的数列,使得该数列的和等于 n 的立方。
例如:
∙ ∙对于 n=1,数列 {1} 的和为 1^3=1;
∙ ∙对于 n=2,数列 {3,5} 的和为 2^3=3+5;
∙ ∙对于 n=3,数列 {7,9,11} 的和为 3^3=7+9+11;
∙ ∙对于 n=4,数列 {13,15,17,19} 的和为 4^3=13+15+17+19。
现在,给定一个正整数 nn,请输出这个数列中的元素从小到大相加的形式。
如果有多个这样的序列,请输出长度为 nn 的那个。
思路:我们可以从已有的序列中进行找规律,我们可以看到,对于任意一个整数n,他的三次方都可以表示成n个连续奇数的加法,而上面的例子中组成1~4的三次方的连续奇数都是从一开始连续的,那我们是不是就可以先猜测组成5的3次方的5个奇数中的最小的那一个奇数是组成5-1=4的三次方中最大的那个奇数的下一个奇数,也就是说5^3=21+23+25+27+29呢?经过验证,这个式子确实是正确的。
上述分析我们就可以得出解题思路,只需要找到组成n的三次方中最小的那个奇数,就可以顺藤摸瓜找到组成n的三次方的剩余的n-1个奇数,现在我们要做的就是如何找到组成n的三次方中最小的那个奇数。
其实这也很简单,我们可以发现4的三次方中最小的奇数13是所有奇数序列中第1+2+3+1=7个奇数,同理,5的三次方中最小的奇数21是所有奇数序列中第1+2+3+4+1=11个奇数,那么组成n的三次方中最小的奇数就是所有奇数序列中第1+2+……+n-1+1个奇数,我们可以通过循环来获得这个奇数。
整理好了思路,我们就可以直接写代码了:
#include <stdio.h>
#include<math.h>
int main()
{int n;scanf("%d",&n);int arr[n];int count=n*(n-1)/2;int i=1;//得到序列中最小的奇数while(count--){i+=2;}//将序列中的奇数存储到数组中for(int j=0;j<n;j++){arr[j]=i;i+=2;}for(int i=0;i<n;i++){if(i!=n-1)printf("%d+",arr[i]);elseprintf("%d",arr[i]);}return 0;
}
题目4:二分查找
写一个二分查找函数
功能:在一个升序数组中查找指定的数值,找到了就返回下标,找不到就返回-1.
思路:通过不断缩小搜索范围来快速定位目标值,每次将搜索区间减半
比如我们有序列1 2 3 4 5 6 7,这是一个升序序列,如果我们现在要查找数据6,我们可以先看序列的中间位置上的元素(此处是4)与要查找元素的大小关系,如果中间元素等于待查找元素,那就说明找到了,直接返回中间位置的下标即可;如果中间元素大于待查找元素,由于数组是升序的,那么我们要查找的元素一定在中间元素的左边,我们只需要再到左序列中进行查找即可,如果中间元素小于待查找元素,我们只需要到中间元素得有序列中在进行查找即可,这样就达到了不断将查找序列减半的效果
int BinarySearch(int* arr, int left, int right,int key)
{int mid = 0;while (left <= right){mid = left + (right - left) / 2;if (arr[mid] == key){return mid;}else if (arr[mid] < key){left = mid + 1;}else {right = mid - 1;}}return -1;
}