本文转载自CSDN@WalkingInTheWind,原文链接:https://blog.csdn.net/luckyxiaoqiang/article/details/7044380

C语言中数组和指针是一种很特别的关系,首先本质上肯定是不同的,本文从各个角度论述数组和指针。

1. 数组与指针的关系

数组和指针是两种不同的类型,数组具有确定数量的元素,而指针只是一个标量值。数组可以在某些情况下转换为指针,当数组名在表达式中使用时,编译器会把数组名转换为一个指针常量,是数组中的第一个元素的地址,类型就是数组元素的地址类型,如int a[5] = {0, 1, 2, 3, 4};,数组名a若出现在表达式中,如int *p = a;,那么它就转换为第一个元素的地址,等价于int *p = &a[0];再来一个int aa[2][5] = { {0,1,2,3,4}, {5,6,7,8,9} };,数组名aa若出现在表达式中,如int (*p)[5] = aa;那么它就转换为第一个元素的地址,等价于int (*p)[5] = &aa[0];。但是int (*p)[5] = aa[0];这个就不对了,根据规则我们推一下就明白了,aa[0]的类型是int [5],是一个元素数量为5的整型数组,就算转化,那么转化成的是数组(int [5])中第一个元素的地址&aa[0][0],类型是int *。所以,要么是int (*p)[5] = aa;,要么是int (*p)[5] = &aa[0];

只有在两种场合下,数组名并不用指针常量来表示。就是当数组名作为sizeof操作符或单目操作符&的操作数时,sizeof返回整个数组的长度,使用的是它的类型信息,而不是地址信息,不是指向数组的指针的长度。取一个数组名的地址所产生的是一个指向数组的指针,而不是指向某个指针常量值的指针。如对上述二维数组a,&a表示的是指向数组a的指针,类型是int (*)[5],所以int* p = &a;是不对的,因为右边是一个整形数组的指针int (*)[5],而p是一个整形指针int *;数组的sizeof问题会在下面中仔细讨论。

2. 数组与指针的下标引用

int a[5]={0,1,2,3,4};,如a[3],用下标来访问数组a中的第三个元素,那么下标的本质是什么?本质就是这样的一个表达式:*(a+3),当然表达式中必须含有有效的数组名或指针变量。其实a[3]和3[a]是等价的,因为他们被翻译成相同的表达式(顶多顺序不同而已),都是访问的数组a中的元素3。指针当然也能用下标的形式了,如int *p=a;,那么p[3]就是*(p+3),等同于3[p],同样访问数组a中的元素3。根据这一规则,我们还能写出更奇怪的表达式,如int aa[2][5] = { {0,1,2,3,4}, {5,6,7,8,9} };,1[aa][2],这个看起来很别扭,首先1[aa],就是*(1+aa),那么1[aa][2]就是*(*(1+aa)+2),也就是aa[1][2]。1[2][aa],这个就不对了,因为前半部分1[2]是不符合要求的。当然在实际中使用这样的表达式是没有意义的,除非就是不想让人很容易的看懂你的代码。

3. 数组与指针的定义和声明

数组和指针的定义与声明必须保持一致,不能一个地方定义的是数组,然后再另一个地方声明为指针。

首先我们解释一下数组名的下标引用和指针的下标应用,它们是不完全相同的。从访问的方式来讲,int a[5] = {0,1,2,3,4}; int* p = a;,对于a[3]和p[3]都会解析成*(a+3)*(p+3),但是实质是不一样的。

  • 首先对于a[3],也就是*(a+3)

    1. 把数组名a代表的数组首地址和3相加,得到要访问数据的地址,这里注意,数组名a直接被编译成数组的首地址;
    2. 访问这个地址,取出数据。
  • 对于p[3],也就是*(p+3)
    1. 从p代表的地址单元里取出内容,也就是数组首地址,指针名p代表的是指针变量的存储地址,变量的地址单元里存放的才是数组的首地址;
    2. 把取出的数组首地址和3相加,得到要访问的数据的地址;
    3. 访问这个地址,取出数据。

下面给出一个例子来说明若定义和声明不一致带来的问题:设test1.cpp中有如下定义:char s[] = "abcdefg";,test2.cpp中有如下声明:extern char* s;,显然编译是没有问题的。那么在test2.cpp中引用s[i]结果怎样呢?如s[3],是‘d’吗?好像是吧?下面我们对test2.cpp中的s[3]进行分析:s的地址当然是由test1.cpp中的定义决定了,因为在定义时才分配内存空间的;我们根据上面给出的指针下标引用的步骤进行计算如下:

  1. 从s代表的地址单元中取出内容(4个字节),这里实际上是数组s中的前4个元素,这个值是“abcd”,也就是16进制64636261h,到这一步应该就能看出来问题了;
  2. 然后把取出的首地址和3相加,得到要访问的数据的地址64636261h+3,这个地址是未分配未定义的;
  3. 取地址64636261h+3的内容,这个地址单元是未定义的,访问就会出错。

下面给出分析的代码(可只需观察有注释的部分):

#include <iostream>
using namespace std;
extern void test();
char s[] = "abcdefg";
int main(void)
{
002E13A0  push        ebp
002E13A1  mov         ebp,esp
002E13A3  sub         esp,0D8h
002E13A9  push        ebx
002E13AA  push        esi
002E13AB  push        edi
002E13AC  lea         edi,[ebp+FFFFFF28h]
002E13B2  mov         ecx,36h
002E13B7  mov         eax,0CCCCCCCCh
002E13BC  rep stos    dword ptr es:[edi]
          char ch;
          int i = 3;
002E13BE  mov         dword ptr [ebp-14h],3
          ch = s[i];
002E13C5  mov         eax,dword ptr [ebp-14h]
002E13C8  mov         cl,byte ptr [eax+011F7000h]  /* s直接翻译成数组首地址和i(eax)相加,得到操作数地址,然后作为byte ptr类型取内容,传给cl */
002E13CE  mov         byte ptr [ebp-5],cl          /* cl的内容传给ch(ebp-5) */
          test();
002E13D1  call        002E1073
          return 0;
002E13D6  xor         eax,eax
}
002E13D8  pop         edi
002E13D9  pop         esi
002E13DA  pop         ebx
002E13DB  add         esp,0D8h
002E13E1  cmp         ebp,esp
002E13E3  call        002E113B
002E13E8  mov         esp,ebp
002E13EA  pop         ebp
002E13EB  ret

test2.cpp // 运行错误

extern char *s;
void test()
{
011F1470  push        ebp
011F1471  mov         ebp,esp
011F1473  sub         esp,0D8h
011F1479  push        ebx
011F147A  push        esi
011F147B  push        edi
011F147C  lea         edi,[ebp+FFFFFF28h]
011F1482  mov         ecx,36h
011F1487  mov         eax,0CCCCCCCCh
011F148C  rep stos    dword ptr es:[edi]
          char ch;
          int i = 3;
011F148E  mov         dword ptr [ebp-14h],3
          ch = s[i];
011F1495  mov         eax,dword ptr ds:[011F7000h]  /* ds没有影响,因为windows中所有的段基址都为0,取011F7000h单元的内容,这里是数组中前四个字节(指针是四个字节)组成的整数,也就是64636261h,也就是这里,把s所指的单元计算成了64636261h */
011F149A  add         eax,dword ptr [ebp-14h]       /* 然后把地址和i相加,也就是64636261h+3,这个地址是未分配定义的,访问当然会出错 */
011F149D  mov         cl,byte ptr [eax]             /* 访问错误 */
011F149F  mov         byte ptr [ebp-5],cl
          return;
}
011F14A2  pop         edi
011F14A3  pop         esi
011F14A4  pop         ebx
011F14A5  mov         esp,ebp
011F14A7  pop         ebp
011F14A8  ret

若test2.cpp中这样声明:extern char s[];,这样就正确了,因为声明和定义一致,访问就没问题了。所以千万不要简单的认为数组名与指针是一样的,否则会吃大亏,数组的定义和声明千万要保持一致性。

4. 数组和指针的sizeof问题

数组的sizeof就是(数组的元素个数*元素大小),而指针的sizeof全都是一样,都是地址类型,32位机器是4个字节。下面给出一些例子:

#include <iostream>
using namespace std;
int main()
{
    int a[6][8] = {0};
    int (*p)[8];
    p = &a[0];
    int (*pp)[6][8];
    pp = &a;

    cout << sizeof(a) << endl;        // 192
    cout << sizeof(*a) << endl;       // 32
    cout << sizeof(&a) << endl;       // 4
    cout << sizeof(a[0]) << endl;     // 32
    cout << sizeof(*a[0]) << endl;    // 4
    cout << sizeof(&a[0]) << endl;    // 4
    cout << sizeof(a[0][0]) << endl;  // 4
    cout << sizeof(&a[0][0]) << endl; // 4
    cout << sizeof(p) << endl;        // 4
    cout << sizeof(*p) << endl;       // 32
    cout << sizeof(&p) << endl;       // 4
    cout << sizeof(pp) << endl;       // 4
    cout << sizeof(*pp) << endl;      // 192
    cout << sizeof(&pp) << endl;      // 4

    system("pause");
    return 0;
}

VS2010在32位windows7下的运行结果如上代码中注释部分(VC6.0不符合标准),下面对程序做逐一简单的解释:

  • sizeof(a);,a的定义为int a[6][8],类型是int [6][8],即元素个数为 6*8 的二维int型数组,它的大小就是 6*8*sizeof(int),这里是192;
  • sizeof(*a);*a这个表达式中数组名a被转换为指针,即数组第一个元素a[0]的地址,*得到这个地址所指的对象,也就是a[0],总的来说*a等价于*(&a[0]),a[0]的类型int [8],即大小为8的一维int型数组,它的大小就是8*sizeof(int),这里是32;
  • sizeof(&a);,'&'取a的地址,类型是int (*)[6][8],地址类型,这里大小是4;
  • sizeof(a[0]);,a[0]的类型int [8],即大小为8的一维int型数组,它的大小就是8*sizeof(int),这里是32;
  • sizeof(*a[0]);*a[0]这个表达式中数组名a[0]被转换为指针,即数组的第一个元素a[0][0]的地址,*得到这个地址所指的元素,也就是a[0][0],总的来说*a[0]等价于*(&a[0][0]),a[0][0]的类型是int,它的大小就是sizeof(int),这里是4;
  • sizeof(&a[0]);,'&'取a[0]的地址,类型是int (*)[8],地址类型,这里大小是4;
  • sizeof(a[0][0]);,a[0][0]的类型是int,它的大小就是sizeof(int),这里是4;
  • sizeof(&a[0][0]);,'&'取a[0][0]的地址,类型是int *,地址类型,这里大小是4;
  • sizeof(p);,p的类型是int (*)[8],指向一个元素个数为8的int型数组,地址类型,这里大小是4;
  • sizeof(*p);*p取得p所指的元素,类型是int [8],大小为8*sizeof(int),这里是32;
  • sizeof(&p);,'&'取p的地址,类型是int (**)[8],地址类型,这里大小是4;
  • sizeof(pp);,pp的类型是int (*)[6][8],指向一个大小为6*8的二维int型数组,地址类型,这里大小为4;
  • sizeof(*pp);*pp取得pp所指的对象,类型是int [6][8],即元素个数为6*8的二维int型数组,它的大小就是6*8*sizeof(int),这里是192;
  • sizeof(&pp);,'&'取pp的地址,类型是int (**)[6][8],地址类型,这里大小是4;

5. 数组作为函数参数

当数组作为函数参数传入时,数组退化为指针,类型是第一个元素的地址类型。“数组名被改写成一个指针参数”,这个规则并不是递归定义的。数组的数组会被改写为“数组的指针”,而不是“指针的指针”。下面给出几个例子:

fun1(char s[10])
{
    // s在函数内部实际的类型是char *;
}

fun2(char s[][10])
{
    // s在函数内部的实际类型是char (*)[10],即char [10]数组的指针;
}

fun3(char *s[15])
{
    // s在函数内部的实际类型是char **,字符型指针的指针;
}

fun4(char (*s)[20])
{
    // s在函数内部的实际类型不变,仍然是char (*)[20],即char [20]数组的指针;
}

以上可以简单的归纳为:数组作为参数被改写为指向数组的第一个元素(这里的元素可以是数组)的指针。数组作为参数必须提供除了最左边一维以外的所有维长度。

我们还要注意char s[][10]char** s作为函数参数是不一样的,因为函数内部指针的类型是不一样的(数组指针和二级指针),尤其在进行指针加减运算以及sizeof运算时。

总结:总结了这么多,应该对数组和指针有个较深入的理解了。这些问题的归根原因还是来自于指针问题,这也正是C语言的精华所在,不掌握这些根本不算掌握C语言,不过掌握了这些也不敢说就等于掌握了C语言:)

深入理解 C/C++ 数组和指针的更多相关文章

  1. 深入理解C/C++数组和指针

    C语言中数组和指针是一种很特别的关系,首先本质上肯定是不同的,本文从各个角度论述数组和指针. 一.数组与指针的关系数组和指针是两种不同的类型,数组具有确定数量的元素,而指针只是一个标量值.数组可以在某 ...

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

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

  3. 关于c语言二维数组与指针的个人理解及处理办法。

    相信大家在学习C语言时,对一维数组和指针的理解应该是自信的,但是,我在学习过程中,看到网上一些博文,发现即便是参加工作的一些专业编程人员,突然碰到二维数组和指针的问题时,也可能会遇到难以处理的诡异问题 ...

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

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

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

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

  6. C语言数组和指针的理解_在取地址运算上的操作_指针加减操作_a 和&a 的区别

    1.一个实例+理论分析 在了解数组和指针的访问方式前提下,下面再看这个例子: main() { int a[5]={1,2,3,4,5}; int *ptr=(int *)(&a+1); pr ...

  7. (C初学) 对数组与指针的一些浅显的理解

    因为课堂上没听懂,又看不懂教科书(<C语言程序设计教程>第3版 谭浩强,张基温编著)上晦涩的表达方式,昨天晚上特意拿<C语言入门经典>这本书自己研究了一晚的数组与指针. 先来一 ...

  8. 程序员之--C语言细节13(二维数组和指针,&amp;*a[i][0]的理解,数组1[e]和e[1]非常可能你没见过)

    主要内容:二维数组和指针.&*a[i][0]的理解.数组1[e]和e[1] #include <stdio.h> #define NUM_ROWS 10 #define NUM_C ...

  9. 关于C语言的指针数组与指针数组的个人理解

    一.指针数组与指针数组 1,指针数组 顾名思义,即一个元素全部是指针的数组,其形式与普通数组相似,形式如 *a[N]. 在理解指针数组的使用方式前,我先来说下我个人对数组的理解. 比如一维整形数组(形 ...

随机推荐

  1. CMake命令之export

    CMake中与export()相关的命令 (注:红色字体是标题,粉色是需要特别需要注意的地方) 总的来说,export()命令想要做的事情可以用一句话概括:Export targets from th ...

  2. Python批量重命名文件

    批量替换文件名中重复字符: # -*- coding: UTF-8 -*- import os path = raw_input("请输入文件夹路径:") oldname = ra ...

  3. format 可以用 * 星号

    procedure TForm1.FormCreate(Sender: TObject); var s:string; a:integer; b:Single; begin a:=; b:=108.4 ...

  4. JAVA内存分配-通俗讲解

    Java的内存分配上,主要分4个块: 一块是用来装代码的,就是编译的东西. 一块是用来装静态变量的,例如用static关键字的变量,例如字符串常量. 一块是stack,也就是栈,是用来装变量和引用类型 ...

  5. dfs--迷宫

    题目背景 给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过.给定起点坐标和终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案.在迷宫中移动有上下左右四种方式,每次只能移 ...

  6. Java 14 有哪些新特性?

    记录为 Java 提供了一种正确实现数据类的能力,不再需要为实现数据类而编写冗长的代码.下面就来看看 Java 14 中的记录有哪些新特性. 作者 | Nathan Esquenazi 译者 | 弯月 ...

  7. win10环境下pyinstaller打包pytorch遇到的问题及解决方案

    pytorch-python源码生成windows的应用程序(.exe),报错OSError: could not get source code Failed to execute script h ...

  8. idea中maven项目依赖jar一直标红线

    网上maven仓库中无法下载某些jar包,这时候就需要手动下载,并导入maven, 导入命令demo: mvn install:install-file -DgroupId=javax.media - ...

  9. 自动按键的Sendkeys工具的下载和使用

    大家好! 下面介绍一款自动按键的小工具:Sendkeys 下载地址 Sendkeys.rar 按键脚本的书写规则如下: 启动本工具后,在工具中打开一个脚本文件,然后在工具中按下Ctrl+A全选所有脚本 ...

  10. React Native 开发

    摘自:<React Native 开发之 IDE 选型和配置> 一个在不断更新的有关React Native讲解:<江清清的技术专栏> ES5和ES6的区别:<React ...