C语言细节 - 指针与数组

本文是读完《c和指针》后记录的一些关于数组和指针中值得注意的细节,如有错误,还望指正;另外要说明的是,c中数组和指针有一定的关联,故讲到数组时,难免会与指针牵扯上关系,所以这篇文章也包含部分指针的注意事项,你还可以阅读这篇文章了解更多指针内容:c语言细节 - 指针

文章目录

    • 一.数组名
        • 1.一维数组
        • 2.多维数组
    • 二.下标引用
        • 1.一维数组
        • 2.多维数组
    • 三.数组的初始化
        • 1.隐式赋值
        • 2.存储顺序
        • 3.部分初始化
        • 4.未定长度初始化
    • 四.函数传参
            • 一维数组
            • 多维数组
    • 五.区分:数组指针&指针数组

一.数组名

1.一维数组

  • 数组名一般作为指针常量,且有 array = &array[0] (其中array为数组名),像下面这样赋值是不可行的:
char a[5] = "abcd";
char b[5] = "efgh";

b = a;
  • 在下面两种情况下数组名不作为指针常量:
    - sizeof(数组名):可以查看这篇文章:c语言笔记 - strlen()与sizeof()
    - 使用取地址符&时:数组名作为数组变量的变量名,而&array相当于取该变量的地址,如下例:
struct
{
    char a[5];
    char b[5];
    char c[5];

}arrays = {"abcd","efgh","igkl"};

int main(void)
{
    printf("%d\n",sizeof(arrays.a));
    printf("%p,%p\n",   arrays.a   ,          &arrays.a   );
    printf("%p,%p\n",   arrays.a+1 ,          &arrays.a+1 );
    printf("%c,%c\n", *(arrays.a+1), *(char*)(&arrays.a+1));

    return 0;
}

结果为:
在这里插入图片描述
这里定义了有3个数组组成的结构体:

  • arrays.a是第一个数组的数组名,指向该数组第一个元素;
  • &arrays.a中arrays.a是第一个数组的变量命名,取地址后的地址值与第一个相同,但指向类型不同
  • 第二行给地址值 + 1,结果是不一样的,第二个的结果为:00402000 + 1×sizeof(arrays.a) = 00402005

以上说明:数组名array和数组名取地址&array,地址值相同,但指向类型不同,实际上&array的类型为数组指针,指向整个数组,而非数组的第一个元素,若定义p为char (*p)[5],则p与array属于同一类型


2.多维数组

char array[3][4];为例:

  • 这里可以看作创建了一个1维数组,其中包含3个元素,每个元素均为一个包含4个元素的数组
  • 数组名array是指向第一个包含4个char型元素的数组指针
int main(void)
{
    char array[3][4] = {"abc","def","ghi"};

    char (*p)[4] = array;

    printf("%s\n",*p);
    printf("%s\n",*(p+1));

    return 0;
}

结果为:
在这里插入图片描述
这里可以看出用数组名array进行指针运算,其步长为4个字节,相当于第二层数组的大小
:你不应该像这样赋值:char (*p)[] = array;,否则在进行指针运算时,将根据空数组的长度进行调整,即步长为0

  • 将多维数组作为参数传入函数时,你需要注意函数的声明,以char array[3][4];为例:像这样是错误的func( char **p ),因为数组名是数组指针,而非指针的指针,应该为func( char (*p)[4] );,或者func( char p[][4] ),这里的关键在于除了第一维,你需要告知编译器后面各维的长度


二.下标引用

除了优先级之外,下标引用与间接访问完全相同
下标引用可以用于任何指针,不仅仅只是数组

1.一维数组

假设p指向char型数组array第一个元素:

  • *(p+i):等价于 p[i] / array[i]
  • 2[p]:等价于*(2+p),即*(p+2),即p[2] / array[2],但这种表达可读性低,一般不用
  • p[-1]:当p指向数组的第一个元数之后的元素才可以这样使用,如p指向array[2],则p[-1]表示array[1],这种情况你需要注意防止越界,如p[9],它实际上等价于array[11],已不在数组范围内

2.多维数组

多维数组中下标是从左到右计算的,数组名是指向第一维第一个元素的指针,类型为数组指针
char array[3][4];为例:

  • *(array + 1) 等价于 array[1],表示一个地址
  • *( *(array + 1) + 1 )等价于 array[1][1]
int main(void)
{
    char array[3][4] = {"abc","def","ghi"};

    printf("%s,%s\n",   *(array+1)   ,array[1]    );
    printf("%c,%c\n", *(*(array+1)+1),array[1][1] );

    return 0;
}

结果为:
在这里插入图片描述
再看一个三维例子:

int main(void)
{
    char array[2][3][3] =
    {
        {"12","34","56"},
        {"78","90","ab"}
    };

    char (*p)[3][3] = array;

    printf("%s,%s\n", *((*(p + 1)) + 1), p[1][1]);

    return 0;
}

结果为:
在这里插入图片描述



三.数组的初始化

1.隐式赋值

数组的初始化会执行多条隐式赋值语句,那么问题在于,在函数体内,当数组元素很多时,这样的初始化会消耗很多时间,你需要考虑的是能否将数组声明为 static 静态变量,来避免执行流每次进入该函数体内时对数组进行初始化


2.存储顺序

数组的存储顺序总是根据最右边的下标率先变化
int array[2][3] = {0,1,2,3,4,5},最左边为array[0][0],往后依次为array[0][1],array[0][2],array[1][0]…


3.部分初始化

部分初始化,其他默认初始化为0;以下两两等价:

//-----------1-------------
int array[4] = {1};
int array[4] = {1,0,0,0};
//-----------2-------------
int array[2][2][3] = {1,2};
int array[2][2][3] = {1,2,0, 0,0,0, 0,0,0, 0,0,0};
//-----------3-------------
int array[2][2][3] = {
    {
        {
            {1}
        }
    },
    {
        {
            {2}
        }
    }
};
int array[2][2][3] = {1,0,0, 0,0,0, 2,0,0, 0,0,0};

4.未定长度初始化

一维数组,若声明中未给出数组长度,则编译器将长度设置为刚好能够容纳所有初始值的长度,而不是弹性可变的

int main(void)
{
    char str[] = {'a','b','c'};
    printf("%d\n",sizeof(str));
    
    str[3] = 'd';
    printf("%d\n",sizeof(str));

    return 0;
}

结果为:
在这里插入图片描述
这里初始化后数组长度就固定为3了,str[3] = 'd';这条语句实际上是非法的,即使编译器未报错,它已经访问了数组外的地址,并且改变了该地址的值,可能会出现问题

多维数组,只有第一维可以不给定长度,剩余几维均要提供长度



四.函数传参

一维数组
  • void func(char *array);
  • void func(char array[]); 不用指出数组长度,因为传入的是地址值
多维数组

二维为例:

  • void func(char (*array)[10]);
  • void func(char array[][10]);第一维不用给出长度,但后面的维度都需要给出


五.区分:数组指针&指针数组

个人认为你只需要记住几个操作符的优先级便能很好地区分:从上到下优先级降低
1 ( ) 聚组
2 [ ] 下标引用
3 * 间接访问
例如:

  • char (*p)[10]:从优先级最高的操作符( )看起,p与 * 结合,则p表示一个指针,后面跟下标引用,那么一定是指向数组的指针,故p为数组指针
  • char *p[ ]:从优先级最高的操作符[ ]看起,p与[ ]结合,则p表示一个数组名,数组中的内容为指针类型,故p为指针数组
int main(void)
{
    char const *array[] = {
        "ab",
        "abc",
        "12",
        "123",
        "ab12"NULL
    };

    printf("%d,%d",sizeof(array),sizeof(array[0]));

    return 0;
}

结果为:
在这里插入图片描述
经验:在初始化char型指针数组时,将最后一个元素定为NULL有时候是很方便的,例如你需要遍历这个数组,可以将NULL作为结束标志,而无需知道数组长度

你可能感兴趣的:(C语言细节)