一、复习题
1. 下面的程序将打印什么内容?
#include <stdio.h>
int main()
{
int ref[] = {8, 4, 0 ,2};
int *ptr;
int index;
for (index = 0, ptr = ref; index < 4; index++, ptr++)
{
printf("%d %d\n", ref[index], *ptr);
}
return 0;
}
答:该循环使用了数组表示法和指针表示法,打印结果如下:
8 8
4 4
0 0
2 2
2. 在复习题 1 中,ref 有多少个元素?
答:可以使用 sizeof(ref) / sizeof(ref[0]) 进行计算,4 个元素。
3. 在复习题 1 中,ref 的地址是什么?ref + 1 是什么意思?++ref 指向什么?
答:ref 的地址是 &ref,整个数组的地址。ref + 1 是数组第 2 个元素的地址。++ref 是一个错误的表达式,ref 作为数组名是常量。(注意,ref + 1 并不会改变 ref 的值,而 ++ref 会改变 ref 的值)。
4. 在下面的代码中,*ptr 和 *(ptr + 2) 的值分别是多少?
// a.
int *ptr;
int torf[2][2] = {12, 14, 16];
ptr = torf[0];
// b.
int *ptr;
int fort[2][2] = { {12}, {14, 16} };
ptr = fort[0];
答:
a. 二维数组 torf 初始化前 3 个元素,最后一个元素为 0 。ptr 指向 torf 第 1 行第 1 个元素,所以 *ptr = 12,*(ptr + 2) = 16 。
b. 二维数组 fort 第 1 行第 2 个元素未初始化,被设置为 0。ptr 指向 fort 第 1 行第 1 个元素,所以 *ptr = 12, *(ptr + 2) = 14;
5. 在下面的代码中,**ptr 和 **(ptr + 1) 的值分别是多少?
// a.
int (*ptr)[2];
int torf[2][2] = {12, 14, 16];
ptr = torf;
// b.
int (*ptr)[2];
int fort[2][2] = { {12}, {14, 16} };
ptr = fort;
答:
a. ptr 是一个指向包含 2 个 int 类型的数组的指针,指向二维数组 torf 的第 1 个元素,所以 **ptr = 12, **(ptr + 1) = 16 。
b. ptr 是一个指向包含 2 个 int 类型的数组的指针,指向二维数组 fort 的第 1 个元素,所以 **ptr = 12, **(ptr + 1) = 14 。
6. 假设有下面的声明:
int grid[30][100];
a. 用 1 种写法表示 grid[22][56] 的地址;
b. 用 2 种写法表示 grid[22][0] 的地址;
c. 用 3 种写法表示 grid[0][0] 的地址;
答:
a. &grid[22][56]
b. grid[22] 和 &grid[22][0]
c. *grid 和 grid[0] 和 &grid[0][0]
7. 正确声明以下各变量:
a. digits 是一个内含 10 个 int 类型值的数组
b. rates 是一个内含 6 个float 类型值的数组
c. mat 是一个内含 3 个元素的数组,每个元素都是内含 5 个整数的数组
d. psa 是一个内涵 20 个元素的数组,每个元素都是指向 char 的指针
e. pstr 是一个指向数组的指针,该数组内含 20 个char 类型的值
答:
a. int digits[10];
b. float rates[6];
c. int mat[3][5];
d. char* psa[20];
e. char (*pstr)[20];
8.
a. 声明一个内含 6 个 int 类型值的数组,并初始化各元素为 1、2、4、6、8、16、32
b. 用数组表示法表示 a 声明的数组的第 3 个元素(其值为 4)
c. 假设编译器支持 C99/C11 标准,声明一个内含 100 int 类型值的数组,并初始化最后一个元素为 -1,其他元素不考虑
d. 假设编译器支持 C99/C11 标准,声明一个内含 100 int 类型值的数组,并初始化下标为 5、10、11、12、13 的元素为 101,其他元素不考虑
答:
a. int arr[6] = {1, 2, 4, 8, 16, 32};
b. arr[2]
c. int arr[100] = { [99] = -1 };
d. int arr[100] = { [5] = 101, [10] = 101, 101, 101, [13] = 101 };
9. 内含 10 个元素的数组下标范围是什么?
答:数组下标从 0 开始,所以 0 - 9 。
10. 假设有下面的声明:
float rootbeer[10], thing[10][5], *pf, value = 2.2;
int i = 3;
判断以下各项是否有效:
a. rootbeer[2] = value;
b. scanf(“%f”, &rootbeer);
c. rootbeer = value;
d. printf(“%f”, rootbeer);
e. things[4][4] = rootbeer[3];
f. things[5] = rootbeer;
g. pf = value;
h. pf = rootbeer;
答:
a. 有效
b. 无效,&rootbeer 代表整个数组的地址,是常量
c. 无效,rootbeer 是数组首元素的地址,常量
d. 无效,rootbeer 是数组首元素的地址,打印使用 %p 格式
e. 有效
f. 无效,things[5] 是数组名,常量
g. 无效,pf 是指针,存储地址,而 value 是 float 值
h. 有效
11. 声明一个 800 * 600 的 int 类型数组。
答:int arr[800][600];
12. 下面声明了 3 个数组:
double trots[20];
short clops[10][30];
long shots[5][10][15];
a. 分别以传统方式和以变长数组为参数编写处理 trots 数组的 void 函数原型的函数调用
b. 分别以传统方式和以变长数组为参数编写处理 clops 数组的 void 函数原型的函数调用
c. 分别以传统方式和以变长数组为参数编写处理 shots 数组的 void 函数原型的函数调用
答:
a.
传统: 原型:void process_arr(double arr[], int n); 调用:process_arr(trots, 20);
变长: 原型:void process_arr(int n, double arr[n]); 调用:process_arr(20, trots);
a.
传统: 原型:void process_arr(double arr[][30], int n); 调用:process_arr(clops, 10);
变长: 原型:void process_arr(int n, int m, double arr[n][m]); 调用:process_arr(10, 30, clops);
a.
传统: 原型:void process_arr(double arr[][10][15], int n); 调用:process_arr(shots, 5);
变长: 原型:void process_arr(int n, int m, int z, double arr[n][m][z]); 调用:process_arr(5, 10, 15, shots);
13. 下面有两个函数原型。
void show(const double ar[], int n); // n 是数组元素的个数
void show2(const double ar[][3], int n); // n 是二维数组的行数
a. 编写一个函数调用,把一个内含 8、3、9 和 2 的复合字面量传递给 show() 函数。
b. 编写一个函数调用,把一个 2 行 3 列 的复合字面量(以 8、3、9 作为第一行,以 5、4、1 作为第 2 行)传递给 show2() 函数。
答:
a. show((double [4]{8, 3, 9, 2), 4);
b. show2(double [][3]{ {8, 3, 9}, {5, 4, 1} }, 2};
二、编程练习
1. 修改程序清单 10.7 的程序,用指针进行计算(仍要声明并初始化数组)。
答:
程序设计分析: 使用指针进行计算实际上就是把它拆成了\ *(a + b)的模式。(代码基本和书本上是一样的,这里就把修改的部分写出,实际上也就是两个 for 循环的计算部分)
代码如下:
for (month = 0, subtot = 0; month < MONTHS; month++)
subtot += *(*(rain + year) + month);
// ...
for (year = 0, subtot = 0; year < YEARS; year++)
subtot += *(*(rain + year) + month);
2. 编写一个程序,初始化一个 double 类型的数组,然后把该数组的内容拷贝至 3 个其他数组中(在 main() 中声明这 4 个数组)。使用带数组表示法的函数进行第 1 份拷贝。使用带指针表示法和指针递增的函数进行第 2 份拷贝。把目标数组名、源数组名和待拷贝的元素个数作为前两个函数的参数。第 3 个函数以目标数组名、源数组名和指向源数组最后一个元素后面的元素的指针。也就是说,给定一下声明,则函数调用如下所示:
double source[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
double target1[5];
double target2[5];
double target3[5];
copy_arr(target1, source, 5);
copy_ptr(target2, source, 5);
copy_ptrs(target3, source, source + 5);
答:
程序设计分析: 这里使用了数组传参的两种形式,指针形式和数组形式,实际上传的都是指针。
代码如下:
#include <stdio.h>
// 常量声明
#define SIZE 5
// 函数声明
void copy_arr1(double t[], double s[], int n);
void copy_arr2(double* t, double* s, int n);
void copy_arr3(double* t, double* b, double* e);
int main()
{
// 所需变量
double source[SIZE] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
double target1[SIZE];
double target2[SIZE];
double target3[SIZE];
// 拷贝
copy_arr1(target1, source, SIZE);
copy_arr2(target2, source, SIZE);
copy_arr3(target3, source, source + SIZE);
// 验证
int i;
for (i = 0; i < SIZE; ++i)
{
printf("%.1lf ", target1[i]);
}
printf("\n");
for (i = 0; i < SIZE; ++i)
{
printf("%.1lf ", target2[i]);
}
printf("\n");
for (i = 0; i < SIZE; ++i)
{
printf("%.1lf ", target3[i]);
}
printf("\n");
return 0;
}
// 函数定义
void copy_arr1(double t[], double s[], int n)
{
int i;
for (i = 0; i < n; ++i)
{
t[i] = s[i];
}
}
void copy_arr2(double* t, double* s, int n)
{
int i;
for (i = 0; i < n; ++i)
{
*(t + i) = *(s + i);
}
}
void copy_arr3(double* t, double* b, double* e)
{
while (b < e)
{
*(t++) = *(b++);
}
}
3. 编写一个函数,返回存储在 int 类型数组中的最大值,并在一个简单的程序中测试该函数。
答:
程序设计分析: 数组传参两种形式任选(最好数组形式,清晰明了)。假设第一个数为最大值,然后剩余数进行比较。
代码如下:
#include <stdio.h>
// 常量声明
#define SIZE 5
// 函数声明
int get_arr_max(int arr[], int n);
int main()
{
// 所需变量
int arr[SIZE] = { 1, 2, 101, 102, 100 };
// 结果
printf("Max: %d\n", get_arr_max(arr, SIZE));
return 0;
}
// 函数定义
int get_arr_max(int arr[], int n)
{
int i, max = arr[0];
for (i = 1; i < n; ++i)
{
if (arr[i] > max)
max = arr[i];
}
return max;
}
4. 编写一个程序,返回存储在 double 类型数组中的最大值的下标,并在一个简单的程序中测试该函数。
答:
程序设计分析: 这道题和上一道题类似,只不过返回的是最大值的下标。
代码如下:
#include <stdio.h>
// 常量声明
#define SIZE 5
// 函数声明
int find_max_index(double arr[], int n);
int main()
{
double arr[SIZE] = { 1.1, 1.2, 1.3, 1.31, 1.29 };
printf("最大值的下标为: %d\n", find_max_index(arr, SIZE));
return 0;
}
// 函数定义
int find_max_index(double arr[], int n)
{
int max = 0;
int i;
for (i = 1; i < n; ++i)
{
if (arr[i] > arr[max])
max = i;
}
return max;
}
5. 编写一个程序,返回存储在 double 类型数组中的最大值和最小值的差值,并在一个简单的程序中测试该函数。
答:
程序设计分析: 在函数中利用两个变量在一次循环中找到最大值和最小值。
代码如下:
#include <stdio.h>
// 常量声明
#define SIZE 5
// 函数声明
double diff_max_min(double arr[], int n);
int main()
{
double arr[SIZE] = { 1.1, 10.2, 11.3, 14.5, 0.9 };
printf("数组中最大值和最小值的差值为:%.1lf\n", diff_max_min(arr, SIZE));
return 0;
}
// 函数定义
double diff_max_min(double arr[], int n)
{
double max = arr[0], min = arr[0];
int i;
for (i = 1; i < n; ++i)
{
// 寻找最大值
if (arr[i] > max)
max = arr[i];
// 寻找最小值
if (arr[i] < min)
min = arr[i];
}
return max - min;
}
6. 编写一个函数,把 double 类型数组中的数据倒序排序,并在一个简单的程序中测试该函数。
答:
程序设计分析: 可以使用冒泡排序法,把大的数往前面放
代码如下:
#include <stdio.h>
// 常量声明
#define SIZE 5
// 函数声明
void reverse_arr(double arr[], int n);
void swap(double* a, double* b);
int main()
{
double arr[SIZE] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
// 逆序排序
reverse_arr(arr, SIZE);
// 输出验证
int i;
for (i = 0; i < SIZE; ++i)
printf("%.1lf ", arr[i]);
printf("\n");
return 0;
}
// 函数定义
void reverse_arr(double arr[], int n)
{
int i;
for (i = 0; i < n - 1; ++i)
{
int j;
for (j = 0; j < n - i - 1; ++j)
{
if (arr[j] < arr[j + 1])
swap(&arr[j], &arr[j + 1]);
}
}
}
void swap(double* a, double* b)
{
double temp = *a;
*a = *b;
*b = temp;
}
7. 编写一个程序,初始化一个 double 类型数组中的数据倒序排列,使用编程练习 2 中的一个拷贝函数把该数组中的数据拷贝至另一个二维数组中(因为二维数组都是数组的数组,所以可以使用处理一维数组的拷贝函数来处理数组中的每个子数组)。
答:
程序设计分析: 随便选取编程练习 2 中的一个函数,然后使用循环对二维数组中的每行数据进行拷贝
代码如下:
#include <stdio.h>
// 常量声明
#define SIZE 5
// 函数声明
void copy_arr(double* t, double* s, int n);
int main()
{
// 所需变量
double arr[SIZE] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
double arr_2[SIZE][SIZE];
// 拷贝
int i;
for (i = 0; i < SIZE; ++i)
copy_arr(arr_2[i], arr, SIZE);
// 打印验证
for (i = 0; i < SIZE; ++i)
{
int j;
for (j = 0; j < SIZE; ++j)
printf("%.1lf ", arr_2[i][j]);
// 下一行
printf("\n");
}
return 0;
}
// 函数定义
void copy_arr(double* t, double* s, int n)
{
int i;
for (i = 0; i < n; ++i)
t[i] = s[i];
}
8. 是用编程练习 2 中的拷贝函数,把一个内含 7 个元素的数组中的第 3 ~ 第 5 个元素拷贝至内含 3 个元素的数组中。该函数本身不需要修改,只需要选择合适的实际参数(实际参数不需要是数组名和数组大小,只需要是数组元素的地址和待处理元素的个数)。
答:
程序设计分析: 使用编程练习 2 中的第 3 个函数,传递要拷贝的头和尾即可
代码如下:
#include <stdio.h>
// 函数声明
void copy_arr(double* target, double* begin, double* end);
int main()
{
double source[5] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
double target[3];
// 拷贝
copy_arr(target, source + 2, source + 5);
// 验证
int i;
for (i = 0; i < 3; ++i)
printf("%.1lf ", target[i]);
printf("\n");
return 0;
}
// 函数定义
void copy_arr(double* target, double* begin, double* end)
{
while (begin < end)
*target++ = *begin++;
}
9. 编写一个程序,初始化一个 double 类型的 35 二维数组,使用一个处理变长数组的函数将其拷贝至另一个二维数组中。还要编写一个以变长数组为形参的函数以显示两个数组的内容。这两个函数能任意处理 NM 数组(如果编译器不支持变长数组,就使用传统 C 函数处理 N * 5 的数组)。
答:
程序设计分析: 作者的编译器不支持变长数组(visual studio 2022),使用传统 C 函数处理 N*5 数组。本题两个函数实际上就是使用嵌套循环来分别进行拷贝和显示数组内容。
代码如下:
#include <stdio.h>
// 常量声明
#define ROW 3
#define COL 5
// 函数声明
void copy_arr(double t[][COL], double s[][COL], int n);
void print_arr(double arr[][COL], int n);
int main()
{
// 所需变量
double source[ROW][COL] = {
{1.1, 2.2, 3.3, 4.4, 5.5}
};
double target[ROW][COL] = { 0 };
// 拷贝
copy_arr(target, source, ROW);
// 打印
print_arr(target, ROW);
return 0;
}
// 函数声明
void copy_arr(double t[][COL], double s[][COL], int n)
{
int i;
for (i = 0; i < n; ++i)
{
int j;
for (j = 0; j < COL; ++j)
t[i][j] = s[i][j];
}
}
void print_arr(double arr[][COL], int n)
{
int i;
for (i = 0; i < n; ++i)
{
int j;
for (j = 0; j < COL; ++j)
printf("%.1lf ", arr[i][j]);
// 下一行
putchar('\n');
}
}
10. 编写一个函数,把两个数组中相对应的元素相加,然后把结果存储到第 3 个数组中。也就是说,如果第 1 个数组中包含的值是 2、4、5、8,数组 2 中包含的值是 1、0、4、6,那么该函数把 3、4、9、14 赋给第 3 个数组。函数接受 3 个数组名和一个数组大小。在一个简单的程序中测试该函数。
答:
程序设计分析: 该函数实际上和前面的拷贝函数差不多,只不过这次拷贝两个数组对应元素之和。
代码如下:
#include <stdio.h>
// 常量声明
#define SIZE 5
// 函数声明
void add(int t[], int s1[], int s2[], int n);
int main()
{
// 所需变量
int arr1[SIZE] = { 1, 2, 3, 4, 5 };
int arr2[SIZE] = { 2, 3, 4, 5, 6 };
int arr3[SIZE] = { 0 };
// 拷贝
add(arr3, arr1, arr2, SIZE);
// 打印验证
int i;
for (i = 0; i < SIZE; ++i)
printf("%d ", arr3[i]);
printf("\n");
return 0;
}
// 函数定义
void add(int t[], int s1[], int s2[], int n)
{
int i;
for (i = 0; i < SIZE; ++i)
t[i] = s1[i] + s2[i];
}
11. 编写一个程序,声明一个 int 类型的 3 * 5 的二维数组,并用合适的值初始化它。该程序打印数组中的值,然后各值翻倍(即是原值的两倍),并显示出个元素的新值。编写一个函数显示数组的内容,在再编写一个函数把各元素的值翻倍。这两个函数都已函数名和行数作为参数。
答:
程序设计分析: 这两个函数的参数都是一样的,只不过一个是打印一个是翻倍。
代码如下:
#include <stdio.h>
// 常量声明
#define ROW 3
#define COL 5
// 函数声明
void print(int arr[][COL], int n);
void twofold(int arr[][COL], int n);
int main()
{
// 所需变量
int arr[ROW][COL] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 打印
print(arr, ROW);
printf("\n");
// 翻倍
twofold(arr, ROW);
// 打印
print(arr, ROW);
return 0;
}
// 函数声明
void print(int arr[][COL], int n)
{
int i;
for (i = 0; i < n; ++i)
{
int j;
for (j = 0; j < COL; ++j)
printf("%5d ", arr[i][j]);
// 下一行
printf("\n");
}
}
void twofold(int arr[][COL], int n)
{
int i;
for (i = 0; i < n; ++i)
{
int j;
for (j = 0; j < COL; ++j)
arr[i][j] *= 2;
}
}
12. 重写程序清单 10.7 中的 rain.c 程序, 把 main() 中的主要任务都使用函数来完成。
答:
程序设计分析: 该程序中 main() 函数的主要任务就只有 3 个:计算年平均降水量,和月平均降水量。这里主要给出两个函数的声明和定义。
代码如下:
#include <stdio.h>
// 常量声明
#define MONTHS 12
#define YEARS 5
// 函数声明
void year_average(const double arr[][MONTHS], int n);
void month_average(const double arr[][MONTHS], int n);
int main()
{
// ...
return 0;
}
// 函数定义
void year_average(const double arr[][MONTHS], int n)
{
// 所需变量
double sum = 0;
double average = 0;
int i;
for (i = 0; i < YEARS; ++i)
{
int j;
for (j = 0; j < MONTHS; ++j)
{
sum += arr[i][j];
}
}
// 输出
printf("%d 年的平均降水量: %.1lf", i + 1, sum / YEARS);
}
void month_average(const double arr[][MONTHS], int n)
{
// 所需变量
double sum = 0;
double average = 0;
int i;
for (i = 0; i < MONTHS; ++i)
{
int j;
for (j = 0; j < YEARS; ++j)
{
sum += arr[i][j];
}
// 输出
printf("%d 月的平均降水量: %.1lf", i + 1, sum / YEARS);
}
}
13. 编写一个程序,提示用户输入 3 组数,每组数包含 5 个 double 类型的数(假设用户都正确响应,不会输入非数值数据)。程序完成下列任务:
a. 把用户输入的数据存储在 3*5 的数组中;
b. 计算每组(5个)数据的平均值;
c. 计算所有数据的平均值;
d. 找出这 15 个数据中的最大值;
e. 打印结果。
答:
程序设计分析: 5 个任务对应 5 个函数。
代码如下:
#include <stdio.h>
// 常量声明
#define ROW 3
#define COL 5
// 函数声明
void input(double arr[][COL], int n);
void set_average(double arr[][COL], int n);
double all_average(double arr[][COL], int n);
double find_max(double arr[][COL], int n);
void print(double arr[][COL], int n);
int main()
{
double arr[ROW][COL];
// 输入
input(arr, ROW);
// 每组平均值
set_average(arr, ROW);
// 所有平均值
printf("总平均值为:%.1lf\n", all_average(arr, ROW));
// 最大值
printf("最大值为:%.1lf\n", find_max(arr, ROW));
// 打印结果
print(arr, ROW);
return 0;
}
// 函数定义
void input(double arr[][COL], int n)
{
printf("请输入 3 组数,每行 5 个,空格隔开:\n");
int i;
for (i = 0; i < n; ++i)
{
int j;
for (j = 0; j < COL; ++j)
{
scanf("%lf", &arr[i][j]);
}
}
}
void set_average(double arr[][COL], int n)
{
double sum = 0;
int i;
for (i = 0; i < n; ++i)
{
int j;
for (j = 0; j < COL; ++j)
{
sum += arr[i][j];
}
// 输出
printf("第 %d 行平均值: %.1lf\n", i+ 1, sum / COL);
sum = 0;
}
}
double all_average(double arr[][COL], int n)
{
double sum = 0;
int i;
for (i = 0; i < n; ++i)
{
int j;
for (j = 0; j < COL; ++j)
{
sum += arr[i][j];
}
}
return sum / (n * COL);
}
double find_max(double arr[][COL], int n)
{
double max = arr[0][0];
int i;
for (i = 0; i < n; ++i)
{
int j;
for (j = 0; j < COL; ++j)
{
if (arr[i][j] > max)
max = arr[i][j];
}
}
return max;
}
void print(double arr[][COL], int n)
{
int i;
for (i = 0; i < n; ++i)
{
int j;
for (j = 0; j < COL; ++j)
printf("%.1lf ", arr[i][j]);
// 下一行
printf("\n");
}
}
14. 以变长数组作为函数参数,完成编程练习 13
答:
程序设计分析: 作者编译器不支持变长数组,这里简单说明一下如何编写代码。实际上上一题的代码基本上不用动,把原来的参数改成变长数组的参数然后修改函数调用即可。