第九章 数组和指针

9.1数组

9.1.1初始化

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月1日08:21:55
//打印每月的天数
#include <stdio.h>
#define MONTHS 12

int main(void)
{
	int days[MONTHS] = {31,28,31,30,31,30,31,30,31,31,30,31};
	int index;

	for( index =0; index < MONTHS; index++ )
		printf( "Month %d 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 30 days.
Month 9 has 31 days.
Month 10 has 31 days.
Month 11 has 30 days.
Month 12 has 31 days.

●对数组使用const的方法

  有时需要使用只读数组,也就是从数组中读取数值,但是程序不向数组中写数据。在这种情况下声明并初始化数组时,建议使用关键字const。示例如下:

  const int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};

  这样,程序会把数组中每个元素当成常量来处理。和普通变量一样,需要在声明const数组时对其进行初始化,因为在声明之后,不能再对它赋值。

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月1日08:49:13
#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    -858993460
 1    -858993460
 2    -858993460
 3    -858993460

  与普通变量相似,在初始化之前数组元素的数值是不定的。编译器使用的数值是存储单元中已有的数值,因此上面的输出结果是不确定。

●存储类解释

  和其他变量相似,数组可以被定义为多种存储类(storage class)。现在提起存储类的原因是:不同存储类有时具有不同的属性,因此不能把本章的知识推广到其他存储类。例如,如果没有进行初始化,一些存储类的变量和数组会把他们的存储单元设置为0。

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月1日09:11:20
#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。如果初始化列表中项目的个数大于数组大小,编译器会毫不留情地认为这是一个错误。然而,可以用另外一种形式以避免受到编译器的此类奚落:您可以省略括号中的数字,从而让编译器自动匹配数组的大小和初始化列表中的项目数目。

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月1日09:25:26
#include <stdio.h>

int main(void)
{
	const int days[] = {31,28,31,30,31,30,31,31,30,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;
}

结果:

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.

  该程序需要注意的是:

●当使用空的方括号对数组进行初始化时,编译器会根据列表中的数值数目来确定数组的大小。

●注意for循环的控制语句。由于人工计算容易出错,因此可以让计算机来计算数组的大小。运算符sizeof给出其后的对象或类型的大小(以字节为单位)。因此sizeof days是整个数组的大小(以字节为单位),sizeof days[0]是一个元素的大小(以字节为单位)。整个数组的大小除以单个元素的大小就是数组中元素的个数。

9.1.2指定初始化项目

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月1日09:40:21
#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;
 }

结果:

 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中这样,则这些数值将用来对后续的数组元素初始化。也就是说,把31赋给days[4]之后,接着把30和31分别赋给days[5]he days[6]。第二,如果多次对一个元素进行初始化,则最后一次有效。例如,在上述程序中,前面把days[1]初始化为28,而后面指定初始化[1]-=29覆盖了前面的数值,于是days[1]的数值最终为29。

9.1.3为数组赋值

  声明完数组后,可以借助数组的索引(即下标)对数组成员进行赋值。C不支持把数组作为一个整体来进行赋值,也不支持用花括号括起来的列表进行赋值(初始化的时候除外)。

9.1.4数组边界

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月1日09:59:36
#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);

	return 0;
}

结果:

value1 = 44, value2 = 88
-1 -1
 0 1
 1 3
 2 5
 3 7
 4 9
 5 -858993460
 6 44
value1 = 44, value2 = 88

  使用超出数组边界的索引会改变其他变量的数值。对于不同的编译器,输出结果可能不同。为什么C会允许这种事情发生?这仍然是出于C信任程序员的原则。不检查边界能够让C程序的运行速度更快。在程序运行之前,索引的值有可能尚未确定,所以编译器此时不能找出所有索引错误。为保证程序的正确性,编译器必须在运行时检查每个索引是否合法的代码,这会导致程序的运行速度减慢。因此,C相信程序员的代码是正确的,从而可以得到速度更快的程序。

9.1.5指定数组大小

  直到C99标准出现之前,声明数组时在方括号内只能使用整数常量表达式。sizeof表达式被认为是一个整数常量,而(C++不一样)一个const值却不是整数常量。并且该表达式的值必须大于0:

int n = 5int 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类型指派为int类型
float a8[n];  //C99之前不允许
float a9[m];  //C99之前不允许

  遵循C90标准的C编译器不允许最后两个声明。而C99标准允许这两个声明,但是这创建了一种新数组,称为变长数组,简称VLA。

9.2多维数组

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//针对若干年的降水量数据,计算降水总量、年降水平均量,以及月降水量
//2022年5月2日09:02:25
#include <stdio.h>
#define MONTHS 12  //一年的月份数
#define YEARS 5  //降水量数据的年份

int main(void)
{
	//把数组初始化为2000年到2004年的降水量数据
	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", 2000 + year, subtot );
		total += subtot;  //所有年度的降水量
	}
	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++)
	{
		for( year = 0, subtot = 0; year < YEARS; year++ )
			subtot += rain[year][month];
		printf( "%4.1f " ,subtot/YEARS );
	}
	printf( "\n" );

	return 0;
}

结果:

YEAR RAINFALL (inches)
 2000            32.4
 2001            37.9
 2002            49.8
 2003            44.0
 2004            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

9.3指针和数组

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月2日09:15:16
#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 %10s\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:   00D6FCD8   00D6FC98
pointers + 1:   00D6FCDA   00D6FCA0
pointers + 2:   00D6FCDC   00D6FCA8
pointers + 3:   00D6FCDE   00D6FCB0

●在C中,对一个指针加1的结果是对该指针增加1个存储单元。对于数组而言,地址会增加到下一个元素的地址,而不是下一个地址。

●指针的数值就是它所指向的对象的地址。抵制的内部表达方式是由硬件来决定的。很多计算机都是以字节编址的,这意味着对每个内存字节顺序进行编号。对于包含多个字节的数据类型,比如double类型的变量,对象的地址通常指的是其首字节的地址。

●在指针前运用运算符*就可以得到该指针所指向的对象的数值。

●在指针加1,等价于对指针的值加上它指向的对象的字节大小。

  下面的等式体现出了C的优点:

dates + 2 == &date[2];//相同的地址

*(dates + 2) == dates[2]; //相同的值

  这些关系总结了数组和指针间的密切关系:可以用指针标识数组的每个元素,并得到每个元素的数值。从本质上说,对一个对象有两种不同的符号表示方法。C语言标准在描述数组时,确实借助了指针的概念。例如,定义ar[n]时,意思是*(ar+n),即“寻址到内存中的ar,然后移动n个单位,再取出数值”。

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月2日09:53:38 
#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 ) );
	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.

9.4函数、数组和指针

  无论在任何情况下,形式int *ar都表示ar是指向int 的指针。形式int ar[]也可以表示ar是指向int的指针,但是只是声明形式参量时才可以这样使用。

●声明数组参量

  由于数组名就是数组首元素的地址,所以如果实际参数是一个数组名,那么形式参量必须与之相匹配的指针。在(而且仅在)这种场合中,C对于int ai[]和int * ar作出同样解释,即ar是指向Int的指针。由于原型允许省略名称,因此下面的4种原型都是等价的:

int sum( int * ar, int n );
int sum( int * , int  );
int sum( int  ar[], int n );
int sum( int  [], int  );

  定义函数时,名称是不可省略的。因此,以下形式等价:

int sum( int * ar, int n )
{
	//代码
}
int sum( int  ar[], int n )
{
	//代码
}

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//sum_arr1.c对一个数组所有元素求和
#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 4 bytes.
The total number of marbles is 190.
The size of marbles is 40 bytes.

9.4.1使用指针参数

  使用数组的函数需要知道何时开始和何时结束数组。函数sum()使用是一个指针参量来确定数组的开始点,使用一个整数参数来指明数组元素的个数(指针参量同时确定了数组中数据的类型)。另一种方法是传递两个指针,第一个指针指明数组的起始地址(同前面的方法相同),第二个指针指明数组的结束地址。

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月3日07:22:13
#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;  //把值累加到total上
		start++;  //把指针向前推进到下一个元素
	}
	return total;
}

结果:

The total number of marbles is 190.

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月3日07:31:48
#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)++ = %\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

9.5指针操作

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月3日07:50:47
#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, *ptrl = %d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1 );
	//指针加法
	ptr3 = ptr1 + 4;
	printf( "\nadding an int to a pointer:\n" );
	printf( "ptr1 + 4 = %p, *(ptr1 + 3) = %d\n", ptr1 + 4, *(ptr1 + 3) );
	ptr1++;  //递增指针
	printf( "\nvalues after ptr1++\n");
	printf( "ptr1 = %p, *ptrl = %d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1 );
	ptr2--;  //递减指针
	printf( "\n values 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( "\nsubstracting one pointer from another:\n");
	printf( "ptr2 = %p, ptr1 = %p, ptr2 - ptr1 = %d\n", ptr1, ptr2, ptr2 - ptr1 );//一个指针减去一个整数
	printf( "\nsubstracting an int from a pointer:\n");
	printf( "ptr3 = %p, ptr3 - ptr2 = %d\n", ptr3, ptr3 - ptr2 );

	return 0;
}

结果:

pointer value, dereferenced pointer, pointer address:
ptr1 = 006FF754, *ptrl = 100, &ptr1 = 006FF748

adding an int to a pointer:
ptr1 + 4 = 006FF764, *(ptr1 + 3) = 400

values after ptr1++
ptr1 = 006FF758, *ptrl = 200, &ptr1 = 006FF748

 values after --ptr2
ptr2 = 006FF758, *ptr2 = 200, &ptr2 = 006FF73C

Pointers reset to original values:
ptr1 = 006FF754, ptr2 = 006FF75C

substracting one pointer from another:
ptr2 = 006FF754, ptr1 = 006FF75C, ptr2 - ptr1 = 2

substracting an int from a pointer:
ptr3 = 006FF764, ptr3 - ptr2 = 2

赋值 ------ 可以把一个地址赋给指针。通常使用数组名或地址运算符&来进行地址赋值。注意:地址应该和指针类型兼容。也就是说,不能把一个double类型的地址赋给指向int类型的指针。C99允许使用类型指派这样做,但是我们不推荐使用这种方法。

求值或取值 ------运算符可取出指针只想地址中存储的数值。

●**取指针地址 **------ 指针变量同其他变量一样具有地址和数值,使用运算符&可以得到存储指针本身的地址。

将一个整数加给指针 ------ 可以使用+运算符来把一个整数加给一个指针,或者把一个指针加给一个正数。两种情况下,这个整数都会和指针所指类型的字节数相乘,然后所得到的结果会加到初始地址上。

增加指针的值 ------ 可以通过一般的加法或增量运算符增加一个指针的值。对指向某数组元素的指针做增量运算,可以让指针指向该数组的下一个元素。

从指针中减去一个整数 ------ 可以使用-运算符从一个指针中减去一个整数。指针必须是第一个操作数,或者是一个指向整数的指针。这个整数都会和指针所指类型的字节数相乘,然后所得的结果会从初始地址中减掉。

减小指针的值 ------ 指针也可以做减量运算

求差值 ------ 可以求出两个指针间的差值。通常对分别指向同一个数组内两个元素的指针求差值,以求出元素之间的距离。差值的单位是相应类型的大小。

比较 ------ 可以使用关系运算符来比较两个指针的值,前提是两个指针具有相同的类型。

  注意,这里有两种形式的减法。可以用一个指针减掉另一个指针得到一个整数,也可以从一个指针中减去一个整数得到一个指针。

  在进行指针的增量和减量运算时,需要牢记一些注意事项。计算机并不检查指针是否仍然是指向某个数组元素。C保证指向数组元素的指针和指向数组后的第一个地址的指针都是有效的。但是如果指针在进行了增量或减量运算后超出了这个范围,后果将是未知的。另外,可以指向一个数组元素的指针进行取值运算。但不能对数组后的第一个地址的指针进行取值运算,尽管这样的指针是合法的。

对未初始化的指针取值

  使用指针,有一个规则需要特别注意:不能对未初始化的指针取值。例如:

int * pt;//未初始化的指针

*pt = 5;//一个可怕的错误

  为什么这样的代码危害极大?这段程序的第二行表示把数值5存储在pt所指向的地址。但是由于pt没有被初始化,因此它的值是随机的,不知道5会被存储到什么位置。这个位置也许对系统危害不大,但也许会覆盖程序数据或者代码,甚至导致程序的崩溃。切记:当创建一个指针时,系统只分配了用来存储指针本身的内存空间,并不分配用来存储数据的内存空间。因此在使用程序之前,必须给它赋予一个已经分配的内存地址。比如,可以把一个已存在的变量地址赋给指针(当您使用带有一个指针参量的函数时,就属于这种情况)。或者使用函数malloc()来分配内存。

9.6保护数组内容

  通常我们直接传递数值;只有需要在函数中修改该值时,我们才传递指针。对于处理数组的函数,只能传递指针,原因是这样能使程序的效率更高。如果通过值向函数传递数组,那么函数中必须分配足够存放一份原数组的拷贝的存储空间,然后把原数组的所有数据复制到这个新数组中。如果简单地把数组的地址传递给函数,然后让函数直接读取原数组,程序的效率会更高。通常C传递数据的值,其原因是要保证原始数据的完整性。函数使用原始数据的一份拷贝,这样它就不会意外地修改原始数据。但是,由于处理数据的函数直接操作原始数据,所以它能够修改原数组。

9.6.1对形式参量使用const

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月3日09:18:09
//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 orginal 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 orginal 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

9.6.2有关const的其他内容

const double PI = 3.14159;

  以上也可以使用#define指令实现。但使用const还可以创建数组常量、指针常量以及指向常量的指针。

#define MONTHS 12;
const int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
days[9] = 44;//编译错误

  指向常量的指针不能用于修改数值。

double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5 };
const double * pd = rates;//pd指向数组开始处

  pd不可用来修改它所指向的数值。

* pd = 29.89;//不允许
pd[2] = 222.22;//不允许
rates[0] = 99.99;//允许,因为rates不是常量

  无论采用数组符号还是指针符号,都不能使用pd修改所指向的值。但请注意因为rates并没有声明为常量,所以仍可以使用rates来修改数值。

pd++;//让pd指向rates[1]-允许

  通常把指向常量的指针用作函数参量,以表明函数不会用这个指针来修改数据。如:
void show_array(const double ar[], int n);

  关于指针赋值和const有一些规则需要注意。首先,将常量或非常量数据的地址赋给指向常量的指针是合法的,然而,只有非常量数据的地址才可以赋给普通的指针。

  还可以用const来声明并初始化指针,以保证指针不会指向别处,关键在于const的位置。

double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5 };
double * const pd = rates;//pd指向数组开始处
pd = &rates[2];//不允许
*pd = 92.99;//可以,更改rates[0]的值

  这样的指针仍然可以用于修改数据,但他只能指向最初赋给它的地址。最后,可以使用两个const来创建指针,这个指针既不可以更改所指向的地址,也不可以修改所指向的数据。

double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5 };
const double * const pd = rates;//pd指向数组开始处
pd = &rates[2];//不允许
*pd = 92.99;//不允许

9.7指针和多维数组

int zippo[4][2];//整数数组的数组

●­因为zippo是数组首元素的地址,所以zippo的值和&zippo[0]相同。另一方面,zippo[0]本身是包含两个整数的数组,因此zippo[0]的值同其首元素(一个整数)的地址&zippo[0][0]相同。简单地说,zippo[0]是一个整数大小对象的地址,而zippo是两个整数大小对象的地址。因为整数和两个整数组成的数组开始于同一个地址,因此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代表其首元素zippop[0]的值,但是zippo[0]本身就是一个int数的地址,即&zippo[0][0],因此*zippo是&zippo[0][0]。对这两个表达式同时应用取值运算符将得到*zippo等价于*&zippo[0][0],后者简化后即为一个int数zippo[0][0]。简言之,zippo是地址的地址,需要两次取值才可以得到通常的值。

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月4日07:34:43
//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 = 010FFDC0,   zippo + 1 = 010FFDC8
zippo[0] = 010FFDC0,   zippo[0] + 1 = 010FFDC4
  *zippo = 010FFDC0,   *zippo + 1 = 010FFDC4
zippo[0][0] = 2
    *zippo[0] = 2
        **zippo = 2
            zippo[2][1] = 3
*(*(zippo + 2) + 1) = 3

  zippo[0]指向4个字节长的数据对象。对zippo[0]加1导致它的值增加4.数组名zippo是包含两个int数的数组的地址,因此它指向8字节长的数据对象。所以,对zippo加1导致它的值增加8。

9.7.1指向多维数组的指针

  zippo是其首元素的地址,而该首元素又是包含两个int值得数组。因此,pz必须指向一个包含两个int值的数组,而不是一个单个int值。下面是正确的代码:

int (* pz) [2];//pz指向一个包含2个int值的数组
int * pax[2];

  首先方括号和pax结合,表示pax是包含两个某种元素的数组。然后和*结合,表示pax是两个指针组成的数组。最后,用int来定义,表示pax是由两个指向int值的指针构成的数组。这种声明会创建两个指向单个int值的指针。但目前的版本通过圆括号使pz首先和*结合,从而创建一个指向包含两个int值的数组的指针。

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月4日08:10:33
//zippo.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,  *(pz[0]+1) = %d\n", pz[0], pz[0]+1, *(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 = 00EFFA50,  pz + 1 = 00EFFA58
pz[0] = 00EFFA50,  pz[0] + 1 = 00EFFA54,  *(pz[0]+1) = 4
 *pz = 00EFFA50,  *pz + 1 = 00EFFA54
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)

9..7.2函数和多维数组

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//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

  一般地,声明N维数组的指针时,除了最左边的方括号可以留空之外,其他都要写数值。这是因为首方括号表示这是一个指针,而其他括号描述的是所指向对象的数据类型。

int sum4d(int (*ar) [12][20][30], int rows);//ar是一个指针

此处ar指向一个12×20×30的Int数组。

9.8变长数组(VLA)

  C99标准引入了变长数组,它允许使用变量定义数组各维。例如,可以使用下面的声明:

int quarters = 4;
int regions = 5;
double sales[regions][quarters];//一个变长数组(VLA)

  变长数组有一些限制。变长数组必须是自动存储类的,这意味着它们必须在函数内部或作为函数参量声明,而且声明时不可以进行初始化。

●变长数组的大小不会变化

  变长数组中的“变”并不表示在创建数组后,您可以修改其大小。变长数组的大小在创建后就是保持不变的。“变”的意思是说其维大小可以用变量来制定。

int sum2d(int rows, int cols, int ar[rows][cols]);

  因为ar的声明中使用了rows和cols,所以在参量列表中,它们两个的声明需要早于ar。

  C99标准规定,可以省略函数原型中的名称;但是如果省略名称,则需要用星号来代替省略的维数:

int sum2d(int, int, int ar[*][*])

  变长数组的函数既可以处理古典C数组也可以处理变长数组。

9.9复合文字

posted @   CodeMagicianT  阅读(55)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示