C语言学习记录——이십팔 指针详解(1)

先回顾一下之前学的指针:

1、指针变量是个变量,用来存放地址,地址唯一标识一块内存空间。

2、指针的大小是固定的4/8个字节(32/64位平台)

3、指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。

4、指针的运算

目录

一、字符指针

二、指针数组

三、数组指针

基本用法


一、字符指针

类型是char*

int main()
{
    char arr[] = "abcdefg";
    char* pc = arr;
    printf("%s\n", arr);
    printf("%s\n", pc);
    return 0;
}

pc接受arr数组首元素地址,输出时从这个地址开始往后输出。同样的,除了字符数组,字符串也一样。

    const char* p = "abcdefg";//这是一个常量字符串
    printf("%s\n", p);
    return 0;

p接受的是a的地址,然后从a开始输出。由于是常量字符串,需要加const,这个是一个权限放大的问题,权限放大不需要理解,在C++中会学到。而关于这个字符串在内存中的布局需要再写一写。

字符串abcdefg\0,存入内存后会有一个起始地址,这个地址被指针变量p存放,p就可以通过这个指针指向a,然后打印整个字符串。

如果在代码里加入*p = 'M',再打印,会出现错误,不会打印Mbcdefg。abcdefg是个常量字符串,不能更改,强行更改代码会崩溃,会出现段错误segmentation fault,访问内存错误等等。为了防止这样的错误,可在char*前加上const,这样*p就不能被更改了。在VS2019中,常量字符串必须加const才能不报错地创建变量,防止程序员出现权限放大的错误。

看一道题

    char arr1[] = "abcdef";
    char arr2[] = "acbdef";
    const char* p1 = "abcdef";
    const char* p2 = "abcdef";
    if (arr1 == arr2)
    {
        printf("hehe\n");
    }
    else
    {
        printf("haha\n");
    }

arr1数组名代表的首元素地址和arr2所代表的不同,所以不一样,打印haha。这里改一下。

把 if (arr1 == arr2)改成if (p1 == p2)

    const char* p1 = "abcdef";
    const char* p2 = "abcdef";

字符串一样,是常量字符串,内存中存放的时候没有给p1和p2两个地址,节省空间,p1和p2指向同一个地址,这个地址也就是abcdef的地址,改变p1不影响p2。所以应该在char前加上const,进一步防止字符串被改变

二、指针数组

是用来存放指针的数组。可以有不同类型,int*, short*等等。比如char* arr[5]。举一个例子

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

查看内存,arr里的四个地址和abcd一样。如果要打印abcd的值,不能用这样的方法

    int i = 0;
    for (i = 0; i < 4; i++)
    {
        printf("%d", *(arr[i]));
    }

可以打出来值,但是指针数组没有这样的用法。

    int arr1[] = { 1, 2, 3, 4, 5 };
    int arr2[] = { 2, 3, 4, 5, 6 };
    int arr3[] = { 3, 4, 5, 6, 7 };
    int* parr[] = { arr1, arr2, arr3 };
    return 0;

看看这个用法,分析一下。三个数组arr1,arr2,arr3,分别存放着内容,parr这个数组,里面的元素类型都是int*,现在存入arr1这个数组名,也就是首元素地址,指向各个数组的首元素地址,这也是一个整形数组。接下来打印所有数字

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

这样使用指针数组就可以正确打印出数据。

指针数组有一级,二级等,也就是char*,char**。

三、数组指针

数组指针是指针。能够指向数组的指针。数组类型的指针,可存放数组的地址。

    int arr[3] = { 1, 2, 3 };
    int(*p)[3] = &arr;
    return 0;

如果写成int *p[3],那么整体就是一个数组,而非指针。因为p和[]结合优先度高于*,所以p先和[3]结合,形成一个数组,再变成一个指针类型。应当用int (*p)[3] = &arr,使p变为一个指针类型,然后指向一个数组,数组元素个数为3,里面存放着arr,这也就是一个数组指针,指针类型就是int*。不过很少这样写:int (*p)[3] = &arr。

&arr,是数组的地址,而不是首元素地址,单纯的一个arr数组名是首元素地址。

基本用法

    int arr[3] = { 1, 2, 3 };
    int(*pa)[3] = &arr;
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        printf("%d\n", *(*pa + i));
        printf("%d\n", (*pa)[i]);
    }
    return 0;

pa存放着arr这个数组地址,解引用其实就相当于*pa == arr。上面两种方法都可,但是不正确。

    int arr[3] = { 1, 2, 3 };
    int* pa = arr;
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        printf("%d\n", *(pa + i));
    }
    return 0;

直接让pa存放arr这个数组即可。

数组指针的用法并不是上面那两种用法,显得繁琐。数组指针用于二维数组以上才方便。

void print1(int arr[3][5], int x, int y)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < x; i++)
    {
        for (j = 0; j < y; j++)
        {
            printf("%d\n", arr[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);
    return 0;
}

这是一个很简单的程序,运用数组指针的话

void print2(int (*p)[5], int x, int y)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < x; i++)
    {
        for (j = 0; j < y; j++)
        {
            printf("%d\n", *(*(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 } };
    print2(arr, 3, 5);
    return 0;
}

数组名是首元素地址,一维数组就是第一个元素地址,而二维数组,可以看做每一行就是一个一维数组,就是一个元素,数组名就是第一行的地址。传参过去后,由于传的是一维数组,接收数组需要用到数组指针,所以int (*p)[5]。*(*(p + i) + j),p代表着一行的元素,p+i意味着跳过 i 行,比如i = 1,就来到了第二行,每行具体的元素,需要加上 j,也可以(*(p + i))[j],也能访问到具体数字。

int main()
{
    int arr[5] = { 1, 2, 3, 4, 5 };
    int i = 0;
    int* p = arr;
    for (i = 0; i < 5; i++)
    {
        printf("%d ", p[i]);
        printf("%d ", *(p + i));
        printf("%d ", *(arr + i));
        printf("%d\n", arr[i]);
    }
}

这四个都可以打印出arr数组里所有的元素。arr被赋值给p后,arr就相当于p,那么*(p + i) == p[i]。依据这个,看之前的二维数组代码,也就可看出:

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

当看完这些后,再加深一下印象

int arr[5]     是一个5个元素的整形数组

int *parr1[10]      parr1是一个有10个元素的数组,元素类型是int*,这是一个指针数组

int (*parr2)[10]    parr2是一个指针,指向一个有十个int类型元素的数组,这是一个数组指针

int (*parr3[10])[5]     该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个,每个元素是int。

结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值