指针详解(2)

指针详解(2)

对数组名的理解

在C语言里数组名还表示着数组首元素地址。

int arr[5] = {1, 2, 3, 4, 5};
int* p = &arr[0];
int* p = arr;

以上这两种,对指针p进行赋值的操作均是等价的,都将数组首元素的地址赋给指针p。

不妨,我们可以测试一下

int main()
{
	int arr[5] = {1, 2, 3, 4, 5};
	printf(" &arr[0] = %p\n", &arr[0]);
	printf(" arr     = %p\n", arr);
	return 0;
}

在这里插入图片描述

更具运行结果能够证明,arr 与 &arr[0] 是等价的,都表示着数组首元素的地址


但也有两个例外,

  • sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位字节
  • &数组名,数组名表示整个数组,取出的是整个数组的地址。(整个数组的地址与首元素地址有区别的)

​ 除此之外,数组名表示首元素的地址

int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	printf("sizeof(arr) = %d\n", sizeof(arr));
	return 0;
}

在这里插入图片描述

int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	int* p = &arr;
	printf("&arr  = %p\n", &arr);
	printf("arr   = %p\n", arr);
	return 0;
}

在这里插入图片描述

这里会不会看着有点懵,&arr,与arr地址是相同的。不妨看看这串代码。

int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	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,表示着数组名首元素的地址

  • &arr[0] + 1 和 arr + 1,表示第二的数组元素的地址,地址大小加4

  • &arr,表示整个整个数组元素,打印的地址是首元素的地址

  • &arr + 1,由00AFF934 - 00AFF920得出地址向后偏移了20个字节,意味着&arr + 1跳出了数组元素的空间,指向了一块没有使用的空间

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

可以运行这窜代码,将会报错,指针变量p 现在是一个野指针,使用printf函数解引用会报错。

一维数组传参本质

有了上述对数组名的理解,看看一下代码试着猜测运行结果。

void test(int arr[])
{
	printf("sizeof(arr) = %d\n", sizeof(arr));
}
int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	printf("sizeof(arr) = %d\n", sizeof(arr));
	test(arr);//等价test(&arr[0]);
	return 0;
}

运行结果:
在这里插入图片描述

很明显在函数里计算数组元素的大小为4,主函数里为20。

根据代码运行结果发现,一维数组在进行传参时,因为数组名是首元素地址,在本质上传递的实际上是数组首元素的地址。在在理论上参数因该使用指针变量来接收数组元素的指针。

函数里使用sizeof(arr)计算数组大小时实际上计算的是指针变量的大小而不是数组的大小。
在这里插入图片描述

这里将编译器改变为64位,重新运行代码后的结果,在64位下计算指针的大小,为8个字节。


由于传递的是地址,在函数里可以使用指针变量来接收

void test(int* arr)
{
	//printf("arr[2] = %d", arr[2]);
	printf("*(arr + 1)= %d", *(arr + 1);//两者等价
}

这里,通过指针的写法来访问数组元素,*(数组名 + 下标),这种写法与 arr[1]是等价的,数组名表示收元素地址,对地址+ 1或+ 其它数字,地址就会跳转到该数字对应下标的数组元素。

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

注意:数组下标最大是4,在使用指针的写法时,*(arr + 5)这样是错误的写法,将会对野指针解引用,程序报错。

二级指针

指针变量用来存放变量地址,那指针变量的地址呢?变量的地址存放在二级指针里

int main()
{
	int a = 0;
	int* pa = &a;
	int** ppa = &pa;
	**ppa = 6;
	printf("%d\n", a);
	return 0;
}

在这里插入图片描述

变量 地址
a=*pa=**ppa 0X00FFA4 0
pa=*ppa 0X00FFA8 0X00FFA4
ppa 0X00FFAC 0X00FFA8

对一级指针pa进行解引用 *pa,找到了变量a,a的值为 0,对二级指针ppa进行解引用找到了一级指针pa,pa的值为0X00FFA4,是变量a的地址,在进行解引用就找到了变量a,a的值为0。


案例:对二级指针 ppa解引用一次

int main()
{
	int a = 0;
	int b = 20;
	int* pa = &a;
	int** ppa = &pa;
	*ppa = &b;
	printf("%d\n", **ppa);
	return 0;
}

这里对二级指针进行解引用(*ppa),实际上就是对一级指针进行操作,将b的地址赋给一级指针pa。它们之间的关系变为:

变量 地址
b=*pa=**ppa 0X00FFA0 20
pa =*ppa 0X00FFA8 0X00FFA0
ppa 0X00FFAC 0X00FFA8

对ppa解引用两次就是b的值,打印的结果为20

在这里插入图片描述

指针数组

指针数组是什么?是指针,还是数组?更具之前学习过的,整形数组(int arr[]),字符数组(char arr[]),整形数组使用来存放整形的数组,字符数组是用来存放字符的数组,指针数组使用来存放指针的数组吗?是滴没错!

在这里插入图片描述

指针数组的写法,它可以是整形,也可以是字符类型,int* arr[5]; char* arr[5];

数字5代表着这个指针数组可以存放5个指针变量。指针数组与整形数组字符数组的理解相比并不难,它只是用来存放指针的数组。

初始化:

int a = 10;
int b = 6;
int* parr[2] = {&a, &b};

int* p[5] = { 0 };

使用指针数组,它与常见的整形数组用法区别不大

printf("%d\n", *parr[0]);

由于数组里存放的是指针,parr[0]是一个指针,对指针进行解引用来获取它指向的数据。

使用指针数组模拟二维数组

首先,二维数组在空间上是连续存放的,int arr[3][5];,arr数组里,每行有5个元素,一共有三行,我们说可以近似的将这个三行的二维数组看作三个一维数组的拼接,诶~事实上是可以这样操作的。是以指针数组就可以完成这样的作法。

#include <stdio.h>
int main()
{
	int i, j;
	int arr1[5] = { 0 };
	int arr2[5] = { 0 };
	int arr3[5] = { 0 };
	int* parr[5] = {arr1, arr2, arr3};
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			parr[i][j] = i + j;
		}
	}
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述

​ 这里呢,使用指针数组模拟了二维数组的赋值和打印,为什么可以这样 parr[i][j]; ,数组下标引用操作符,本质上可以理解为 *(arr + 1),对数组首元素加有效的数字,就获得数组下标对应元素的地址,然后进行解引用,就获取了地址对应的值,parr[i][j]; 同样是这样理解,第一个下标引用操作符找到这个指针数组的元素,

parr[0][j] 等价 arr1[j] ,在对arr1使用数组下标引用操作符就可以找到 i,j对应的位置,和元素。

​ 同理,也可以使用二级指针模拟二维数组。首先对二级指针开辟空间一块整形指针类型的空间,然后在循环里

对每一个整形指针开辟相同大小的空间,就完成了对二维数组结构建立,之后就可以正常使用下标引用操作符,对二级指针赋值,打印之类的操作。

有效的数字,就获得数组下标对应元素的地址,然后进行解引用,就获取了地址对应的值,parr[i][j]; 同样是这样理解,第一个下标引用操作符找到这个指针数组的元素,

parr[0][j] 等价 arr1[j] ,在对arr1使用数组下标引用操作符就可以找到 i,j对应的位置,和元素。

​ 同理,也可以使用二级指针模拟二维数组。首先对二级指针开辟空间一块整形指针类型的空间,然后在循环里

对每一个整形指针开辟相同大小的空间,就完成了对二维数组结构建立,之后就可以正常使用下标引用操作符,对二级指针赋值,打印之类的操作。

相关推荐

  1. <span style='color:red;'>指针</span>(<span style='color:red;'>2</span>)

    指针(2)

    2024-07-12 23:38:05      20 阅读
  2. 详解C指针 (二)

    2024-07-12 23:38:05       41 阅读
  3. C语言-->指针详解

    2024-07-12 23:38:05       22 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-12 23:38:05       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-12 23:38:05       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-12 23:38:05       58 阅读
  4. Python语言-面向对象

    2024-07-12 23:38:05       69 阅读

热门阅读

  1. 【C++】C++中struct结构体和class类的区别

    2024-07-12 23:38:05       16 阅读
  2. CAS详解

    CAS详解

    2024-07-12 23:38:05      16 阅读
  3. Go语言详细教程

    2024-07-12 23:38:05       19 阅读
  4. Windows 安装Zookeeper

    2024-07-12 23:38:05       19 阅读
  5. 初学者必看的 3 个 Python 小项目

    2024-07-12 23:38:05       21 阅读
  6. 【Linux】docker和docker-compose 区别是什么

    2024-07-12 23:38:05       17 阅读
  7. EG800K GPS开发

    2024-07-12 23:38:05       20 阅读
  8. js之空值合并运算符 ‘??‘

    2024-07-12 23:38:05       23 阅读
  9. 代码优化方法记录

    2024-07-12 23:38:05       22 阅读
  10. 创建I/O文件fopen

    2024-07-12 23:38:05       15 阅读