C:指针和数组之间的关系-学习笔记

 

目录

闲话:

引言:

1、数组名的理解

2、指针访问数组

3、一维数组传参的本质

4、二级指针

5、指针数组

6、指针数组模拟二维数组

结语:


闲话:

指针这个模块更新的比较慢,主要是小编还得学习指针的知识点,虽然说是学习笔记,但是也是需要小编自己学会后才能够更好的将知识点介绍给大家,所以还请见谅一下哈!!!

引言:

本篇文章将带来数组与指针之间的关系介绍,希望能对大家有所帮助!


1、数组名的理解

int arr[10] = {0};

从概念上讲:

数组名代表数组在内存中的起始地址。可以将数组名视为一个指向数组首个元素的指针。

例如,定义一个整型数组 int arr[10]  , arr  就代表了这个数组的起始地址。

来看一组对比:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf(" arr    = %p\n", arr);
	return 0;

 结果:(x86环境下展示的地址)

我们发现使用数组名打印地址和取首元素地址打印的结果相同。

 因此可以更加确定数组名就是数组首元素的地址。

但是!在 C 语言中,数组名具有特殊的含义和性质。因此,数组名肯定不能只有这么单一的用法喽!

来看一组代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf(" %zd\n",sizeof(arr));
	return 0;
}

这个结果会是多少呢?4还是8,或者都不是?验证一下结果

嗯,都不是,结果是40。其实对于这个结果也不是很出乎意料,毕竟我们前面还使用表达式sizeof (arr) / sizeof(arr[0])来计算数组元素个数

不过到这里我们就应该明白了,在这种情况下的数组名就不是首元素地址了。

并不是说数组名是首元素地址是错误的,只是有凡是都有例外嘛!数组名也是如此,数组名有两个例外:

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

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

我们来比较一下&arr,arr,&arr[0]这三个打印地址的结果

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
    printf("&arr[0]  =%p\n",&arr[0]);
    printf("arr      =%p\n",arr);
	printf("&arr     =%p\n",&arr);	
	return 0;
}

结果:

从上面的结果来看,&arr,arr,&arr[0]似乎没有什么区别,打印的结果也都是一样的,我们现在分别给它们+1,,再来看一下结果:

&arr[0]和arr在+1后的结果是相同的,都是跳过4个字节,而&arr则不同

&arr+1后跳过了40个字节,怎么算的呢?

十六进制计算
A8-80=28
A是10,10-8 = 2,8-0 = 8,
所以是28,十六进制的28对应的十进制数字是2 * 16 + 8 * 1= 40 

 

理解:关于&arr[0]和arr在+1后跳了4个字节,我们将首元素地址看作是一个整体,那么当我们+1的时候就会跳过首元素地址,来到第二个元素的地址;而&arr 取的是整个数组的地址,因此我们将整个数组看作一个整体,那么&arr+1后就应该跳过整个数组,由于该数组有十个元素,所以就是跳过40个字节。

问: 数组+1 后跳过几个字节是取决与什么?

答:指针类型决定了指针+-整数时候跳过多少字节。

&arr[0] 的指针类型是 int*

arr 的指针类型是 int*

总结:数组名就是首元素的地址,除了sizeof(数组名)和&数组名以外。

2、指针访问数组

前面说了数组名就是首元素地址,也可以看作是一个指向首元素的指针。既然如此,我们就可以很方便的使用指针来访问数组了。

怎么理解指针访问数组呢?

当我们使用指针来访问数组时,实际上是通过指针指向数组的起始地址,然后根据指针的移动和运算来访问数组中的各个元素。

先来看一下不使用指针访问一个一维数组的代码展示,也就是直接使用下标来访问数组

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
    //输入
	for (i = 0; i < sz; i++)
	{
		scanf("%d",&arr[i]);
	}
    //输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

我们都知道数组内的元素地址是连续的,如果我们能得到首元素地址,就可以访问整个数组,关于整型数组,我们希望一个整数一个整数的访问,因此我们使用整型指针最为合适

&arr[i]这个就是取数组中下标为 i 的元素的地址

现在我们来使用指针访问数组

与上代码变化:添加int* p = arr ,将输入for循环里的&arr[i]改写为p+i,将输出for循环里的&arr[i]改写为*(p+i)

代码展示:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	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元素的地址
	}
    //输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ",*(p+i));
	}
	return 0;
}

int* p = arr这个表达式中我们将arr赋值给p。也就是说p其实是等价于arr的。p = arr ,arr是首元素地址,我们将arr赋给p的时候,p里面存放的也就是首元素地址。

既如此,我们是否可以将*(p+i)中的p换做arr ? 也就是*(arr+i),我们来检验一下

#include <stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数

   //输入

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

   //输出
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *(arr + i));
    }
    return 0;
}

 结果展示:

程序还是可以运行的,现在是不是更能够理解p等价于arr了?

 还有一些就不一 一举例说明了,直接总结一下吧!(不一定全,希望能够有大佬帮忙补充一下)

关于 p 等价于 arr

在输入中:

p + i   =  arr + i

在输出中:

arr[i] --- *(arr+i)   完全等价

arr[i]这种写法编译器在执行的时候也会转换成*(arr+i)来执行,也就是说数组底层运算逻辑也是通过指针的方式来执行的。

*(p+i) ---  p + i  完全等价

在整个流程中:

arr[i] = p[i] 

 关于arr[i]还有一个更加抽象的写法,arr[i] = i[arr] ,这里就是说明一下[]就是一个操作符而已,不推荐这种写法。

3、一维数组传参的本质

数组传参是指在函数调用时将数组作为参数传递给函数。

int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	test(arr);
	return 0;}

数组传参只需要写数组名就可以了。注意:数组名是arr,而不是arr[10]

数组传参形参该怎么写呢?

void test(int arr[])//元素个数写不写无所谓

等下会说为什么写不写都不影响

现在我们来分别在(test)函数外部与函数内部计算数组元素的个数、

来,展示!

可以看到在函数内部sz2结果为1,而函数外部sz1结果为10;这是为什么呢?

关于sz1 = 10;的结果我们都清楚,sizeof(arr)求得数组的总大小,sizeof(arr[0])求得数组首元素的大小,然后得出元素个数,但是为什么在test函数内部求得的元素个数结果变为1了呢?

我们来逆推一下,首先sizeof(arr[0])表示的是数组首元素的大小是不变的,因此sizeof(arr[0])等于4

sz2 = sizeof(arr) / 4 = 1;因此sizeof(arr)也等于4,那么在什么情况下能得到aizeof(arr) = 4 呢?

在数组传参的时候 test(arr);我们传递的是整个数组吗?还记得前面关于数组名的理解吗?这里arr既不是在sizeof中,前面也没有&符号,所以,test(arr)中的arr指的就是数组首元素的大小,因此我们传参过去的是首元素的地址,这便是一维数组传参的本质,既如此,我们便可以明白aizeof(arr) = 4是怎么得到的了,地址在32位机器上占4个字节,在64位机器上占8个字节 ,小编是在32位上操作的,所以最终得到izeof(arr) = 4 的结果。

void test(int arr[])//元素个数写不写无所谓
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}
#include <stdio.h>
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;}

 综上:数组传参传递的就是首元素地址

1.我们传递的不是整个数组,函数形参的部分是不会真实创建数组的,所以就不需要数组大小,也就是形参部分元素大小写不写都无所谓,没有什么影响

2.数组传过去的是数组首元素地址,地址应该拿指针来接收,所以函数形参部分应该使用指针变量来接收,而我们写成int arr[])是为了更加方便我们的理解。

void test(int arr[])可以写为void test(int* arr)

注意

一维数组传参的时候,形参可以写成数组的方式,主要是为了方便理解,形参也可以写成指针变量的方式 

如果我们想要在函数内部获取数组元素的个数,该怎么写呢?

void test(int arr[10],int sz)
{
    //遍历数组
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf(" %d ", arr[i]);
	}
}
#include <stdio.h>
int main() 
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	test(arr,sz);
	return 0;
}

4、二级指针

我们以前所接触的指针都叫一级指针,在介绍二级指针前,我们先谈一谈一级指针,一级指针的出现是为了什么呢?也就是一级指针的作用是什么?

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;//pa是指针变量,一级指针变量
	return 0;
}

一级指针变量也是变量,既然如此,在32位平台下我们要申请4个字节空间,在64位平台下我们要申请8个字节空间,因为该指针变量所存放是地址。所以一级指针变量的作用是存储了一个变量的地址

a是一个变量,a的类型是int ,通过&a(0x0012ff40)存放到指针变量pa中;

pa也是一个变量,pa的类型是int*,我们也可以通过&pa(取出pa的地址:0x0012ff48)存放起来,而存放pa的地址的变量该怎么写呢? int** ppa = &pa; 这里的ppa就是二级指针变量。

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

再来理解一下

int * 中*说明pa是个指针变量,int是说明pa指向的变量是int类型的

int* * 中第二个*是说明ppa是指针变量,而前面的int* 是说明ppa指向的变量是int*类型的

5、指针数组

只谈及指针数组是不是感觉比较陌生?但是我们可以类比一下整型数组,字符数组。

int main()
{
	int arr1[] = { 1,2,3 };//整型数组
	char arr2[] = { 'a' };//字符数组
}

整型数组就是用来存放整型的数组,字符数组就是用来存放字符的数组,所以指针数组就是用来存放指针的数组

整数类型int,字符类型char,但是指针类型有很多,int*,char*,因此指针数组该怎么写呢?

int main()
{
	int* arr1[5] = { 0 };//整型指针数组
	char* arr2[10] = { 0 };//字符指针数组
     ……
}
#include <stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int* arr[3] = { &a,&b,&c };	
	int i = 0;//下标
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}
}

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

6、指针数组模拟二维数组

#include <stdio.h>
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* int*
	int* arr[] = { arr1,arr2,arr3 };//arr是指针数组
	return 0;
}

理解指针数组arr,通过找到arr1,arr2,arr3首元素地址进而找到arr1,arr2,arr3中的全部元素 

我们怎么通过首元素地址打印全部元素呢?

	int i = 0;
	for (i = 0; i < 3; i++)
	{
			int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
	}

上面 i 是arr中的下标,j 则是arr1,arr2,arr3中数组的下标

我们先通过下标 i 找到arr1,arr2,arr3的首元素,然后再通过下标 j 分别找到arr1 ,arr2 ,arr3 中的元素。

总代码:

#include <stdio.h>
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* int*
	int* arr[] = { arr1,arr2,arr3 };//arr是指针数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
			int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
      printf("\n");
	}
	return 0;
}

结果展示:


结语:

本篇文章到这里就结束了,希望本篇文章能够带你了解数组和指针之间的关系。下篇文章再见!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值