单项选择题

用户标识符

1、以下不能定义为用户标识符的是( )

scanf 答案错误!

scanf 不属于 C 语言关键字的任意一个。他是 <stdio.h> 头文件中的其中一个函数名。用户可以自定义一个 scanf 函数名去 替换 掉原有的 scanf 函数。

Void 答案错误!

要知道,C 语言是严格区分大小写的,所以 Void 不等于 关键字 中的 Void

_3com 答案错误!

C 语言的变量名可以用 字母或下划线开头

int 正确,int 属于 C 语言中 32 个关键字 中的其中一个,故不能成为用户标识符。

查看解释

本题重点是:关键字,变量命名规则

  • C 标识符是用来标识变量、函数,或任何其他用户自定义项目的名称。一个标识符以字母 A-Za-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。
  • C 标识符内 不允许 出现标点字符,比如 @$%。C 是区分大小写的编程语言。因此,在 C 中,Manpowermanpower 是两个不同的标识符。
  • 下表列出了 C 中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称。
关键字 说明
auto 声明自动变量
break 跳出当前循环
case 开关语句分支
char 声明字符型变量或函数返回值类型
const 定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变
continue 结束当前循环,开始下一轮循环
default 开关语句中的”其它”分支
do 循环语句的循环体
double 声明双精度浮点型变量或函数返回值类型
else 条件语句否定分支(与 if 连用)
enum 声明枚举类型
extern 声明变量或函数是在其它文件或本文件的其他位置定义
float 声明浮点型变量或函数返回值类型
for 一种循环语句
goto 无条件跳转语句
if 条件语句
int 声明整型变量或函数
long 声明长整型变量或函数返回值类型
register 声明寄存器变量
return 子程序返回语句(可以带参数,也可不带参数)
short 声明短整型变量或函数
signed 声明有符号类型变量或函数
sizeof 计算数据类型或变量长度(即所占字节数)
static 声明静态变量
struct 声明结构体类型
switch 用于开关语句
typedef 用以给数据类型取别名
unsigned 声明无符号类型变量或函数
union 声明共用体类型
void 声明函数无返回值或无参数,声明无类型指针
volatile 说明变量在程序执行中可被隐含地改变
while 循环语句的循环条件

表达式与数据类型

设有定义:float a = 2, b = 4, h = 3; 以下 C 语言表达式中与代数式 的计算结果不相符的是( )

查看算出来的值

(a + b) * h / 2

(1 / 2) * (a + b) * h
哎呀!这不是和上面的代数式一模一样嘛?怎么和计算结果不相符呢?别急,看下面解释。

(a + b) * h * 1 / 2

h / 2 * (a + b)

查看解释

本题重点是:数据类型

这要是用数学的思维计算,好家伙,全是 9,那不没答案嘛。你正想要去找老师抬杠呢,信心满满的打开 Visual Studio,往里面一输入:

#include<stdio.h>

int main() {
float a = 2, b = 4, h = 3;
printf("%f\n", (a + b) * h / 2);
printf("%f\n", (1 / 2) * (a + b) * h);
printf("%f\n", (a + b) * h * 1 / 2);
printf("%f\n", h / 2 * (a + b));
return 0;
}

结果一看输出:

9.000000
0.000000
9.000000
9.000000

好家伙,我计算机出问题了?这怎么算出个 0 呢?仔细一看。。。奥!原来问题就出在 (1 / 2) 这个坑里。

在 C 语言中,整数相除,会舍弃掉小数点后面的值,甚至没有四舍五入。所以 (1 / 2) = 0;(1.0 / 2.0) = 0.5

脑子过程序 1

有以下程序:

#include <stdio.h>

void main() {
int i = 1, j = 1, k = 2;
if ((j++ || k++) && i++)
printf("%d, %d, %d\n", i, j, k);
}

执行后的输出结果是( )

1, 1, 2

1, 1, 2 答错了!

2, 2, 1

2, 2, 1 答错了!

2, 2, 2

2, 2, 2 答对了!

2, 2, 3

2, 2, 3 答错了!

查看解释
#include <stdio.h>

void main() {
int i = 1, j = 1, k = 2;
if ((j++ || k++) && i++)
printf("%d, %d, %d\n", i, j, k);
}

这是程序员最烦最烦的题目。但是总喜欢出这种没营养的题目,是为了训练学生的 C 语言基础,所以还是得忍痛学习。这题主要有以下几点:

  1. C 逻辑中,非零为 真 True
  2. if 语句有短路
  3. 自增

非零为真 其实不是本题关键,因为 i j k 都是非 0 值。重点在 自增和短路

  1. 首先,i = 1, j = 1, k = 2;
  2. 到 if 语句时,执行两个判断,首先是 (j++ || k++),才到 ((j++ || k++) && i++)
    1. 当执行 (j++ || k++) 会触发一种机制,短路。因为 j++ 后,j = 2,非 0,所以或语句并没有执行 k++,直接返回 true
    2. 接下来执行 (true && i++),这时候需要执行 i++,所以 i = 2
  3. 所以 i = 2, j = 2, k = 2;

衍生说逻辑判断中的短路:

短路的意思是不会执行逻辑判断中后续的语句

  1. 逻辑与 &&:如果或中间的其中一个值为 0 假 false,则发生短路
  2. 逻辑或 ||:如果与中间的其中一个值为 1 真 true,则发生短路

脑子过程序 2

有以下程序:

#include <stdio.h>

void main() {
int i, s = 0;
for (i = 1; i < 10; i += 2)
s += i + 1;
printf("%d\n", s);
}

执行后的输出结果是( )

自然数 1~9 的累加和 答错了!

自然数 1~10 的累加和 答错了!

自然数 1~9 中奇数之和 答对了!

自然数 1~10 中偶数之和 答错了!

查看解释
  1. 注意看 for 循环变量增值操作(第三个表达式)是 i += 2,所以要么奇数要么偶数。
  2. 可以两种观点:
    1. for 循环的循环变量赋值操作(第一个表达式)是 i = 1,所以只能是 1 3 5 7 9 奇数。
    2. for 循环中的循环条件(第二个表达式)为 i < 10,所以不可能会是 1 - 10

脑子过程序 3

有以下函数定义:

void fun(int n, double x) { ... ... }

若以下选项中的变量都已经正确定义并赋值,则对函数fun的正确调用语句是( )

fun(int y, double m);

fun(int y, double m); 答错了!这不知道是什么奇怪的写法。。。

k = fun(10, 12.5);

k = fun(10, 12.5); 答错了!咋一看很像是对的是吧,可是这个函数的返回值是 void。所以他还是错了。

fun(x, n);

fun(x, n); 答对了!

void fun(n, x);

void fun(n, x); 答错了!这是 声明函数 的语句

阅读程序 填空

有一个 3 * 4 矩阵,求其中的最大元素。

#include<stdio.h>

int max_value(int array[][4] /* 填空 1:定义参数 */) {
int i, j, max;
max = array[0][0];
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++)
if (array[i][j] > max)
max = array[i][j];
return max;
}

int main() {
int a[3][4], i, j;
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++)
a[i][j] = i * j; // 填空 2:赋值
printf("max value is % d\n", max_value(a));
return 0;
}
  1. 第一个空是函数行禅定义,我们通过下边的代码观察到 array 变量未在函数中被定义,所以推断形参就是 array。下方 main 函数中对 max_value(a) 的调用也能验证参数只有一个。且是二维数组。而二维数组的定义,第一个方括号可以留空,但是第二个不行 [][4]。同理,一维数组中的方括号不能留空 []
  2. 第二个空是赋值,说实话,这个空在这里好奇怪,因为我想不到还能填什么值,索性就以 i * j 的结果赋值给矩阵中。访问二维矩阵的方式为 数组名[行下标][列下标]

输入 x、y 两个整数,按先大后小的顺序输出 x、y。

#include<stdio.h>

int main() {
int x, y, * px, * py, * p;
scanf("%d%d", &x, &y);
px = &x; // 填空 3:将 x 的地址赋值给 px 指针
py = &y; // 填空 4:将 y 的地址赋值给 py 指针
if (x < y)
{
p = px; px = py; py = p;
}
printf("x = %d, y = %d\n", x, y);
printf("MAX = %d, MIN = %d\n", *px, *py);
return 0;
}

这题主要看它的交换操作那块,那是对地址进行操作,所以我们需要初始化地址指向 xy 的指针。

注意,我们需要用 & 取地址符,来获取 x 和 y 变量的地址。


用选择排序法对任意10个整数按照由大到小排序。

#include<stdio.h>

int main() {
int a[11], i, j, k, t;
printf("Please input 10 numbers:\n");
for (i = 1; i < 11; i++)
scanf_s("%d", &a[i]);
printf("\n");
for (i = 1; i <= 9; i++)
{
k = i; // 填空 5:初始化 k 为 i
for (j = i + 1; j <= 10/* 填空 6:循环到数组的最后一个元素 */; j++)
if (a[j] > a[k])
k = j;
if (k != i)
{
t = a[k]; a[k] = a[i]; a[i] = t;
}
}
printf("The sorted numbers:\n");
for (i = 1; i < 11; i++) printf("%d ", a[i]);
}
  1. 填空 5: 在选择排序中,变量 k 用于记录当前轮次中最大元素的索引。在每轮开始时,应将 k 初始化为当前轮次的起始位置 i
  2. 填空 6: 修改循环条件,使其循环到数组的最后一个元素。

选择排序的解释

选择排序是一种简单直观的排序算法,其基本思想是每一次从待排序的数据元素中选择最小(或最大)的一个元素,放到序列的起始位置,然后再从剩余未排序的元素中选择最小(或最大)的元素,放到已排序序列的末尾。依此类推,直到所有元素均排序完毕。

具体步骤如下:

  1. 初始状态: 假设待排序的序列有n个元素,将序列分为已排序和未排序两部分。初始时,已排序部分为空,未排序部分包含所有n个元素。
  2. 选择最小元素: 在未排序部分中选择最小的元素,记其下标为k。
  3. 交换位置: 将未排序部分中的第一个元素与第2步中选择的最小元素交换位置。此时,已排序部分增加一个元素,未排序部分减少一个元素。
  4. 重复步骤2-3: 重复执行步骤2和步骤3,直到未排序部分变为空。这样,每一次循环都会找到未排序部分的最小元素,并将其加入已排序部分。
  5. 结束: 当未排序部分为空时,排序完成。

选择排序的时间复杂度为O(n^2),其中n是待排序序列的长度。虽然选择排序相对简单,但对于大型数据集来说,性能较差,因此在实际应用中更常使用其他更高效的排序算法,如快速排序或归并排序。

基础程序设计题

第 9 题

编写一个程序。实现输入一个小写字母,程序输出它对应的大写字母。 (提示:小写字母的ASCII码值比大些字母大32。)

第 9 题思路

  1. 接收用户输入
  2. 将小写转换成大写
  3. 输出结果

第 9 题题解

#include<stdio.h>
int main() {
int ch;
// 1. 接收用户输入
scanf_s("%c", &ch);
// 2. 将小写转换成大写
// 3. 输出结果
printf("%c\n", ch - 32);
}

第 9 题思路(字符串版本)

这是之前将题目会错意了,大家可以当课外看

  1. 接收用户输入
  2. 循环字符串
  3. 判断是否为小写字母
  4. 转换成大写
  5. 输出结果

第 9 题题解(字符串版本)

#include<stdio.h>
#define SIZE 100 // 定义数组长度

int main() {
char str[SIZE];
int i;

// 1. 接收用户输入
gets(str);

// 2. 循环字符串
for (i = 0; i < SIZE; i++) {
// 3. 判断是否为小写字母
if (str[i] >= 'a' && str[i] <= 'z') {
// 4. 转换成大写
str[i] -= 32;
}
}

// 5. 输出结果
puts(str);
return 0;
}

我在原版的基础上,加上了:

  1. 使用 sizeof,动态计算数组长度
  2. 增加用户输入提示
  3. 检测字符串结尾,提前退出循环
#include<stdio.h>

int main() {
char str[100];
int i;
int len; // 黑科技,计算字符串长度

// 1. 接收用户输入
printf("请输入一段字符串:");
gets(str);

// 2. 循环字符串
len = sizeof(str) / sizeof(*str); // 记不住的话,len = 100 (和字符串数组长度一样就行)
for (i = 0; i < len; i++) {
if (str[i] == '\0') break; // 已经到字符串结尾,直接退出循环

// 3. 判断是否为小写字母
if (str[i] >= 'a' && str[i] <= 'z') {
// 4. 转换成大写
str[i] -= 32;
}
}

// 5. 输出结果
puts(str);
return 0;
}

第 10 题

编写一个程序,计算 100 + 101 + 102 + ⋯ + 300 的值。

第 10 题思路

  1. 定义总和变量
  2. 从 100 循环到 300
  3. 加总和
  4. 输出结果

第 10 题题解

#include<stdio.h>

int main() {
// 1. 定义总和变量
int sum = 0, i;

// 2. 从 100 循环到 300
for (i = 100; i <= 300; i++) {
// 3. 加总和
sum += i;
}

// 4. 输出结果
printf("%d", sum);
return 0;
}

注意:定义 sum 变量时,必须赋初值!


第 11 题

已知学生成绩(100 分为满分)与等级的对应关系为:

成绩(grade) 等级
90 <= grade <= 100 A
80 <= grade < 90 B
70 <= grade < 80 C
60 <= grade < 70 D
grade < 60 E

请你编写一个程序,要求:输入学生成绩,输出学生成绩对应的等级。(提示:使用 switch⋯.case语句)

第 11 题思路与题解

switch…case 版本

  1. 接收用户输入
  2. 因为是每个档次以 10 分为分水岭,所以直接将分数除 10,得到它的十进制。
  3. switch...case 判断 10 进制
  4. 输出结果
#include <stdio.h>

int main() {
int grade;

// 1. 接收用户输入
printf("请输入学生成绩(0-100):");
scanf_s("%d", &grade);

// 2. 因为是每个档次以 10 分为分水岭,所以直接将分数除 10,得到它的十进制。
// 3. 用 `switch...case` 判断 10 进制
switch (grade / 10) {
case 10:
case 9:
// 4. 输出结果
printf("等级:A\n");
break;
case 8:
printf("等级:B\n");
break;
case 7:
printf("等级:C\n");
break;
case 6:
printf("等级:D\n");
break;
default:
if (grade >= 0 && grade <= 100) {
printf("等级:E\n");
}
else {
printf("输入无效的成绩\n");
}
}

return 0;
}

if…else 版本

  1. 接收用户输入
  2. 既然它的条件都已经写给你了,何不用 if…else?
  3. 将他的条件拆分成:if (上限 <= grade && grade < 下限) { ... } eise if ( ... ) { ... } else { ... } 完美解决
  4. 输出结果
#include <stdio.h>

int main() {
int grade;

// 1. 接收用户输入
printf("请输入学生成绩(0-100):");
scanf("%d", &grade);

// 2. 既然它的条件都已经写给你了,何不用 if...else?
// 使用 if-else if 语句判断等级
if (grade >= 90 && grade <= 100) {
// 4. 输出结果
printf("等级:A\n");
}
else if (grade >= 80 && grade < 90) {
printf("等级:B\n");
}
else if (grade >= 70 && grade < 80) {
printf("等级:C\n");
}
else if (grade >= 60 && grade < 70) {
printf("等级:D\n");
}
else if (grade >= 0 && grade < 60) {
printf("等级:E\n");
}
else {
printf("输入无效的成绩\n");
}

return 0;
}

综合程序设计题

第 12 题

输入一个圆半径 r,当 r >= 0 时,计算并输出圆的面积和周长,否则,输出提示信息。

第 12 题思路

  1. 获取用户输入
  2. 判断输入是否合法
  3. 计算面积
  4. 输出结果

第 12 题题解

#include <stdio.h> // 包含标准输入输出头文件
#define PI 3.1415926

int main() {
// 1. 获取用户输入
double r; // 定义圆半径变量

printf("请输入圆的半径:");
scanf("%lf", &r); // 通过scanf获取用户输入的半径值

// 2. 判断输入是否合法
if (r >= 0) { // 判断半径是否大于等于0
// 3. 计算面积
double area = PI * r * r; // 使用圆周率π计算圆的面积

// 4. 输出结果
printf("圆的面积为:%lf\n", area); // 输出计算得到的圆的面积
} else {
printf("半径不能为负数\n"); // 输出提示信息,半径不能为负数
}

return 0; // 返回程序结束状态
}


第 13 题

从键盘输入 10 个整数,统计其中正数、负数和零的个数,并在屏幕上输出。

第 13 题思路

  1. 定义一个整型数组
  2. 获取用户输入
  3. 遍历数组
  4. 判断整数类型
  5. 统计
  6. 输出结果

第 13 题题解

#include <stdio.h>

int main() {
// 1. 定义一个整型数组
int numbers[10];
int positiveCount = 0, negativeCount = 0, zeroCount = 0;

// 2. 获取用户输入
printf("请输入10个整数:\n");
for (int i = 0; i < 10; i++) {
scanf_s("%d", &numbers[i]);
}

// 3. 遍历数组
for (int i = 0; i < 10; i++) {
// 4. 判断整数类型
if (numbers[i] > 0) {
positiveCount++; // 5. 统计正数个数
}
else if (numbers[i] < 0) {
negativeCount++; // 5. 统计负数个数
}
else {
zeroCount++; // 5. 统计零的个数
}
}

// 6. 输出结果
printf("正数个数:%d\n", positiveCount);
printf("负数个数:%d\n", negativeCount);
printf("零的个数:%d\n", zeroCount);

return 0;
}

第 14 题

编写程序,其中自定义一函数用来判断数组 a[8] = {1,3,4,7,8,9,10,12} 中的每个数组元素是否为素数,输出是否为素数。子函数声明格式为:int prime(int *p)

第 14 题思路

  1. int prime(int *p) 函数
    1. 如果 prime 等于 1 则返回 0(1 不是素数)
    2. 暴力尝试是否能被自身以外的值整除
  2. 主函数
    1. 定义数组
    2. 遍历数组中的值
    3. 调用 prime 函数,判断是否为素数
    4. 输出结果

第 14 题题解

#include <stdio.h>

// 自定义函数判断素数
int prime(int* p) {
if (*p <= 1) {
return 0; // 1不是素数
}

// 2. 尝试是否能被自身以外的值整除
for (int i = 2; i * i <= *p; i++) {
if (*p % i == 0) {
return 0; // 能被整除,不是素数
}
}

return 1; // 是素数
}

int main() {
// 1. 定义数组
int a[8] = { 1, 3, 4, 7, 8, 9, 10, 12 };

printf("判断数组中的素数:\n");

// 2. 遍历数组中的值
for (int i = 0; i < 8; i++) {
// 3. 调用 prime 函数,判断是否为素数
if (prime(&a[i])) {
// 4. 输出结果
printf("%d 是素数\n", a[i]);
}
else {
printf("%d 不是素数\n", a[i]);
}
}

return 0;
}

在这个程序中,prime 函数接受一个整数指针作为参数,判断该整数是否为素数。然后,在 main 函数中,对数组中的每个元素调用 prime 函数,并输出结果。

在这个程序中,指针起到的作用是允许函数prime直接修改传递给它的数组元素的值。以下是对指针的作用的解释:

  1. 传递数组元素的地址: 函数 prime 的参数是一个整数指针 int *p,它接受一个整数的地址作为输入。这样,当在 main 函数中调用 prime(&a[i]) 时,实际上是将数组元素 a[i] 的地址传递给了 prime 函数。
  2. 通过指针修改数组元素:prime 函数中,使用解引用操作符 *p 来访问传递进来的整数的值。这允许 prime 函数直接修改 main 函数中数组元素的值。在这个程序中,虽然 prime 函数没有对数组元素的值进行修改,但是通过指针,它具有修改的能力。
  3. 避免数组元素的复制: 通过传递指针,而不是整数的副本,可以避免在函数调用中进行数组元素的复制。这在处理大型数组时可以提高程序的效率。

第 15 题

一个学生具有学号、姓名、3 门课程成绩共 5 个信息。定义结构体变量,调用函数求 3 门课程的平均分。

第 15 题思路

  1. 定义学生结构体,包含学号、姓名和3门课程成绩
  2. 声明一个函数用于计算三门课程的平均分
  3. 在主函数中定义学生结构体变量,接收用户输入的学生信息和三门课程成绩
  4. 调用函数计算平均分
  5. 输出计算结果

第 15 题题解

#include <stdio.h> // 包含标准输入输出头文件

// 定义学生结构体
struct Student {
int studentId; // 学号
char name[50]; // 姓名
double grades[3]; // 3门课程成绩
};

// 声明函数用于计算课程平均分
double calculateAverage(struct Student student);

int main() {
// 定义学生结构体变量
struct Student student1;

// 输入学生信息
printf("请输入学生信息:\n");
printf("学号:");
scanf("%d", &student1.studentId);

printf("姓名:");
scanf("%s", student1.name);

printf("输入三门课程的成绩:\n");
for (int i = 0; i < 3; i++) {
printf("课程%d成绩:", i + 1);
scanf("%lf", &student1.grades[i]);
}

// 调用函数计算平均分
double average = calculateAverage(student1);

// 输出结果
printf("%s的三门课程平均分为:%lf\n", student1.name, average);

return 0; // 返回程序结束状态
}

// 函数定义:计算课程平均分
double calculateAverage(struct Student student) {
double sum = 0;

// 计算成绩总和
for (int i = 0; i < 3; i++) {
sum += student.grades[i];
}

// 计算平均分
double average = sum / 3;

return average;
}

观众姥爷赏口饭吃

教材准备不易,可否打个赏?

要个饭