文档章节

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

fzyz_sb
 fzyz_sb
发布于 2013/09/15 16:31
字数 2490
阅读 128
收藏 0
点赞 0
评论 4

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;
}



© 著作权归作者所有

共有 人打赏支持
fzyz_sb
粉丝 404
博文 209
码字总数 447144
作品 0
武汉
程序员
加载中

评论(4)

fzyz_sb
fzyz_sb

引用来自“richiard”的评论

问一下现在工作没有用到c和unix编程,是否非常有必要自己学习看看c和unix的知识。
我学习UNIX/C纯粹是为了打牢自己的基础,而非为了找这方面的工作....我个人的建议是:如果你工作完成后,下班闲暇有时间可以学习一下,但是不要因为学习而耽搁了工作--这是我所经历的教训.
r
richiard
问一下现在工作没有用到c和unix编程,是否非常有必要自己学习看看c和unix的知识。
fzyz_sb
fzyz_sb

引用来自“罗亚超”的评论

感谢分享,有个问题需要改一下。指针传值传址那儿写的不准确,我们想把world复制给hello,经过函数调用,对buffer指向的值做出改变了。想对值进行改变,那么必定是对地址进行操作。所以这儿是传址,不是传值。建议可以再研究一下swap。

好的,谢谢啦...最近忙于redmine的研究,都好久没碰C语言了..
罗亚超
罗亚超
感谢分享,有个问题需要改一下。指针传值传址那儿写的不准确,我们想把world复制给hello,经过函数调用,对buffer指向的值做出改变了。想对值进行改变,那么必定是对地址进行操作。所以这儿是传址,不是传值。建议可以再研究一下swap。
转:再再再论指针

  一直以来想把2005年写的《再再论指针》修改一下,因为经过了这么多年,对C/C++的理解与05年相比又有了一些进展。但公司的工作一直很忙,没有时间进行修改工作。直到10月初的长假...

IMGTN ⋅ 2012/06/20 ⋅ 1

《Beginning Linux Programming》读书笔记(四)

1, 读写空指针 先看第一种情况,printf试图访问空指针,以打印出字符串,而sprintf试图向空指针写入字符串,这时,linux会在GNU C函数库的帮助下,允许读空指针,但不允许写空指针。 复制代...

嗯哼9925 ⋅ 01/08 ⋅ 0

C++快速入门

只学过Java的我最近笔试接触到各种C++的题目,于是稍微恶补了一下C++的基础部分,以下内容是基于《21天学通C++》的部分读书笔记,按照章节写的。 第二章 C++程序的组成部分 ①#include 预处理...

waffle930 ⋅ 2016/10/02 ⋅ 0

做游戏,学编程(C语言) 网易云课堂MOOC视频

应一些同学的要求,把这学期上C语言编程课的讲课视频录制剪辑,上传到网易云课堂,感兴趣的朋友可以在线观看,欢迎多提宝贵意见。 MOOC视频链接:http://study.163.com/course/introduction....

童晶 ⋅ 2017/11/07 ⋅ 0

第一章 数组与指针概念剖析

数组与指针生来就是双胞胎,多数人就是从数组的学习开始指针的旅程的。在学习的过程中,很自然就会经常听到或见到关于数组与指针的各种各样的看法,下面我节选一些在各种论坛和文章里经常见到...

北极心 ⋅ 2016/08/11 ⋅ 0

《程序员面试宝典》精华 编程语言部分

《程序员面试宝典》精华 编程语言部分 正所谓取其精华,去其糟粕。本文谨记录下《程序员面试宝典》一些关键的知识点、易错点,对于一些虽然重要但书中没有解释清楚的地方不做记录。当然这里的...

modernizr ⋅ 2014/08/11 ⋅ 2

13篇文章,教你学会ES6知识点

ES6 深入理解ES6》学习笔记 本文用于汇总链接到各个子章节的内容,github 欢迎大家题issues和PR,如果对你有帮助,也可以给 star 支持 :) 目录 第一章 块级绑定 第二章 字符串和正则表达式 ...

你听___ ⋅ 05/08 ⋅ 0

[编程语言]C陷阱与缺陷

内容摘要 作者以自己1985年在Bell实验室时发表的一篇论文为基础,结合自己的工作经验扩展成为这本对C程序员具有珍贵价值的经典著作。写作本书的出发点不是要批判C语言,而是要帮助C程序员绕过...

21gprs ⋅ 2014/05/23 ⋅ 0

C语言-第八章、构造数据类型

8_1 结构体的概念和结构体变量 _1.1 结构体的概念 C语言中允许用户自己构造由不同数据类型的数据所组成的集合体,称为结构体。结构体属于数据类型,每一个结构体有一个名字,称为结构体名。一...

南风末 ⋅ 2016/11/05 ⋅ 0

第二章 数组名是一个指针常量吗?

数组名是一个指针常量这种观点来源于数组名在表达式计算中与指针的结果等效性。例如下面的代码: int a[10], *p = a, *q; q = a + 1; q = p + 1; 在效果上看,a + 1与 p + 1是相同的,这很容...

北极心 ⋅ 2016/08/11 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

OSChina 周日乱弹 —— 这么好的姑娘都不要了啊

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @TigaPile :分享曾惜的单曲《讲真的》 《讲真的》- 曾惜 手机党少年们想听歌,请使劲儿戳(这里) @首席搬砖工程师 :怎样约女孩子出来吃饭,...

小小编辑 ⋅ 26分钟前 ⋅ 1

Jenkins实践3 之脚本

#!/bin/sh# export PROJ_PATH=项目路径# export TOMCAT_PATH=tomcat路径killTomcat(){pid=`ps -ef | grep tomcat | grep java|awk '{print $2}'`echo "tom...

晨猫 ⋅ 今天 ⋅ 0

Spring Bean的生命周期

前言 Spring Bean 的生命周期在整个 Spring 中占有很重要的位置,掌握这些可以加深对 Spring 的理解。 首先看下生命周期图: 再谈生命周期之前有一点需要先明确: Spring 只帮我们管理单例模...

素雷 ⋅ 今天 ⋅ 0

zblog2.3版本的asp系统是否可以超越卢松松博客的流量[图]

最近访问zblog官网,发现zlbog-asp2.3版本已经进入测试阶段了,虽然正式版还没有发布,想必也不久了。那么作为aps纵横江湖十多年的今天,blog2.2版本应该已经成熟了,为什么还要发布这个2.3...

原创小博客 ⋅ 今天 ⋅ 0

聊聊spring cloud的HystrixCircuitBreakerConfiguration

序 本文主要研究一下spring cloud的HystrixCircuitBreakerConfiguration HystrixCircuitBreakerConfiguration spring-cloud-netflix-core-2.0.0.RELEASE-sources.jar!/org/springframework/......

go4it ⋅ 今天 ⋅ 0

二分查找

二分查找,也称折半查找、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于...

人觉非常君 ⋅ 今天 ⋅ 0

VS中使用X64汇编

需要注意的是,在X86项目中,可以使用__asm{}来嵌入汇编代码,但是在X64项目中,再也不能使用__asm{}来编写嵌入式汇编程序了,必须使用专门的.asm汇编文件来编写相应的汇编代码,然后在其它地...

simpower ⋅ 今天 ⋅ 0

ThreadPoolExecutor

ThreadPoolExecutor public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, ......

4rnold ⋅ 昨天 ⋅ 0

Java正无穷大、负无穷大以及NaN

问题来源:用Java代码写了一个计算公式,包含除法和对数和取反,在页面上出现了-infinity,不知道这是什么问题,网上找答案才明白意思是负的无穷大。 思考:为什么会出现这种情况呢?这是哪里...

young_chen ⋅ 昨天 ⋅ 0

前台对中文编码,后台解码

前台:encodeURI(sbzt) 后台:String param = URLDecoder.decode(sbzt,"UTF-8");

west_coast ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部