C和指针---第八章:数组

8.1 一维数组

8.1.1 数组名

int a;
int b[4];
那么,b的类型是什么?实际上不能说b表示的是整个数组。

数组名的值是一个指针常量,也就是数组第一个元素的地址。它的类型取决于数组元素的类型:如果它们是int类型,那么数组名的类型是“指向int的常量指针”;如果它们是其他类型,那么数组名的类型就是“指向其他类型的常量指针。”

但数组和指针是不同的。例如:数组具有确定数量的元素,而指针指示一个标量值。编译器用数组名来记住这些属性。只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。且指针常量是不可改变的:

#include <stdio.h>

int main(void)
{
	int a[2] = {1,2};
	a = (int*)100;

	return 0;
}
备注:这段代码是错误的。

因为指针常量所指向的是内存中数组的起始位置,如果修改这个指针常量,唯一可行的操作就是把整个数组移动到内存的其他位置。但是,在程序完成链接之后,内存中数组的位置是固定的,所以当程序运行时,再想移动数组就为时已晚。因此,数组名的值是一个指针常量。

只有在两种场合下,数组名并不用指针常量表示---就是当数组名作为sizeof操作符或单目操作符&的操作数时。sizeof返回整个数组的长度,而不是指向数组的指针的长度。

sizeof(arr) / sizeof(*arr);//计算数组的长度
取一个数组名的地址所产生的是一个指向数组的指针。考虑下面这个例子:
int a[10];
int b[10];
int *c;
c = &a[0];
表达式&a[0]是一个指向数组第一个元素的指针。所以它等价于:
c = a;
而下面的运算是非法的:
b = a;//a,b为指针常量
a = c;//指针常量不可修改
8.1.2 下标引用
int array[10];
int *ap = array + 2;
ap     = array + 2 或 &array[2]

*ap = array[2]

ap[0] = *(ap) = array[2]

ap+6 = array + 8 或 &array[8]

*ap+6 = array[2] + 6

*(ap+6) = array[8]

ap[6] = array[8]

&ap = ap的地址,未知

ap[-1] = array[1]

ap[9] 非法,越界

8.1.3 指针与下标

规则:下标绝不会比指针更有效率,但指针有时会比下标更有效率。

例子:下标初始化

int array[10];
int a;
for ( a = 0; a < 10; a++ ){
	array[a] = 0;
}
指针初始化:
int array[10];
int *ap;
for ( ap = array; ap < array + 10; ap++ ){
	*ap = 0;
}
两者的区别在于:

在下标初始化中,a是相对于&array[0]进行偏移计算的,所以在运行阶段要进行10次的偏移计算。而ap是相对于上一个指针(ap-1)进行偏移计算的,而偏移量固定下来,所以在运行阶段只要进行1次偏移的计算。

而下面的代码只进行一个的偏移运算,故效率一样。

int array[10];
int *ap;
for ( ap = array; ap < array + 10; ap++ ){
	*ap = 0;
}
a = get_value();
*( array + a ) = 0;
8.1.5 数组和指针

指针和数组并不相等的。

int a[5];
int *b;
声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为指向任何现有的内存空间。

所以*a是合法的,而*b则是未定义的。

b++可以通过编译,但是由于a是指针常量,a++却无法通过编译。

8.1.6 作为函数参数的数组名

如何理解C语言中传递数组名的时候依旧是传值而不是传址呢?请看下面的例子:

#include <stdio.h>

void strcpy( char *buffer, char const *string )
{
	while ( ( *buffer++ = *string++) != '\0' ){
		;
	}
}

int main(void)
{
	char buffer[] = "hello";
	char string[] = "world";
	printf("%x\n", buffer);
	strcpy( buffer, string );
	printf("%s\n", buffer);
	printf("%x\n", buffer);

	return 0;
}
程序输出:

我们会发现,buffer的地址根本就没有改变过。所以,实际上的传值,副本是buffer(指针)而不是*buffer(指针所指向的值),所以buffer并为被改变,而*buffer则可以被改变。

我们这里对const进行一次复习:

char  *const string =  "hello";
string[0] = 'a';
这是合法的,const修饰的是char*,表明指针所指向的内容不可被修改,所以下面代码有误:
char  *const string =  "hello";
string = "hello";
同样的道理,下面代码是正确的:
char  const *string =  "hello";
string = "hello";
而下面这段代码则有误:
char  const *string =  "hello";
string[0] = 'a';
8.1.7 声明数组参数
int strlen( char *string );
int strlen( char string[] );
两种方式都正确,但是指针方式更准确一些。


8.2 多维数组

如何解释多维数组呢?

int a;
int b[10];
int c[6][10];
int d[3][6][10];
a是个简单的整数,b增加了1维,故为一个向量,包含10个整型元素。c只是在b的基础上增加一维,所以c为包含6个元素的向量,而每个元素本身是一个包含10个整型元素的向量。而d包含3个元素的数组,每个元素包含6个元素的数组,而这6个元素中的每一个又都是包含10个整型元素的数组。

8.2.2 数组名

一维数组名的值是一个指针常量,它的类型是“指向元素类型的指针”。而多维数组,如:

int matrix[3][10];
中的matrix则是一个指向一个包含10个整型元素的数组的指针。


8.2.4 指向数组的指针

int matrix[3][10];
int *mp = matrix;
声明是否正确?答案是否定的。mp被声明为一个指向整型的指针。但是matrix并不是一个指向整型的贺子珍,而是一个指向整型数组的指针。那我们如何声明指向整型数组的指针呢?我们必须先声明指针,然后在指针的基础上声明数组,所以下面的括号是必要的:
int (*p)[10] = matrix;
10为什么是必要的呢?因为我们进行指针偏移的时候,要确定偏移量是多少。下面这段代码说明了二维数组的指针声明方式:
#include <stdio.h>


int main(void)
{
	int matrix[3][10];
	int i = 0; 
	int j = 0;
	int k = 0;
	int (*p)[10] = matrix;
	for ( i = 0; i < 3; i++ ){
		for ( j = 0; j < 10; j++ ){
			matrix[i][j] = k++;
		}
	}
	
	printf("%d\n", **p);
	printf("%d\n", **( p + 1 ));

	return 0;
}
程序输出:

当然,如果想读取matrix[1][5],我们可以这样编写代码:

printf("%d\n", *( *( p + 1 ) + 5 ) );
多编写几次就习惯了。

由于多维数组的存储方式依旧是顺序存储的,所以如果我们希望逐个访问数据中的元素,那应该如何声明?请看下面的代码:

#include <stdio.h>


int main(void)
{
	int matrix[3][10];
	int i = 0; 
	int j = 0;
	int k = 0;
	int *p = matrix[0];
	//或者为&matrix[0][0]
	for ( i = 0; i < 3; i++ ){
		for ( j = 0; j < 10; j++ ){
			matrix[i][j] = k++;
		}
	}
	
	printf("%d\n", *( p + 15 ) );

	return 0;
}
程序输出:

8.2.5 作为函数参数的多维数组

int matrix[3][10];
func2( matrix );
那么,函数原型是什么?
void func2( int (*mat)[10] );
void func2( int mat[][10] );
两种都可以,但是第一种更好,因为代表的含义是:指向整型数组的指针。

当然,下面这个函数原型是错误的:(这个我之前还以为是正确的)

void func2( int **mat );
8.3 指针数组

我们用一个例子说明指针数组的作用:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int lookup_keyword( char const * const desired_word, char const * keyword_table[])
{
	char const **kwp;

	for ( kwp = keyword_table; *kwp != NULL; kwp++ ){
		if ( 0 == strcmp( desired_word, *kwp ) ){
			return kwp - keyword_table;
		}
	}

	return -1;
}

int main(void)
{
	char const *keyword[] = {
		"do",
		"for",
		"if",
		"register",
		"return",
		"switch",
		"while",
		NULL
	};

	printf("%d\n", lookup_keyword( "if", keyword ) );
	printf("%d\n", lookup_keyword( "return", keyword ) );
	printf("%d\n", lookup_keyword( "hello", keyword ) );

	return 0;
}
程序输出:

习题:

0. 判断字符串是否为回文字符:

#include <stdio.h>

int isRight( char *buffer )
{
	char *temp;
	for ( temp = buffer; *temp != '\0'; temp++ ){
		;
	}
	temp--;

	while ( buffer < temp ){
		if ( *buffer != *temp ){
			return 0;
		}
		buffer++;
		temp--;
	}

	return 1;
}

int main(void)
{
	printf("%d\n", isRight( "helloollehh" ) );

	return 0;
}
3,4.
#include <stdio.h>

int isIdentity( int (*arr)[10], int len )
{
	int i = 0; 
	int j = 0;
	for ( i = 0; i < len; i++ ){
		for ( j = 0; j < len; j++ ){
			if ( i == j ){
				if ( 0 == arr[i][j] ){
					return 0;
				}
			}
			else{
				if ( 1 == arr[i][j] ){
					return 0;
				}
			}
		}
	}

	return 1;
}

int main(void)
{
	int a[10][10];
	int i = 0;
	int j = 0;
	for ( i = 0; i < 10; i++ ){
		for ( j = 0; j < 10; j++ ){
			if ( i == j ){
				a[i][j] = 1;
			}
			else{
				a[i][j] = 0;
			}
		}
	}
	printf("%d\n", isIdentity( a, 10 ) );

	a[3][4] = 1;
	printf("%d\n", isIdentity( a, 10 ) );

	return 0;
}

程序输出:

5.

#include <stdio.h>

void matrix_multiply( int (*m1)[2], int (*m2)[4], int (*r)[4], int x, int y, int z )
{
	int i = 0; 
	int j = 0;
	int k = 0;
	int m = 0; 
	int n = 0;
	for ( i = 0; i < x; i++ ){
		for ( j = 0; j < z; j++ ){
			for ( k = 0; k < y; k++ ){
				r[m][n] += m1[i][k] * m2[k][j];
			}
			n++;
		}
		m++;
		n = 0;		//记得清零
	}
}

int main(void)
{
	int m1[3][2] = { 2, -6, 3, 5, 1, -1 };
	int m2[2][4] = { 4, -2, -4, -5, -7, -3, 6, 7 };
	int r[3][4];
	int i = 0;
	int j = 0;

	for ( i = 0; i < 3; i++ ){
		for ( j = 0; j < 4; j++ ){
			r[i][j] = 0;
		}
	}

	matrix_multiply( m1, m2, r, 3, 2, 4 );
	for ( i = 0; i < 3; i++ ){
		for ( j = 0; j < 4; j++ ){
			printf("%d ", r[i][j]);
		}
		printf("\n");
	}

	return 0;
}

程序输出:


8. 智商不够,写不出八皇后问题,以下答案摘自网站上:

#include <stdio.h>

int
is_safe(int rows[8], int x, int y)  
{
	int i;

	for (i=1; i <= y; ++i) {
		if (rows[y-i] == x || rows[y-i] == x-i || rows[y-i] == x+i)
			return 0;
	}

	return 1;
}

void
putboard(int rows[8])  
{
	static int s = 0;
	int x, y;

	printf("\nSolution #%d:\n---------------------------------\n", ++s);
	for (y=0; y < 8; ++y) {
		for (x=0; x < 8; ++x)
			printf(x == rows[y] ? "| Q " : "|   ");
		printf("|\n---------------------------------\n");
	}
}

void
eight_queens_helper(int rows[8], int y)
{
	int x;

	for (x=0; x < 8; ++x) {
		if (is_safe(rows, x, y)) {
			rows[y] = x;
			if (y == 7)
				putboard(rows);
			else
				eight_queens_helper(rows, y+1);
		}
	}
}

int main()
{
	int rows[8];

	eight_queens_helper(rows, 0);

	return 0;
}



转载于:https://my.oschina.net/voler/blog/161537

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值