C语言:指针(2)

发布于:2025-08-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

1. 数组名的理解

在前面我们知道,当数组名单独使用的时候代表的数组中首元素的地址,也就是说,下面的两句代码是等效的:

int arr[10] = { 0 };
int *p1 = arr;
int *p2 = &arr[0];

我们可以尝试将地址打印出来检测是否正确:

int main()
{
    int arr[10] = { 0 };
    int *p1 = arr;
    int *p2 = &arr[0];
    printf("p1 = %p\np2 = %p\n",p1,p2);
    return 0;
}
p1 = 000000381DDFFC5C 
p2 = 000000381DDFFC5C

我们发现,两组打印结果相同,证明上面的两句代码的效果是一样的。

但是,我们又会想到,之前在计算数组的大小以及数组中元素个数时,我们会将代码写成如下形式:

int main()
{
    int arr[10] = { 0 };
    int a = sizeof(arr);
    int b = sizeof(arr)/ sizeof(arr[0]);
    printf("a = %d b = %d\n",a,b);
}

我们发现,a 输出值是40,可是如果 arr 代表的是首元素的地址的话,这里的 a 值结果应该是4或8才对,那这又是什么原因呢?

其实,这是因为,大部分情况下,数组名都是代表了数组中首元素的地址,但是有两种情况例外:

1. sizeof(数组名):当在sizeof()单独放入数组名的话,这个时候,数组名表示整个数组,计算出的是整个数组的大小,单位是字节。

2.&数组名:当取地址操作符与数组名一起使用时,数组名代表的是整个数组,取出的地址计算整个数组的地址(整个数组的地址与首元素的地址是有区别的)。

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

为了体现不同,我们可以使用下面的代码尝试一下:

int main()
{
    int arr[10] = { 0 };
    printf("&arr[0]     = %p\n",&arr[0]);
    printf("&arr        = %p\n",&arr);
    printf("arr         = %p\n",arr);
    printf("&arr[0] + 1 = %p\n",&arr[0] + 1);
    printf("&arr    + 1 = %p\n",&arr + 1);
    printf("arr     + 1 = %p\n",arr + 1);
    return 0;
}
&arr[0]     = 00000039D51FF650
&arr        = 00000039D51FF650
arr         = 00000039D51FF650
&arr[0] + 1 = 00000039D51FF654
&arr    + 1 = 00000039D51FF678
arr     + 1 = 00000039D51FF654

我们可以发现,上面三行代码的打印的结果完全相同,证明arr,&arr[0],&arr所指向的都是一个地址,但是当我们让指针与整数进行加法运算后,我们发现,arr和&arr[0]加上1后都只跳过了4个字节,而&arr则跳过了40个字节。

这是因为,arr与arr[0]都是首元素的地址,所以+1只能跳过一个元素,即4个字节。而&arr是数组的地址,所以+1后就是跳过一个数组,在这里就跳过了40个字节。

2. 使用指针访问数组

了解了上面的知识后,我们就可以根据数组的特点,用指针来访问元素:

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

搞清楚这里代码后,我们知道,arr其实就是指针,并且还可以赋值给p,我们可以用p来访问数组中的元素,那p与arr就是等效的。我们可以用arr[i]来访问数组元素,那是不是也可以用p[i]来访问数组元素呢?

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

我们发现,将*(p + i)换为p[i]后,代码也能正常运行,说明p[i] 与 *(p+i)是等效的。那么arr[i]也等效于 *(arr+i),编译器在访问数组元素的时候也是将其转换为首元素地址+偏移量的形式来获得地址,然后解引用来访问的。

3. 一维数组传参的本质

我们知道,数组也是可以作为参数传递给函数的。在之前,我们在计算数组元素个数的时候都是在函数外部进行的,那如果我们将数组作为参数传递给函数,能否在函数内部正确计算元素个数?

void test(int arr[])
{
    int sz2 = sizeof(arr)/ sizeof(arr[0]);
    printf("sz2 = %d\n",sz2);
}
int main()
{
    int arr[10] = { 0 };
    int sz1 = sizeof(arr)/ sizeof(arr[0]);
    printf("sz1 = %d\n",sz1);
    test(arr);
    return 0;
}
sz1 = 10
sz2 = 2

我们发现,函数并未得到正确的结果,这是因为数组名是首元素的地址,所以函数在传参时会将数组名作为指针处理,在计算时,将arr作为首元素的地址进行计算,因此得出结果为2(64位环境下指针大小为8字节)。

所以说,数组传参本质上传递的是首元素的地址。

4. 冒泡排序

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

如果我们要将数组排序为降序的形式,当靠前的元素小于靠后的元素时,我们就交换两个元素的值。而很明显一轮只能保证最小的数字被移到最后,所以我们要进行多轮。当数组中一共有n个元素时,我们就只需要进行n - 1轮就可以完成排序。而我们每一轮结束就可以确定一个数字的位置,下一轮所需要遍历的元素个数就减少一个,所以每一轮需要遍历的元素个数是与轮次有关的,因此,第i轮只需要遍历n-1-i个元素。另外,我们可以创建一个变量flag,用来确定排序是否已经完成,避免多余的运算

因此,我们可以得到如下的代码:

void Bubble(int* arr,int sz)
{
    int i,j,count = 0,temp;
    for(i = 0;i < sz - 1;i++)
    {
        for(j = 0;j < sz - 1 - i;j++)
        {
            if(*(arr + j) < *(arr + j + 1))
            {
                temp = *(arr + j);
                *(arr + j) = *(arr + j + 1);
                *(arr + j + 1) = temp;
                count++;
            }
            if(count == 0)
                break;
        }
    }
}

我们可以进行测试:

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

结果符合预期,代码正常运行。

5. 二级指针

我们知道,指针变量也是变量,而变量就需要地址来存储,那么指针变量存储在哪里呢?

这里我们就需要学习二级指针

用代码可以表示为:

int a = 0;
int *pa = &a;
int **ppa = &p1;

那么,根据指针的知识来类推,对pa进行解引用操作得到的是a,那对ppa进行解引用操作得到的就是pa。所以,如果我们希望通过ppa来得到a的话就需要进行两次解引用操作,即:

**ppa = a;
//*(*ppa) = a;
//*pa = a

6. 指针数组

类比一下整型数组中存放整型数据,那么指针数组中存放的就是指针。

而指针又有类型的区别,所以类比一下整型数组的定义语法,我们定义整型指针数组时,可以写成这个样子:

int* arr[10];

其中,arr是数组名,10是数组大小,而int* 是数组中元素的类型。

7. 指针数组模拟二维数组

上面我们知道,指针数组中存放的都是指针,而指针又可以指向一个地址。那么我们是不是可以根据这个特点来用指针数组模拟二维数组呢?

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

数组名就是首元素地址,我们将三个数组的数组名放入指针数组p中,通过p[i]来访问数组的地址,再加上[j]来访问数组中的元素,通过解引用操作得到对应的元素的值。这样,我们就通过指针数组模拟了二维数组的效果。

但是实际上,这并不等同于二维数组。因为二位数组在内存中是连续的,而在这里,我们只是将不同的数组的地址放在了一起,进行访问,而在内存中,这些数组并没有连续储存在一起。


网站公告

今日签到

点亮在社区的每一天
去签到