指针与数组

本文详细探讨了C语言中指针与数组的关系,包括一维数组的定义和访问方式,数组作为函数参数的传递,以及多维数组的内存布局。指出了数组名在某些情况下可被视为指向首元素的常量指针,但数组和指针在内存分配和使用上有本质区别。特别强调了在使用字符串常量和多维数组时应注意的问题。

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

在C和C++语言中指针之所以复杂多变,我认为主要原因在于指针与数组错综复杂的关系。如果数组不能转为指针那么就不存在指针的数学运算,也不存在指针的比较。因此,深入的理解指针与数组的关系是发挥指针强大作用的必经之路。

一维数组

一维数组的定义

int a1[5];
int a2[] = {1, 2, 3, 4, 5};
char b1[6];
char b2[] = {'a', 'b', 'c', 'd', 'e', '\0'};
char b3[] = "abcde";  //是b2初始化方式的简化版本
  • 数组定义的时候可以初始化,也可以不初始化,但必须指明数组元素的个数;
  • 数据元素的个数可以通过初始化隐身确定,上面代码中a1和a2的元素个数相同,b1,b2,b3的元素个数相同,而且b2和b3存储的数据是相同的;

数组名一般表示,指向数组第一个元素的常量指针,对数组其他的理解都要基于这个认识之上。只有两种特殊情况,对于数组名用于sizeof返回整个数组占的字节,&数组名返回的是数组的首地址,而不是指向数组首地址的地址;

对数组元素的访问
对数组元素的访问有两种方式,一种是指针的间接访问,另一种是下标访问。下标访问的效率绝不可能比指针高,但是下标访问的程序可读性更高。没有必要为了几微秒的效率而牺牲可读性。

int array[10];
int *ap = array + 2;

ap[0] == *ap == array[2] == *(array + 2)
ap[2] == *(ap + 2) = array[4] == *(array + 4)

一维数组做形参

  • 数组不能进行拷贝,在作为参数传入函数的时候,也是采用传值的方式,只不过这个值是一个地址;
  • 使用数组的地方,自动转换为指针进行运算
  • 以下三种形式做函数形参的结果是一样的,都是转化为指针进行运算的,该指针就是指向数组首地址的常量指针,而数组的个数需要通过额外的参数传入;
void fun(int * array){}
void fun(int array[]){}
void fun(int array[10]){}

字符数组与字符串常量

char a[] = "abcde";
char b = "abcde";

其中a是字符数组,b是字符串常量。两者的区别在于:a存储在堆栈上,b存储在常量区;a所指向的对象可以更改,而b所指的对象一旦创建就不能更改;a在运行过程中会被销毁,b会一直存在知道程序结束。
所以使用字符串处理函数时,要格外小心字符串常量,字符串常量只能做const char *的参数,例如用strcat函数处理两个常量字符串时就是出错。

多维数组

多维数组实际上是数组中存放数组

图是直接百度上找的,但是足以说明问题了,下图是个int a[3][4]的数组在内存中存放格式。
这里写图片描述

多维数组的定义

int a[3];        //一维数组
int b[2][3];     //二维数组
int c[1][2][3];  //三维数组

通过大量的分析总结出一句话:多维数组的数组名是指向比定义的数组低一维的数组的指针,话说起很绕口,举例子就明白了。上面的定义中,a是一维数组,数组名a是指向数组第一个元素的指针,该指针是int*类型的;b是二维数组,数组名b是指向第一行数组的指针,即int (*p)[3];c是三维数组,数组名c是指向一个两行三列的数组的指针,即int (*p)[2][3]。对于其他更高维数的指针类似分析即可。

int p1[3]与int(*p2)[3]的区别:int *p1[3]定义的p1是一个一维数组,该数组中存放了三个int类型的指针,而int(*p2)[3]定义的p2是一个指针,该指针指向是一个一维数组,该数组中存放了三个int类型的数据。因为,数组名就是指针,因此p1和p2都是指针。但是最大的区别在哪里展现出来呢?考虑++p1和++p2,指针会分别往后移动多少个字节?++p1是int类型指针,因此会移动四个字节,而++p2会移动3*4 = 12,移动12个字节。

还有一点需要注意的是:

int array[3][4];
int (*p)[4] = array;
注意:int **p = array, 这是错误的。

只有在另一种情况下使用指针数组:
char ss[] = {"abc", "def", "hij"};
char** str = ss;
char* strarray[3] = ss; 

看一个问题:下面这段代码的输出结果应该是多少?

#include <iostream>

using namespace std;

int _tmain(int args, char* argv[])
{
    int a1[3][2] = {11, 12, 21, 22, 31, 32}; //二维数组的初始化
    int (*p2)[2];

    p2 = a1;  //注意这里的复制,说明二维数组的数组名是一个指向数组的指针
    p2++;

    cout  << p2[1][1] << endl;  //p2[1][1] = *(*(p2 + 1) + 1)

    return 0;
}

应该是32,因为用a1初始化p2后,p2指向数组的第一行数组,p2++后指向第二行数组。
p2进行下标访问与指针访问相同,等同于((p2 + 1) + 1),把p2指针加1后指向了第三行数组,然后解引用得到该数组的地址,因为p2是指向数组的指针,而数组又是用一个指针来表达的,因此解引用后得到了第三行数组的首地址,然后加1指向第二个元素,最后解引用得到的就是第三行的第二个元素,也就是32了。

另一种设计多维数组的方法,在堆上分配内存,注意销毁

#include <tchar.h>
#include <iostream>

using namespace std;

int _tmain(int args, TCHAR* argv[])
{

    int **p1;

    p1 = new int*[3];

    for (int i = 0; i < 3; i++)
    {
        p1[i] = new int[2];
    }

    p1[0][0] = 1;
    p1[0][1] = 2;

    p1[1][0] = 4;
    p1[1][1] = 5;

    p1[2][0] = 7;
    p1[2][1] = 8;

    return 0;
}
  • 二维数组做函数形参

规定:如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数,数组的行数没有太大关系,可以指定也可以不指定。因为函数调用时传递的是一个指针,它指向由行向量构成的一维数组。
因此二维数组作为函数参数正确写法如下所示:

fun(int array[3][10]);
fun(int array[][10]);
fun(int (*array)[10]);

void main()
{
    int a[3][10];
    fun(a);
}

错误的传参方法:

//指针错误
fun(int *array[10]); 
//未指明列数
fun(int array[][]);

指针与数组的区别

写了这么多,似乎指针与数组的区别越来越模糊了。在数组做函数形参时,指针和数组是完全等同的。但是,指针与数组最大的区别在于:定义一个指针,只会给其分配一个4字节(为了便于描述,定为4个字节)的空间,用于存放地址。而定义一个指针,会给其分配数组中所有元素所需要的空间。定义一个指针的初始化,要么指向已经分配内存的空间,要么动态分配内存。

参考文章:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值