指针详解(1)
Suzhou 30 0

::: tip 指针的概念: 1.指针是一个用来存放地址的变量,地址唯一标识一块内存空间。 2.指针的大小是固定的4/8个字节( 32位平台/64位平台)。 3.指针的类型决定了指针的±整数的步长,指针解引用操作时能操作内存空间的大小。 4.指针的运算。 :::


字符指针

两种用法:

  1. 一般用法
    int main()
    {
     char a = 'w';
     char* p = &a;
     *p = 'w';
     return 0;
    }
  2. 特殊
    char* p = "abcdefg";

上述代码的含义是将字符串中首字符的地址赋给p。 常量字符串abcdefg赋给指针变量p时,该字符串加上\0共有8个字节,超出指针p大小,所以其实是将第一个字节a的地址赋给p。

printf("%c\n",*p);  //a

指针p中存放的字符串首字符地址,*p解引用可以得到a

printf("%s\n",p);  //abcdefg

从p中存放的地址处开始打印一个字符串,即从a的地址出向后打印一个字符串(遇到字符串结尾的\0打印停止)。

::: warning 上述代码中的char* p = "abcdefg";准确写法如下:

const char* p = "abcdefg";

“abcdefg”是一个常量字符串,不能通过指针解引用的方式修改,加入const可以在编译时就提示报错。 :::

题目
int main()
{
    char arr1[] = "abcdefg";
    char arr2[] = "abcdefg";
    if (arr1 == arr2)  
    {
        printf("haha");
    }
    else
    {
        printf("hehe");
    }
    return 0;
}    
hehe

上述代码中创建两个数组占据内存中两个不同的空间,数组名是数组首元素地址,所以arr1和arr2不相等。

int main()
{
    const char* p1 = "abcdefg";
    const char* p2 = "abcdefg";
    if (p1 == p2)  
    {
        printf("haha");
    }
    else
    {
        printf("hehe");
    }
    return 0;
}    
haha

上述代码第3行和第4行中的"abcdefg"是常量字符串,完全一致且不可修改,出于节省内存空间,两个常量字符串"abcdefg"只存储了一个,p1和p2都指向了同一块内存空间的起始位置,即p1 == p2。


指针数组

指针数组是用来存放指针的数组。

int main()
{
    int a = 10;
    int b = 20;
    int c = 30;
    int d = 40;
    int* p[] = { &a,&b,&c,&d };
    return 0;
}

指针详解(1)

指针数组的使用场景
int main()
{
    int arr1[] = { 1,2,3,4,5,6 };
    int arr2[] = { 2,3,4,5,6,7 };
    int arr3[] = { 3,4,5,6,7,8 };
    int* parr[] = { arr1,arr2,arr3 };
    int i = 0;
    int j = 0;
    for (i = 0; i < 3; i++)
    {
        //printf("%d ", *parr[i]);  得到每个数组的首元素1、2、3
        for (j = 0; j < 6; j++)
        {
            printf("%d ", *(parr[i]+j));  //数组首元素地址向后偏移
        }
        printf("\n");
    }
    return 0;
}
1 2 3 4 5 6
2 3 4 5 6 7
3 4 5 6 7 8

上述代码中第14行先通过parr[i]找到了指针数组中每一个元素,即arr1,arr2,arr3,数组名是数组首元素的地址,每次+j遍历出数组中所有的元素的地址,最后解引用打印数组中所有的元素。


数组指针

::: warning 数组指针是能够指向数组的指针。 arr - 首元素地址 &arr[0] - 首元素地址 &arr - 数组地址 ::: &arr是数组地址,不能使用int整型接收,应该使用int( * p)[]的数组指针形式接收。 ::: tip int * p[10]中[]的优先级高于 * ,p先和[]结合变为数组,整体是一个存放指针的数组。如果 * 要和p先结合,需要加圆括号。 *p是指针,其余的int [10]是数组类型,结合起来int( * p)[10]是数组指针( * p指向数组[10],数组中每个元素类型是int)。 ::: 指针详解(1)


数组指针的用法

数组指针一般用在二维数组以上,一维数组的使用时比较繁琐。

int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };

数组名arr除了取地址&和sizeof以外都表示数组的首元素地址。 ::: tip 二维数组的首元素需要先把二维数组当作一个一维数组,每一行当作一维数组的一个元素,即arr[3][5]中有三个元素。 二维数组的首元素地址是第一行一维数组的地址。 二维数组第一行是一个一位数组,其中有5个int类型的元素。 :::

void Print2(int(*p)[5], int x, int y)  //使用指针形式接收
//数组的地址应该使用数组指针接收。
//数组指针指向的一维数组是二维数组的第一行,共有5个整型元素。
{
    //p是指向第一行数组的指针,p+1跳过一个数组(5个元素),p每次+1指向下一行
    int i = 0;
    for (i = 0; i < x; i++)
    {
        int j = 0;
        for (j = 0; j < y; j++)
        {
            //方法1
            //printf("%d ", *(*(p + i) + j));
            //p + 1跳过i行指向下标为i的一行,解引用后找到i行
            //*(p + i)取得i行的一维数组名,即i行首元素的地址
            //*(p + i) + j取得该行下标为j的元素地址
            //*(*(p + i) + j)对该地址解引用取得i行j列的元素

            //方法2
            //printf("%d ", (*(p + i))[j]);
            //p + 1跳过i行指向下标为i的一行,解引用后找到i行
            //*(p + i)取得i行的一维数组名
            //(*(p + i))[j]表示数组名+下标取得数组中每一个元素
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
    Print1(arr, 3, 5);  //数组传参
    printf("\n");
    Print2(arr, 3, 5);  //数组传参
    //数组名arr是首元素地址,首元素是第一行,arr代表第一行的地址。
    //第一行是一个一维数组,即传到函数Print中的是一个一维数组的地址。
    return 0;
}
数组指针形式的几种打印写法
int main()
{
    int arr[] = { 1,2,3,4,5,6 };
    int* p = arr;
    int i = 0;
    for (i = 0; i < 6; i++)
    {
        printf("%d ", *(arr + i));
        printf("%d ", *(p + i));
        printf("%d ", arr[i]);  //以arr为起始地址访问下标为i的元素
        printf("%d ", p[i]);  //以p为起始地址访问下标为i的元素
    }
    return 0;
}

上述代码中的四种打印方式的结果完全相同。 int * p = arr;表示p和arr是等价的。

arr[i] == *(arr + i) == p[i] == *(p + i)

由上可得,上述代码的Print()函数还有以下两种打印形式:

printf("%d ", *(p[i] + j));
printf("%d ", *(p[i][j]);

::: warning 处理二维数组的思路: 将二维数组想象成一维数组,此时二维数组的首元素地址是第一行元素的地址。 第一行元素组成一个一维数组,地址可以存在一个指向该一维数组的指针中。此时数组可以写成指针形式,并可以用该指针访问二维数组。 :::

参考视频:C语言从入门到进阶 第35节指针详解(2) https://www.bilibili.com/video/BV1oi4y1g7CF?p=35&spm_id_from=pageDriver&vd_source=df7bbd0bde58584ffdbf01858a3d03c6


小结1

几个概念:

int arr[5];
//arr是一个5个元素组成的整型数组
int* parr1[10];
//parr1是一个10个元素组成的指针数组,每一个元素的类型是int*
//parr1优先和[10]结合组成数组([]的优先级高)
int(*parr2)[10];
//parr2是一个数组指针,该指针指向一个有10个int类型元素的数组
int(*parr3[10])[5];
//parr3优先和[10]结合组成数组,
//parr3是数组名,[10]是数组个数,其余int(*)[5]是类型。
//int(*)[5];类似于int(*parr2)[10]的形式,即该类型也是指向数组的指针类型。
//可以认为parr3是一个有10个元素的数组,每一个元素是一个数组指针,
//该数组指针指向一个包含5个int类型元素的数组。

int(*parr3[10])[5];的存储如下图: 指针详解(1)


评论区

索引目录