下面的内容节选自由我所执笔的会议记录。对于本文的不足之处,各位可以提出自己的看法。

Q1:指针和数组到底是怎么一回事?

A:指针和数组有本质的不同。指针就是一个内存地址,在32位系统下,一个指针永远占4个字节;数组是一块连续的内存空间,我们从一个已定义的数组中可以获得数组大小以及这块连续内存空间的起始地址。这个起始地址即数组首元素的地址,更具体的说是数组中首个元素的首地址。

在C语言中,只有一维数组。但是,当一个一维数组的元素是仍然是一维数组时,就形成了所谓的一维数组。如何印证这一点?二维数组在内存中的存储方式仍然是按照一维数组那样线性存储的,只不过这个数组的元素按照另一个一维数组线性存储。多维数组照此类推。

Q2:我还能获得关于数组和指针更详细的说明吗?

A:当然可以。

我们通常会这样使用一个指针:

int a[5];
int *p=a;

我们首先定义了一个指向int类型的指针p,在定义p的同时将其初始化为a;通常我们将这种行为称为“p指向一个大小为5的整形数组a”。事实上,我们可以更具体的想一下:p真的指向整个数组吗?当然不是。p只是指向一个int型变量而已。在这个例子中,p指向的是a数组的首元素。换句话说,p存储的是&a[0]。另外,p是变量,我们可以对p进行自加自减(在a数组有效的范围内),让p指向a数组的其他元素。换句话说,p可以存储整个数组中任意一个元素的地址(更具体的是这个元素的首地址)。而数组名a虽然也是个地址,但是它是一个常量,它永远存储的是数组的首地址,不可改变。

对于上述所言的p指针,我们可以通过指针方式*(p+i)和下标方式p[i]访问数组。对于数组名a而言,我们也可以通过*(a+i)和下标方式a[i]来访问数组。对于p指针而言,以指针方式访问数组的过程是这样的:我们通过p指针首先获取这个数组的首地址,然后加上i个偏移量,得到数组中第i个元素的地址,最后通过引用,得到具体的值。而对于p[i]这种下标访问方式,最终还是会被转化成上述指针访问方式。对于数组名a而言,与p方式相同。

另外还要注意,上述所说的i个偏移量,并不是加上i个字节那么简单;而是i个元素的总字节数。因此,给指针p加上一个整数和给指针p的二进制表示形式加上同样的整数,两者的含义是截然不同的。
另外,刚说到a是一个常量的问题,我想到了const关键字。上次在现代软件工程课程上,我听到有的同学说const所修饰的是常量。const修饰的当然是一个变量,只不过这个变量是只读的,即便这个变量的特性和常量相似。

Q3:对于数组a[3][4],我该如何理解更多的信息?

A:我们首先可以获知以下信息:数组a是一个包含有3个元素的数组,每个数组元素是一个大小为4的一维数组。因此,我们通常说这个数组有三行四列(但是你要清楚:内存中并不会出现3*4的那样的表格)。具体来说,二维数组a有以下三个元素:a[0],a[1],a[2];每一个元素又都是一个一维数组。对于一维数组a[0]来说,它又有4个元素:a[0][0],a[0][1],a[0][2],a[0][3];这4个元素是int型的。

以上就是关于这个数组的基本结构,下面从指针的角度来分析这个数组。

对于上述的数组名a,我们称为指向指针的指针(a是一个常量)。对于一维数组我们知道,数组名是数组中首元素的地址。对于上述所言的数组a,a当然也是这个数组首元素的地址。那么,数组a的首元素是什么?对了,就是a[0]。那么a这个指针存储的就是a[0]的地址,即a等价于&a[0]。那么a+i也就很明显了:a[i]的地址。再来想a[i]是什么?它是数组a中第i个元素。在数组a中,第i个元素是什么?它是一个大小为4的一维数组。对应到上述所言的“三行四列”,a+i即是指向第i行的指针。可以看到,i这个偏移量此刻是以行为单位的。

我们上述已说明,a+i指向a[i],a[i]是一个一维数组。a+i是一个指向指针的指针,那么现在取出a+i的值,也就是*(a+i)。从我们所说的“指向指针的指针”来看,*(a+i)也是一个指针,但是这个指针指向什么?它指向数组a[i]的首元素的地址,也就是a[i][0]的地址,即&a[i][0]。那么数组a[i]中第j个元素的地址是什么?那就是*(a+i)+j,即&a[i][j]。当然,也可以这么写:a[i]+j。

另外要说明的是,上述的a+i和*(a+i)的内存地址其实是相同,但是含义是完全不同的。具体原因,你可以从上面的陈述中得到答案。

Q4:难道指针就只有这么多内容吗?

A:当然不是。关于指针更高级的使用,还有指向一维数组的指针;指针数组;函数指针。可能它们的定义方式会让你迷惑,但是,无论如何,它们本质上仍然是指针。先了解最基本的指针,然后再理解这些高级指针就简单了许多。

上述Q1~Q3从某种角度来说,叙述的过于罗嗦,甚至有时候必须得咬文嚼字;并且,必须在实践的基础上理解上述内容。我个人的建议是,指针部分必须建立在“理论——实践——理论”这样反复的过程中,否则——套用现在最fashion的话——“指针神马的一切都是浮云~”。

指向一维数组的指针

今天在看到typedef int (*int_arry)[10];这条语句时,因为对这样的定义使用较少,就想着编写一个test.c来试试看。不过,当我编写完一个简单的测试程序时,却发现我对指向一维数组的指针的使用了解甚少。

起初,我的程序是这样:

    #include  < stdio.h >
    typedef int (*int_array)[10];
    int main()
    {
    
        int a[10]={1,2,3,4,5};
        int_array i=&a;
    
        printf("%d=%d\n",i[4],a[4]);
          return 0;11
    }

编译后,提示如下错误:

test.c: In function ‘main’:

test.c:10: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘int *’

也就是说,i[4]是一个int*型的指针。为什么会出现这样的错误?既然i是一个指向有10个整形元素数组的指针。那么将i指向数组a,然后使用i[4]获取第四个元素有什么错?

那我们从另一个角度来分析,一般i[4]这样的形式都可以看成*(i+4)这样的形式。i+4是什么?对了!i是一个数组指针,那么i+4也就是一个数组指针。如果将i所指的数组看作一个二维表的第1行,那么i+4就是指向第5行的指针。也就是说它相对于i所指向位置的偏移量为4*sizeof(int)*10个字节。因此*(i+4)仍然是一个指针,只不过它指向第5行的首个元素。

看来我们找到问题所在,i[4]并不是一个整形元素,而是一个指向整形元素的指针。上面程序中,我原本的意思是通过i来打印数组a中第四个元素。那么此刻我们应该这么修改:

printf("%d=%d\n",i[0][4],a[4]);

或者下面任意一句:

printf("%d=%d\n",(*(i+0))[4],a[4]);
printf("%d=%d\n",*(i[0]+4),a[4]);
printf("%d=%d\n",*(*(i+0)+4),a[4]);

你会发现,如果p是一个指向指针的指针,那么总能够通过两次的*、两次的[]或者一次*和一次[]取得这个指针最终指向的数据,因为说到底[]总能够化成*的形式。理解了这些,上面的语句对你也就不成问题了。got it?

C语言中的指针和数组的更多相关文章

  1. 深入理解C语言中的指针与数组之指针篇

    转载于http://blog.csdn.net/hinyunsin/article/details/6662851     前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本 ...

  2. 深入理解C语言中的指针与数组之指针篇(转载)

    前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本所在.相信,任意一家公司如果想要考察一个人对C语言的理解,指针和数组绝对是必考的一部分. 但是之前一方面之前一直在忙各种事情 ...

  3. C语言中的指针与数组的定义与使用

    指针的特点 他就是内存中的一个地址 指针本身运算 指针所指向的内容是可以操作的 操作系统是如何管理内存的 栈空间 4M~8m的大小 当进入函数的时候会进行压栈数据 堆空间 4g的大小 1g是操作系统 ...

  4. C语言中的指针数组

    C语言中的指针数组是什么,像 char *a[]={"ddd","dsidd","lll"}; 这里讲一下注意如果我们使用了a也就是首元素的 ...

  5. 转: 浅谈C/C++中的指针和数组(二)

    转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组 ...

  6. C语言中的指针笔记

    C语言指针 得到变量的地址 可以使用&运算符找到变量保存在内存中的位置 int x = 1; printf("x的内存地址是"%p\n",&x) %p格式 ...

  7. C语言中的指针加减偏移量

    C语言指针偏移技巧(也是一个要注意的坑) - 陈杰柱的博客 - CSDN博客  https://blog.csdn.net/cjzjolly/article/details/82116772 C语言中 ...

  8. 转:浅谈C/C++中的指针和数组(一)

    再次读的时候实践了一下代码,结果和原文不一致 error C2372: 'p' : redefinition; different types of indirection 不同类型的间接寻址 /// ...

  9. 【ZZ】C 语言中的指针和内存泄漏 & 编写高效的C程序与C代码优化

    C 语言中的指针和内存泄漏 http://www.ibm.com/developerworks/cn/aix/library/au-toughgame/ 本文讨论了几种在使用动态内存分配时可以避免的陷 ...

随机推荐

  1. ADB用法

    作为android开发人员,adb是常用的工具之一.具体怎么使用了. 1. 安装完ADB后(ADB的安装请参考<Android开发平台搭建及配置.doc>),用电脑USB连接机器,然后使用 ...

  2. Maven私库安装与配置

    Maven私库安装与配置 https://www.cnblogs.com/dengyulinBlog/p/6398310.html

  3. java list按照元素对象的指定多个字段属性进行排序

    ListUtils.java---功能类 package com.enable.common.utils; import java.lang.reflect.Field;import java.tex ...

  4. [Backbone] Customzing Backbone

    Convert the AppointmentForm view below to use Mustache templating. Make sure you remember to change ...

  5. php平滑升级

    php平滑升级 1.既然是平滑升级,是不需要宕机,也不需要停止服务,是不存在关闭网站的说法 2.而升级最后的重启,重启的是PHP加载的配置文件,不影响你当前运行的网站 3.php-fpm是属于Fast ...

  6. NYoj-119-士兵杀敌(3)-RMQ算法

    士兵杀敌(三) 时间限制:2000 ms  |  内存限制:65535 KB 难度:5 描写叙述 南将军统率着N个士兵,士兵分别编号为1~N,南将军常常爱拿某一段编号内杀敌数最高的人与杀敌数最低的人进 ...

  7. JavaScript 之 回调函数的返回值给全局变量赋值问题

    jQuery 中,会遇到$.get(url,data,callback,type) 或 $.post(url,data,callback,type) 返回值给全局变量赋值的问题: 例如: <sc ...

  8. Appium Python 三:Desired capabilities

    简介 Desired capabilities 是一些键值对的集合.python里面就采用字典的方式. 客户端将这些键值对发给服务端,告诉服务端我们想要怎么测试.比如,我们可以把 platformNa ...

  9. PyQt5教程——组件 Ⅱ(八)

    这部分的教程将会继续介绍PyQt5的组件.我们这节教程的内容将包括像素图(QPixmap),单行文本框(QLineEdit)和下拉列表框(QComboBox) 像素图(QPixmap) 像素图(QPi ...

  10. 浅析MySQL各种索引

    MySQL各种索引(由于是浅析大多都不刻意区分搜索引擎) INDEX(普通索引):最主要的索引.没有不论什么限制 ALTER TABLE `table_name` ADD INDEX index_na ...