初始化数组
int powers[8] = {1,2,4,6,8,16,32,64};
/* 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;
}
/*
Month 1 has 31 days.
Month 2 has 28 days.
Month 3 has 31 days.
Month 4 has 30 days.
Month 5 has 31 days.
Month 6 has 30 days.
Month 7 has 31 days.
Month 8 has 31 days.
Month 9 has 30 days.
Month 10 has 31 days.
Month 11 has 30 days.
Month 12 has 31 days.
*/
注意:使用const声明数组
有时需要把数组设置为只读。
这样,程序只能从数组中检索值,不能把新值写入数组。
要创建只读数组,应该用const声明和初始化数组。
因此, 程序清单10.1中初始化数组应改成:
const int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
这样修改后,程序在运行过程中就不能修改该数组中的内容。
和普通变量一样,应该使用声明来初始化 const 数据,因为一旦声明为 const,便不能再给它赋值。
如果不初始化数组, 数组元素和未初始化的普通变量一样,其中储存的都是垃圾值;
但是,如果部分初始化数组,剩余的元素就会被初始化为0。
如果初始化列表的项数多于数组元素个数,编译器将其视为错误。
但是,可以省略方括号中的数字,让编译器自动匹配数组大小和初始化列表中的项数(见程序清单10.4)。
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++)
指定初始化器
int arr[6] = {[5] = 212}; // 把arr[5]初始化为212
//对于一般的初始化,在初始化一个元素后,未初始化的元素都会被设置为0。
int stuff[] = {1, [6] = 23};
int staff[] = {1, [6] = 4, 9, 10};
//编译器会把数组的大小设置为足够装得下初始化的值。
//所以,stuff数组有7个元素,编号为0~6;
//而staff数组的元素比stuff数组多两个(即有9个元素)。
多维数组
float rain[5][12]; // rain是一个内含5个元素的数组
这说明数组rain有5个元素,
至于每个元素的情况,要查看声明的其余部分:
floatrain[5][12] ; // 一个内含12个float类型元素的数组
这说明每个元素的类型是float[12],
也就是说,rain的每个元素本身都是一个内含12个float类型值的数组。
const float rain[5][12] =
{
{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。
如果某列表中的数值个数超出了数组每行的元素个数,则会出错,但是这并不会影响其他行的初始化。
初始化时也可省略内部的花括号,只保留最外面的一对花括号。
*/
指针和数组
数组名是数组首元素的地址。
也就是说,如果flizny是一个数组,下面的语句成立:
flizny == &flizny[0]; // 数组名是该数组首元素的地址
flizny和&flizny[0]都表示数组首元素的内存地址(&是地址运算符)。
两者都是常量,在程序的运行过程中,不会改变。
但是,可以把它们赋值给指针变量,然后可以修改指针变量的值,如程序清单10.8所示。
注意指针加上一个数时,它的值发生了什么变化(转换说明%p通常以十六进制显示指针的值)。
//程序清单10.8 pnt_add.c程序
// 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
第2行打印的是两个数组开始的地址,下一行打印的是指针加1后的地址,以此类推。
注意,地址是十六进制的,因此dd比dc大1,a1比a0大1。
但是,显示的地址是怎么回事?
0x7fff5fbff8dc + 1是否是0x7fff5fbff8de?
0x7fff5fbff8a0 + 1是否是0x7fff5fbff8a8?
我们的系统中,地址按字节编址,short类型占用2字节,double类型占用8字节。
在C中,指针加1指的是增加一个存储单元。
对数组而言,这意味着把加1后的地址是下一个元素的地址,而不是下一个字节的地址(见图 10.3)。
这是为什么必须声明指针所指向对象类型的原因之一。
只知道地址不够,因为计算机要知道储存对象需要多少字节
(即使指针指向的是标量变量,也要知道变量的类型,否则*pt就无法正确地取回地址上的值)。
*/
dates + 2 == &date[2] // 相同的地址
*(dates + 2) == dates[2] // 相同的值
*(dates + 2) // dates第3个元素的值
*dates + 2 // dates第1个元素的值加2
等价代码形式:
int sum(int *ar, int n)
{
// 其他代码已省略
}
int sum(int ar[], int n);
{
//其他代码已省略
}
程序清单10.12 order .c程序
/* 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
只有(*p3)++改变了数组元素的值,其他两个操作分别把p1和p2指向数组的下一个元素。
*/
指针操作
//程序清单10.13 ptr_ops.c程序
// ptr_ops.c -- 指针操作
#include <stdio.h>
int main(void)
{
int urn[5] = { 100, 200, 300, 400, 500 }; //声明1个数组并初始化
int * ptr1, *ptr2, *ptr3; //声明3个指针变量
ptr1 = urn; // 把一个地址赋给指针ptr1(urn第一个元素的地址)
ptr2 = &urn[2]; // 把一个地址赋给指针ptr2(urn第3个元素的地址)
// 解引用指针,以及获得指针的地址
printf("pointer value, dereferenced pointer, pointer address:\n");
printf("ptr1 = %p, *ptr1 =%d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
//ptr1 = 0x7fff5fbff8d0, *ptr1 = 100, &ptr1 = 0x7fff5fbff8c8
//ptr1为元素urn[0]的地址
//*ptr1为元素urn[0]的值
//&ptr1为这个指针指向的地址
//和所有变量一样,指针变量也有自己的地址和值。
//对指针而言, &运算符给出指针本身的地址。
//本例中,ptr1储存在内存编号为 0x7fff5fbff8c8 的地址上,该存储单元储存的内容是0x7fff5fbff8d0,即urn的地址。
//因此&ptr1是指向ptr1的指针,而ptr1是指向utn[0]的指针。
// 指针加法
ptr3 = ptr1 + 4; //ptr1为一个指针,存储一个地址,地址加4
//整数会和指针所指向类型的大小(以字节为单位)相乘,然后把结果与初始地址相加。
//在C中,指针加1指的是增加一个存储单元。
//对数组而言,这意味着把加1后的地址是下一个元素的地址,而不是下一个字节的地址。
//因此 ptr1 + 4 与 &urn[4] 等价
printf("\nadding an int to a pointer:\n");
printf("ptr1 + 4 = %p, *(ptr1 + 4) = %d\n", ptr1 + 4, *(ptr1 + 4));
//ptr1 + 4 = 0x7fff5fbff8e0, *(ptr1 + 4) = 500
//ptr + 4为元素urn[4]的地址
//*(ptr + 4)为元素urn[4]的值
ptr1++;// 递增指针
printf("\nvalues after ptr1++:\n");
printf("ptr1 = %p, *ptr1 =%d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
//ptr1 = 0x7fff5fbff8d4, *ptr1 = 200, &ptr1 = 0x7fff5fbff8c8
//递增后ptr1表示下一个元素urn[1]的地址,加*表示值,加&表示该指针指向的地址
ptr2--;// 递减指针
printf("\nvalues after --ptr2:\n");
printf("ptr2 = %p, *ptr2 = %d, &ptr2 = %p\n", ptr2, *ptr2, &ptr2);
//ptr2 = 0x7fff5fbff8d4, *ptr2 = 200, &ptr2 = 0x7fff5fbff8c0
//ptr2原来是表示urn[2]的地址,递减后表示urn[1]的地址
--ptr1;// 恢复为初始值
++ptr2;// 恢复为初始值
printf("\nPointers reset to original values:\n");
printf("ptr1 = %p, ptr2 = %p\n", ptr1, ptr2);
//ptr1 = 0x7fff5fbff8d0, ptr2 = 0x7fff5fbff8d8
// 一个指针减去另一个指针
printf("\nsubtracting one pointer from another:\n");
printf("ptr2 = %p, ptr1 = %p, ptr2 - ptr1 = %td\n", ptr2, ptr1, ptr2 - ptr1);
//ptr2 = 0x7fff5fbff8d8, ptr1 = 0x7fff5fbff8d0, ptr2 - ptr1 = 2
//指针相减表示元素urn[0]与urn[2]的距离,是指相隔2个int,而不是2字节
/*
可以计算两个指针的差值。
通常,求差的两个指针分别指向同一个数组的不同元素,通过计算求出两元素之间的距离。
差值的单位与数组类型的单位相同。
例如,程序清单10.13的输出中,ptr2 - ptr1得2,
意思是 这两个指针所指向的两个元素相隔两个int,而不是2字节。
只要两个指针都指向相同的数组(或者其中一个指针指向数组后面的第1个地址),C都能保证相减运算有效。
如果指向两个不同数组的指针进行求差运算可能会得出一个值,或者导致运行时错误。
*/
// 一个指针减去一个整数
printf("\nsubtracting an int from a pointer:\n");
printf("ptr3 = %p, ptr3 - 2 = %p\n", ptr3, ptr3 - 2);
//ptr3 = 0x7fff5fbff8e0, ptr3 - 2 = 0x7fff5fbff8d8
/*
可以使用-运算符从一个指针中减去一个整数。
指针必须是第1个运算对象,整数是第2个运算对象。
该整数将乘以指针指向类型的大小(以字节为单位),然后用初始地址减去乘积。
所以ptr3 - 2与 &urn[2]等价,因为ptr3指向的是&arn[4]。
如果相减的结果超出了初始指针所指向数组的范围,计算结果则是未定义的。
除非正好超过数组末尾第一个位置,C保证该指针有效。
*/
/*
比较:使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象。
注意,这里的减法有两种。
可以用一个指针减去另一个指针得到一个整数,或者用一个指针减去一个整数得到另一个指针。
*/
return 0;
}
编程练习
1、
修改程序清单10.7的rain.c程序,用指针进行计算(仍然要声明并初始化数组)。
//程序清单10.7 rain.c程序
/* 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];
//改成指针 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;
}
/*
下面是该程序的输出:
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
*/
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>
void copy_arr();
void copy_ptr();
void copy_ptrs();
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];
for (int i = 0; i < 5; i++)
{
printf("source[%d] = %.1lf ", i, source[i]);
}
printf("\n");
copy_arr(target1, source, 5);
copy_ptr(target2, source, 5);
copy_ptrs(target3, source, source + 5);
return 0;
}
void copy_arr(double x[], double y[], int z)
{
for (int i = 0; i < z; i++)
{
x[i] = y[i];
printf("target1[%d] = %.1lf ", i, x[i]);
}
printf("\n");
}
void copy_ptr(double * x, double * y, int z)
{
for (int i = 0; i < z; i++)
{
*(x + i) = *(y + i);
printf("target2[%d] = %.1lf ", i, x[i]);
}
printf("\n");
}
void copy_ptrs(double * x, double * y, double * z)
{
while (y < z)
{
*x = *y;
x++;
y++;
}
for (int i = 0; i < 5; i++)
{
printf("target3[%d] = %.1lf ", i, *(x+1+i));
}
printf("\n");
}
3、
编写一个函数,返回储存在int类型数组中的最大值,
并在一个简单的程序中测试该函数。
#include <stdio.h>
int max();
int main(void)
{
int a[7] = { 19,97,12,26,3,9,8 };
int m;
m = max(a);
printf("max = %d\n", m);
return 0;
}
int max(int * x)
{
int m = 0;
for (int i = 0; i < 7; i++)
{
if (m < x[i])
m = x[i];
}
return m;
}
4、
编写一个函数,返回储存在double类型数组中最大值的下标,
并在一个简单的程序中测试该函数。
#include <stdio.h>
int max();
int main(void)
{
int a[7] = { 19,97,12,26,3,9,8 };
int m;
m = max(a);
printf("max = %d\n", m);
return 0;
}
int max(int * x)
{
int m = 0;
int z;
for (int i = 0; i < 7; i++)
{
if (m < x[i])
{
m = x[i];
z = i;
}
}
return z;
}
5、
编写一个函数,返回储存在double类型数组中最大值和最小值的差值,
并在一个简单的程序中测试该函数。
#include <stdio.h>
int max();
int main(void)
{
int a[7] = { 19,97,12,26,3,9,8 };
int m;
m = max(a);
printf("max = %d\n", m);
return 0;
}
int max(int * x)
{
int m = 0;
int z;
for (int i = 0; i < 7; i++)
{
if (m < x[i])
m = x[i];
}
int c = m;
for (int i = 0; i < 7; i++)
{
if (c > x[i])
c = x[i];
}
int s = m - c;
return s;
}
6、
编写一个函数,把double类型数组中的数据倒序排列,
并在一个简单的程序中测试该函数。
#include<stdio.h>
#define SIZE 8
void revert_arr(double arr[], int n);
int main()
{
int i;
double arr[SIZE];
printf("Before enter %d double numbers:\n", SIZE);
for (i = 0; i < SIZE; i++)
scanf("%lf", &arr[i]);
printf("Before revert, the array is:\n");
for (i = 0; i < SIZE; i++)
{
printf(" %.2lf", arr[i]);
}
printf("\n");
revert_arr(arr, SIZE);
printf("After revert, the array is:\n");
for (i = 0; i < SIZE; i++)
{
printf(" %.2lf", arr[i]);
}
printf("\n");
return 0;
}
void revert_arr(double arr[], int n)
{
int i;
for (i = 0; i < n / 2; i++)
{
double x = arr[i];
arr[i] = arr[n - 1 - i];
arr[n - 1 - i] = x;
}
}
7、
编写一个程序,初始化一个double类型的二维数组,
使用编程练习2中的一个拷贝函数把该数组中的数据拷贝至另一个二维数组中
(因为二维数组是数组的数组,所以可以使用处理一维数组的拷贝函数来处理数组中的每个子数组)。
#include <stdio.h>
void copy_arr();
int main(void)
{
double source[3][4] =
{
{1,2,3,4},
{5,6,7,8},
{9,0,1,2}
};
double target1[3][4];
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("source[%d][%d] = %.1lf ", i, j,source[i][j]);
}
printf("\n");
}
printf("\n");
printf("\n");
copy_arr(target1, source, 3,4);
return 0;
}
void copy_arr(double x[][4], double y[][4], int m,int n) //多维不能写成x[][]
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
x[i][j] = y[i][j];
printf("target1[%d][%d] = %.1lf ", i,j, x[i][j]);
}
printf("\n");
}
printf("\n");
}
8、
使用编程练习2中的拷贝函数,
把一个内含7个元素的数组中第3~第5 个元素拷贝至内含3个元素的数组中。
该函数本身不需要修改,只需要选择合适的实际参数(实际参数不需要是数组名和数组大小,只需要是数组元素的地址和待处理元素的个数)。
/* Programming Exercise 10-8 */
#include <stdio.h>
#define LEN1 7
#define LEN2 3
void copy_arr(int ar1[], const int ar2[], int n);
void show_arr(const int[], int);
int main(void)
{
int orig[LEN1] = { 1,2,3,4,5,6,7 };
int copy[LEN2];
show_arr(orig, LEN1);
copy_arr(copy, orig + 2, LEN2);
show_arr(copy, LEN2);
return 0;
}
void copy_arr(int ar1[], const int ar2[], int n)
{
int i;
for (i = 0; i < n; i++)
ar1[i] = ar2[i];
}
void show_arr(const int ar[], int n)
{
int i;
for (i = 0; i < n; i++)
printf("%d ", ar[i]);
putchar('\n');
}
9、
编写一个程序,初始化一个double类型的3×5二维数组,使用一个处理变长数组的函数将其拷贝至另一个二维数组中。
还要编写一个以变长数组为形参的函数以显示两个数组的内容。
这两个函数应该能处理任意N×M数组 (如果编译器不支持变长数组,就使用传统C函数处理N×5的数组)。
10、
编写一个函数,把两个数组中相对应的元素相加,然后把结果储存到第 3 个数组中。
也就是说,如果数组1中包含的值是2、4、5、8,数组2中包含的值是1、0、4、6,那么该函数把3、4、9、14赋给第3个数组。
函数接受3个数组名和一个数组大小。
在一个简单的程序中测试该函数。
#include <stdio.h>
#define LEN 4
void add();
int main(void)
{
int x[LEN] = { 2,4,5,8 };
int y[LEN] = { 1,0,4,6 };
int z[LEN];
for (int i = 0; i < LEN; i++)
{
printf("x[%d] = %d ", i, x[i]);
}
printf("\n");
for (int i = 0; i < LEN; i++)
{
printf("y[%d] = %d ", i, y[i]);
}
printf("\n");
add(x, y, z, LEN);
return 0;
}
void add(int a1[], int a2[],int a3[], int n)
{
for (int i = 0; i < LEN; i++)
{
a3[i] = a1[i] + a2[i];
printf("z[%d] = %d ", i, a3[i]);
}
printf("\n");
}
11、
编写一个程序,声明一个int类型的3×5二维数组,并用合适的值初始化它。
该程序打印数组中的值,然后各值翻倍(即是原值的2倍),并显示出各元素的新值。
编写一个函数显示数组的内容,再编写一个函数把各元素的值翻倍。
这两个函数都以函数名和行数作为参数。
#include <stdio.h>
void doublex();
void show();
int main(void)
{
int x[3][5] =
{
{1,2,3,2,1},
{3,2,1,2,3},
{1,2,3,2,1}
};
show(x, 3);
int y[3][5];
doublex(x, y, 3);
show(y, 3);
return 0;
}
void show(int a[][5],int n)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n + 2; j++)
{
printf("x[%d][%d] = %d ", i,j, a[i][j]);
}
printf("\n");
}
printf("\n");
}
void doublex(int a1[][5], int a2[][5], int n)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n + 2; j++)
{
a2[i][j] = a1[i][j] * 2;
}
}
}
12、
重写程序清单10.7的rain.c程序,把main()中的主要任务都改成用函数来完成。
13、
编写一个程序,提示用户输入3组数,每组数包含5个double类型的数 (假设用户都正确地响应,不会输入非数值数据)。
该程序应完成下列任务。
a.把用户输入的数据储存在3×5的数组中
b.计算每组(5个)数据的平均值
c.计算所有数据的平均值
d.找出这15个数据中的最大值
e.打印结果
每个任务都要用单独的函数来完成(使用传统C处理数组的方式)。
完成任务b,要编写一个计算并返回一维数组平均值的函数,利用循环调用该函数3次。
对于处理其他任务的函数,应该把整个数组作为参数,完成任务c和d的函数应把结果返回主调函数。
14、
以变长数组作为函数形参,完成编程练习13。