C语言 --- 指针(2)

目录

一.数组名的理解

二.使用指针访问数组

三.一维数组传参的本质 

四.冒泡排序

五.二级指针 

六.指针数组 

七.利用指针数组模拟二位数组


一.数组名的理解

在我们学习指针访问数组内容的过程中,有这样的代码。

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];

这里我们使用&arr[0]拿到了数组首元素的地址,但是其实数组名本来就是地址,而且是数组首元素的地址,这里我们可以做一个测试:

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

将上述代码在vs2022上运行,可以得到结果是:

我们发现数组名和数组首元素的地址打印出的结果是一模一样的,数组名就是数组首元素的地址。 

知道了这个,那么下面的代码应该怎么理解呢?

#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;
}

当我们运行后会发现问题,如果arr是数组首元素的地址,那么输出结果应该是4/8才对。那么为什么会是40呢?数组名是数组首元素的地址,但是有两个例外情况:

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

 到底有什么区别了?我们可以尝试以下下面代码。

#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;
}

输出结果: 

 三个的结果都一模一样,那到底有什么区别?不妨试一下这个代码

#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表示整个数组的地址 ,而数组的大小是40个字节,我们发现十六进制下B0-88的值正好是40(十进制)。所以这就是&arr与另外二者的不同,即使值是一样的。

二.使用指针访问数组

有了前面的知识的支持,我们就可以很方便的使用指针访问数组了。

#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);//这里使用arr+i也可以
    }       
    //输出
    for (i = 0; i < sz; i++) {
        printf("%d ",*(p+i));
    }   
    return 0;
}

 在这里数组名arr和p是等价的,既然我们可以使用arr[i]访问数组的元素,我们也可以使用p[i]访问数组的元素。同理我们知道*(p+i)=*(i+p)=arr[ i ]=p[ i ]=i[ p ];

以上这些都是等价的,为什么呢?

其实是数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移量求出数组元素的地址,然后解引用来访问的。 

三.一维数组传参的本质 

首先从一个问题开始,我们之前都是在函数外部计算数组的元素个数,然后再交给这个函数,为什么不直接在函数内部计算这个函数的数组元素个数 ?

#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);
    return 0;
}

输出结果(vs2022 X64环境下): 

为什么会得到这个结果呢?

明显函数的内部是没有得到正确的数组元素的个数。这时我们就需要学习数组传参的本质了。

数组名是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就本质上来说数组传参传递的是数组首元素的地址,是一个指针。

所以函数的形参部分理论上应该写一个指针变量来接受首元素的地址。那么在函数内部我们写sizeof(arr)的时候计算的是这个地址占了多少字节而不是整个数组占了多少字节。在X64环境下,指针变量的大小是8个字节,而sizeof(arr[0])还是4个字节,所以得到的结果是2,如果在X86环境下结果就是1;

void test(int arr[])即使这个部分写成了数组的形式本质上还是指针。所以我们以后在数组传参的时候最后将参数写成一个指针变量的形式。void test(int *arr)

一维数组传参,参数的部分可以写成数组的形式,也可以写成指针的形式。 

四.冒泡排序

冒泡排序的核心思想:两个相邻元素进行比较 ;

如果存在十个数无序排列,比如: 2,3,1, 4,10,7, 5,6, 8,9;

如果我们要将重新排列,变成左边小右边大的情况,我们就可以用到冒泡排序。如何进行冒泡排序呢? 

首先我们先比较第一个数和第二个数,如果第一个数大于第二个数就交换位置,反之就不交换,然后我们再比较第二数和第三个数,同理如果第二个数更大就交换,就这样一直进行下去,直到完成第九个数和第十个数。就这样我们完成了一轮交换。

读者可以自行用上面的数进行尝试,我们会发现最大的那个数经过了一轮交换后(因为最大的数一定比它右边的数大,所以它会一直交换下去,直到最后一个位置),就到了最后一个位置,但其他的数还是乱的,所以我们要接着进行下一轮交换,但是因为最大的数已经在它该在的位置上了,这样在下一轮的交换中我们就不需要比较第九个数和第十个数了;同理后面也是如此…………。

第一轮中我们比较了9次,第二轮比较8次,第三轮比较7次,总共需要比较多少轮呢?

答案是9轮。第九轮的时候比较一次,也就是第一个位置上的数和第二个位置上的数进行比较,第二个位置上的数确定了后,同理第一个位置上的数也应该是最小的了,所以就不用比较了。

代码: 

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

五.二级指针 

指针变量也是变量,是变量就有地址,那么指针变量的地址存放在哪里呢?

以上我们学习也可以叫做一级指针,二级指针就是存放一级指针变量地址的变量; 

#include<stdio.h>
int main()
{
    int a = 10;
    int* p = &a;
    int** pp = &p;

    printf("%p\n",&a);
    printf("%p\n",p);
    printf("%p\n",pp);
    return 0;
}

输出结果: 

 

pp就是二级指针. 

对pp进行解引用,*pp找到的就是p;

六.指针数组 

指针数组是指针还是数组呢?

我们可以类比一下,整形数组是存放整形的数组,字符数组就是存放字符的数组.

同理,指针数组就是存放指针的数组;指针数组的每一个元素都是用来存放地址的; 

int* a[5];

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

七.利用指针数组模拟二位数组

int main()
{
    int arr1[] = {1,2,3,4,5};
    int arr2[] = {2,3,4,5,6};
    int arr3[] = {3,4,5,6,7};
    //数组名是数组元素的地址,类型是int* ,可以直接放在指针数组中
    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;
}

pArr[i]访问的是指针数组中的元素,pArr[i]找到的数组元素指向了一个整形一维数组,在加上[j]就可以访问一维数组中的元素了.

  • 38
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值