指向数组元素的指针

一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)。所谓数组元素的指针就是数组元素的地址。
    int a[10];   //定义一个整型数组a,它有10个元素
    int *p;  //定义一个基类型为整型的指针变量p
    p=&a[0];  //将元素a[0]的地址赋给指针变量p,使p指向a[0]
在C++中,数组名代表数组中第一个元素(即序号为0的元素)的地址。因此,下面两个语句等价:
    p=&a[0];
    p=a;
在定义指针变量时可以给它赋初值:
    int *p=&a[0];  //p的初值为a[0]的地址
也可以写成
    int *p=a;  //作用与前一行相同
可以通过指针引用数组元素。假设p已定义为一个基类型为整型的指针变量,并已将一个整型数组元素的地址赋给了它,使它指向某一个数组元素。如果有以下赋值语句:
    *p=1;  //对p当前所指向的数组元素赋予数值1
如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。

如果p的初值为&a[0],则:
1) p+i和a+i就是a[i]的地址,或者说,它们指向a数组的第i个元素,见图6.12。


图6.12

2) *(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。

可以看出,[]实际上是变址运算符。对a[i]的求解过程是: 先按a+i×d计算数组元素的地址,然后找出此地址所指向的单元中的值。

3) 指向数组元素的指针变量也可以带下标,如p[i]与*(p+i)等价。

根据以上叙述,引用一个数组元素,可用以下方法:

  • 下标法,如a[i]形式;
  • 指针法,如*(a+i)或*(p+i)。其中a是数组名,p是指向数组元素的指针变量。如果已使p的值为a,则*(p+i)就是a[i]。可以通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高。

【例6.5】输出数组中的全部元素。假设有一个整型数组a,有10个元素。要输出各元素的值有3种方法:

1) 下标法。

  1. #include <iostream>
  2. using namespace std;
  3. int main( )
  4. {
  5. int a[10];
  6. int i;
  7. for(i=0;i<10;i++)
  8. cin>>a[i]; //引用数组元素a[i]
  9. cout<<endl;
  10. for(i=0;i<10;i++)
  11. cout<<a[i]<<" "; //引用数组元素a[i]
  12. cout<<endl;
  13. return 0;
  14. }

运行情况如下:
9 8 7 6 5 4 3 2 1 0↙            (输入10个元素的值)
9 8 7 6 5 4 3 2 1 0              (输出10个元素的值)

2) 指针法。
将上面程序第7行和第10行的“a[i]”改为“*(a+i)”,运行情况与(1)相同。

3) 用指针变量指向数组元素。

  1. #include <iostream>
  2. using namespace std;
  3. int main( )
  4. {
  5. int a[10];
  6. int i,*p=a; //指针变量p指向数组a的首元素a[0]
  7. for(i=0;i<10;i++)
  8. cin>>*(p+i); //输入a[0]~a[9]共10个元素
  9. cout<<endl;
  10. for(p=a;p<(a+10);p++)
  11. cout<<*p<<" "; //p先后指向a[0]~a[9]
  12. cout<<endl;
  13. return 0;
  14. }

运行情况与前相同。请仔细分析p值的变化和*p的值。

对3种方法的比较:
方法(1)和(2)的执行效率是相同的。第(3)种方法比方法(1)、(2)快。这种方法能提高执行效率。

用下标法比较直观,能直接知道是第几个元素。用地址法或指针变量的方法都不太直观,难以很快地判断出当前处理的是哪一个元素。在用指针变量指向数组元素时要注意: 指针变量p可以指向有效的数组元素,实际上也可以指向数组以后的内存单元。如果有
   int a[10], *p=a;    //指针变量p的初值为&a[0]
   cout<<*(p+10);    //要输出a[10]的值
在使用指针变量指向数组元素时,应切实保证指向数组中有效的元素。

指向数组元素的指针的运算比较灵活,务必小心谨慎。下面举几个例子。

如果先使p指向数组a的首元素(即p=a),则:
1) p++(或p+=1)。使p指向下一元素,即a[1]。如果用*p,得到下一个元素a[1]的值。

2) *p++。由于++和*同优先级,结合方向为自右而左,因此它等价于*(p++)。作用是: 先得到p指向的变量的值(即*p),然后再使p的值加1。例6.5(3)程序中最后一个for语句:
    for(p=a;p<a+10;p++)
    cout<<*p;
可以改写为
    for(p=a;p<a+10;)
    cout<<*p++;

3) *(p++)与*(++p)作用不同。前者是先取*p值,然后使p加1。后者是先使p加1,再取*p。若p的初值为a(即&a[0]),输出*(p++)得到a[0]的值,而输出*(++p)则得到a[1]的值。

4) (*p)++表示p所指向的元素值加1,即(a[0])++,如果a[0]=3,则(a[0])++的值为4。注意: 是元素值加1,而不是指针值加1。

5) 如果p当前指向a[i],则
    *(p--)    先对p进行“*”运算,得到a[i],再使p减1,p指向a[i-1]。
    *(++p)   先使p自加1,再作*运算,得到a[i+1]。
    *(--p)   先使p自减1,再作*运算,得到a[i-1]。
将++和--运算符用于指向数组元素的指针变量十分有效,可以使指针变量自动向前或向后移动,指向下一个或上一个数组元素。例如,想输出a数组100个元素,可以用以下语句:
    p=a;
    while(p<a+100)
    cout<<*p++;

    p=a;
    while(p<a+100)
    {
        cout<<*p;
        p++;
    }
在用*p++形式的运算时,很容易弄错,一定要十分小心,弄清楚先取p值还是先使p加1。

用指针变量作函数参数接收数组地址

在前面介绍过可以用数组名作函数的参数。前面已经多次强调: 数组名代表数组首元素的地址。用数组名作函数的参数,传递的是数组首元素的地址。很容易推想: 用指针变量作函数形参,同样可以接收从实参传递来的数组首元素的地址(此时,实参是数组名)。下面将第5章5.4节中的例5.7程序改写,用指针变量作函数形参。

【例6.6】将10个整数按由小到大的顺序排列。在例5.7程序的基础上,将形参改为指针变量。

  1. #include <iostream>
  2. using namespace std;
  3. int main( )
  4. {
  5. void select_sort(int *p,int n); //函数声明
  6. int a[10],i;
  7. cout<<"enter the originl array:"<<endl;
  8. for(i=0;i<10;i++) //输入10个数
  9. cin>>a[i];
  10. cout<<endl;
  11. select_sort(a,10); //函数调用,数组名作实参
  12. cout<<"the sorted array:"<<endl;
  13. for(i=0;i<10;i++) //输出10个已排好序的数
  14. cout<<a[i]<<" ";
  15. cout<<endl;
  16. return 0;
  17. }
  18. void select_sort(int *p,int n) //用指针变量作形参
  19. {
  20. int i,j,k,t;
  21. for(i=0;i<n-1;i++)
  22. {
  23. k=i;
  24. for(j=i+1;j<n;j++)
  25. if(*(p+j)<*(p+k)) k=j; //用指针法访问数组元素
  26. t=*(p+k);*(p+k)=*(p+i);*(p+i)=t;
  27. }
  28. }

运行情况与例5.7相同。


图 6.13

本例与例5.7在程序的表现形式上虽然有不同,但实际上,两个程序在编译以后是完全相同的。C++编译系统将形参数组名一律作为指针变量来处理。

实际上在函数调用时并不存在一个占有存储空间的形参数组,只有指针变量。

实参与形参的结合,有以下4种形式:
实  参                 形  参
数组名              数组名       (如例5.7)
数组名            指针变量     (如例6.6)
指针变量          数组名
指针变量        指针变量

在此基础上,还要说明一个问题: 实参数组名a代表一个固定的地址,或者说是指针型常量,因此要改变a的值是不可能的。如:
    a++;  //语法错误,a是常量,不能改变
而形参数组名是指针变量,并不是一个固定的地址值。它的值是可以改变的。在函数调用开始时,它接收了实参数组首元素的地址,但在函数执行期间,它可以再被赋值。如:

  1. f(array[], int n)
  2. {
  3. cout<<array; //输出array[0]的值
  4. array=array+3; //指针变量array的值改变了,指向array[3]
  5. cout<<*arr<<endl; //输出array[3]的值
  6. }

多维数组与指针

用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。

1) 多维数组元素的地址
设有一个二维数组a,它有3行4列。它的定义为:
    int a[3][4]={{1,3,5,7},{9,11,13,15},{17,18,21,23}};
a是一个数组名。a数组包含3行,即3个元素:a[0],a[1],a[2]。而每一元素又是一个一维数组,它包含4图6.14个元素(即4个列元素),例如,a[0]所代表的一维数组又包含4个元素: a[0][0], a[0][1], a[0][2], a[0][3],见图6.14。可以认为二维数组是“数组的数组”,即数组a是由3个一维数组所组成的。


图6.14

从二维数组的角度来看,a代表二维数组首元素的地址,现在的首元素不是一个整型变量,而是由4个整型元素所组成的一维数组,因此a代表的是首行的起始地址(即第0行的起始地址,&a[0]),a+1代表a[1]行的首地址,即&a[1]。

a[0],a[1],a[2]既然是一维数组名,而C++又规定了数组名代表数组首元素地址,因此a[0]代表一维数组a[0]中0列元素的地址,即&a[0][0]。a[1]的值是&a[1][0],a[2]的值是&a[2][0]。


图6.15

0行1列元素的地址可以直接写为&a[0][1],也可以用指针法表示。a[0]为一维数组名,该一维数组中序号为1的元素显然可以用a[0]+1来表示,见图6.16。

欲得到a[0][1]的值,用地址法怎么表示呢?既然a[0]+1是a[0][1]元素的地址,那么,*(a[0]+1) 就是a[0][1]元素的值。而a[0]又是和*(a+0)无条件等价的,因此也可以用*(*(a+0)+1)表示a[0][1]元素的值。依此类推,*(a[i]+j)或*(*(a+i)+j)是a[i][j]的值。


图6.16

2) 指向多维数组元素的指针变量

① 指向数组元素的指针变量
【例6.7】输出二维数组各元素的值。这里采用的方法是用基类型为整型的指针变量先后指向各元素,逐个输出它们的值。

  1. #include <iostream>
  2. using namespace std;
  3. int main( )
  4. {
  5. int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
  6. int *p; //p是基类型为整型的指针变量
  7. for(p=a[0];p<a[0]+12;p++)
  8. cout<<*p<<" ";
  9. cout<<endl;
  10. return 0;
  11. }

运行结果如下:
1 3 5 7 9 11 13 15 17 19 21 23

关于指向数组元素的指针变量的几点说明:

  • p是指向整型数据的指针变量,在for语句中对p赋初值a[0],也可以写成“p=&a[0][0]”。
  • 循环结束的条件是“p<a[0]+12”,只要满足p<a[0]+12,就继续执行循环体。
  • 执行“cout<<*p;”输出p当前所指的列元素的值,然后执行p++,使p指向下一个列元素。

②指向由m个元素组成的一维数组的指针变量
可以定义一个指针变量,它不是指向一个整型元素,而是指向一个包含m个元素的一维数组。这时,如果指针变量p先指向a[0](即p=&a[0]),则p+1不是指向a[0][1],而是指向a[1],p的增值以一维数组的长度为单位,见图6.17。


图6.17

【例6.8】输出二维数组任一行任一列元素的值。

  1. #include <iostream>
  2. using namespace std;
  3. int main( )
  4. {
  5. int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
  6. int (*p)[4],i,j;
  7. cin>>i>>j;
  8. p=a;
  9. cout<<*(*(p+i)+j)<<endl;
  10. return 0;
  11. }

运行情况如下:
2 3↙
23

由于执行了“p=a”,使p指向a[0]。因此p+2是二维数组a中序号为2的行的起始地址(由于p是指向一维数组的指针变量,因此p加1,就指向下一个一维数组),见图6.18。*(p+2)+3是a数组2行3列元素地址。*(*(p+2)+3)是a[2][3]的值。


图6.18

3) 用指向数组的指针作函数参数
一维数组名可以作为函数参数传递,多维数组名也可作函数参数传递。

【例6.9】输出二维数组各元素的值。题目与例6.7相同,但本题用一个函数实现输出,用多维数组名作函数参数。

  1. #include <iostream>
  2. using namespace std;
  3. int main( )
  4. {
  5. void output(int (*p)[4]); //函数声明
  6. int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
  7. output(a); //多维数组名作函数参数
  8. return 0;
  9. }
  10. void output(int (*p)[4]) //形参是指向一维数组的指针变量
  11. {
  12. int i,j;
  13. for(i=0;i<3;i++)
  14. for(j=0;j<4;j++)
  15. cout<<*(*(p+i)+j)<<" ";
  16. cout<<endl;
  17. }

运行情况如下:
1 3 5 7 9 11 13 15 17 19 21 23

C++数组与指针的更多相关文章

  1. 把《c++ primer》读薄(4-2 c和c++的数组 和 指针初探)

    督促读书,总结精华,提炼笔记,抛砖引玉,有不合适的地方,欢迎留言指正. 问题1.我们知道,将一个数组赋给另一个数组,就是将一个数组的元素逐个赋值给另一数组的对应元素,相应的,将一个vector 赋给另 ...

  2. C语言核心之数组和指针详解

    指针 相信大家对下面的代码不陌生: int i=2; int *p; p=&i;这是最简单的指针应用,也是最基本的用法.再来熟悉一下什么是指针:首先指针是一个变量,它保存的并不是平常的数据,而 ...

  3. 《征服 C 指针》摘录3:数组 与 指针

    一.数组 和 指针 的微妙关系 数组 是指将固定个数.相同类型的变量排列起来的对象. 正如之前说明的那样,给指针加 N,指针前进“当前指针指向的变量类型的长度 X N”. 因此,给指向数组的某个元素的 ...

  4. C指针-数组和指针的归一

    int bArr[] = {1,2,3}; int *iarr = bArr; *iarr = 6; printf("%d\n",*iarr); printf("%d\n ...

  5. 《C专家编程》第四章——令人震惊的事实:数组和指针并不相同

    数组和指针是C语言里相当重要的两部分内容,也是新手程序员最容易搞混的两个地方,本章我们锁定指针与数组,探讨它们的异同点. 首先来看指针与数组在声明上的区别: int a[10]; int *p; 很明 ...

  6. (C语言)数组与指针的区别

    以前常常听过这种说法,说数组和指针这两者比较像,但是不能混淆,可是一直没能理解.刚刚在李云的<专业嵌入式软件开发>中,看了讲述数组与指针区别的一章,似乎有所领悟.本着知乎上看到的这张图,我 ...

  7. C语言教学--二维数组和指针的理解

    对于初学者对二维数组和指针的理解很模糊, 或者感觉很难理解, 其实我们和生活联系起来, 这一切都会变得清晰透彻. 我们用理解一维数组的思想来理解二维数组, 对于一维数组,每个箱子里存放的是具体的苹果, ...

  8. 【C语言学习】《C Primer Plus》第10章 数组和指针

    学习总结 1.数组初始化方式: int a[]={1,2,3} int a[SIZE]={1,2,3} //SIZE是宏定义,数组初始化个数不能大于SIZE,否则报错:当个数小 //SIZE,自动补0 ...

  9. C语言学习004:数组与指针

    在C语言中,字符串实际上就是字符数组,在内存中字符串"Shatner"存储的形式是这样的

  10. C语言--指向多维数组的指针和指针数组

    #include <stdio.h> //void show(char *p[]); ]); int main(){ ][]={","abc","x ...

随机推荐

  1. c# 搭建服务端 传输协议(2)

    在网络的数据传输中,要将需要传输的数据转换为二进制数据后传输,才能被服务端正常的接收,socket传输中,接收到的数据都会被放入byte[]中存放,所以在数据发送前,对二进制的数组进行有规律的排序,才 ...

  2. javascript数组排序-----1

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. hive集群安装配置

    hive 是JAVA写的的一个数据仓库,依赖hadoop.没有安装hadoop的,请参考http://blog.csdn.net/lovemelovemycode/article/details/91 ...

  4. QT 信号与槽 QT简单加法器的实现

    信号与槽 背景: 面向过程 模块之间低耦合设计(高内聚). 函数调用: 直接调用 回调调用(低耦合) 面向对象 模块之间低耦合设计(高内聚) 对象调用 直接调用 接口调用 QT: 信号与槽解决问题: ...

  5. 切点算法模板(Cut-vertex)

    下面是一个模板被切割点,也cut_vertex_num[]排列(array)什么是切 - 点记录 Int cut_vertex_num[]; void dfs(int cur,int pa) { in ...

  6. JavaScript引用类型之Object类型

    在JavaScript中大多数的引用类型都是Object的实例,Object类型也是使用最多的类型! 创建Object类型实例的方式有两种,下面分别来分析一下: (1)第一种是使用new操作符后跟Ob ...

  7. English - in the light of(按照,根据)与according to的区别是什么

    according to 表示as stated by 像陈述的那样 如According to Sarah they're not getting on very well at the momen ...

  8. Windows Server 2003 安装Sql Server 2005 问题处理

    安装途中遇到: 问题1.无法找到产品Microsoft SQL Server Native Client的安装程序包.请使用安装包sqlncli.msi的有效副本重新安装? 答:安装SQL Serve ...

  9. 指定hive输出格式

    0.11版本以前: sed -e 's/\x01/|/g' file 0.11版本以后: insert overwrite local directory '/opt/aimcpro/libc/tes ...

  10. AOP 切面编程

    简介 如果你很熟悉面向方面编程(AOP),你就会知道给代码增加“切面”可以使代码更清晰并且具有可维护性.但是AOP通常都依赖于第三方类库或者硬编码的.net特性来工作.虽然这些实现方式的好处大于它们的 ...