实验五
task_1_1
点击查看代码
#include <stdio.h>
#define N 5
void input(int x[], int n);
void output(int x[], int n);
void find_min_max(int x[], int n, int *pmin, int *pmax);
int main() {
int a[N];
int min, max;
printf("录入%d个数据:\n", N);
input(a, N);
printf("数据是: \n");
output(a, N);
printf("数据处理...\n");
find_min_max(a, N, &min, &max);
printf("输出结果:\n");
printf("min = %d, max = %d\n", min, max);
return 0;
}
void input(int x[], int n) {
int i;
for(i = 0; i < n; ++i)
scanf("%d", &x[i]);
}
void output(int x[], int n) {
int i;
for(i = 0; i < n; ++i)
printf("%d ", x[i]);
printf("\n");
}
void find_min_max(int x[], int n, int *pmin, int *pmax) {
int i;
*pmin = *pmax = x[0];
for(i = 0; i < n; ++i)
if(x[i] < *pmin)
*pmin = x[i];
else if(x[i] > *pmax)
*pmax = x[i];
}

- 函数 find_min_max 的功能
该函数用于找出整型数组 x 前 n 个元素中的最小值和最大值,并通过指针参数 pmin 和 pmax 将结果返回给调用者。
函数内部先将 *pmin 和 *pmax 初始化为数组的第一个元素 x[0]。
然后遍历数组(索引 i 从 0 到 n-1):
若当前元素 x[i] 小于 *pmin,则更新 *pmin 为 x[i];
否则若 x[i] 大于 *pmax,则更新 *pmax 为 x[i]。
最终调用者通过传入的指针变量获得最小值和最大值。
2.line 45 为*pmin = *pmax = x[0];
pmin 指向 min
pmax指向 max
task_1_2
点击查看代码
#include <stdio.h>
#define N 5
void input(int x[], int n);
void output(int x[], int n);
int *find_max(int x[], int n);
int main(){
int a[N];
int *pmax;
printf("录入%d个数据:\n",N);
input(a,N);
printf("数据是: \n");
output(a, N);
printf("数据处理...\n");
pmax = find_max(a, N);
printf("输出结果:\n");
printf("max = %d\n", *pmax);
return 0;
}
void input(int x[], int n) {
int i;
for(i=0; i<n; ++i)
scanf("%d", &x[i]);
}
void output(int x[], int n){
int i;
for(i = 0; i < n; ++i)
printf("%d", x[i]);
printf("\n");
}
int *find_max(int x[], int n){
int max_index = 0;
int i;
for(i=0; i < n; ++i)
if(x[i] > x[max_index])
max_index = i;
return &x[max_index];
}

- 函数 find_max 功能是?返回的是什么?
功能:找出整型数组 x 的前 n 个元素中的最大值(第一个出现的最大值)。
返回值:返回指向该最大元素的指针(即最大元素在数组中的地址)
- 改写后的代码可以实现同样功能吗?
可以,改写后的代码与原函数功能完全相同。
原函数:通过下标 max_index 记录最大值的下标,最后返回 &x[max_index]。
改写函数:直接用指针 ptr 指向当前找到的最大值元素,初始指向 &x[0],遍历时若遇到更大的值就更新 ptr 指向该元素,最后返回 ptr。
改写的代码
点击查看代码
int *find_max(int x[], int n) {
int *ptr = &x[0];
int i;
for(i = 0; i < n; ++i)
if(x[i] > *ptr)
ptr = &x[i];
return ptr;
task_2_1
点击查看代码
#include <stdio.h>
#include <string.h>
#define N 80
int main() {
char s1[N] = "Learning makes me happy";
char s2[N] = "Learning makes me sleepy";
char tmp[N];
printf("sizeof(s1) vs. strlen(s1): \n");
printf("sizeof(s1) = %d\n", sizeof(s1));
printf("strlen(s1) = %d\n", strlen(s1));
printf("\nbefore swap: \n");
printf("s1: %s\n", s1);
printf("s2: %s\n", s2);
printf("\nswapping...\n");
strcpy(tmp, s1);
strcpy(s1, s2);
strcpy(s2, tmp);
printf("\nafter swap: \n");
printf("s1: %s\n", s1);
printf("s2: %s\n", s2);
return 0;
}

问题1:数组 s1 的大小是多少?sizeof(s1) 计算的是什么?strlen(s1) 统计的是什么?
数组 s1 的大小:由宏 #define N 80 定义,因此 s1 是一个长度为 80 的字符数组。
sizeof(s1):计算整个数组 s1 占用的内存字节数。由于 char 类型占 1 字节,结果为 80。
strlen(s1):统计字符串的实际长度(从起始位置到第一个空字符 '\0' 之前的字符个数),不包括结尾的 '\0'。
字符串 "Learning makes me happy" 共有 23 个字符,因此结果为 23。
问题2:line 7不能替换为
点击查看代码
char s1[N];
s1 = "Learning makes me happy";
原因:
char s1[N]; 定义了一个字符数组,数组名 s1 是地址常量(即数组首元素的地址),不能作为左值被赋值。
而 s1 = "..." 试图将字符串字面量的首地址赋给数组名,这在 C 语言中是非法操作,编译时会报错:
正确做法:
如果已经定义了数组,可以使用 strcpy(s1, "Learning makes me happy"); 进行字符串复制,但这不是初始化。
问题3:
交换了
s1、s2 和 tmp 都是长度为 N(80)的字符数组。
strcpy 函数逐字符复制字符串(包括结尾的 '\0')。
借助临时数组 tmp 保存 s1 的原始内容,然后将 s2 复制给 s1,最后将 tmp 中保存的原 s1 内容复制给 s2。
交换完成后,s1 中存储的是原 s2 的字符串 "Learning makes me sleepy",s2 中存储的是原 s1 的字符串 "Learning makes me happy"。
task_2_2
点击查看代码
#include <stdio.h>
#include <string.h>
#define N 80
int main() {
char *s1 = "Learning makes me happy";
char *s2 = "Learning makes me sleepy";
char *tmp;
printf("sizeof(s1) vs. strlen(s1): \n");
printf("sizeof(s1) = %d\n", sizeof(s1));
printf("strlen(s1) = %d\n", strlen(s1));
printf("\nbefore swap: \n");
printf("s1: %s\n", s1);
printf("s2: %s\n", s2);
printf("\nswapping...\n");
tmp = s1;
s1 = s2;
s2 = tmp;
printf("\nafter swap: \n");
printf("s1: %s\n", s1);
printf("s2: %s\n", s2);
return 0;
}

问题1:指针变量 s1 中存放的是什么?sizeof(s1) 计算的是什么?strlen(s1) 统计的是什么?
s1 中存放的是:字符串常量 "Learning makes me happy" 在内存中的首地址(即指向该字符串的指针)。
sizeof(s1):计算指针变量 s1 本身所占的字节数。在 64 位系统中通常为 8,32 位系统为 4。它不是字符串长度,也不是数组大小。
strlen(s1):统计从 s1 指向的地址开始,到第一个空字符 '\0' 之前的字符个数,即字符串的实际长度 23("Learning makes me happy" 共 23 个字符)
问题2:line6 代码能否替换成下面的写法?对比 task2_1.c 中的 line6,说明二者的语义区别。
点击查看代码
char *s1;
s1 = "Learning makes me happy";
问题3:line19-21 交换的是什么?字符串常量在内存中是否交换?
交换的是:指针变量 s1 和 s2 的值,即它们所指向的地址。
字符串常量在内存中是否交换? → 不交换。两个字符串常量 "Learning makes me happy" 和 "Learning makes me sleepy" 在内存中的存储位置是固定的(只读数据段),不会被移动或修改。交换后,s1 原本指向第一个字符串,现在指向第二个字符串;s2 则相反。字符串本身的位置没有变化,只是指针的指向发生了变化。
task_3
点击查看代码
#include <stdio.h>
int main() {
int x[2][4] = {{1, 9, 8, 4}, {2, 0, 4, 9}};
int i, j;
int *ptr1; // 指针变量,存放int类型数据的地址
int(*ptr2)[4]; // 指针变量,指向包含4个int元素的一维数组
printf("输出1: 使用数组名、下标直接访问二维数组元素\n");
for (i = 0; i < 2; ++i) {
for (j = 0; j < 4; ++j)
printf("%d ", x[i][j]);
printf("\n");
}
printf("\n输出2: 使用指针变量ptr1(指向元素)访问\n");
for (ptr1 = &x[0][0], i = 0; ptr1 < &x[0][0] + 8; ++ptr1, ++i) {
printf("%d ", *ptr1);
if ((i + 1) % 4 == 0)
printf("\n");
}
printf("\n输出3: 使用指针变量ptr2(指向一维数组)访问\n");
for (ptr2 = x; ptr2 < x + 2; ++ptr2) {
for (j = 0; j < 4; ++j)
printf("%d ", *(*ptr2 + j));
printf("\n");
}
return 0;
}

line16——line19
ptr1 初始指向第一个元素 &x[0][0]。
循环条件 ptr1 < &x[0][0] + 8:总共有 8 个元素,所以移动 8 次后停止。
ptr1++ 移动一个 int,*ptr1 取出元素值。
i 用于控制每输出 4 个元素换一行(即每行对应原二维数组的一行)。
本质:把二维数组当成一维数组(元素线性排列)顺序访问,通过指针算术逐个遍历
line24——29
ptr2 = x:数组名 x 在表达式中退化指向首行(类型为 int(*)[4]),与 ptr2 匹配。
循环条件 ptr2 < x + 2:x+2 指向第 2 行之后(即越界地址),循环执行 2 次(行数)。
++ptr2:指针移动一行(sizeof(int[4]) 字节),指向下一行。
内层循环 for (j=0; j<4; ++j) 遍历当前行的每个元素。
ptr2:取当前行(一维数组)的首地址,类型为 int。
(ptr2 + j):等价于 (*ptr2)[j],即当前行的第 j 个元素。
*ptr2 + j 是第 j 个元素的地址。
再加一层 * 取出值。
本质:ptr2 保持“行”的概念,每轮外循环处理一行,内循环处理该行各列。
ptr1 把二维数组看作连续的元素序列,适合元素级遍历。
ptr2 保持二维结构,适合按行处理。

task_4
点击查看代码
#include <stdio.h>
#define N 80
void replace(char *str, char old_char, char new_char); // 函数声明
int main() {
char text[N] = "Programming is difficult or not, it is a question.";
printf("原始文本: \n");
printf("%s\n", text);
replace(text, 'i', '*'); // 函数调用 注意字符形参写法,单引号不能少
printf("处理后文本: \n");
printf("%s\n", text);
return 0;
}
// 函数定义
void replace(char *str, char old_char, char new_char) {
int i;
while(*str) {
if(*str == old_char)
*str = new_char;
str++;
}
}

-
函数 replace 的功能是?
遍历指针 str 指向的字符串(以空字符 '\0' 结尾),将所有与 old_char 相等的字符替换为 new_char。该函数直接修改原字符串,不返回值。 -
line24 圆括号里循环条件可以写成 str != '\0' 吗?
可以。
原代码中的 while(str) 等价于 while(*str != '\0'),因为当 *str 为 '\0' 时其值为 0(假),循环结束。两种写法在语义上完全等价,都表示“当未遇到字符串结束符时继续循环”。
task_5
点击查看代码
#include <stdio.h>
#define N 80
char *str_trunc(char *str, char x);
int main() {
char str[N];
char ch;
while(printf("输入字符串: "), gets(str) != NULL) {
printf("输入一个字符: ");
ch = getchar();
printf("截断处理...\n");
str_trunc(str, ch);
printf("截断处理后的字符串: %s\n\n", str);
getchar(); // line18
}
return 0;
}
// 函数str_trunc定义
char *str_trunc(char *str, char x) {
char *p = str;
while (*p != '\0') {
if (*p == x) {
*p = '\0'; // 截断:在此位置放字符串结束符
break;
}
p++;
}
return str; // 返回原字符串地址
}

去掉 main 函数 line18 的 getchar(); 后,多组输入测试结果有何不同?
正常情况(有 getchar()):
每次输入完字符并回车后,getchar() 读取字符 ch,缓冲区中残留的换行符 '\n' 被 line18 的 getchar() 消耗掉。下一次循环执行 gets(str) 时,缓冲区为空,程序会正常等待用户输入新字符串。
去掉 line18 后:
第一次循环结束时,缓冲区中仍留有换行符(来自输入字符后的回车)。第二次循环开始,gets(str) 会立即读取这个换行符,将其解释为一个空字符串(即 str 中只包含 '\0'),并继续执行后续代码。这导致:
程序不会等待用户输入新字符串,直接提示“输入一个字符: ”。
用户输入的字符会被读取,但字符串 str 为空,截断处理后输出空串。
后续循环会重复同样的问题,造成程序执行混乱,无法按预期进行多组输入。
line18 的作用:
消耗输入缓冲区中残留的换行符,确保下一次 gets() 能够正常等待用户输入新的字符串,避免因缓冲区遗留的换行符导致 gets() 立即返回空字符串。这是处理混合使用 gets() 和 getchar() 时的常见缓冲区清理技巧。
task_6
点击查看代码
#include <stdio.h>
#include <string.h>
#define N 5
int check_id(char *str); // 函数声明
int main()
{
char *pid[N] = {"31010120000721656X",
"3301061996X0203301",
"53010220051126571",
"510104199211197977",
"53010220051126133Y"};
int i;
for (i = 0; i < N; ++i)
if (check_id(pid[i])) // 函数调用
printf("%s\tTrue\n", pid[i]);
else
printf("%s\tFalse\n", pid[i]);
return 0;
}
// 函数定义
// 功能: 检查指针str指向的身份证号码串形式上是否合法
// 形式合法,返回1,否则,返回0
int check_id(char *str) {
int len = strlen(str);
// 长度必须为18
if (len != 18) return 0;
// 前17位只能是数字
for (int i = 0; i < 17; i++) {
if (str[i] < '0' || str[i] > '9') return 0;
}
// 第18位可以是数字或大写X
if ((str[17] >= '0' && str[17] <= '9') || str[17] == 'X')
return 1;
else
return 0;
}

task_7
点击查看代码
#include <stdio.h>
#define N 80
void encoder(char *str, int n);
void decoder(char *str, int n);
int main() {
char words[N];
int n;
while (1) {
printf("\n输入英文文本: ");
if (gets(words) == NULL) break;
printf("输入n: ");
scanf("%d", &n);
while (getchar() != '\n');
printf("编码后的英文文本: ");
encoder(words, n);
printf("%s\n", words);
printf("对编码后的英文文本解码: ");
decoder(words, n);
printf("%s\n", words);
}
return 0;
}
void encoder(char *str, int n) {
while (*str) {
if (*str >= 'a' && *str <= 'z')
*str = (*str - 'a' + n) % 26 + 'a';
else if (*str >= 'A' && *str <= 'Z')
*str = (*str - 'A' + n) % 26 + 'A';
str++;
}
}
void decoder(char *str, int n) {
while (*str) {
if (*str >= 'a' && *str <= 'z')
*str = (*str - 'a' - n + 26) % 26 + 'a';
else if (*str >= 'A' && *str <= 'Z')
*str = (*str - 'A' - n + 26) % 26 + 'A';
str++;
}
}

task_8
点击查看代码
#include <stdio.h>
int main(int argc, char *argv[]) {
int i;
for(i = 1; i < argc; ++i)
printf("hello, %s\n", argv[i]);
return 0;
}
改进后
点击查看代码
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
// 对命令行参数(除程序名外)按字典序升序排序
for (int i = 1; i < argc - 1; ++i) {
for (int j = i + 1; j < argc; ++j) {
if (strcmp(argv[i], argv[j]) > 0) {
char *temp = argv[i];
argv[i] = argv[j];
argv[j] = temp;
}
}
}
// 按排序后的顺序打印问候
for (int i = 1; i < argc; ++i) {
printf("hello, %s\n", argv[i]);
}
return 0;
}

核心收获
通过 ptr1(元素指针)和 ptr2(行指针)访问二维数组,理解了指针类型决定步长和解引用层级。数组名在表达式中退化为首元素(或首行)指针,但不等同于指针变量。
用指针遍历字符串(while(*str))比下标更灵活。
字符串截断的本质:在指定位置插入 '\0'。
命令行参数排序
修改 argv 中的指针指向(交换指针值)即可实现参数字典序排序,无需复制字符串内容,效率高。
缓冲区清理的重要性
混合使用 gets(或 scanf)与 getchar 时,残留的换行符会导致后续输入异常。通过 while(getchar()!='\n'); 清理缓冲区是必要的。
浙公网安备 33010602011771号