C专家编程(学习笔记)_第4章 令人震惊的事实:数组和指针并不相同

本文深入探讨了数组和指针之间的关键区别,包括它们在内存分配、访问方式、定义与声明上的不同,以及如何正确匹配定义与声明避免编程错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.数组并非指针

        本章将完整地解释数组什么时候等同于指针,什么时候又不等同于指针及原因所在。

2.我的代码为什么无法运行

文件1:
int mango[100];
文件2:
extern int *mango;

      对数组的引用总是可以写成对指针的引用,而且确实存在一种指针和数组定义完全相同的上下文环境。不幸的是,这只是数组的一种极其普通的用法,并非所有情况下都是如此。但是,人们却自然而然地归纳并假定在所有的情况下数组和指针都是等同的,包括上面完全错误的“数组定义等同于指针的外部声明”这种情况。

3.什么是什么,什么是定义

定义是一种特殊声明,它创建了一个对象;声明简单地说明了在其它地方创建的对象的名字,它允许你使用这个名字。
------------------------------------------------------------------------------------------
定义 只能出现在一个地方 确定对象的类型并分配内存,用于创建新的对象。例如:int my_array[100];
声明 可以多次出现      描述对象的类型,用于指代其它定义的对象(例如在其它文件里)例:extern int my_array[];

区分定义和声明:
声明相当于普通的声明:它所说明的对象并非自身,而是描述其它地方的创建的对象;
定义相当于特殊的声明:它为对象分配内存;

extern对象声明告诉编译器对象的类型和名字,对象的内存分配则在别处进行。由于并未在声明中位数组分配内
存,所以并不需要提供关于数组长度的信息。对于多维数组需要提供除最左边一维之外其他维的长度。

   3.1数组和指针是如何访问的

                            X=Y;
在这个上下文环境里,符号X              在这个上下文环境里,符号Y的含义
的含义是X所代码的地址。                是Y所代表的地址的内容。

这被称为左值                          这被称为右值

左值在编译时可知,左值表示             右值直到运行时才知,如无特别说明
存储结果的方法。                      右值表示“Y的内容”。
char a[9]="abcdefgh";                      c=a[i]
编译器符号表具有一个地址9980
                运行步骤1:取i的值,将它与9980相加
                运行步骤2:取地址(9980+i)的内容
           ___|__|__|__|__|___|__|__
           9980 +1 +2 +3 +4 ... +i
                图A 数组的下标引用
char *p            ...                c=*p;
编译器符号表有一个符号p:它的地址为4624
             运行步骤1:取地址4624的内容,就是‘5081’。
             运行步骤2:取地址5081的内容。
         5 | 0 | 8 | 1 |   ——>|__|
    ——>4624                   5081
             图B 对指针的引用

  3.2当你"定义为指针,但以数组方式引用"时会发生什么

char *p="abcdefgh";                      c=p[i];
编译器符号表具有一个p,地址为4624
       运行步骤1:取地址4624的内容,即‘5081’。
       运行步骤2:取得i的值,并将它与5081相加。
       运行步骤3:取地址[5081+i]的内容。
     5 | 0 | 8 | 1 |  ——(5081+i)——>   |__|__|__|__|__|__|__| 
——>4624                           5081 +1 +2 +3 +4 ... +i
            图C 对指针进行下标引用

对照图C的访问方式:
char *p="abcdefgh";    P[3]
和图A的访问方式:
char a[]="abcdefgh";   a[3]
在这种情况下,都可以取得字符'd',但两者途径非常不一样。
当书写了extern char *p,然后用p[3]来引用其中的元素时,其实质是图A和图B访问方式的组合。首先进行图B所
示的间接引用。然后如图A所示用下标作为偏移量进行直接访问。更为正式的说明是,编译器将会:
(1)取的符号表中p的地址,提取存储于此处的指针;
(2)把下标所表示的偏移量与指针的值相加,产生一个地址;
(3)访问上面这个地址,取得字符;

4.使声明与定义相匹配

      指针的外部声明与数组定义不匹配的问题很容易修正,只要修改声明,使之与定义相匹配即可,如下所示:

文件1:
int mango[100];
文件2:
extern int mango[];
...
/*引用mango[i]的一些代码*/
mango数组的定义分配了100个int的空间,而指针定义:
int *raisin;
则申请一个地址容纳该指针。指针的名字是raisin,它可以指向任何一个int变量(int型数组)。指针变量raisin本身
始终位于同一地址,但它的内容在任何时候都可以不相同,指向不同地址的int变量。这些不同的int变量可以有不同的值。
mango数组的地址并不能改变,在不同的时候它的内容可以不同,但它总是表示100个连续的内存空间。

5.数组和指针的其它区别

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

数组和指针都可以在它们的定义中用字符串常量进行初始化。  定义指针时,编译器并不为指针所指向的对象分配
空间,它只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化。
初始化指针时所创建的字符串常量被定义为只读。如果试图通过指针修改这个字符串的值,程序就会出现未定义的行为。
与指针相反,由字符串常量初始化的数组是可以修改的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值