C/C++数组和指针详解 (一)

2014-11-24 00:59:35 · 作者: · 浏览: 9

/****************************************************************/

/* 学习是合作和分享式的!

/* Author:Atlas /* 转载请注明本文出处:

/****************************************************************/

1.引文
本文不会对数组和指针的基本概念和操作做讲解,不是很清楚的请看c/c++相关书籍,这里默认读者对这些内容熟知。

首先看一个例子:以下两个程序,程序1使用整形变量,程序2使用整形指针,但是在编译运行后,会是什么样的?

program 1:

1: #include 2: int main(){ 3: int int_input; 4: cin>>int_input; 5: cout<<(int_input + 4)< program 2:
1: #include 2: int main(){ 3: int *int_ptr = new int[1]; 4: cin>>*int_ptr; 5: cout<< (*int_ptr + 4)< 答案显而易见,都是需要你输入一个整数,然后输出刚才输入的值。问题就来了,这两个程序运行结果是一样的,你能说这两个程序是完全一样的么?? Eexactly,NO! Why?我想当你看完这篇文章,肯定豁然开朗(分析见最后)。

通过以上简单的引入例子,可以对数组和指针有个简单认识,接下来本文从数组和指针在访问方式上的区别、在函数调用上的区别以及数组和指针内在的联系上进行分析。

2.在内存访问方式上,数组和指针的区别
本小节讲述对数组的引用和对指针的引用的不同之处,学习资料来源于《expert c programing》。

为了对数组和指针对内存访问方式的理解,首先对“地址y”和“地址y的内容”做简单的区别,详细内容如下图:


\


(ps:这个图中文字描述上,我觉的语言欠妥。什么是X所代表的地址?左值在编译时可知,左值表示存储结果的地方?等等,我看了原版,感觉也不是翻译的问题,让人非似懂的。大家的意见呢?)

编译器为每个变量分配地址(左值)。这个地址在编译时可知且一直存在,而它的右值在运行时才能知道。通俗的说:每个变量都有一个地址,这个地址在编译时可以知道,而地址里存储的内容(也就是变量的右值)只有在运行时才能知道。如果需要用到变量的值(也就是已知地址存储的值),那么编译器发出指令从指定地址读入变量值并放入相应寄存器中。

(1)数组的访问方式

关键之处在于每个符号的地址在编译时可知,如果编译器需要一个地址(比如说加上偏移量)来执行某种操作,可以可以直接操作。相反、对于指针,必须在运行时取得它的地址,然后才能

对它进行接触引用操作。下图A展示了对数组下标的引用,体现数组的访问形式:


看到这里,就明白为什么extern char a[]和extern char a[100]相同的原因了。这两个声明都是提示a是一个数组,也就是一个内存地址,数组内的字符可以由这个地址(加偏移量)找到。

(2)指针的访问方式

若声明的是一个指针,如 extern char *p,它表示p指向一个字符,为了取得这个字符,必须知道地址p的内容,把它作为字符的地址并从这个地址中取得这个字符。如下图B所示,体现指针访问的形式。

\

(3)若“定义为指针,但是以数组的方式引用”,这样会发生什么?

当一个外部数组的实际定义是一个指针,但却以数组的方式对其引用时,会发生些什么?会向图A中方式直接的引用访问?事实是编译器所执行的是图B中的对内存间接引用。因为我们告诉编译器拥有的一个指针,如图C所示。

\

我们这里对照图C中的访问方式:

1: char *p = "abcdefgh"; ... p[3];
与图A中的访问方式:

1: char a[] = "abcdefgh"; ... a[3];
这两种方式都可以取得字符‘d’,但是途径不一致。

(4)数组和指针的一些特点对比

表1 数组和指针的区别

指针
数组

保存数据的地址 保存数据
间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据。
如果指针有一个下标[i],就把指针的内容加上i作为地址,从中提取数据 直接访问数据,a[i]只是简单的以a+i为地址取得数据
通常用于动态数据结构 通常用于存储固定数目且数据类型相同的元素
相关的函数为malloc(),free() 隐式分配和删除
通常指向匿名数据 自身即为数据名

数组和指针可以在定义中用字符串常量进行初始化,尽管看上去一样,底层的机制却不同。

定义指针时,编译器并不为它所指向的对象分配空间,只为指针本身分配空间。除非在定义同时付给一个指针一字符窜常量进行初始化。

如:char *p = "breadfruit"; (注意:只有对字符串常量才是如此,不能指望为浮点数之类的常量分配空间例如:float *p = 3.1415 ,!!!这是错误的)

一般情况下初始化指针时创建的字符串变量被定义为只读。如果试图修改就会出现未定义的行为。

数组可以用字符串常量进行初始化:

char a[] = “gooseberry“;

与指针相反,由字符串常量初始化的数组可以修改的。

3再论数组和指针
在内存访问方式上,数组和指针存在区别。意识到数组和指针是不同的,但是在实际应用中有时候数组和指针却是相同的!?多么神奇!如果你是编程老手,就会知道在实际应用中数组和指针可以互换的情形要比两者不可互换的情形更为常见。

(1)“声明”和“使用”情形下

声明本身还可以进一步分为三种情况:

1)外部数组的声明(external array)

2)数组的定义(它是声明的一种特殊情况,它分配内存空间,并可能提供一个初值)

3)函数参数的声明

所有作为函数参数的数组名在编译时是会转换为指针(有种说法是“退化为指针”,一个意思),而其他情况的声明,数组的声明就是数组 ,指针的声明就是指针,两者不混淆。但是在使用数组时,数组总是可以写成指针的形式,两者可以互换。如下图所示:

\


上图在声明和使用上说明了数组和指针在什么情况下可以互换使用,即是相同的。然而,数组和指针在编译器处理时是不同的,在运行时的表现形式也是不同的,并可能产生不同的代码,这点是需要牢记的!具体原因,如果看了前面的第一部分内容,很容易理解。

(2)什么时候数组和指针是相同的

C语言标准中,对此有如下说明

1)表达式中的数组名(与声明不同)被编译器当作一个指向该数组第一个元素的指针;

在表达式中,指针和数组是可以互换的,因为他们在编译器中最终形式都是指针。如何理解这句话,请看下面的这个例子。

例子:

1: int a[10],*p,i = 2;可以通过以下任一种方式访问a[i]

p=a;

p[i];
p = a;

*(p+i);
p = a+i;

*p;

原因是对数组的引用如a[i]