文章目录
本章不熟悉的知识较多,不再在此列举。
见加粗部分。几乎都是指针的知识。
数组和指针
本章内容
本章介绍以下内容:
- ·关键字——static;
- ·运算符——&、*(一元);
- ·如何创建并初始化数组;
- ·指针(在已学过的基础上)、指针和数组的关系;
- ·编写处理数组的函数;
- ·二维数组。
数组
- 数组由数据类型相同的一系列元素组成。
- 需要使用数组时,通过声明数组告诉编译器数组中内含多少元素和这些元素的类型。编译器根据这些信息正确地创建数组。普通变量可以使用的类型,数组元素都可以用。
/* 一些数组声明*/
int main(void)
{
float candy[365]; /* 内含365个float类型元素的数组 */
char code[12]; /*内含12个char类型元素的数组*/
int states[50]; /*内含50个int类型元素的数组 */
...
}
- 要访问数组中的元素,通过使用数组下标数(也称为索引)表示数组中的各元素。数组元素的编号从0开始,所以candy[0]表示candy数组的第1个元素,candy[364]表示第365个元素,也就是最后一个元素。
初始化数组
- 只存储单个值的变量有时也称为标量变量
int main(void)
{
int powers[8] = {1,2,4,6,8,16,32,64}; /* 从ANSI C开始支持这种初始化 */
...
}
- 如上所示,用以逗号分隔的值列表(用花括号括起来)来初始化数组,各值之间用逗号分隔。
- 不支持ANSI的编译器会把这种形式的初始化识别为语法错误,在数组声明前加上关键字static可解决此问题。第12章将详细讨论这个关键字
/* day_mon1.c -- 打印每个月的天数 */
#include <stdio.h>
#define MONTHS 12
int main(void)
{
int days[MONTHS] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int index;
for (index = 0; index < MONTHS; index++)
printf("Month %2d has %2d days.\n", index + 1, days[index]);
return 0;
}
-
注意该例使用了符号常量MONTHS表示数组大小,这是我们推荐且常用的做法。
-
注意 使用const声明数组
有时需要把数组设置为只读。这样,程序只能从数组中检索值,不能把新值写入数组。要创建只读数组,应该用const声明和初始化数组。因此,程序清单10.1中初始化数组应改成:const int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
-
这样修改后,程序在运行过程中就不能修改该数组中的内容。和普通变量一样,应该使用声明来初始化const数据,因为一旦声明为const,便不能再给它赋值。明确了这一点,就可以在后面的例子中使用const了。
/* no_data.c -- 为初始化数组 */
#include <stdio.h>
#define SIZE 4
int main(void)
{
int no_data[SIZE]; /* 未初始化数组 */
int i;
printf("%2s%14s\n", "i", "no_data[i]");
for (i = 0; i < SIZE; i++)
printf("%2d%14d\n", i, no_data[i]);
return 0;
}
-
该程序的输出如下(系统不同,输出的结果可能不同):
i no_data[i]
0 0
1 4204937
2 4219854
3 2147348480
-
使用数组前必须先初始化它。与普通变量类似,在使用数组元素之前,必须先给它们赋初值。编译器使用的值是内存相应位置上的现有值,因此,读者运行该程序后的输出会与该示例不同。
注意 存储类别警告
数组和其他变量类似,可以把数组创建成不同的存储类别(storage class)。第12章将介绍存储类别的相关内容,现在只需记住:本章描述的数组属于自动存储类别,意思是这些数组在函数内部声明,且声明时未使用关键字static。到目前为止,本书所用的变量和数组都是自动存储类别。在这里提到存储类别的原因是,不同的存储类别有不同的属性,所以不能把本章的内容推广到其他存储类别。对于一些其他存储类别的变量和数组,如果在声明时未初始化,编译器会自动把它们的值设置为0。
/* some_data.c -- 部分初始化数组 */
#include <stdio.h>
#define SIZE 4
int main(void)
{
int some_data[SIZE] = { 1492, 1066 };
int i;
printf("%2s%14s\n", "i", "some_data[i]");
for (i = 0; i < SIZE; i++)
printf("%2d%14d\n", i, some_data[i]);
return 0;
}
- 下面是该程序的输出:
i some_data[i]
0 1492
1 1066
2 0
3 0
- 如上所示,编译器做得很好。当初始化列表中的值少于数组元素个数时,编译器会把剩余的元素都初始化为0。也就是说,如果不初始化数组,数组元素和未初始化的普通变量一样,其中存储的都是垃圾值;但是,如果部分初始化数组,剩余的元素就会被初始化为0。
- 如果初始化列表的项数多于数组元素个数,编译器可没那么仁慈,它会毫不留情地将其视为错误。但是,没必要因此嘲笑编译器。其实,可以省略方括号中的数字,让编译器自动匹配数组大小和初始化列表中的项数
/* day_mon2.c -- 让编译器计算元素个数 */
#include <stdio.h>
int main(void)
{
const int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31 };
int index;
for (index = 0; index < sizeof days / sizeof days[0]; index++)
printf("Month %2d has %d days.\n", index + 1, days[index]);
return 0;
}
- ·如果初始化数组时省略方括号中的数字,编译器会根据初始化列表中的项数来确定数组的大小。
·注意for循环中的测试条件。由于人工计算容易出错,所以让计算机来计算数组的大小。sizeof运算符给出它的运算对象的大小(以字节为单位)。所以sizeof days是整个数组的大小(以字节为单位),sizeof days[0]是数组中一个元素的大小(以字节为单位)。整个数组的大小除以单个元素的大小就是数组元素的个数。
指定初始化器(C99)
- C99增加了一个新特性:指定初始化器
- int arr[6] = {[5] = 212}; // 把arr[5]初始化为212
// designate.c -- 使用指定初始化器
#include <stdio.h>
#define MONTHS 12
int main(void)
{
int days[MONTHS] = { 31, 28, [4] = 31, 30, 31, [1] = 29 };
int i;
for (i = 0; i < MONTHS; i++)
printf("%2d %d\n", i + 1, days[i]);
return 0;
}
- 该程序在支持C99的编译器中输出如下:
1 31
2 29
3 0
4 0
5 31
6 30
7 31
8 0
9 0
10 0
11 0
12 0
- 以上输出揭示了指定初始化器的两个重要特性。第一,如果指定初始化器后面有更多的值,如该例中的初始化列表中的片段:[4] = 31,30,31,那么后面这些值将被用于初始化指定元素后面的元素。也就是说,在days[4]被初始化为31后,days[5]和days[6]将分别被初始化为30和31。第二,如果再次初始化指定的元素,那么最后的初始化将会取代之前的初始化。
- 如果未指定元素大小会怎样?
int stuff[] = {1, [6] = 23}; //会发生什么?
int staff[] = {1, [6] = 4, 9, 10}; //会发生什么?
- 编译器会把数组的大小设置为足够装得下初始化的值。所以,stuff数组有7个元素,编号为0~6;而staff数组的元素比stuff数组多两个(即有9个元素)。
给数组元素赋值
/* 给数组的元素赋值 */
#include <stdio.h>
#define SIZE 50
int main(void)
{
int counter, evens[SIZE];
for (counter = 0; counter < SIZE; counter++)
evens[counter] = 2 * counter;
...
}
- C不允许把数组作为一个单元赋给另一个数组,除初始化以外也不允许使用花括号列表的形式赋值。下面的代码段演示了一些错误的赋值形式:
/* 一些无效的数组赋值 */
#define SIZE 5
int main(void)
{
int oxen[SIZE] = {5,3,2,8}; /* 初始化没问题 */
int yaks[SIZE];
yaks = oxen; /* 不允许 */
yaks[SIZE] = oxen[SIZE]; /* 数组下标越界 */
yaks[SIZE] = {5,3,2,8}; /* 不起作用 */
- oxen数组的最后一个元素是oxen[SIZE-1],所以oxen[SIZE]和yaks[SIZE]都超出了两个数组的末尾。
数组边界
- int doofi[20]; 那么在使用该数组时,要确保程序中使用的数组下标在0~19的范围内,因为编译器不会检查出这种错误
// bounds.c -- 数组下标越界
#include <stdio.h>
#define SIZE 4
int main(void)
{
int value1 = 44;
int arr[SIZE];
int value2 = 88;
int i;
printf("value1 = %d, value2 = %d\n", value1, value2);
for (i = -1; i <= SIZE; i++)
arr[i] = 2 * i + 1;
for (i = -1; i < 7; i++)
printf("%2d %d\n", i, arr[i]);
printf("value1 = %d, value2 = %d\n", value1, value2);
printf("address of arr[-1]: %p\n", &arr[-1]);
printf("address of arr[4]: %p\n", &arr[4]);
printf("address of value1: %p\n", &value1);
printf("address of value2: %p\n", &value2);
return 0;
}
value1 = 44, value2 = 88
-1 -1
0 1
1 3
2 5
3 7
4 9
5 1624678494
6 32767
value1 = 9, value2 = -1
address of arr[-1]: 0x7fff5fbff8cc
address of arr[4]: 0x7fff5fbff8e0
address of value1: 0x7fff5fbff8e0
address of value2: 0x7fff5fbff8cc
- 注意,该编译器似乎把value2存储在数组的前一个位置,把value1存储在数组的后一个位置(其他编译器在内存中存储数据的顺序可能不同)。在上面的输出中,arr[-1]与value2对应的内存地址相同,arr[4]和value1对应的内存地址相同。因此,使用越界的数组下标会导致程序改变其他变量的值。不同的编译器运行该程序的结果可能不同,有些会导致程序异常中止。
- 最好是在声明数组时使用符号常量来表示数组的大小:
#define SIZE 4
int main(void)
{
int arr[SIZE];
for (i = 0; i < SIZE; i++)
…
- 这样做能确保整个程序中的数组大小始终一致。
指定数组的大小
int* n = 5;
int* m = 8;
float* a1[5]; // 可以
float* a2[5*2 + 1]; //可以
float* a3[sizeof(int) + 1]; //可以
float* a4[-4]; // 不可以,数组大小必须大于0
float* a5[0]; // 不可以,数组大小必须大于0
float* a6[2.5]; // 不可以,数组大小必须是整数
float* a7[(int)2.5]; // 可以,已被强制转换为整型常量
float* a8[n]; // C99之前不允许
float* a9[m]; // C99之前不允许
- 上面的注释表明,以前支持C90标准的编译器不允许后两种声明方式。而C99标准允许这样声明,这创建了一种新型数组,称为变长数组(variable-length array)或简称VLA(C11放弃了这一创新的举措,把VLA设定为可选,而不是语言必备的特性)。
多维数组
float rain[5][12]; // 内含5个数组元素的数组,每个数组元素内含12个float类型的元素
- rain[0]是一个数组,那么它的首元素就是rain[0][0],第2个元素是rain[0][1],以此类推。简而言之,数组rain有5个元素,每个元素都是内含12个float类型元素的数组,rain[0]是内含12个float值的数组,rain[0][0]是一个float类型的值。假设要访问位于2行3列的值,则使用rain[1][2]。
/* rain.c -- 计算每年的总降水量、年平均降水量和5年中每月的平均降水量 */
#include <stdio.h>
#define MONTHS 12 // 一年的月份数
#define YEARS 5 // 年数
int main(void)
{
// 用2010~2014年的降水量数据初始化数组
const float rain[YEARS][MONTHS] =
{
{ 4.3, 4.3, 4.3, 3.0, 2.0, 1.2, 0.2, 0.2, 0.4, 2.4, 3.5, 6.6 },
{ 8.5, 8.2, 1.2, 1.6, 2.4, 0.0, 5.2, 0.9, 0.3, 0.9, 1.4, 7.3 },
{ 9.1, 8.5, 6.7, 4.3, 2.1, 0.8, 0.2, 0.2, 1.1, 2.3, 6.1, 8.4 },
{ 7.2, 9.9, 8.4, 3.3, 1.2, 0.8, 0.4, 0.0, 0.6, 1.7, 4.3, 6.2 },
{ 7.6, 5.6, 3.8, 2.8, 3.8, 0.2, 0.0, 0.0, 0.0, 1.3, 2.6, 5.2 }
};
int year, month;
float subtot, total;
printf(" YEAR RAINFALL (inches)\n");
for (year = 0, total = 0; year < YEARS; year++)
{ // 每一年,各月的降水量总和
for (month = 0, subtot = 0; month < MONTHS; month++)
subtot += rain[year][month];
printf("%5d %15.1f\n", 2010 + year, subtot);
total += subtot; // 5年的总降水量
}
printf("\nThe yearly average is %.1f inches.\n\n", total / YEARS);
printf("MONTHLY AVERAGES:\n\n");
printf(" Jan Feb Mar Apr May Jun Jul Aug Sep Oct ");
printf(" Nov Dec\n");
for (month = 0; month < MONTHS; month++)
{ // 每个月,5年的总降水量
for (year = 0, subtot = 0; year < YEARS; year++)
subtot += rain[year][month];
printf("%4.1f ", subtot / YEARS);
}
printf("\n");
return 0;
}
- 下面是该程序的输出:
YEAR RAINFALL (inches)
2010 32.4
2011 37.9
2012 49.8
2013 44.0
2014 32.9
The yearly average is 39.4 inches.
MONTHLY AVERAGES:
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
7.3 7.3 4.9 3.0 2.3 0.6 1.2 0.3 0.5 1.7 3.6 6.7
- 程序使用了两个嵌套for循环。第1个嵌套for循环的内层循环,在year不变的情况下,遍历month计算某年的总降水量;而外层循环,改变year的值,重复遍历month,计算5年的总降水量。这种嵌套循环结构常用于处理二维数组,一个循环处理数组的第1个下标,另一个循环处理数组的第2个下标:
- 第2个嵌套for循环和第1个的结构相同,但是内层循环遍历year,外层循环遍历month。记住,每执行一次外层循环,就完整遍历一次内层循环。因此,在改变月份之前,先遍历完年,得到某月5年间的平均降水量,以此类推
初始化二维数组
- 初始化二维数组是建立在初始化一维数组的基础上。首先,初始化一维数组如下:
sometype ar1[5] = {val1, val2, val3, val4, val5};
- 这里,val1、val2等表示sometype类型的值。例如,如果sometype是int,那么val1可能是7;如果sometype是double,那么val1可能是11.34,诸如此类。但是rain是一个内含5个元素的数组,每个元素又是内含12个float类型元素的数组。所以,对rain而言,val1应该包含12个值,用于初始化内含12个float类型元素的一维数组,如下所示:
{4.3,4.3,4.3,3.0,2.0,1.2,0.2,0.2,0.4,2.4,3.5,6.6}
-
也就是说,如果arl每个元素是一个内含12个double类型元素的数组,那么val1就是一个由12个double类型值构成的数值列表。因此,为了初始化二维数组rain,要用逗号分隔5个这样的数值列表:
const float rain[YEARS][MONTHS] =
{
{4.3,4.3,4.3,3.0,2.0,1.2,0.2,0.2,0.4,2.4,3.5,6.6},
{8.5,8.2,1.2,1.6,2.4,0.0,5.2,0.9,0.3,0.9,1.4,7.3},
{9.1,8.5,6.7,4.3,2.1,0.8,0.2,0.2,1.1,2.3,6.1,8.4},
{7.2,9.9,8.4,3.3,1.2,0.8,0.4,0.0,0.6,1.7,4.3,6.2},
{7.6,5.6,3.8,2.8,3.8,0.2,0.0,0.0,0.0,1.3,2.6,5.2}
};
-
如果第1个列表中只有10个数,则只会初始化数组第1行的前10个元素,而最后两个元素将被默认初始化为0。如果某列表中的数值个数超出了数组每行的元素个数,则会出错,但是这并不会影响其他行的初始化。
-
初始化时也可省略内部的花括号,只保留最外面的一对花括号。只要保证初始化的数值个数正确,初始化的效果与上面相同。但是如果初始化的数值不够,则按照先后顺序逐行初始化,直到用完所有的值。后面没有值初始化的元素被统一初始化为0。
其他多维数组
- 可以这样声明一个三维数组:
int box[10][20][30];
- 可以把一维数组想象成一行数据,把二维数组想象成数据表,把三维数组想象成一叠数据表。例如,把上面声明的三维数组box想象成由10个二维数组(每个二维数组都是20行30列)堆叠起来。
- 还有一种理解box的方法是,把box看作数组的数组。也就是说,box内含10个元素,每个元素是内含20个元素的数组,这20个数组元素中的每个元素是内含30个元素的数组。或者,可以简单地根据所需的下标值去理解数组。
- 通常,处理三维数组要使用3重嵌套循环,处理四维数组要使用4重嵌套循环。对于其他多维数组,以此类推。在后面的程序示例中,我们只使用二维数组。
指针和数组
-
数组名是数组首元素的地址。也就是说,如果flizny是一个数组,下面的语句成立:
flizny == &flizny[0]; // 数组名是该数组首元素的地址
-
flizny和&flizny[0]都表示数组首元素的内存地址(&是地址运算符)。两者都是常量,在程序的运行过程中,不会改变。但是,可以把它们赋值给指针变量,然后可以修改指针变量的值,如程序清单10.8所示。注意指针加上一个数时,它的值发生了什么变化
// pnt_add.c -- 指针地址
#include <stdio.h>
#define SIZE 4
int main(void)
{
short dates[SIZE];
short * pti;
short index;
double bills[SIZE];
double * ptf;
pti = dates; // 把数组地址赋给指针
ptf = bills;
printf("%23s %15s\n", "short", "double");
for (index = 0; index < SIZE; index++)
printf("pointers + %d: %10p %10p\n", index, pti + index, ptf + index);
return 0;
}
-
下面是该例的输出示例:
short double
pointers + 0: 0x7fff5fbff8dc 0x7fff5fbff8a0
pointers + 1: 0x7fff5fbff8de 0x7fff5fbff8a8
pointers + 2: 0x7fff5fbff8e0 0x7fff5fbff8b0
pointers + 3: 0x7fff5fbff8e2 0x7fff5fbff8b8
-
我们的系统中,地址按字节编址,short类型占用2字节,double类型占用8字节。在C中,指针加1指的是增加一个存储单元。对数组而言,这意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址。这是为什么必须声明指针所指向对象类型的原因之一。只知道地址不够,因为计算机要知道存储对象需要多少字节(即使指针指向的是标量变量,也要知道变量的类型,否则*pt就无法正确地取回地址上的值)。
-
·指针的值是它所指向对象的地址。地址的表示方式依赖于计算机内部的硬件。许多计算机(包括PC和Macintosh)都是按字节编址,意思是内存中的每个字节都按顺序编号。这里,一个较大对象的地址(如double类型的变量)通常是该对象第一个字节的地址。
-
·在指针前面使用*运算符可以得到该指针所指向对象的值。
-
·指针加1,指针的值递增它所指向类型的大小(以字节为单位)。
- dates + 2 == &dates[2] // 相同的地址
- *(dates + 2) == dates[2] // 相同的值
- *(dates + 2) // dates第3个元素的值
- *dates + 2 // dates第1个元素的值加2
/* day_mon3.c -- uses pointer notation */
#include <stdio.h>
#define MONTHS 12
int main(void)
{
int days[MONTHS] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int index;
for (index = 0; index < MONTHS; index++)
printf("Month %2d has %d days.\n", index + 1,
*(days + index)); //与 days[index]相同
return 0;
}
- 这里,days是数组首元素的地址,days + index是元素days[index]的地址,而*(days + index)则是该元素的值,相当于days[index]。for循环依次引用数组中的每个元素,并打印各元素的内容。
函数、数组和指针
- 假设要编写一个处理数组的函数,该函数返回数组中所有元素之和,待处理的是名为marbles的int类型数组。应该如何调用该函数?也许是下面这样:
total = sum(marbles); // 可能的函数调用
-
那么,该函数的原型是什么?记住,数组名是该数组首元素的地址,所以实际参数marbles是一个存储int类型值的地址,应把它赋给一个指针形式参数,即该形参是一个指向int的指针:
int sum(int * ar); // 对应的函数原型
-
注意,该参数并未包含数组元素个数的信息。我们有两种方法让函数获得这一信息。第一种方法是,在函数代码中写上固定的数组大小:
int sum(int * ar) // 相应的函数定义
{
int i;
int total = 0;
for (i = 0; i < 10; i++) // 假设数组有10个元素
total += ar[i]; // ar[i] 与 *(ar + i) 相同
return total;
}
- 另一个比较灵活的方法是把数组大小作为第2个参数:
int sum(int * ar, int n) // 更通用的方法
{
int i;
int total = 0;
for (i = 0; i < n; i++) // 使用 n 个元素
total += ar[i]; // ar[i] 和 *(ar + i) 相同
return total;
}
- 关于函数的形参,还有一点要注意。只有在函数原型或函数定义头中,才可以用int ar[]代替int * ar:
int sum (int ar[], int n);
*int ar形式和int ar[]形式都表示ar是一个指向int的指针。但是,int ar[]只能用于声明形式参数。第2种形式(int ar[])提醒读者指针ar指向的不仅仅是一个int类型值,还是一个int类型数组的元素。
// sum_arr1.c -- 数组元素之和
// 如果编译器不支持 %zd,用 %u 或 %lu 替换它
#include <stdio.h>
#define SIZE 10
int sum(int ar[], int n);
int main(void)
{
int marbles[SIZE] = { 20, 10, 5, 39, 4, 16, 19, 26, 31, 20 };
long answer;
answer = sum(marbles, SIZE);
printf("The total number of marbles is %ld.\n", answer);
printf("The size of marbles is %zd bytes.\n",
sizeof marbles);
return 0;
}
int sum(int ar[], int n) // 这个数组的大小是?
{
int i;
int total = 0;
for (i = 0; i < n; i++)
total += ar[i];
printf("The size of ar is %zd bytes.\n", sizeof ar);
return total;
}
The size of ar is 8 bytes.
The total number of marbles is 190.
The size of marbles is 40 bytes.
- 注意,marbles的大小是40字节。这没问题,因为marbles内含10个int类型的值,每个值占4字节,所以整个marbles的大小是40字节。但是,ar才8字节。这是因为ar并不是数组本身,它是一个指向marbles数组首元素的指针。我们的系统中用8字节存储地址,所以指针变量的大小是8字节(其他系统中地址的大小可能不是8字节)。简而言之,在程序清单10.10中,marbles是一个数组,ar是一个指向marbles数组首元素的指针
使用指针形参
- 还有一种方法是传递两个指针,第1个指针指明数组的开始处(与前面用法相同),第2个指针指明数组的结束处。
/* sum_arr2.c -- 数组元素之和 */
#include <stdio.h>
#define SIZE 10
int sump(int * start, int * end);
int main(void)
{
int marbles[SIZE] = { 20, 10, 5, 39, 4, 16, 19, 26, 31, 20 };
long answer;
answer = sump(marbles, marbles + SIZE);
printf("The total number of marbles is %ld.\n", answer);
return 0;
}
/* 使用指针算法 */
int sump(int * start, int * end)
{
int total = 0;
while (start < end)
{
total += *start; // 把数组元素的值加起来
start++; // 让指针指向下一个元素
}
return total;
}
-
因为while循环的测试条件是一个不相等的关系,所以循环最后处理的一个元素是end所指向位置的前一个元素。这意味着end指向的位置实际上在数组最后一个元素的后面。C保证在给数组分配空间时,指向数组后面第一个位置的指针仍是有效的指针。这使得while循环的测试条件是有效的,因为start在循环中最后的值是end。注意,使用这种“越界”指针的函数调用更为简洁:
answer = sump(marbles, marbles + SIZE);
-
虽然C保证了marbles + SIZE有效,但是对marbles[SIZE](即存储在该位置上的值)未作任何保证,所以程序不能访问该位置。
-
还可以把循环体压缩成一行代码:
total += *start++; -
一元运算符*和++的优先级相同,但结合律是从右往左,所以start++先求值,然后才是*start。也就是说,指针start先递增后指向。使用后缀形式(即start++而不是++start)意味着先把指针指向位置上的值加到total上,然后再递增指针。
-
虽然*start++的写法比较常用,但是(start++)这样写更清楚。*
/* order.c -- 指针运算中的优先级 */
#include <stdio.h>
int data[2] = { 100, 200 };
int moredata[2] = { 300, 400 };
int main(void)
{
int * p1, *p2, *p3;
p1 = p2 = data;
p3 = moredata;
printf(" *p1 = %d, *p2 = %d, *p3 = %d\n",*p1, *p2, *p3);
printf("*p1++ = %d, *++p2 = %d, (*p3)++ = %d\n",*p1++, *++p2, (*p3)++);
printf(" *p1 = %d, *p2 = %d, *p3 = %d\n",*p1, *p2, *p3);
return 0;
}
-
下面是该程序的输出:
*p1 = 100, *p2 = 100, *p3 = 300
*p1++ = 100, *++p2 = 200, (*p3)++ = 300
*p1 = 200, *p2 = 200, *p3 = 301
指针操作
- 用%td转换说明打印地址的差值
// ptr_ops.c -- 指针操作
#include <stdio.h>
int main(void)
{
int urn[5] = { 100, 200, 300, 400, 500 };
int * ptr1, *ptr2, *ptr3;
ptr1 = urn; // 把一个地址赋给指针
ptr2 = &urn[2]; // 把一个地址赋给指针
// 解引用指针,以及获得指针的地址
printf("pointer value, dereferenced pointer, pointer address:\n");
printf("ptr1 = %p, *ptr1 =%d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
// 指针加法
ptr3 = ptr1 + 4;
printf("\nadding an int to a pointer:\n");
printf("ptr1 + 4 = %p, *(ptr1 + 4) = %d\n", ptr1 + 4, *(ptr1 + 4));
ptr1++; // 递增指针
printf("\nvalues after ptr1++:\n");
printf("ptr1 = %p, *ptr1 =%d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
ptr2--; // 递减指针
printf("\nvalues after --ptr2:\n");
printf("ptr2 = %p, *ptr2 = %d, &ptr2 = %p\n", ptr2, *ptr2, &ptr2);
--ptr1; // 恢复为初始值
++ptr2; // 恢复为初始值
printf("\nPointers reset to original values:\n");
printf("ptr1 = %p, ptr2 = %p\n", ptr1, ptr2);
// 一个指针减去另一个指针
printf("\nsubtracting one pointer from another:\n");
printf("ptr2 = %p, ptr1 = %p, ptr2 - ptr1 = %td\n", ptr2, ptr1, ptr2 - ptr1);
// 一个指针减去一个整数
printf("\nsubtracting an int from a pointer:\n");
printf("ptr3 = %p, ptr3 - 2 = %p\n", ptr3, ptr3 - 2);
return 0;
}
pointer value, dereferenced pointer, pointer address:
ptr1 = 0x7fff5fbff8d0, *ptr1 =100, &ptr1 = 0x7fff5fbff8c8
adding an int to a pointer:
ptr1 + 4 = 0x7fff5fbff8e0, *(ptr1 + 4) = 500
values after ptr1++:
ptr1 = 0x7fff5fbff8d4, *ptr1 =200, &ptr1 = 0x7fff5fbff8c8
values after --ptr2:
ptr2 = 0x7fff5fbff8d4, *ptr2 = 200, &ptr2 = 0x7fff5fbff8c0
Pointers reset to original values:
ptr1 = 0x7fff5fbff8d0, ptr2 = 0x7fff5fbff8d8
subtracting one pointer from another:
ptr2 = 0x7fff5fbff8d8, ptr1 = 0x7fff5fbff8d0, ptr2 - ptr1 = 2
subtracting an int from a pointer:
ptr3 = 0x7fff5fbff8e0, ptr3 - 2 = 0x7fff5fbff8d8
- ·赋值:可以把地址赋给指针。例如,用数组名、带地址运算符(&)的变量名、另一个指针进行赋值。在该例中,把urn数组的首地址赋给了ptr1,该地址的编号恰好是0x7fff5fbff8d0。变量ptr2获得数组urn的第3个元素(urn[2])的地址。注意,地址应该和指针类型兼容。也就是说,不能把double类型的地址赋给指向int的指针,至少要避免不明智的类型转换。C99/C11已经强制不允许这样做。
- ·解引用:*运算符给出指针指向地址上存储的值。因此,*ptr1的初值是100,该值存储在编号为0x7fff5fbff8d0的地址上
- ·取址:和所有变量一样,指针变量也有自己的地址和值。对指针而言,&运算符给出指针本身的地址。本例中,ptr1存储在内存编号为0x7fff5fbff8c8的地址上,该存储单元存储的内容是0x7fff5fbff8d0,即urn的地址。因此&ptr1是指向ptr1的指针,而ptr1是指向utn[0]的指针。
- ·指针与整数相加:可以使用+运算符把指针与整数相加,或整数与指针相加。无论哪种情况,整数都会和指针所指向类型的大小(以字节为单位)相乘,然后把结果与初始地址相加。因此ptr1+4与&urn[4]等价。如果相加的结果超出了初始指针指向的数组范围,计算结果则是未定义的。除非正好超过数组末尾第一个位置,C保证该指针有效。
- ·递增指针:递增指向数组元素的指针可以让该指针移动至数组的下一个元素。因此,ptr1++相当于把ptr1的值加上4(我们的系统中int为4字节),ptr1指向urn[1](见图10.4,该图中使用了简化的地址)。现在ptr1的值是0x7fff5fbff8d4(数组的下一个元素的地址),*ptr的值为200(即urn[1]的值)。注意,ptr1本身的地址仍是0x7fff5fbff8c8。毕竟,变量不会因为值发生变化就移动位置。
- ·指针减去一个整数:可以使用-运算符从一个指针中减去一个整数。指针必须是第1个运算对象,整数是第2个运算对象。该整数将乘以指针指向类型的大小(以字节为单位),然后用初始地址减去乘积。所以ptr3 - 2与&urn[2]等价,因为ptr3指向的是&urn[4]。如果相减的结果超出了初始指针所指向数组的范围,计算结果则是未定义的。除非正好超过数组末尾第一个位置,C保证该指针有效。
·递减指针:当然,除了递增指针还可以递减指针。在本例中,递减ptr2使其指向数组的第2个元素而不是第3个元素。前缀或后缀的递增和递减运算符都可以使用。注意,在重置ptr1和ptr2前,它们都指向相同的元素urn[1]。 - ·指针求差:可以计算两个指针的差值。通常,求差的两个指针分别指向同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数组类型的单位相同。例如,程序清单10.13的输出中,ptr2 - ptr1得2,意思是这两个指针所指向的两个元素相隔两个int,而不是2字节。只要两个指针都指向相同的数组(或者其中一个指针指向数组后面的第1个地址),C都能保证相减运算有效。如果指向两个不同数组的指针进行求差运算可能会得出一个值,或者导致运行时错误。
- ·比较:使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象。
解引用未初始化的指针
说到注意事项,一定要牢记一点:千万不要解引用未初始化的指针。例如,考虑下面的例子:
int * pt; // 未初始化的指针
*pt = 5; // 严重的错误
为何不行?第2行的意思是把5存储在pt指向的位置。但是pt未被初始化,其值是一个随机值,所以不知道5将存储在何处。这可能不会出什么错,也可能会擦写数据或代码,或者导致程序崩溃。切记:创建一个指针时,系统只分配了存储指针本身的内存,并未分配存储数据的内存。因此,在使用指针之前,必须先用已分配的地址初始化它。
保护数组中的数据
- 传递地址会导致一些问题。C通常都按值传递数据,因为这样做可以保证数据的完整性。如果函数使用的是原始数据的副本,就不会意外修改原始数据。但是,处理数组的函数通常都需要使用原始数据,因此这样的函数可以修改原数组。有时,这正是我们需要的。
- 然而,其他函数并不需要修改数据。
对形式参数使用const
- ANSI C提供了一种预防手段。如果函数的意图不是修改数组中的数据内容,那么在函数原型和函数定义中声明形式参数时应使用关键字const。
int sum(const int ar[], int n); /* 函数原型 */
int sum(const int ar[], int n) /* 函数定义 */
{
int i;
int total = 0;
for( i = 0; i < n; i++)
total += ar[i];
return total;
}
- 这里一定要理解,这样使用const并不是要求原数组是常量,而是该函数在处理数组时将其视为常量,不可更改。这样使用const可以保护数组的数据不被修改,就像按值传递可以保护基本数据类型的原始值不被改变一样。
- 一个函数显示数组的内容,另一个函数给数组每个元素都乘以一个给定值。因为第1个函数不用改变数组,所以在声明数组形参时使用了const;而第2个函数需要修改数组元素的值,所以不使用const。
/* arf.c -- 处理数组的函数 */
#include <stdio.h>
#define SIZE 5
void show_array(const double ar[], int n);
void mult_array(double ar[], int n, double mult);
int main(void)
{
double dip[SIZE] = { 20.0, 17.66, 8.2, 15.3, 22.22 };
printf("The original dip array:\n");
show_array(dip, SIZE);
mult_array(dip, SIZE, 2.5);
printf("The dip array after calling mult_array():\n");
show_array(dip, SIZE);
return 0;
}
/* 显示数组的内容 */
void show_array(const double ar[], int n)
{
int i;
for (i = 0; i < n; i++)
printf("%8.3f ", ar[i]);
putchar('\n');
}
/* 把数组的每个元素都乘以相同的值 */
void mult_array(double ar[], int n, double mult)
{
int i;
for (i = 0; i < n; i++)
ar[i] *= mult;
}
The original dip array:
20.000 17.660 8.200 15.300 22.220
The dip array after calling mult_array():
50.000 44.150 20.500 38.250 55.550
const的其他内容
-
可以创建const数组、const指针和指向const的指针。
-
使用const关键字保护数组:
#define MONTHS 12
…
const int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
-
指向const的指针不能用于改变值。考虑下面的代码:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double * pd = rates; // pd指向数组的首元素
-
第2行代码把pd指向的double类型的值声明为const,这表明不能使用pd来更改它所指向的值:
*pd = 29.89; // 不允许
pd[2] = 222.22; //不允许
rates[0] = 99.99; // 允许,因为rates未被const限定
-
可以让pd指向别处:
pd++; /* 让pd指向rates[1] – 没问题 */
-
指向const的指针通常用于函数形参中,表明该函数不会使用指针改变数据。
-
关于指针赋值和const需要注意一些规则。首先,把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};*
const double locked[4] = {0.08, 0.075, 0.0725, 0.07};*
const double * pc = rates; // 有效*
pc = locked; //有效
pc = &rates[3]; //有效 然而,只能把非const数据的地址赋给普通指针:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};*
const double locked[4] = {0.08, 0.075, 0.0725, 0.07};
double * pnc = rates; // 有效
pnc = locked; // 无效
pnc = &rates[3]; // 有效
这个规则非常合理。否则,通过指针就能改变const数组中的数据。
-
const还有其他的用法。例如,可以声明并初始化一个不能指向别处的指针,关键是const的位置:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
double * const pc = rates; // pc指向数组的开始
pc = &rates[2]; // 不允许,因为该指针不能指向别处
*pc = 92.99; // 没问题 – 更改rates[0]的值
可以用这种指针修改它所指向的值,但是它只能指向初始化时设置的地址。
-
最后,在创建指针时还可以使用const两次,该指针既不能更改它所指向的地址,也不能修改指向地址上的值:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double * const pc = rates;
pc = &rates[2]; //不允许
*pc = 92.99; //不允许
指针和多维数组
int zippo[4][2]; /* 内含int数组的数组 */
- 然后数组名zippo是该数组首元素的地址。在本例中,zippo的首元素是一个内含两个int值的数组,所以zippo是这个内含两个int值的数组的地址。下面,我们从指针的属性进一步分析。
- ·因为zippo是数组首元素的地址,所以zippo的值和&zippo[0]的值相同。而zippo[0]本身是一个内含两个整数的数组,所以zippo[0]的值和它首元素(一个整数)的地址(即&zippo[0][0]的值)相同。简而言之,zippo[0]是一个占用一个int大小对象的地址,而zippo是一个占用两个int大小对象的地址。由于这个整数和内含两个整数的数组都开始于同一个地址,所以zippo和zippo[0]的值相同。
- ·给指针或地址加1,其值会增加对应类型大小的数值。在这方面,zippo和zippo[0]不同,因为zippo指向的对象占用了两个int大小,而zippo[0]指向的对象只占用一个int大小。因此,zippo + 1和zippo[0] + 1的值不同。
- ·解引用一个指针(在指针前使用*运算符)或在数组名后使用带下标的[]运算符,得到引用对象代表的值。因为zippo[0]是该数组首元素(zippo[0][0])的地址,所以*(zippo[0])表示存储在zippo[0][0]上的值(即一个int类型的值)。与此类似,*zippo代表该数组首元素(zippo[0])的值,但是zippo[0]本身是一个int类型值的地址。该值的地址是&zippo[0][0],所以*zippo就是&zippo[0][0]。对两个表达式应用解引用运算符表明,**zippo与*&zippo[0][0]等价,这相当于zippo[0][0],即一个int类型的值。简而言之,zippo是地址的地址,必须解引用两次才能获得原始值。地址的地址或指针的指针是就是双重间接(double indirection)的例子。
- /* zippo1.c -- zippo的相关信息 */
#include <stdio.h>
int main(void)
{
int zippo[4][2] = { { 2, 4 }, { 6, 8 }, { 1, 3 }, { 5, 7 } };
printf(" zippo = %p, zippo + 1 = %p\n",zippo, zippo + 1);
printf("zippo[0] = %p, zippo[0] + 1 = %p\n",zippo[0], zippo[0] + 1);
printf(" *zippo = %p, *zippo + 1 = %p\n",*zippo, *zippo + 1);
printf("zippo[0][0] = %d\n", zippo[0][0]);
printf(" *zippo[0] = %d\n", *zippo[0]);
printf(" **zippo = %d\n", **zippo);
printf(" zippo[2][1] = %d\n", zippo[2][1]);
printf("*(*(zippo+2) + 1) = %d\n", *(*(zippo + 2) + 1));
return 0;
}
/*
zippo = 0x0064fd38, zippo + 1 = 0x0064fd40
zippo[0]= 0x0064fd38, zippo[0] + 1 = 0x0064fd3c
*zippo = 0x0064fd38, *zippo + 1 = 0x0064fd3c
zippo[0][0] = 2
*zippo[0] = 2
**zippo = 2
zippo[2][1] = 3
*(*(zippo+2) + 1) = 3
*/
- 输出显示了二维数组zippo的地址和一维数组zippo[0]的地址相同。它们的地址都是各自数组首元素的地址,因而与&zippo[0][0]的值也相同。
- 尽管如此,它们也有差别。在我们的系统中,int是4字节。前面讨论过,zippo[0]指向一个4字节的数据对象。zippo[0]加1,其值加4(十六进制中,38+4得3c)。数组名zippo是一个内含2个int类型值的数组的地址,所以zippo指向一个8字节的数据对象。因此,zippo加1,它所指向的地址加8字节(十六进制中,38+8得40)。
- 该程序演示了zippo[0]和*zippo完全相同,实际上确实如此。然后,对二维数组名解引用两次,得到存储在数组中的值。使用两个间接运算符(*)或者使用两对方括号([])都能获得该值(还可以使用一个*和一对[],但是我们暂不讨论这么多情况)。
- 要特别注意,与zippo[2][1]等价的指针表示法是*(*(zippo+2) + 1)。看上去比较复杂,应最好能理解。
zippo ←二维数组首元素的地址(每个元素都是内含两个int类型元素的一维数组)
zippo+2 ←二维数组的第3个元素(即一维数组)的地址
*(zippo+2) ←二维数组的第3个元素(即一维数组)的首元素(一个int类型的值)地址
*(zippo+2) + 1 ←二维数组的第3个元素(即一维数组)的第2个元素(也是一个int类型的值)地址
*(*(zippo+2) + 1) ←二维数组的第3个一维数组元素的第2个int类型元素的值,即数组的第3行第2
列的值(zippo[2][1])
指向多维数组的指针
int (* pz)[2]; // pz指向一个内含两个int类型值的数组
-
以上代码把pz声明为指向一个数组的指针,该数组内含两个int类型值。为什么要在声明中使用圆括号?因为[]的优先级高于*。考虑下面的声明:
int * pax[2]; // pax是一个内含两个指针元素的数组,每个元素都指向int的指针
/* zippo2.c -- 通过指针获取zippo的信息 */
#include <stdio.h>
int main(void)
{
int zippo[4][2] = { { 2, 4 }, { 6, 8 }, { 1, 3 }, { 5, 7 } };
int(*pz)[2];
pz = zippo;
printf(" pz = %p, pz + 1 = %p\n", pz, pz + 1);
printf("pz[0] = %p, pz[0] + 1 = %p\n", pz[0], pz[0] + 1);
printf(" *pz = %p, *pz + 1 = %p\n", *pz, *pz + 1);
printf("pz[0][0] = %d\n", pz[0][0]);
printf(" *pz[0] = %d\n", *pz[0]);
printf(" **pz = %d\n", **pz);
printf(" pz[2][1] = %d\n", pz[2][1]);
printf("*(*(pz+2) + 1) = %d\n", *(*(pz + 2) + 1));
return 0;
}
pz = 0x0064fd38, pz + 1 = 0x0064fd40
pz[0] = 0x0064fd38, pz[0] + 1 = 0x0064fd3c
*pz = 0x0064fd38,000 *pz + 1 = 0x0064fd3c
pz[0][0] = 2
*pz[0] = 2
**pz = 2
pz[2][1] = 3
((pz+2) + 1) = 3
zippo[m][n] == ((zippo + m) + n)
pz[m][n] == ((pz + m) + n)
指针的兼容性
- 例如,不用类型转换就可以把int类型的值赋给double类型的变量,但是两个类型的指针不能这样做。
int n = 5;
double x;
int * p1 = &n;
double * pd = &x;
x = n; // 隐式类型转换
pd = p1; // 编译时错误
// 更复杂的类型也是如此。假设有如下声明:
int * pt;
int (*pa)[3];
int ar1[2][3];
int ar2[3][2];
int **p2; // 一个指向指针的指针
// 有如下的语句:
pt = &ar1[0][0]; // 都是指向int的指针
pt = ar1[0]; // 都是指向int的指针
pt = ar1; // 无效
pa = ar1; // 都是指向内含3个int类型元素数组的指针
pa = ar2; // 无效
p2 = &pt; // 都是指向int *的指针
*p2 = ar2[0]; // 都是指向int的指针
p2 = ar2; // 无效
- 上面的最后两个例子有些棘手。变量p2是指向指针的指针,它指向的指针指向int,而ar2是指向数组的指针,该数组内含2个int类型的元素。所以,p2和ar2的类型不同,不能把ar2赋给p2。但是,*p2是指向int的指针,与ar2[0]兼容。因为ar2[0]是指向该数组首元素(ar2[0][0])的指针,所以ar2[0]也是指向int的指针。
int x = 20;
const int y = 23;
int * p1 = &x;
const int * p2 = &y;
const int ** pp2;
p1 = p2; // 不安全 -- 把const指针赋给非const指针
p2 = p1; // 有效 -- 把非const指针赋给const指针
pp2 = &p1; // 不安全 –- 嵌套指针类型赋值
-
但是进行两级解引用时,这样的赋值也不安全,例如,考虑下面的代码:
const int **pp2; int *p1; const int n = 13; pp2 = &p1; // 允许,但是这导致const限定符失效(根据第1行代码,不能通过**pp2修改它所指向的内容) *pp2 = &n; // 有效,两者都声明为const,但是这将导致p1指向n(*pp2已被修改) *p1 = 10; //有效,但是这将改变n的值(但是根据第3行代码,不能修改n的值)
C const和C++ const
C和C++中const的用法很相似,但是并不完全相同。区别之一是,C++允许在声明数组大小时使用const整数,而C却不允许。区别之二是,C++的指针赋值检查更严格:
const int y;
const int * p2 = &y;
int * p1;
p1 = p2; // C++中不允许这样做,但是C可能只给出警告
C++不允许把const指针赋给非const指针。而C则允许这样做,但是如果通过p1更改y,其行为是未定义的。
函数和多维数组
// array2d.c -- 处理二维数组的函数
#include <stdio.h>
#define ROWS 3
#define COLS 4
void sum_rows(int ar[][COLS], int rows);
void sum_cols(int [][COLS], int); // 省略形参名,没问题
int sum2d(int(*ar)[COLS], int rows); // 另一种语法
int main(void)
{
int junk[ROWS][COLS] = {
{ 2, 4, 6, 8 },
{ 3, 5, 7, 9 },
{ 12, 10, 8, 6 }
};
sum_rows(junk, ROWS);
sum_cols(junk, ROWS);
printf("Sum of all elements = %d\n", sum2d(junk, ROWS));
return 0;
}
void sum_rows(int ar[][COLS], int rows)
{
int r;
int c;
int tot;
for (r = 0; r < rows; r++)
{
tot = 0;
for (c = 0; c < COLS; c++)
tot += ar[r][c];
printf("row %d: sum = %d\n", r, tot);
}
}
void sum_cols(int ar[][COLS], int rows)
{
int r;
int c;
int tot;
for (c = 0; c < COLS; c++)
{
tot = 0;
for (r = 0; r < rows; r++)
tot += ar[r][c];
printf("col %d: sum = %d\n", c, tot);
}
}
int sum2d(int ar[][COLS], int rows)
{
int r;
int c;
int tot = 0;
for (r = 0; r < rows; r++)
for (c = 0; c < COLS; c++)
tot += ar[r][c];
return tot;
}
-
该程序的输出如下:
row 0: sum = 20
row 1: sum = 24
row 2: sum = 36
col 0: sum = 17
col 1: sum = 19
col 2: sum = 21
col 3: sum = 23
Sum of all elements = 80
-
注意,下面的声明不正确:
int sum2(int ar[][], int rows); // 错误的声明
-
前面介绍过,编译器会把数组表示法转换成指针表示法。例如,编译器会把ar[1]转换成ar+1。编译器对ar+1求值,要知道ar所指向的对象大小。下面的声明:
int sum2(int ar[][4], int rows); // 有效声明
-
表示ar指向一个内含4个int类型值的数组(在我们的系统中,ar指向的对象占16字节),所以ar+1的意思是“该地址加上16字节”。如果第2对方括号是空的,编译器就不知道该怎样处理。
也可以在第1对方括号中写上大小,如下所示,但是编译器会忽略该值:int sum2(int ar[3][4], int rows); // 有效声明,但是3将被忽略
-
一般而言,声明一个指向N维数组的指针时,只能省略最左边方括号中的值:
int sum4d(int ar[][12][20][30], int rows);
-
因为第1对方括号只用于表明这是一个指针,而其他的方括号则用于描述指针所指向数据对象的类型。下面的声明与该声明等价:
int sum4d(int (*ar)[12][20][30], int rows); // ar是一个指针
-
这里,ar指向一个12×20×30的int数组。
变长数组(VLA)
- C99新增了变长数组(variable-length array,VLA),允许使用变量表示数组的维度。如下所示:
int quarters = 4;
int regions = 5;
double sales[regions][quarters]; // 一个变长数组(VLA)
-
前面提到过,变长数组有一些限制。变长数组必须是自动存储类别,这意味着无论在函数中声明还是作为函数形参声明,都不能使用static或extern存储类别说明符(第12章介绍)。而且,不能在声明中初始化它们。最终,C11把变长数组作为一个可选特性,而不是必须强制实现的特性。
-
变长数组不能改变大小
变长数组中的“变”不是指可以修改已创建数组的大小。一旦创建了变长数组,它的大小则保持不变。这里的“变”指的是:在创建数组时,可以使用变量指定数组的维度。
首先,要声明一个带二维变长数组参数的函数,如下所示:
int sum2d(int rows, int cols, int ar[rows][cols]); // ar是一个变长数组(VLA)
注意前两个形参(rows和cols)用作第3个形参二维数组ar的两个维度。因为ar的声明要使用rows和cols,所以在形参列表中必须在声明ar之前先声明这两个形参。因此,下面的原型是错误的:
int sum2d(int ar[rows][cols], int rows, int cols); // 无效的顺序
C99/C11标准规定,可以省略原型中的形参名,但是在这种情况下,必须用星号来代替省略的维度:
int sum2d(int, int, int ar[*][*]); // ar是一个变长数组(VLA),省略了维度形参名
-
是否可以在声明数组时使用const变量?
const int SZ = 80;
…
double ar[SZ]; // 是否允许?
-
C90标准不允许(也可能允许)。数组的大小必须是给定的整型常量表达式,可以是整型常量组合,如20、sizeof表达式或其他不是const的内容。
-
C99/C11标准允许在声明变长数组时使用const变量。所以该数组的定义必须是声明在块中的自动存储类别数组。
复合字面量
-
C99新增了复合字面量(compound literal)。字面量是除符号常量外的常量。例如,5是int类型字面量,81.3是double类型的字面量,'Y’是char类型的字面量,"elephant"是字符串字面量。发布C99标准的委员会认为,如果有代表数组和结构内容的复合字面量,在编程时会更方便。
-
下面的复合字面量创建了一个和diva数组相同的匿名数组,也有两个int类型的值:
(int [2]){10, 20} // 复合字面量
注意,去掉声明中的数组名,留下的int [2]即是复合字面量的类型名。 -
复合字面量也可以省略大小,编译器会自动计算数组当前的元素个数:
(int []){50, 20, 90} // 内含3个元素的复合字面量
-
因为复合字面量是匿名的,所以不能先创建然后再使用它,必须在创建的同时使用它。使用指针记录地址就是一种用法。也就是说,可以这样用:
int * pt1;
pt1 = (int [2]) {10, 20};
-
还可以把复合字面量作为实际参数传递给带有匹配形式参数的函数:
int sum(const int ar[], int n);
…
int total3;
total3 = sum((int []){4,4,4,5,5,5}, 6);
// flc.c -- 有趣的常量
#include <stdio.h>
#define COLS 4
int sum2d(const int ar[][COLS], int rows);
int sum(const int ar[], int n);
int main(void)
{
int total1, total2, total3;
int * pt1;
int(*pt2)[COLS];
pt1 = (int[2]) { 10, 20 };
pt2 = (int[2][COLS]) { {1, 2, 3, -9}, { 4, 5, 6, -8 } };
total1 = sum(pt1, 2);
total2 = sum2d(pt2, 2);
total3 = sum((int []){ 4, 4, 4, 5, 5, 5 }, 6);
printf("total1 = %d\n", total1);
printf("total2 = %d\n", total2);
printf("total3 = %d\n", total3);
return 0;
}
int sum(const int ar [], int n)
{
int i;
int total = 0;
for (i = 0; i < n; i++)
total += ar[i];
return total;
}
int sum2d(const int ar [][COLS], int rows)
{
int r;
int c;
int tot = 0;
for (r = 0; r < rows; r++)
for (c = 0; c < COLS; c++)
tot += ar[r][c];
return tot;
}
-
要支持C99的编译器才能正常运行该程序示例(目前并不是所有的编译器都支持),其输出如下:
total1 = 30
total2 = 4
total3 = 27
复习题
-
下面的程序将打印什么内容?
#include <stdio.h> int main(void) { 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; }
-
在复习题1中,ref有多少个元素?
-
在复习题1中,ref的地址是什么?ref + 1是什么意思?++ref指向什么?
-
在下面的代码中,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]; -
在下面的代码中,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; -
假设有下面的声明:
int grid[30][100];
a.用1种写法表示grid[22][56]的地址
b.用2种写法表示grid[22][0] 的地址
c.用3种写法表示grid[0][0] 的地址 -
正确声明以下各变量:
a.digits是一个内含10个int类型值的数组
b.rates是一个内含6个float类型值的数组
c.mat是一个内含3个元素的数组,每个元素都是内含5个整数的数组
d.psa是一个内含20个元素的数组,每个元素都是指向char的指针
e.pstr是一个指向数组的指针,该数组内含20个char类型的值 -
a.声明一个内含6个int类型值的数组,并初始化各元素为1、2、4、8、16、32
b.用数组表示法表示a声明的数组的第3个元素(其值为4)
c.假设编译器支持C99/C11标准,声明一个内含100个int类型值的数组,并初始化最后一个元素为-1,其他元素不考虑
d.假设编译器支持C99/C11标准,声明一个内含100个int类型值的数组,并初始化下标为5、10、11、12、13的元素为101,其他元素不考虑 -
内含10个元素的数组下标范围是什么?
-
假设有下面的声明:
float rootbeer[10], things[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; -
声明一个800×600的int类型数组。
-
下面声明了3个数组:
double trots[20];
short clops[10][30];
long shots[5][10][15];
a.分别以传统方式和以变长数组为参数的方式编写处理trots数组的void函数原型和函数调用
b.分别以传统方式和以变长数组为参数的方式编写处理clops数组的void函数原型和函数调用
c.分别以传统方式和以变长数组为参数的方式编写处理shots数组的void函数原型和函数调用 -
下面有两个函数原型:
void show(const double ar[], int n); // n是数组元素的个数
void show2(const double ar2[][3], int n); // n是二维数组的行数
a.编写一个函数调用,把一个内含8、3、9和2的复合字面量传递给show()函数。
b.编写一个函数调用,把一个2行3列的复合字面量(8、3、9作为第1行,5、4、1作为第2行)传递给show2()函数。
- 打印的内容如下:
8 8
4 4
0 0
2 2 - 数组ref有4个元素,因为初始化列表中的值是4个。
- 数组名ref指向该数组的首元素(整数8)。表达式ref + 1指向该数组的第2个元素(整数4)。++ref不是有效的表达式,因为ref是一个常量,不是变量。
- ptr指向第1个元素,ptr + 2指向第3个元素(即第2行的第1个元素)。
a.12和16。
b.12和14(初始化列表中,用花括号把12括起来,把14和16括起来,所以12初始化第1行的第1个元素,而14初始化第2行的第1个元素)。 - ptr指向第1行,ptr + 1指向第2行。ptr指向第1行的第1个元素,而(ptr + 1)指向第2行的第1个元素。
a.12和16。
b.12和14(同第4题,12初始化第1行的第1个元素,而14初始化第2行的第1个元素)。 - a.&grid[22][56]
b.&grid[22][0]或grid[22]
(grid[22]是一个内含100个元素的一维数组,因此它就是首元素grid[22][0]的地址。)
c.&grid[0][0]或grid[0]或(int *) grid
(grid[0]是int类型元素grid[0][0]的地址,grid是内含100个元素的grid[0]数组的地址。这两个地址的数值相同,但是类型不同,可以用强制类型转换把它们转换成相同的类型。) - a.int digits[10];
b.float rates[6];
c.int mat[3][5];
d.char * psa[20] ;
注意,[]比*的优先级高,所以在没有圆括号的情况下,psa先与[20]结合,然后再与*结合。因此该声明与char *(psa[20]);相同。
e.char (*pstr)[20];
注意
对第e小题而言,char *pstr[20];不正确。这会让pstr成为一个指针数组,而不是一个指向数组的指针。具体地说,如果使用该声明,pstr就指向一个char类型的值(即数组的第1个成员),而pstr + 1则指向下一个字节。使用正确的声明,pstr是一个变量,而不是一个数组名。而且pstr + 1指向起始字节后面的第20个字节。 - a.int sextet[6] = {1, 2, 4, 8, 16, 32};
b.sextet[2]
c.int lots[100] = { [99] = -1};
d.int pots[100] = { [5] = 101, [10] = 101,101, 101, [3]=101}; - 0~9
- a.rootbeer[2] = value;有效。
b.scanf("%f", &rootbeer );无效,rootbeer不是float类型。
c.rootbeer = value;无效,rootbeer不是float类型。
d.printf("%f", rootbeer);无效,rootbeer不是float类型。
e.things[4][4] = rootbeer[3];有效。
f.things[5] = rootbeer;无效,不能用数组赋值。
g.pf = value;无效,value不是地址。
h.pf = rootbeer;有效。 - int screen[800][600] ;
- a.
void process(double ar[], int n);
void processvla(int n, double ar[n]);
process(trots, 20);
processvla(20, trots);
b.
void process2(short ar2[30], int n);
void process2vla(int n, int m, short ar2[n][m]);
process2(clops, 10);
process2vla(10, 30, clops);
c.
void process3(long ar3[10][15], int n);
void process3vla(int n, int m,int k, long ar3[n][m][k]);
process3(shots, 5);
process3vla(5, 10, 15, shots); - a.
show( (int [4]) {8,3,9,2}, 4);
b.
show2( (int [][3]){{8,3,9}, {5,4,1}}, 2);
编程练习
- 修改程序清单10.7的rain.c程序,用指针进行计算(仍然要声明并初始化数组)。
- 编写一个程序,初始化一个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) - 编写一个函数,返回存储在int类型数组中的最大值,并在一个简单的程序中测试该函数。
- 编写一个函数,返回存储在double类型数组中最大值的下标,并在一个简单的程序中测试该函数。
- 编写一个函数,返回存储在double类型数组中最大值和最小值的差值,并在一个简单的程序中测试该函数。
- 编写一个函数,把double类型数组中的数据倒序排列,并在一个简单的程序中测试该函数。
- 编写一个程序,初始化一个double类型的二维数组,使用编程练习2中的一个拷贝函数把该数组中的数据拷贝至另一个二维数组中(因为二维数组是数组的数组,所以可以使用处理一维数组的拷贝函数来处理数组中的每个子数组)。
- 使用编程练习2中的拷贝函数,把一个内含7个元素的数组中第3~第5个元素拷贝至内含3个元素的数组中。该函数本身不需要修改,只需要选择合适的实际参数(实际参数不需要是数组名和数组大小,只需要是数组元素的地址和待处理元素的个数)。
- 编写一个程序,初始化一个double类型的3×5二维数组,使用一个处理变长数组的函数将其拷贝至另一个二维数组中。还要编写一个以变长数组为形参的函数以显示两个数组的内容。这两个函数应该能处理任意N×M数组(如果编译器不支持变长数组,就使用传统C函数处理N×5的数组)。
- 编写一个函数,把两个数组中相对应的元素相加,然后把结果存储到第3个数组中。也就是说,如果数组1中包含的值是2、4、5、8,数组2中包含的值是1、0、4、6,那么该函数把3、4、9、14赋给第3个数组。函数接受3个数组名和一个数组大小。在一个简单的程序中测试该函数。
- 编写一个程序,声明一个int类型的3×5二维数组,并用合适的值初始化它。该程序打印数组中的值,然后各值翻倍(即是原值的2倍),并显示出各元素的新值。编写一个函数显示数组的内容,再编写一个函数把各元素的值翻倍。这两个函数都以函数名和行数作为参数。
- 重写程序清单10.7的rain.c程序,把main()中的主要任务都改成用函数来完成。
- 编写一个程序,提示用户输入3组数,每组数包含5个double类型的数(假设用户都正确地响应,不会输入非数值数据)。该程序应完成下列任务。
a.把用户输入的数据存储在3×5的数组中
b.计算每组(5个)数据的平均值
c.计算所有数据的平均值
d.找出这15个数据中的最大值
e.打印结果
每个任务都要用单独的函数来完成(使用传统C处理数组的方式)。完成任务b,要编写一个计算并返回一维数组平均值的函数,利用循环调用该函数3次。对于处理其他任务的函数,应该把整个数组作为参数,完成任务c和d的函数应把结果返回主调函数。 - 以变长数组作为函数形参,完成编程练习13。
-
#include <stdio.h> #define MONTHS 12 // 一年的月份数 #define YEARS 5 // 年数 int main(void) { // 用2010~2014年的降水量数据初始化数组 const float rain[YEARS][MONTHS] = { {4.3, 4.3, 4.3, 3.0, 2.0, 1.2, 0.2, 0.2, 0.4, 2.4, 3.5, 6.6}, {8.5, 8.2, 1.2, 1.6, 2.4, 0.0, 5.2, 0.9, 0.3, 0.9, 1.4, 7.3}, {9.1, 8.5, 6.7, 4.3, 2.1, 0.8, 0.2, 0.2, 1.1, 2.3, 6.1, 8.4}, {7.2, 9.9, 8.4, 3.3, 1.2, 0.8, 0.4, 0.0, 0.6, 1.7, 4.3, 6.2}, {7.6, 5.6, 3.8, 2.8, 3.8, 0.2, 0.0, 0.0, 0.0, 1.3, 2.6, 5.2}}; int year, month; float subtot, total; printf(" YEAR RAINFALL (inches)\n"); for (year = 0, total = 0; year < YEARS; year++) { // 每一年,各月的降水量总和 for (month = 0, subtot = 0; month < MONTHS; month++) // subtot += rain[year][month]; subtot += *(*(rain + year) + month); printf("%5d %15.1f\n", 2010 + year, subtot); total += subtot; // 5年的总降水量 } printf("\nThe yearly average is %.1f inches.\n\n", total / YEARS); printf("MONTHLY AVERAGES:\n\n"); printf(" Jan Feb Mar Apr May Jun Jul Aug Sep Oct "); printf(" Nov Dec\n"); for (month = 0; month < MONTHS; month++) { // 每个月,5年的总降水量 for (year = 0, subtot = 0; year < YEARS; year++) // subtot += rain[year][month]; subtot += *(*(rain + year) + month); printf("%4.1f ", subtot / YEARS); } printf("\n"); return 0; }
-
#include <stdio.h> void copy_arr(double target[], double source[], int size); void copy_ptr(double target[], double source[], int size); void copy_ptrs(double target[], double *start, double *end); void display(double arr[], int size); // 展示数组元素,测试用 int main(void) { 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); display(target1, 5); display(target2, 5); display(target3, 5); return 0; } void copy_arr(double target[], double source[], int size) { for (int i = 0; i < size; i++) { target[i] = source[i]; } } void copy_ptr(double target[], double source[], int size) { for (int i = 0; i < size; i++) { *(target + i) = *(source + i); } } void copy_ptrs(double target[], double *start, double *end) { // int *i = start; int i = 0; while (start < end) { *(target + i++) = *start++; } } void display(double arr[], int size) { for (int i = 0; i < size; i++) { printf("%g ", arr[i]); } printf("\n"); }
-
#include <stdio.h> int i_max(int arr[], int size); int main(void) { int arr[] = {1, 55, 3535, 5, 5466754, 1, 74574}; // 随便写的数组 int max = i_max(arr, sizeof arr / sizeof arr[0]); printf("max = %d\n", max); return 0; } int i_max(int arr[], int size) { int max = arr[0]; for (int i = 1; i < size; i++) { if (arr[i] > max) { max = arr[i]; } } return max; }
-
#include <stdio.h> int max_index(double arr[], int size); int main(void) { double arr[] = {1.1, 2.2, 5.5, 3.3, 4.4}; int index = max_index(arr, sizeof arr / sizeof arr[0]); printf("%d\n", index); return 0; } int max_index(double arr[], int size) { double max = arr[0]; int index; for (int i = 1; i < size; i++) { if (arr[i] > max) max = arr[i]; } for (int i = 0; i < size; i++) { if (arr[i] == max) { index = i; break; } } return index; }
-
#include <stdio.h> double sub_max_min(double arr[], int size); int main(void) { double arr[] = {1.1, 2.2, 3.3, 4.4, 5.5}; double result = sub_max_min(arr, sizeof arr / sizeof arr[0]); printf("%g\n", result); return 0; } double sub_max_min(double arr[], int size) { double max = arr[0], min = arr[0]; for (int i = 1; i < size; i++) { if (arr[i] > max) max = arr[i]; if (arr[i] < min) min = arr[i]; } return max - min; }
-
#include <stdio.h> void reverse(double arr[], int size); void display(double arr[], int size); int main(void) { // double arr[] = {1.1, 2.2, 3.3, 4.4, 5.5}; double arr[] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6}; // 长度为奇数和偶数分别测试 reverse(arr, sizeof arr / sizeof arr[0]); display(arr, sizeof arr / sizeof arr[0]); return 0; } void reverse(double arr[], int size) { for (int i = 0; i < size / 2; i++) { double temp = arr[i]; arr[i] = arr[size - i - 1]; arr[size - i - 1] = temp; } } void display(double arr[], int size) { for (int i = 0; i < size; i++) { printf("%g ", arr[i]); } printf("\n"); }
-
#include <stdio.h> void copy_arr(double target[], double source[], int size); void display(double arr[], int size); int main(void) { double arr[3][5] = {{1.1, 2.2, 3.3, 4.4, 5.5}, {2.2, 3.3, 4.4, 5.5, 1.1}, {1.1, 2.2, 3.3, 4.4, 6.6}}; double target[3][5]; for (int i = 0; i < 3; i++) { copy_arr(target[i], arr[i], 5); } for (int i = 0; i < 3; i++) { display(target[i], 5); } return 0; } // 拷贝编程题2 void copy_arr(double target[], double source[], int size) { for (int i = 0; i < size; i++) { target[i] = source[i]; } } void display(double arr[], int size) { for (int i = 0; i < size; i++) { printf("%g ", arr[i]); } printf("\n"); }
#include <stdio.h>
void copy_ptrs(double target[], double *start, double *end);
void display(double arr[], int size);
int main(void)
{
double arr[7] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
double target[3];
// 需要传结束元素后面一个位置的指针
copy_ptrs(target, arr + 2, arr + 5);
display(target, 3);
return 0;
}
// 复制题2
void copy_ptrs(double target[], double *start, double *end)
{
// int *i = start;
int i = 0;
while (start < end)
{
*(target + i++) = *start++;
}
}
void display(double arr[], int size)
{
for (int i = 0; i < size; i++)
{
printf("%g ", arr[i]);
}
printf("\n");
}
-
#include <stdio.h> void copy_arr(int row, int col, double source[][col], double target[][col]); void display(int row, int col, double arr[][col]); int main(void) { int row = 3, col = 5; double arr[3][5] = { {1.0, 2.0, 3.0, 4.0, 5.0}, {2.0, 2.0, 3.0, 4.0, 5.0}, {3.0, 2.0, 3.0, 4.0, 5.0}, }; double target[row][col]; copy_arr(row, col, arr, target); display(row, col, arr); display(row, col, target); return 0; } void copy_arr(int row, int col, double source[][col], double target[][col]) { for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { target[i][j] = source[i][j]; } } } void display(int row, int col, double arr[][col]) { for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { printf("%g ", arr[i][j]); } printf("\n"); } printf("\n"); }
-
#include <stdio.h> void add_arr(double arr1[], double arr2[], double result[], int size); void display(double arr[], int size); int main(void) { double arr1[] = {2.0, 4.0, 5.0, 8.0}; double arr2[] = {1.0, 0.0, 4.0, 6.0}; double result[] = {0, 0, 0, 0}; add_arr(arr1, arr2, result, sizeof arr1 / sizeof arr1[0]); display(result, sizeof arr1 / sizeof arr1[0]); return 0; } void display(double arr[], int size) { for (int i = 0; i < size; i++) { printf("%g ", arr[i]); } printf("\n"); } void add_arr(double arr1[], double arr2[], double result[], int size) { for (int i = 0; i < size; i++) { result[i] = arr1[i] + arr2[i]; } }
-
#include <stdio.h> void display(int row, int arr[][5]); void doubling(int row, int arr[][5]); int main(void) { int arr[3][5] = { {1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}}; display(3, arr); doubling(3, arr); display(3, arr); return 0; } void display(int row, int arr[][5]) { for (int i = 0; i < row; i++) { for (int j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } printf("\n"); } void doubling(int row, int arr[][5]) { for (int i = 0; i < row; i++) { for (int j = 0; j < 5; j++) { arr[i][j] *= 2; } } }
-
#include <stdio.h> #define MONTHS 12 // 一年的月份数 #define YEARS 5 // 年数 float get_total(const float arr[][MONTHS], int row); void ave_months(const float arr[][MONTHS], int row); int main(void) { // 用2010~2014年的降水量数据初始化数组 const float rain[YEARS][MONTHS] = { {4.3, 4.3, 4.3, 3.0, 2.0, 1.2, 0.2, 0.2, 0.4, 2.4, 3.5, 6.6}, {8.5, 8.2, 1.2, 1.6, 2.4, 0.0, 5.2, 0.9, 0.3, 0.9, 1.4, 7.3}, {9.1, 8.5, 6.7, 4.3, 2.1, 0.8, 0.2, 0.2, 1.1, 2.3, 6.1, 8.4}, {7.2, 9.9, 8.4, 3.3, 1.2, 0.8, 0.4, 0.0, 0.6, 1.7, 4.3, 6.2}, {7.6, 5.6, 3.8, 2.8, 3.8, 0.2, 0.0, 0.0, 0.0, 1.3, 2.6, 5.2}}; int year, month; float subtot, total; printf(" YEAR RAINFALL (inches)\n"); total = get_total(rain, YEARS); printf("\nThe yearly average is %.1f inches.\n\n", total / YEARS); printf("MONTHLY AVERAGES:\n\n"); printf(" Jan Feb Mar Apr May Jun Jul Aug Sep Oct "); printf(" Nov Dec\n"); ave_months(rain,YEARS); return 0; } float get_total(const float arr[][MONTHS], int row) { float total, subtot; int year,month; for (year = 0, total = 0; year < row; year++) { // 每一年,各月的降水量总和 for (month = 0, subtot = 0; month < MONTHS; month++) subtot += arr[year][month]; printf("%5d %15.1f\n", 2010 + year, subtot); total += subtot; // 5年的总降水量 } return total; } void ave_months(const float arr[][MONTHS], int row) { float subtot; int month,year; for (month = 0; month < MONTHS; month++) { // 每个月,5年的总降水量 for (year = 0, subtot = 0; year < YEARS; year++) subtot += arr[year][month]; printf("%4.1f ", subtot / YEARS); } printf("\n"); }
-
#include <stdio.h> void input(double arr[][5], int row); double sub_ave(double arr[], int size); double average(double arr[][5], int row); double max(double arr[][5], int row); int main(void) { double arr[3][5]; input(arr, 3); for (int i = 0; i < 3; i++) { double result = sub_ave(arr[i], 5); printf("%g ", result); } printf("\n"); double result = average(arr, 3); printf("%g\n", result); result = max(arr, 3); printf("%g\n", result); return 0; } void input(double arr[][5], int row) { printf("enter five groups of numbers and every group has five numbers.\n"); for (int i = 0; i < row; i++) { printf("enter 第 %d group:\n", i + 1); for (int j = 0; j < 5; j++) { scanf("%lf", &arr[i][j]); } } } double sub_ave(double arr[], int size) { double sum = 0.0; for (int i = 0; i < size; i++) { sum += arr[i]; } return sum / size; } double average(double arr[][5], int row) { double sum = 0.0; for (int i = 0; i < row; i++) { for (int j = 0; j < 5; j++) sum += arr[i][j]; } return sum / 5 / row; } double max(double arr[][5], int row) { double max = arr[0][0]; for (int i = 0; i < row; i++) { for (int j = 0; j < 5; j++) if (arr[i][j] > max) max = arr[i][j]; } return max; }
-
#include <stdio.h> void input(int row, int col, double arr[row][col]); double sub_ave(double arr[], int size); double average(int row, int col, double arr[row][col]); double max(int row, int col, double arr[row][col]); int main(void) { double arr[3][5]; input(3, 5, arr); for (int i = 0; i < 3; i++) { double result = sub_ave(arr[i], 5); printf("%g ", result); } printf("\n"); double result = average(3, 5, arr); printf("%g\n", result); result = max(3, 5, arr); printf("%g\n", result); return 0; } void input(int row, int col, double arr[row][col]) { printf("enter five groups of numbers and every group has five numbers.\n"); for (int i = 0; i < row; i++) { printf("enter 第 %d group:\n", i + 1); for (int j = 0; j < col; j++) { scanf("%lf", &arr[i][j]); } } } double sub_ave(double arr[], int size) { double sum = 0.0; for (int i = 0; i < size; i++) { sum += arr[i]; } return sum / size; } double average(int row, int col, double arr[row][col]) { double sum = 0.0; for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) sum += arr[i][j]; } return sum / 5 / row; } double max(int row, int col, double arr[row][col]) { double max = arr[0][0]; for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) if (arr[i][j] > max) max = arr[i][j]; } return max; }