
❄专栏传送门:《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题、洛谷刷题、C/C++基础知识知识强化补充、C/C++干货分享&学习过程记录
🍉学习方向:C/C++方向学习者
⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平
前言:距离我们学完C语言已经过去一段时间了,在学习了初阶的数据结构之后,博主还要更新的内容就是【C语言16天强化训练】,之前博主更新过一个【C语言刷题12天IO强训】的专栏,那个只是从入门到进阶的IO模式真题的训练。【C语言16天强化训练】既有IO型,也有接口型。和前面一样,今天依然是训练五道选择题和两道编程算法题,希望大家能够有所收获!

目录
[1.1 题目1](#1.1 题目1)
[1.2 题目2](#1.2 题目2)
[1.3 题目3](#1.3 题目3)
[1.4 题目4](#1.4 题目4)
[1.5 题目5](#1.5 题目5)
[2.1 图片整理](#2.1 图片整理)
[2.1.1 题目理解](#2.1.1 题目理解)
[2.1.2 思路](#2.1.2 思路)
[2.2 寻找数组的中心下标](#2.2 寻找数组的中心下标)
[2.2.1 尝试暴力写代码](#2.2.1 尝试暴力写代码)
[2.2.2 加深题目理解](#2.2.2 加深题目理解)
[2.2.3 优化思路](#2.2.3 优化思路)
[2.2.4 优化方案实现](#2.2.4 优化方案实现)
正文
一、五道选择题
1.1 题目1
**题干:**以下对C语言函数的有关描述中,正确的有( )【多选】
A. 在C语言中,一个函数一般由两个部分组成,它们是函数首部和函数体
B. 函数的实参和形参可以是相同的名字
C. 在main()中定义的变量都可以在其它被调函数中直接使用
D. 在C程序中,函数调用不能出现在表达式语句中
解析:正确选项是AB。
A. 正确。函数首部包括函数返回类型、函数名和参数列表,函数体包括一对花括号内的语句序列。
B. 正确。实参和形参的作用域不同,实参是调用函数时的实际值,形参是函数定义中的变量,它们可以同名,但代表不同的实体。
C. 错误。在main函数中定义的变量是局部变量,只能在main函数内部使用,其他函数不能直接访问。除非这些变量是全局变量或通过参数传递。
D. 错误。函数调用可以出现在表达式语句中,例如:int result = add(a,b); 或 printf("Hello"); 都是表达式语句中包含函数调用。
1.2 题目2
**题干:**在C语言中,以下正确的说法是( )
A. 实参和与其对应的形参各占用独立的存储单元
B. 实参和与其对应的形参共占用一个存储单元
C. 只有当实参和与其对应的形参同名时才共占用存储单元
D. 形参是虚拟的,不占用存储单元
解析:正确选项是A选项。
A. 正确。实参和形参各占用独立的存储单元。
B. 错误。它们不共享存储单元,而是各自独立。
C. 错误。是否同名不影响存储单元的占用,即使同名也是不同的存储单元(因为作用域不同);
D. 错误。形参是函数内的局部变量,会占用存储单元(通常在栈上)。
在C语言中,函数参数的传递是值传递(pass by value)。这意味着:(1)当函数被调用时,实参的值会被复制到形参中;
(2)因此,实参和形参占用不同的存储单元 (即独立的内存空间);
(3)对形参的修改不会影响实参(除非通过指针间接修改)。
1.3 题目3
**题干:**在上下文及头文件均正常的情况下,下列代码的输出是( )(注:print 已经声明过了)
cpp
int main()
{
char str[] = "Geneius";
print(str);
return 0;
}
print(char *s)
{
if(*s)
{
print(++s);
printf("%c", *s);
}
}
A. suiene B. neius C. run-time error D. suieneG
解析:正确选项是A选项。
代码理解:
1、main函数定义了一个字符串 str = "Geneius",然后调用 print(str); 。
2、printf 函数是一个递归函数------
(1)如果当前字符 *s 不是空字符(即字符串未结束),则递归调用 print(++s)(注意:这里先递增 s,然后传入递归)。
(2)递归返回后,打印当前字符 *s(注意:此时 s 已经递增过一次,所以指向的是下一个字符)。
执行过程:
原始字符串: "Geneius"(注意:字符串以空字符 '\0' 结尾)。
递归调用过程(每次调用 print(++s),所以指针不断后移):
第一次调用:s指向"Geneius" -> 递归调用 print(++s)(现在s指向"eneius");
第二次调用:s指向"eneius" -> 递归调用 print(++s)(现在s指向"neius");
第三次调用:s指向"neius" -> 递归调用 print(++s)(现在s指向"eius");
第四次调用:s指向"eius" -> 递归调用 print(++s)(现在s指向"ius");
第五次调用:s指向"ius" -> 递归调用 print(++s)(现在s指向"us");
第六次调用:s指向"us" -> 递归调用 print(++s)(现在s指向"s");
第七次调用:s指向"s" -> 递归调用 print(++s)(现在s指向空字符'\0');
第八次调用:s指向'\0',条件if(*s)为假,递归终止并返回。
调用完然后开始回溯(从最深层的递归返回):
第七次调用:s原本指向"s",但调用时是print(++s),所以递归返回后,s现在指向空字符(即'\0'之后?实际上已经越界了)。但注意:在递归调用中,s的值已经被改变(因为++s是前置递增,修改了s本身)。所以每次递归返回后,s指向的是原来字符串的下一个字符(甚至最后是空字符之后)。
具体回溯时打印的字符:
从最深层的递归(第八次调用)返回第七次调用:此时s指向空字符(即'\0')之后?但实际第七次调用时,传入的s是递增后的(指向空字符),然后递归返回后,s仍然指向空字符(但字符串已经结束)。所以打印的是空字符(不显示),不过这里的代码实际上是有问题的。
关键错误:
在递归调用中,使用了 print(++s),这修改了指针s的值。当递归返回时,s已经指向下一个字符(而不是原来的字符)。因此------
(1)第一次递归调用后,s指向"eneius"(即第二个字符);
(2)当递归返回时,打印的是当前s指向的字符(即第二个字符'e'),而不是第一个字符'G'。
类似地,整个回溯过程打印的是字符串从第二个字符开始逆序 (但最后一个是空字符之后,未定义) ,实际上,字符串"Geneius"的长度为7 (加上空字符共8个字节)。递归调用直到遇到空字符(第八次调用时,s指向空字符,递归停止) 。然后回溯------
第七次调用:s原本指向"s"(最后一个字符),但调用print(++s)后,s指向空字符。然后递归返回后,打印*s(即空字符,不显示)。
第六次调用:s原本指向"us"(即字符'u'),调用print(++s)后,s指向"s"。递归返回后,打印*s(即's')。
第五次调用:s原本指向"ius"(即字符'l'),调用print(++s)后,s指向"us"。递归返回后,打印*s(即'u')。
第四次调用:s原本指向"eius"(即字符'e'),调用print(++s)后,s指向"lus"。递归返回后,打印*s(即'l')。
第三次调用:s原本指向"neius"(即字符'n'),调用print(++s)后,s指向"eius"。递归返回后,打印*s(即'e')。
第二次调用:s原本指向"eneius"(即字符'e'),调用print(++s)后,s指向"neius"。递归返回后,打印*s(即'n')。
第一次调用:s原本指向"Geneius"(即字符'G'),调用print(++s)后,s指向"eneius"。递归返回后,打印*s(即'e')。
所以打印的字符序列是(从最深回溯):' '(空字符,不显示)、's'、'u'、'i'、'e'、'n'、'e'。
即最终输出为:"suien"(但缺少第一个字符'G',并且似乎多打了一个'e')。
实际上,输出应该是:"suiene"(但注意第一个字符'e'是第二次调用打印的,而第一次调用打印的是'e'(第二个字符))。
观察字符串"Genelus":
索引0: 'G'
索引1: 'e'
索引2: 'n'
索引3: 'e'
索引4: 'i'
索引5: 'u'
索引6: 's'
索引7: '\0'
回溯打印:
递归深度7(s指向索引7): 打印*s(即'\0',不显示);
递归深度6(s指向索引6): 打印*s(即's');
递归深度5(s指向索引5): 打印*s(即'u');
递归深度4(s指向索引4): 打印*s(即'i');
递归深度3(s指向索引3): 打印*s(即'e');
递归深度2(s指向索引2): 打印*s(即'n');
递归深度1(s指向索引1): 打印*s(即'e')。
所以输出是:suiene(即"suiene")。
**注意:**原字符串是"Geneius",逆序应为"suieneG",但这里由于递归偏移,打印的是从第二个字符开始逆序(即"eneius"的逆序)为"suiene",而没有第一个'G',所以输出是"suiene"。
选项D是"suieneG"(多了一个G),实际上,代码不会打印第一个字符'G',因为第一次调用就递增了指针;另外,最后递归到空字符时,尝试打印空字符(不显示),所以没有额外输出。
我们来看这四个选项------
选项A正确;
选项B "neius" 是正序"Geneius"的一部分(错误);
选项C "run-time error":可能因为最后打印空字符(但不会错误,只是不显示),或者指针越界(但通常不会立即错误);
选项D "suieneG" 包含第一个字符'G',但实际没有。
所以正确答案就是A. suiene。
1.4 题目4
**题干:**对于函数 void f(int x); ,下面调用正确的是( )
A. int y=f(9); B. f(9); C. f(f(9)); D. x=f();
解析:正确选项是B选项。
于函数 void f(int x); ,这是一个返回类型为 void 的函数,即它不返回任何值。因此:
A. 错误,因为 f(9) 没有返回值,不能赋值给 int y。
B. 正确,直接调用函数,不需要使用返回值。
C. 错误,因为 f(9) 没有返回值,不能作为 f 的参数。
D. 错误,因为 f 需要一个 int 类型的参数,这里没有提供参数;而且 f 没有返回值,不能赋值给 x。
1.5 题目5
**题干:**给定 fun 函数如下,那么 fun(10) 的输出结果是( )
cpp
int fun(int x)
{
return (x==1) ? 1 : (x + fun(x-1));
}
A. 0 B. 10 C. 55 D. 3628800
解析:正确选项是C选项。
这道题很简单,函数 fun(int x) 是一个递归函数,其逻辑如下:
1)如果 x == 1,返回 1;
2)否则,返回 x + fun(x-1)。
因此,fun(10) 的计算过程如下所示:
cpp
fun(10) = 10 + fun(9)
fun(9) = 9 + fun(8)
fun(8) = 8 + fun(7)
fun(7) = 7 + fun(6)
fun(6) = 6 + fun(5)
fun(5) = 5 + fun(4)
fun(4) = 4 + fun(3)
fun(3) = 3 + fun(2)
fun(2) = 2 + fun(1)
fun(1) = 1
我们代入计算之后得到------
cpp
fun(2) = 2 + 1 = 3
fun(3) = 3 + 3 = 6
fun(4) = 4 + 6 = 10
fun(5) = 5 + 10 = 15
fun(6) = 6 + 15 = 21
fun(7) = 7 + 21 = 28
fun(8) = 8 + 28 = 36
fun(9) = 9 + 36 = 45
fun(10) = 10 + 45 = 55
所以,fun(10) 的结果是 55,正确答案选择C选项。
选择题答案如下:
1.1 AB
1.2 A
1.3 A
1.4 B
1.5 C
校对一下,大家都做对了吗?
二、两道算法题
2.1 图片整理
牛客网链接:HJ34 图片整理
题目描述:

2.1.1 题目理解
我们根据题目要求,需要对输入的字符串按照ASCII码值进行排序。
这道题是接口型的,下面是C语言的模版(如果是IO型就可以不用管它们了)------

2.1.2 思路
(1)使用scanf读取输入字符串;
(2)使用qsort函数进行排序,需要提供一个;
(3)比较函数compare比较函数直接通过字符的ASCII码值相减来确定顺序;
(4)最后使用printf输出排序后的字符串。
代码演示:
cpp
//C语言
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int compare(const void* a, const void* b)
{
return (*(char*)a - *(char*)b);
}
int main()
{
char s[1001];
scanf("%s", s);
int len = strlen(s);
qsort(s, len, sizeof(char), compare);
printf("%s", s);
return 0;
}
时间复杂度 :O(1);
空间复杂度 :O(1)。
我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样------

代码演示:
cpp
//C++复盘
#include <iostream>
#include"string"
#include"algorithm"
using namespace std;
int main()
{
string s;
cin >> s;
sort(s.begin(), s.end());
cout << s << endl;
return 0;
}
时间复杂度: O(logn)****,空间复杂度: O(1)****。
我们目前要写出来C++的写法是非常考验前面C++的学习情况好不好的,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待!
2.2 寻找数组的中心下标
力扣题解链接:前缀和优化法解决【寻找数组的中心下标】问题
题目描述:

2.2.1 尝试暴力写代码
我们先尝试用传统暴力方法:(循环)直接解题------
大致思路:
cpp
// 时间复杂度 O(n²)
for (int i = 0; i < n; i++)
{
int leftSum = 0, rightSum = 0;
// 计算左侧和:O(i)
for (int j = 0; j < i; j++)
{
leftSum += nums[j];
}
// 计算右侧和:O(n-i-1)
for (int j = i + 1; j < n; j++)
{
rightSum += nums[j];
}
if (leftSum == rightSum)
return i;
}
有了第一种思路,博主展示一下用【暴力方法】解题的代码:
cpp
//C语言实现------迭代暴力方法
int pivotIndex(int* nums, int numsSize)
{
for (int i = 0; i < numsSize; i++)
{
int leftsum = 0, rightsum = 0;
for (int j = 0; j < i; j++)
{
leftsum += nums[j];
}
for (int j = i + 1; j < numsSize; j++)
{
rightsum += nums[j];
}
if (leftsum == rightsum)
{
return i;
}
}
return -1;
}
时间复杂度 :O(n^2);
空间复杂度 :O(1)。
我们不推荐这种方法,时间复杂度不好,还能再优化一下。
2.2.2 加深题目理解
我们可以用前缀和优化法处理诸如【寻找数组的中心下标】这类"寻找平衡点"问题; 这种方法避免了每次计算左右两侧和的重复计算,通过数学关系直接判断,效率很高。
2.2.3 优化思路
(1)**计算总和:**首先遍历整个数组,计算所有元素的总和;
(2)遍历寻找中心下标: 再次遍历数组,维护一个左侧和的变量;
(3)**检查条件:**对于每个位置i,检查 左侧和 == 总和 - 左侧和 - 当前元素,
等价于检查 2 * 左侧和 == 总和 - 当前元素;
(4)**返回结果:**如果找到满足条件的位置,立即返回;如果遍历完都没找到,返回-1。
这道题是接口型的,下面是C语言的模版(如果是IO型就可以不用管它们了)------

2.2.4 优化方案实现
代码演示:
cpp
int pivotIndex(int* nums, int numsSize) {
int total = 0;
// 计算数组所有元素的总和
for (int i = 0; i < numsSize; i++)
{
total += nums[i];
}
int leftSum = 0;
for (int i = 0; i < numsSize; i++)
{
// 右侧元素之和 = 总和 - 左侧元素之和 - 当前元素
if (leftSum == total - leftSum - nums[i])
{
return i;
}
leftSum += nums[i];
}
return -1;
}
时间复杂度 :O(1);
空间复杂度 :O(1)。
我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样------

代码演示:
cpp
//C++实现
#include<vector>
using namespace std;
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int total = 0;
// 计算数组所有元素的总和
for (int num : nums)
{
total += num;
}
int leftSum = 0;
for (int i = 0; i < nums.size(); i++)
{
// 右侧元素之和 = 总和 - 左侧元素之和 - 当前元素
if (leftSum == total - leftSum - nums[i])
{
return i;
}
leftSum += nums[i];
}
return -1;
}
};
时间复杂度: O(n)****,空间复杂度: O(1)****。
我们目前要写出来C++的写法是非常考验前面C++的学习情况好不好的,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待!
结尾
本文内容到这里就全部结束了,希望大家练习一下这几道题目,这些基础题最好完全掌握!
往期回顾:
结语: 感谢大家的阅读,记得给博主"一键四连",感谢友友们的支持和鼓励!