椋鸟C语言笔记#20

指针数组、指针模拟二维数组、数组指针、二维数组传参的本质、字符指针与字符串

Featured image

萌新的学习笔记,写错了恳请斧正。

指针数组

整型数组存放整型,那么指针数组自然是存放指针的数组。

要定义一个指针数组,我们应该像这样写:

int* parr[3] = { pa, pb, pc };

因为方括号的优先级要高于解引用操作符,所以 parr 先与方括号结合,规定其为数组。其中存储的数据类型为 int*(整型指针)。

示意图1

指针模拟二维数组

#include <stdio.h>

int main()
{
    int arr1[] = {1,2,3,4,5};
    int arr2[] = {2,3,4,5,6};
    int arr3[] = {3,4,5,6,7};
    int* parr[3] = {arr1, arr2, arr3};
    int i = 0;
    int j = 0;
    for (i = 0; i < 3; i++)
    {
        for(j=0; j<5; j++)
        {
            printf("%d ", parr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

上述代码就是用指针数组来模拟实现一个三行五列的二维数组

示意图2

parr[i] 是访问 parr 数组的元素,找到的数组元素指向了整型一维数组,parr[i][j] 就是整型一维数
组中的元素。
上述的代码模拟出二维数组的效果,但实际上并非不是二维数组,每一行不是连续的。

数组指针

相反的,那数组指针就是指向数组的指针

但是如果我们写成下面这样,定义的就是指针数组而不是数组指针

int* parr[3] = { pa, pb, pc };

所以我们就要避免 parr 与方括号先结合,导致被认为是数组

那么就只需加一个括号就行:

int (*parr)[3] = &arr;

这样 parr 先与解引用操作符结合,被认为是指针,指向 int [3] 类型的数据(数组)

数组指针的初始化

可以看到,上面我是通过取地址数组名来初始化数组指针的。

因为数组指针是指向整个数组的,一般只能用这种方式来初始化与赋值(数组名的特例,上一篇笔记讲了)(类型应当是这种格式:int[3] *)

二维数组传参的本质

我们之前把二维数组作为参数传递给函数是这么写的:

#include <stdio.h>

void func(int arr[8][9], int m, int n)
{
    //...
}

int main()
{
    int arr[8][9] = { 0 };
    func(arr, 8, 9);
    return 0;
}

实参是二维数组名,形参直接写成二维数组

我们知道,二维数组其实是元素为一维数组的数组

根据数组名是数组首元素的地址,二维数组的数组名表示的就是第一个一维数组的地址

上述代码中,第一行的一维数组的类型就是 int [9],所以第一行的地址的类型就是数组指针类型 int(*)[9]。所以说二维数组传参本质上也是传递了地址,传递的是第一个一维数组的地址,那么形参也是可以写成指针形式的。如下:

#include <stdio.h>

void func(int (*arr)[9], int m, int n)
{
    //...
}

int main()
{
    int arr[8][9] = { 0 };
    func(arr, 8, 9);
    return 0;
}

字符指针与字符串

字符指针,就是指向字符的指针

一般我们这么用:

#include <stdio.h>

int main()
{
    char ch = 'a';
    char* pch = &ch;
    printf("%c", *pch);
    return 0;
}

但是,我们 看看下面这一段代码:

#include <stdio.h>

int main()
{
    const char* pch = "abc";    //这里const可以省略
    printf("%s", pch);
    return 0;
}

这一段代码其实也是对的,能正常打印字符串 abc,那我们如何理解呢?

首先我们要知道一点,%s 作为格式控制字符,接受的其实是字符串第一个字符的地址。随后从这个地址开始,一个一个打印字符,直到遇到 \ 0 为止

那第一句怎么理解呢?难道是把字符串存储在字符指针 pch 中吗?

不是的,这里就涉及到字符串双引号的作用了。

字符串的双引号有 3 个作用:

所以第一句其实是创建了字符串常量并将首元素地址传递给 pch

那么,我们其实也就可以这么写:

#include <stdio.h>

int main()
{
    printf("%s", "abc");
    return 0;
}

这样,同样可以将字符串 abc 打印出来。

!!!注意!!!

用等号对字符数组整体赋值只能与初始化同时进行,不能分开!

初始化的时候使用等号只是规定了其一开始指向的位置。在初始化完成后数组名就是一个指向首元素地址的指针常量了,不能进行等号赋值操作(不能改变指向)。

这时如果想整体赋值可以使用 strcpy 函数(后面会讲)

为了更好的理解字符串双引号的作用,我们来看看这一段代码:

#include <stdio.h>

int main()
{
    char str1[] = "abcdef";
    char str2[] = "abcdef";
    const char *str3 = "abcdef";    //const可省略
    const char *str4 = "abcdef";    //const可省略

    if (str1 == str2)
        printf("str1 and str2 are same\n");
    else
        printf("str1 and str2 are not same\n");
    if (str3 == str4)
        printf("str3 and str4 are same\n");
    else
        printf("str3 and str4 are not same\n");

    return 0;
}

上述代码的输出结果是什么呢?

答案是:

str1 and str2 are not same.

str3 and str4 are same.

为什么呢?

因为 str1 与 str2 是两个字符数组变量的首元素的地址,而创建的两个数组的首元素地址肯定不一样,所以输出的就是 str1 and str2 are not same.

而 str3 和 str4 是创建的字符指针,其指向的是由双引号在常量区创建的字符串常量 abcdef。常量不像变量一样会重复创建,所以两个指针最终指向的其实就是同一个地址,输出 str3 and str4 are same.