深⼊理解指针-通过指针引用数组

1. 数组名的理解(补充知识点)

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

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 }; //数组在内存中是连续存放的,所以只用找到数组首元素的地址即可找到整个数组
	//int* p = &arr[0]; //数组名是数组首元素的地址
	printf("&arr[0] = %p\n", &arr[0]); //取出首元素,得到数组首元素的地址
	printf("arr     = %p\n", arr); //数组名就是数组首元素的地址

	return 0;
}

输出结果(一下是在x86(打印的地址较短)架构下编译的):

请添加图片描述

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

这时候有同学会有疑问?数组名如果是数组首元素的地址,那下面的代码怎么理解呢?

#include <stdio.h>
int main()
{
    int arr[ 10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };
    printf("%d\n", sizeof(arr)); //sizeof是就是计算数组大小的,如果数组名表示首元素地址,x86——>4,x64——>8,但是在x86打印出的是40,x64打印出的是80,这时候就有点迷糊了。
    return 0 ;
}

请添加图片描述
输出的结果是: 40 ,如果arr是数组首元素的地址,那输出应该的应该是 4 / 8 才对

其实数组名就是数组首元素(第一个元素)的地址是对的,但是有两个例外:

  • sizeof(数组名) ,sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节,所以打印出来的结果是40

  • &数组名 ,这里的数组名表示整个数组,取出的是 整个数组的地址 (整个数组的地址和数组首元素的地址是有区别的)

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

这时有好奇的同学,再试一下这个代码:

#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有啥区别呢?

总结

指针类型决定了指针的差异


#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 ); //int*类型指针,地址加1,跳过第一个元素,4个字节

    printf("arr = %p\n", arr); //数组名打印数组首元素的地址
    printf("arr+1 = %p\n", arr+ 1 ); //int*类型指针,地址加1,跳过第一个元素,4个字节

    printf("&arr = %p\n", &arr); //打印整个数组的地址
    printf("&arr+1 = %p\n", &arr+ 1 ); //int*类型指针,地址加1,跳过整个数组,40个字节

    return 0 ;
}

输出结果:

&arr[ 0 ] = 0077F 820
&arr[ 0 ]+ 1 = 0077F 824

arr = 0077F 820
arr+ 1 = 0077F 824

&arr = 0077F 820
&arr+ 1 = 0077F 848

这里我们发现&arr[ 0 ]和&arr[ 0 ]+ 1 相差 4 个字节,arr和arr+ 1 相差 4 个字节,是因为&arr[ 0 ] 和 arr都是首元素的地址,+ 1 就是跳过一个元素

但是&arr和&arr+ 1 相差 40 个字节,这就是因为&arr是数组的地址,+ 1 操作是跳过整个数组的

到这里大家应该搞清楚数组名的意义了吧

数组名是数组首元素的地址,但是有 2 个例外

2.使用指针访问数组

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


以下是使用数组下标访问数组元素的过程:


#include <stdio.h>
int main()
{
    int arr[ 10 ] = { 0 };
    int sz = sizeof(arr)/sizeof(arr[ 0 ]); //整个数组大小/一个元素的大小=数组的元素个数

    //输入
    int i = 0;
    for(i= 0 ; i<sz; i++)
    {
       scanf("%d", &arr[i]);
    }
    //输出
    for(i= 0 ; i<sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0 ;
}

输出结果:

输出结果


以下是使用指针访问数组元素的过程:


#include <stdio.h>
int main()
{
    int arr[ 10 ] = { 0 };
    int sz = sizeof(arr)/sizeof(arr[ 0 ]); //整个数组大小/一个元素的大小=数组的元素个数

    //输入
    int i = 0 ;
    int* p = arr;

    for(i= 0 ; i<sz; i++)
    {
       scanf("%d", p+i);//p+i是下标为i的元素的地址
    }
    //输出
    for(i= 0 ; i<sz; i++)
    {
        printf("%d ", *(p+i)); //对(p+i)进行指针加法,解引用,得到元素的值
    }
    return 0 ;
}

输出结果:

请添加图片描述

数组名arr是数组首元素的地址,可以赋值给p,其实数组名arr和p在这里是等价的。那我们可以使用arr[i]可以访问数组的元素,那p[i]是否也可以访问数组呢?


#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); //p+i是下标为i的元素的地址
        scanf("%d", arr+i);//也可以这样写
    }

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

输出结果:
请添加图片描述

在第 18 行的地方,将(p+i)换成p[i]也是能够正常打印的,所以本质上p[i]是等价于(p+i)**。

同理arr[i]应该等价于(arr+i),数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的*。

总结

请添加图片描述

内容总结

请添加图片描述

3.一维数组传参的本质

首先从一个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把数组传给一个函数后,函数内部求数组的元素个数吗?

#include <stdio.h>

//数组传参的时候,形参可以写成指针形式,也可以写成数组形式
//写成数组形式,最简单,是为了方便理解,容易接受这种语法
//但是即使写成数组形式,本质上还是指针变量

          //int * arr
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 ]); //szl=10
    printf("sz1 = %d\n", sz1); //10
    
    test(arr); //arr是数组名,本质上是数组首元素的地址
    //数组传参的本质,传递的是数组首元素的地址
    //所以形参即使写成数组形式,本质上也是一个指针变量

    return 0 ;
}

输出的结果:

请添加图片描述

总结:

数组名是数组首元素的地址;

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

所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。 那么在函数内部我们写sizeof(arr)计算的是一个地址的大小(单位字节)而不是数组的大小(单位字节)。 正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。

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

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

4.冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较,不满足顺序就交换位置,满足顺序就找下一个数字

void input(int *arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", arr + i);
	}
}

int count = 0;
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-1-i; 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;
		}
	}
}

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

int main()
{
	int arr[10] = { 0 };
	//输入一些值
	//1 2 3 4 5 6 7 8 9 10
	//9 8 7 6 5 4 3 2 1 0
	//2 1 9 7 0 3 4 8 5 6 
	int sz = sizeof(arr) / sizeof(arr[0]);
	input(arr, sz);

	//排序 - 写一个函数完成数组的排序,排成升序
	bubble_sort(arr, sz);//使用冒泡排序
	print_arr(arr, sz);
	printf("\ncount = %d\n", count);
	return 0;
}

画图理解

请添加图片描述

5.二级指针

什么是二级指针?

char*
int*
double*
//一级指针

这就是二级指针

#include <stdio.h>

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

    printf("%d\n", **ppa); //输出10
    return 0 ;
}
**ppa == *pa == a ==10 //三者等价

请添加图片描述

请添加图片描述

对于二级指针的运算有:

  • *ppa通过对ppa中的地址进行解引用,这样找到的是pa,*ppa其实访问的就是pa.
int b = 20 ;
*ppa = &b;//等价于 pa = &b;
  • **ppa先通过*ppa找到pa,然后对pa进行解引用操作:*pa,那找到的是a.
**ppa = 30 ;
//等价于*pa = 30;
//等价于a = 30;

总结

二级指针变量是用来存放一级指针变量的地址的。

6.指针数组

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

我们类比一下,整型数组,是存放整型的数组,字符数组是存放字符的数组。

char arr[10]; //字符数组-存储字符的数组
int arr[5]; //整型数组-存储整型的数组

那指针数组呢?

指针数组-存放指针的数组,数组的每个元素其实是指针类型,都是用来存放地址的。

char* arr[5]; //存放字符指针的数组
int* arr[5]; //存放整型指针的数组

是存放指针的数组。

请添加图片描述

指针数组的每个元素都是用来存放地址(指针)的

如下图:

请添加图片描述

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

通过一个简单的例子来说明:

#include <stdio.h>

int main()
{
    int a = 10;
    int b = 20;
    int c = 30;
    
    //使用指针变量,这样太麻烦了,所以使用指针数组
    //int* p1 = &a;
    //int* p2 = &b;
    //int* p3 = &c;

    //使用指针数组    
    int* arr[3] = { &a, &b, &c }; //存放3个整型指针的数组
    //              0    1   2  //通过下标进行指针解引用

    int i = 0;
    for(i = 0; i < 3; i++)
    {
        printf("%d ", *(arr[i])); //解引用,输出地址的值
    }
    return 0;
}

*输出结果:

10 20 30

说明:

  1. 指针数组的每个元素是地址,解引用可以得到地址的值。

  2. 指针数组的本质就是存放指针的数组,数组的每个元素都是指针类型,都是用来存放地址的。

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

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

#include <stdio.h>
int main()
{
    int arr1[5] = { 1 , 2 , 3 , 4 , 5 }; //定义3个整型一维数组
    int arr2[5] = { 2 , 3 , 4 , 5 , 6 };
    int arr3[5] = { 3 , 4 , 5 , 6 , 7 };
    //arr1 arr2 arr3 是三个数组的数组名,表示三个数组的首元素的地址

    //数组名是数组首元素的地址,类型是int*的,就可以存放在arr数组中
    int* arr[ 3 ] = {arr1, arr2, arr3};
    int i = 0 ;
    
    for(i= 0 ; i< 3 ; i++) //通过for循环,访问三个数组的数组名
    {
        int j = 0 ;
        for(j= 0 ; j< 5 ; j++) //通过for循环,访问三个数组中某个数组的元素
        {
            printf("%d ", arr[i][j]); //*(*(arr+i)+j)
        }
         printf("\n");
    }
    return 0 ;
}

输出结果:

1 2 3 4 5
2 3 4 5 6
3 4 5 6 7

下面是对上述第10~20行代码的解释:

请添加图片描述

arr[i]访问arr数组的元素arr[i]找到的数组元素指向了整型一维数组arr[i][j]就是整型一维数组中的元素

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值