C语言指针和数组详讲

一维数组
数组名
int a;
int b[10]

在c中,在几乎所有使用数组名的表达式中,数组名的值是一个指针常量,也就是数组第1个元素的地址(要注意数组名在这里指的是一个地址)。它的类型取决于数组元素的类型:如果它们是int类型, 那么数组名的类型就是“指向int的常量指针”;如果它们是其他类型,那么数组名的类型就是“指 向其他类型的常量指针”。同时,还要注意这并不代表数组就等同于指针,数组有不同的特征,数组有确定数量的元素,而指针是标量。
再看下面例子

int a[10];  //声明整形数组 
int b[10]; //声明整形数组 
int *c;//定义指针变量c
c = &a [0];//‘&’为取地址符,即取数组中第一个元素的地址赋给指针C 
c=a; //数组名代表第一个元素的地址,所以将地址发给指针c

那么能不能反过来赋值即

a=c;
b=c;

这样做显然是不行的,但是我们知道数组名代表的是一个指针常量,即该地址是不可更改的,我们不能作为变量来进行赋值。

下标引用
  • 第一个例子
*(b+3);

首先,b的值是一个指向整型的指针,所以3这个值根据整型值的长度进行调整。加法运算的结果是另一个指向整型的指针(及括号里面的,实际表示的是另一个地址),它所指向的是数组第1个元素向后移3个整数长度的位置。然后,间接访问操作(即间接访问操作符’ * ')访问这个新位置,或者取得那里的值(右值),或者把一个新值存储于该处 (左值)。
它等同于另一种表示

*(b+3); //先确定第三个元素的地址,间接访问第三个元素的值
b[3];//数组中底三个元素的值
  • 详细分析
    为了避免混淆,我们进一步说明数组名,下标和操作符与指针的使用
int      array[10];
int      *ap = array + 2;//定义ap指针并赋值

记住,在进行指针加法运算时会对2进行调整。运算结果所产生的指针ap指向array[2],如下图所示
在这里插入图片描述

  1. Ap 就是arry[2]的地址:array+2。另外,&array[2]也是与它对等的表达式。
    • ap 是一个值,也就是array[2]元素的值。你也可以这样写:*(array+2)。
  2. ap[0] “你不能这样做,ap不是一个数组!"如果你是这样想的,你就陷入了 “其他语言不能这样做”这个惯性思维中了。记住,C的下标引用和间接访问表达式是一样的。在现在这种情况 下,对等的表达式是*(ap+(0)),除去0和括号,其结果与前一个表达式相等。因此,它的答案和上 一题相同:array[2],注意表示的仍是一个值。
  3. ap+6 如果ap指向array[2],这个加法运算产生的指针所指向的元素是array[2]向后移动6 个整数位置的元素。与它对等的表达式是array+8或&array[8]。
    • ap+6 小心!这里有两个操作符,哪一个先执行呢?是间接访问。间接访问的结果再与6 相加,所以这个表达式相当于表达式array[2]+6。如果arry[2]的值等于1,那么相加就等于7.
    • (ap+6)括号迫使加法运算首先执行,所以我们这次得到的值是array[8]。注意这里的间接访问操作和下标引用操作的形式是完全一样的。
  4. ap[6] 间接访问,原理同3把这个下标表达式转换为与其对应的间接访问表达式形式,你会发现它就是我们刚刚完成的那个表达式,所以它们的答案相同。
  5. &ap 这个表达式是完全合法的,但此时并没有对等的涉及array的表达式,因为你无法预
    测编译器会把ap放在相对于array的什么位置。
  6. ap[-1] 负值的下标!下标引用就是间接访问表达式,你只要把它转换为那种形 式并对它进行求值。ap指向第3个元素(就囲个下标值为2的元素),所以使用偏移量-1使我们 得到它的前一个元素,也就是array[l]。(也就是ap[-1]等价于ap±1,表示的是下标为1的元素)。
  7. ap[9] 这个表达式看上去很正常,但实际上却存在问题。它对等的表达式是arraylll],但问 题是这个数组只有10个元素。这个下标表达式的结果是一个指针表达式,但它所指向的位置越过了 数组的右边界。根据标准,这个表达式是非法的。但是,很少有编译器能够检测到这类错误,所以程序能够顺利地继续运行。
  • 指针与下标
    将数元素初始化为零
int array[10];
int for (int a = 0; a < 10; a++ )
array[a] = 0;
int array[10],* ap;
int for (ap= array; ap< 10; ap++ )
* ap = 0;

下标和指针等价,如果你可以互换地使用指针表达式和下标表达式,那么你应该使用哪一个呢?和往常一样,这里并没有一个简明答案。对于绝大多数人而言,下标更容易理解,尤其是在多维数组中。所以,在 可读性方面,下标有一定的优势。但在另一方面,这个选择可能会影响运行时效率。假定这两种方法都是正确的,下标绝不会比指针更有效率,但指针有时会比下标更有效率。

数组和指针

指针和数组并不是相等的。为了说明这个概念,请考虑下面这两个声明:

int a[5];                               •
int *b;

a和b能够互换使用吗?它们都具有指针值,它们都可以进行间接访问和下标引用操作。但是, 它们还是存在相当大的区别。
声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保 留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为指向任何现有的内存空间,如果它是一个自动变量,它甚至根本不会被初始化。把这两个声明用图的方法来表示, 你可以发现它们之间存在显著不同。
在这里插入图片描述

因此,上述声明之后,表达式* a是完全合法的,但表达式* b“却是非法的(注意和声明一个指针区别开,int* b,它是指针声明,而不是* b,实际应是int* b)。“将访问内存中某 个不确定的位置,或者导致程序终止。另一方面,表达式b++可以通过编译,但a++却不行,因为a 的值是个常量。

多维数组
  • 理解多维
    为了准确理解多维数组,我们从多维的角度来理解
    int a;
    int b[10];
    int  c[6][10];
    int d[3] [6] [10];

a是个简单的整数。接下来的那个声明增加了一个维数,所以b就是一个向量,它包含10个整 型元素。
c只是在b的基础上再增加一维,所以我们可以把c看作是一个包含6个元素的向量,只不过 它的每个元素本身是一个包含10个整型元素的向量。换句话说,c是个一维数组的一维数组。d也 是如此:它是一个包含3个元素的数组,每个元素都是包含6个元素的数组,而这6个元素中的每 一个又都是包含10个整型元素的数组。简洁地说,d是一个3排6行10列的整型三维数组。
理解这个视点是非常重要的,因为它正是C实现多维数组的基础。

多维数组存储顺序
int array[3];//含有3个元素的数组

它的存储形式用图形表示就是
在这里插入图片描述

int array[3][6];//含有三个元素,每个元素由包含6个元素的数组。

在这里插入图片描述

实线方框表示第1维的3个元素,虚线用于划分第2维的6个元素。按照从左到右的顺序,上 面每个元素的下标值分别是:
0, 0 0,1 0,2 0,3 0, 4 0,5
1,0 1,1 1,2 1,3 1,4 1,5
2,0 2,1 2,2 2,3 2,4 2,5
这个例子说明了数组元素的存储顺序(storageorder)。在C中,多维数组的元素存储顺序按照最 右边的下标率先变化的原则,称为行主序(row major order)„知道了多维数组的存储顺序有助于回答 一些有用的问题比如你应该按照什么样的顺序来编写初始化列表的值。

  • 再看一个例子
    int matrix[6] [10]; int *mp;
    mp = &matrix[3][8];
    printf( "First value is %d\nw, *mp );
    printf( MSecond value is %d\n"r *++mp );
    printf( "Third value is %d\nM, *++mp );

很显然,第1个被打印的值将是matrix⑶[8]的内容,但下一个被打印的又是什么呢?存储顺序 可以回答这个问题 ——下一个元素将是最右边下标首先变化的那个,也就是matrix[3][9].再接下去 又轮到谁呢?第9列可是一行中的最后一列啦。不过,根据存储顺序规定,一行存满后就轮到下一 行,所以下一个被打印的元素将是matrix[4][0].

多维数组数组名与下标
int array[3][10];

创建了 matrix,它可以看作是一个一维数组,包含3个元素,只是每个元素恰好是包含10个整 型元素的数组。
matrix这个名字的值是一个指向它第1个元素的指针,所以matrix是一个指向一个包含10个整型元素的数组的指针。
图形表示array
在这里插入图片描述

图形表示array+1(因为1这个值根据包含10个整型元素的数组的长度进行调整,所以它指向matrix的 下一行。)如果对其执行间接访问操作,就如下图随箭头所示
在这里插入图片描述

  * (array+1)

事实上标识了一个包含10个整型元素的子数组。数组名的值是个常量指针,它指向数组的第1 个元素,在这个表达式中也是如此。它的类型是“指向整型的指针”,我们现在可以在下一维的上下 文坏境中显示它的值:
在这里插入图片描述

注意,它表示的实际上是第二行第一个元素的地址(也可以说是指针),而不是值,这与一维数组不同,如果按一维的话

  • (array+1)表示的是下标为1的元素。这一点一定要进行区分
    那么,再看看这个它表示什么
*(array+1)+5;

前一个表达式是个指向整型值的指针,所以5这个值根据整型的氐度进行调整。整个表达式的 结果是一个指针,它指向的位置比原先那个表达式所指向的位置向后移动了5个整型元素。
在这里插入图片描述

*(*(array+1)+5);

它所访问的正是上图中的那个整型元素。如果它作为右值使用,你就取得存储于那个位置的值。 如果它作为左值使用,这个位置将存储一个新值。

作为函数参数的数组
  • 一位数组
    函数
void fund ( int *vec );
void fund (int vec []);
//两者等价

主函数

.....
int       vector[10];
fund (vector);//数组名作为参数对函数调用
.....
  • 多维数组(二维数组为例)
    函数
void fund ( int (*mat)[10] );
void fund (int mat[ ][10]);

//两者等价

主函数

int mat[3][10];
fund(mat);

在这个函数中,mat的第1个下标根据包含10个元素的整型数组的长度进行调整,接着第2个 下标根据整型的长度进行调整,这和原先的matrix数组一样。
这里的关键在于编译器必须知道第2个及以后各维的长度才能对各下标进行求值,因此在原型 中必须声明这些维的长度。第1维的长度并不需要,因为在计算下标值时用不到它。
注意,多维数组中,第一个维度长度是可以缺省的,而之后的维度不能省略,必须有确定的长度

检测及练习

如果你想知道自己是否完全掌握上面所提到的内容,可以做一下下面的这道题。(当然,一遍肯定是难以琢磨明白,多读几遍,然后找几个指针题目练习一下,只要理解透彻,掌握还是很容易的)
在这里插入图片描述在这里插入图片描述

答案
在这里插入图片描述附:
C语言指针
文章引用:C和指针

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值