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

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. Linq编程小趣味爱因斯坦谜题

    最近看到一个比较老的题目,题目----在一条街上,有5座房子,喷了5种颜色,每个房里住着不同国籍的人,每个人喝不同的饮料,抽不同品牌的香烟,养不同的宠物,问题---谁养鱼? 以前没事还做过这个题,现在 ...

  2. Best Time to Buy and Sell Stock leetcode java

    题目: Say you have an array for which the ith element is the price of a given stock on day i. If you w ...

  3. MySql的入侵测试以及防范

    在做了之前的SQL SERVER之后,便很想尝试一下MYSQL的入侵测试已经防范,与大家一起分享. 总的来说,我一直在用的是MYSQL,对MYSQL比较熟悉,相比较而言,感觉MYSQL更安全,这只是我 ...

  4. 【7】AccessDB快速数据访问

    阅读目录 C#和VB数据访问的比较 AccessDB的设计 数据库的连接 三种主要操作 错误输出及调试 小结 回到顶部 C#和VB数据访问的比较 C#中要进行一次普通的数据库查询,需要创建连接,再根据 ...

  5. ADO.Net 之 数据库连接池(二)

    连接到数据库服务器通常由几个需要很长时间的步骤组成.必须建立物理通道(例如套接字或命名管道),必须与服务器进行初次握手,必须分析连接字符串信息,必须由服务器对连接进行身份验证,必须运行检查以便在当前事 ...

  6. iOS SEL类型和创建

    SEL selAction =NSSelectorFromString([actionArrayobjectAtIndex:indexArray]); [item addTarget:self act ...

  7. Python3.2官方文档翻译--标准库概览(一)

    7.1 操作系统接口 Os模块提供主要很多与操作系统交互的函数. >>> import os >>> os.getcwd() # Return the curren ...

  8. 事务的四大特性ACID介绍

    事务是恢复和并发控制的基本单位.ACID 事务应该具有4个属性:原子性.一致性.隔离性.持续性.这四个属性通常称为ACID特性. 原子性(atomicity).一个事务是一个不可分割的工作单位,事务中 ...

  9. lnmp环境的使用教程

    lnmp环境的使用 安装的软件都安装到了:/usr/local 管理nginx service nginx start|stop|restart|reload 管理mysql 直接执行mysql即可登 ...

  10. svn 和 git的区别

    1.速度: 克隆一份全新的目录,以同样拥有五个(才五个)分支来说,SVN是同时复製5个版本的文件,也就是说重复五次同样的动作.而Git只是获取文件的每个版本的元素,然后只载入主要的分支(master) ...