C_深入理解指针(二)—— 数组名的理解、使用指针访问数组、一维数组传参的本质、冒泡排序、二级指针、指针数组、指针数组模拟二维数组

目录

一、数组名的理解

二、使用指针访问数组

三、一维数组传参的本质

四、冒泡排序

五、二级指针

六、指针数组

七、指针数组模拟二维数组


一、数组名的理解

使用指针访问数组时,有这样的代码:

cpp 复制代码
int arr[10] = {1,2,3,4,5,6,7,8,9,10};

int *p = &arr[0];

这里使用 &arr[0] 的方式拿到了数组第⼀个元素的地址,
但是其实 数组名本来就是地址,而且 是数组首元素的地址。

cpp 复制代码
#include <stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("&arr[0] = %p\n", &arr[0]);
    printf("arr = %p\n", arr);

    return 0;
}

输出结果:

可以发现数组名和数组首元素的地址打印出的结果⼀模⼀样数组名就是数组首元素(第⼀个元素)的地址 。**疑问:**若真是如此,那 下面的代码怎么理解呢?

cpp 复制代码
#include <stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("%d\n", sizeof(arr));

    return 0;
}

输出的结果是:40,如果arr是数组⾸元素的地址,那 在不同的环境下输出应该是4/8 才对。
其实数组名就是数组首元素(第⼀个元素)的地址是对的,但是有两个例外:

  1. sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小, 单位是字节。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)。

除此之外, 任何地方使用数组名,数组名都表示首元素的地址。

cpp 复制代码
#include <stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("&arr[0] = %p\n", &arr[0]);
    printf("arr = %p\n", arr);
    printf("&arr = %p\n", &arr);

    return 0;
}

上面的三个打印结果⼀模⼀样,这时候又纳闷了,那arr和&arr有啥区别呢?

cpp 复制代码
#include <stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("&arr[0] = %p\n", &arr[0]);
    printf("&arr[0]+1 = %p\n", &arr[0]+1);
    printf("arr = %p\n", arr);
    printf("arr+1 = %p\n", arr+1);
    printf("&arr = %p\n", &arr);
    printf("&arr+1 = %p\n", &arr+1);

    return 0;
}

输出结果:

分析:
第一:&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是 因为&arr[0] 和 arr 都是首元素的地址,+1就是跳过⼀个元素
**第二:&arr 和 &arr+1相差40个字节,**是 因为&arr是数组的地址,+1 操作是跳过整个数组的。

二、使用指针访问数组

cpp 复制代码
#include <stdio.h>
int main()
{
    int arr[10] = {0};

    //输⼊
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);

    //输⼊
    int* p = arr;
    for(i=0; i<sz; i++)
    {
        scanf("%d", p+i);
        //scanf("%d", arr+i);//也可以这样写
    }

    //输出
    for(i=0; i<sz; i++)
    {
        printf("%d ", *(p+i));
    }
    return 0;
}

数组名arr是数组首元素的地址,可以赋值给p,其实数组名arr和p在这里是等价的。

疑问: 我们可以使⽤arr[i]可以访问数组的元素,那p[i]是否也可以访问数组呢?

cpp 复制代码
#include <stdio.h>
int main()
{
    int arr[10] = {0};

    //输⼊
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);

    //输⼊
    int* p = arr;
    for(i=0; i<sz; i++)
    {
        scanf("%d", p+i);
        //scanf("%d", arr+i);//也可以这样写
    }

    //输出
    for(i=0; i<sz; i++)
    {
        printf("%d ", p[i]);
    }
    return 0;
}

在第18行的地方,将*(p+i)换成p[i]也是能够正常打印的,所以 本质上p[i] 是等价于 *(p+i)
同理 arr[i] 等价于 *(arr+i) ,**数组元素的访问在编译器处理的时候,**也是 转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的
指针类型决定了指针的差异。

总结:

  1. 数组就是数组,是一块连续的空间,是可以存放一个或者多个数组的。
  2. 指针变量是一个变量,是可以存放地址的变量。
  3. 数组和指针不同,但是可以用指针来访问数组。

疑问:为什么可以使用指针来访问数组呢?

  1. 数组在内存中是连续存放的;
  2. 指针的元素很方便的可以遍历数组,取出数组的内容(指针运算)。

三、一维数组传参的本质

数组可传递给函数 ,这里学习⼀下数组传参的本质。
首先从⼀个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把数组传给⼀个函数后,函数内部求数组的元素个数吗?

cpp 复制代码
#include <stdio.h>
void test(int arr[])
{
    int sz2 = sizeof(arr)/sizeof(arr[0]);
    printf("sz2 = %d\n", sz2);
}

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int sz1 = sizeof(arr)/sizeof(arr[0]);
    printf("sz1 = %d\n", sz1);

    test(arr);//arr是数组名,数组名表示数组首元素的地址

    return 0;
}

输出的结果:

目前的问题: 我们发现在函数内部是没有正确获得数组的元素个数
因为数组名是数组⾸元素的地址,所以在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。 由此可见函数形参的部分理论上应该使用指针变量来接收⾸元素的地址

那么**在函数内部我们写sizeof(arr) 计算的是⼀个地址的大小(单位字节)而不是数组的大小(单位字节)。**正是因为函 **数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。**形参即使写成数组的形式,本质上也是一个指针变量。

cpp 复制代码
void test(int arr[])//参数写成数组形式,本质上还是指针
{
    printf("%d\n", sizeof(arr));
}

void test(int* arr)//参数写成指针形式
{
    printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩
}

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    test(arr);
    return 0;
}

通过传参的方式计算⼀个指针变量的大小,正确写法:

cpp 复制代码
#include <stdio.h>

void test(int arr[], int sz)//参数写成数组形式,本质上还是指针
{
    int i = 0;
    for(i=0; i<sz; i++)
    {
        printf("%d", arr[i]);
    }
}

void test(int* arr, int sz)//参数写成指针形式
{
    int i = 0;
    for(i=0; i<sz; i++)
    {
        printf("%d", arr[i]);//计算⼀个指针变量的⼤⼩
    }
}

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int sz1 = sizeof(arr)/sizeof(arr[0]);
    printf("sz1 = %d\n", sz1);

    test(arr, sz1);//arr是数组名,数组名表示数组首元素的地址

    return 0;
}

总结: 一维数组传参,形参的部分可以写成数组的形式(简单方便理解),也可以写成指针的形式(本质)。

四、冒泡排序

冒泡排序的核心思想就是:

两两相邻的元素进行比较。不满足顺序就互相交换,满足顺序就找下一对。

cpp 复制代码
//⽅法1
void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
    //确定外面趟数
    int i = 0;
    for(i=0; i<sz-1; i++)
    {
        //每一趟内部的比较
        int j = 0;
        for(j=0; j<sz-i-1; j++)
        {
            if(arr[j] > arr[j+1])
            {
                //交换
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}

int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};    
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr, sz);
    int i = 0;
    for(i=0; i<sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}


//⽅法2 - 优化
void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
    int i = 0;
    for(i=0; i<sz-1; i++)
    {
        int flag = 1;//假设这⼀趟已经有序了
        int j = 0;
        for(j=0; j<sz-i-1; j++)
        {
            count++;//计数器用来计算比较的次数

            if(arr[j] > arr[j+1])
            {
                flag = 0;//发⽣交换就说明,⽆序
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
             }
        }
        if(flag == 1)//这⼀趟没交换就说明已经有序,后续⽆序排序了
            break;
    }
}

int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr, sz);
    int i = 0;
    for(i=0; i<sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("%d ", count);
    return 0;
}

若没有flag作为标识来判断是否已经排序好,则内部比较趟数会很多:

若有flag作为标识来判断是否已经排序好,则内部比较趟数会很少:

五、二级指针

  1. 指针变量也是变量,是变量就有地址,则指针变量的地址存放在二级指针里。
  2. 二级指针变量是用来存放一级指针变量的地址。
  3. 二级指针和二级数组没有对应关系。
cpp 复制代码
#include <stdio.h>

int main()
{
    int a = 10;
    
    int* pa = &a;//pa是一级指针变量

    int** pa = &pa;//ppa是二级指针变量

    return 0;
}


对于二级指针的运算有:

  1. *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa.

    cpp 复制代码
    int b = 20;
    
    *ppa = &b;//等价于 pa = &b;
  2. **ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .

    cpp 复制代码
    **ppa = 30;
    //等价于*pa = 30;
    //等价于a = 30;

六、指针数组

类比 整型数组是存放整型的数组,字符数组是存放字符的数组;
因此 指针数组是数组,是存放指针的数组。

指针数组的每个元素都是用来存放地址(指针)的。
如下图:

指针数组的每个元素是地址,又可以指向一块区域 。

cpp 复制代码
#include <stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int c = 30;
    //int* pa = &a;
    //int* pb = &b;
    //int* pc = &c;
    int* arr[3]={ &a, &b, &c };
    //             0   1   2
    int i = 0;
    for(i = 0;i< 3;i++)
    {
        printf("%d",*(arr[i]));
    }
    return 0;
}

七、指针数组模拟二维数组

cpp 复制代码
#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数组中
    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]);//等价于*(*(parr+i)+j)
        }
        printf("\n");
    }    
    return 0;
}


分析: parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数组中的元素。
重点: parr[i][j]的等价于 *(*(parr+i)+j) ,这个等价关系很重要:

上述的代码 模拟出二维数组的效果实际上并非完全是二维数组, 因为 每一行并非是连续的

相关推荐
戊辰happy1 小时前
arcface
算法
浊酒南街2 小时前
决策树python实现代码1
python·算法·决策树
冠位观测者3 小时前
【Leetcode 热题 100】208. 实现 Trie (前缀树)
数据结构·算法·leetcode
西猫雷婶3 小时前
python学opencv|读取图像(十九)使用cv2.rectangle()绘制矩形
开发语言·python·opencv
liuxin334455664 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
码农W4 小时前
QT--静态插件、动态插件
开发语言·qt
ke_wu4 小时前
结构型设计模式
开发语言·设计模式·组合模式·简单工厂模式·工厂方法模式·抽象工厂模式·装饰器模式
code04号4 小时前
python脚本:批量提取excel数据
开发语言·python·excel
小王爱吃月亮糖4 小时前
C++的23种设计模式
开发语言·c++·qt·算法·设计模式·ecmascript
hakesashou5 小时前
python如何打乱list
开发语言·python