深入理解指针2:数组名理解、一维数组传参本质、二级指针、指针数组和数组指针、函数中指针变量

目录

1、数组名理解

2、一维数组传参本质

3、二级指针

4、指针数组和数组指针

5、函数指针变量


1、数组名理解

首先来看一段代码:

cpp 复制代码
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才对。

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

  • sizeof(数组名)。sizeof中单独放数组名,这里的数组表示整个数组,计算的是整个数组的大小,单位是字节
  • &数组名。这里的数组名表示整个数组,取出的是整个数组的地址(不是首元素的地址)。

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

再来看一段代码:

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

输出结果:

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

但是&arr 和 &arr + 1 相差40个字节,这就是因为&arr是数组的地址,+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);
	return 0;
}

输出结果

在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组首元素的地址。所以函数形参的部分理论上应该使用指针变量来接受首元素的地址。

3、二级指针

假设有下面的声明:

cpp 复制代码
int zippo[4][2];//内含int数组的数组
  • 因为zippo是数组首元素的地址,所以zippo的值和&zippo0相同。而zippo0本身是一个内含两个整数的数组,所以zippo0的值和它首元素(一个整数)的地址(即&zippo00的值相同。简而言之,zippo0是一个占用一个int大小对象的地址。所以zippo和zippo0的值相同。
  • 给指针或地址加1,其值会增加对应类型大小的值。在这方面,zippo和zippo0不同,因为zippo指向的对象占用了两个int大小,而zippo0指向的对象只占用一个int大小。因此,zippo+1和zippo0+1的值不同。
  • zippo0是该数组首元素(zippo00)的地址,所以 *(zippo0)表示存储在zippo00上的值。与此类似,*zippo代表该数组首元素(zippo0)的值,但是zippo0本身是一个int类型值的地址。该值的地址是&zippo00。所以*zippo就是&zippo00。**zippo与*&zippo00等价。
cpp 复制代码
#include <stdio.h>
int main()
{
	int zippo[4][2] = { {2,4},{6,8},{1,3},{5,7} };

	printf("   zippo = %p,      zippo + 1 = %p\n", zippo, zippo + 1);
	printf("zippo[0] = %p,   zippo[0] + 1 = %p\n", zippo[0], zippo[0] + 1);
	printf("  *zippo = %p,     *zippo + 1 = %p\n", *zippo, *zippo + 1);
	printf("zippo[0][0] = %d\n", zippo[0][0]);
	printf("  *zippo[0] = %d\n", *zippo[0]);
	printf("    **zippo = %d\n", **zippo);
	printf("zippo[2][1] = %d\n", zippo[2][1]);
	printf("*(*(zippo+2) + 1) = %d\n", *(*(zippo + 2) + 1));

	return 0;
}

运行结果:

zippo 二维数组首元素的地址(每个元素都是内含两个 int 类型元素的一维数组)

zippo+2 二维数组的第 3个元素 (即一维数组)的地址

*(zippo+2) 二维数组的第 3个元素(即一维数组)的首元素(一个 int 类型的值)地址

*(zippo+2) + 1 二维数组的第 3个元素(即一维数组)的第 2个元素(也是一个 int 类型的值)地址

*(*(zippo+2) + 1) 二维数组的第 3个一维数组元素的第 2个int 类型元素的值,即数组的第3行第2

列的值 (zippo21)

4、指针数组和数组指针

指针数组的本质其实是数组,它里面存放的是指针,数组中每个元素的类型是指针类型(int*或char*)。

指针数组的创建方式:

cpp 复制代码
int * arr[10] = {0};

数组指针的本质其实是指针变量,存放的是数组的地址,能够指向数组的指针变量。

数组指针的创建方式:

cpp 复制代码
int (*pt)[10];

数组指针的初始化:

cpp 复制代码
int arr[10] = {0};
int (*pt)[10] = &arr;

字符指针变量:

cpp 复制代码
char* p = "hello";

这里本质上是把字符串"hello",首字符h的地址存放在p中。

5、函数指针变量

我们先来看一段代码:

cpp 复制代码
#include <stdio.h>
void test()
{
	printf("hehe\n");
}
int main()
{
	printf("test:  %p\n", test);
	printf("&test: %p\n", &test);
	return 0;
}

运行结果:

这说明函数是有地址的,和数组一样,函数名就是函数的地址。当然也可以通过&函数名的方式后的函数的地址。

如果我们要将函数的地址存放起来,就得创建函数指针变量。

cpp 复制代码
void test()
{
	printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;

int Add(int x, int y)
{
	return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

函数指针变量的使用:通过函数指针调用指针指向的函数。

cpp 复制代码
#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf3)(int, int) = Add;
	printf("%d\n", (*pf3)(2, 3));
	printf("%d\n", pf3(3, 5));
	return 0;
}

结果是 5 8

相关推荐
良木林10 小时前
子串 - LeetCode hot 100
算法·leetcode·职场和发展
陌诺曦.11 小时前
Python扩展小练习
算法
天空'之城11 小时前
C 语言工业级通用组件手写 30:安全内存拷贝组件
c语言·内存拷贝·工业级组件·安全内存操作
coder!mq12 小时前
说几个常见的语法糖?
java·开发语言·算法
LuminousCPP13 小时前
数据结构-双向循环链表
c语言·数据结构·笔记·链表
不爱学英文的码字机器13 小时前
推荐算法梳理,六种主流模型与九步训练流程
算法·机器学习·推荐算法
白狐_79814 小时前
408数据结构第5章:树与二叉树②——遍历、线索树、森林与哈夫曼
数据结构·算法·深度优先
小僧景贤15 小时前
嵌入式C语言 第二篇:基础语法|嵌入式C与标准C的核心差异
c语言·开发语言·嵌入式c语言
Jasmine_llq15 小时前
《P15800 [GESP202603 六级] 选数》
数据结构·long long·逆序线性 dp·从后往前推导·转移严格遵循题意·防止总和溢出·复杂度on