C语言指针(中篇)

C语言指针(中篇),第1张

🐱作者:一只大喵咪1201
🐱专栏:《C语言学习》
🔥格言:你只管努力,剩下的交给时间!

C语言指针(中篇)
  • 😹指针和数组
  • 😹二级指针
  • 😹字符指针
  • 😹指针数组
  • 😹数组指针
    • 🦊数组指针的定义
    • 🦊数组指针的使用
  • 😹数组指针数组

在 C语言指针(上篇)中我们已经了解到了什么是指针,指针该怎么用,以及它的一些注意事项,下面我们来对指针进行更深一步的了解。

😹指针和数组

本喵的文章数组名的实质中讲到过,一维数组的数组名就是首元素的地址,那么我们就可以将数组名放在指针变量中进行访问数组

#include 
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = arr; //指针存放数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p + i);
	}
	return 0;
}

运行结果

可以看到,p+i的值就是数组arr中下标为i的元素的地址,接下来我们通过指针变量来访问数组

int main()
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* p = arr; //指针存放数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

p+i找到数组中元素所在的地址,然后通过解引用就可以访问的数组中的元素。

😹二级指针

指针变量也是变量,它也是存放在内存中的,所以指针变量也有地址,它的地址也是放在指针变量中。

存放一级指针变量地址的指针变量叫做二级指针变量。
同样有三级指针,四级指针等等。

我们创建一个二级指针

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;

	return 0;
}

二级指针创建规则:
int** ppa = &pa;可以写成int* * ppa = &pa;
等号的左边同样可以写成3个部分,int*, * 号,ppa
*号表示这是一个指针,ppa表示这是指针变量的名字,int * 表示这个指针变量中存放的数据类型是int * 的,也就是一级int指针变量类型的数据。
所以ppa是一个二级指针。

来看一下它的对应关系:

  1. 我们先在内存中创建一个int类型的变量a,它的值是10,放在内存中,假设它的地址是0x0012ff40。
  2. 接着创建一个一级指针变量pa,类型是int*类型的,它里面放的值就是变量a的地址。假设pa的地址是0x0012ff60。
  3. 再创建一个二级指针变量ppa,类型是int**类的,它里面放的值就是一级指针变量pa的地址,假设ppa的地址是0x0012fff0。
  4. 可以继续创建更高级的指针变量来存放低一级的指针变量的地址。

二级指针变量的用法参照一级指针变量即可,只是它里面的内容是一级指针变量的地址,解引用访问一个具体数据时需要解引用俩次。

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;

	printf("**ppa = %d\n", **ppa);
	printf("*pa = %d\n", *pa);

	return 0;
}

将二级指针解引用俩次就可以访问到变量a

可以看到

**ppa和*pa是等价的

😹字符指针

字符指针就是char*类型的指针,它指向的数据类型是char类型的,字符指针有俩种用法:

  1. 字符指针变量中存放的是字符数组名
int main()
{
	char arr[] = "abcdef";
	char* p1 = arr;
	printf("p1 = %p\n", p1);
	return 0;
}


打印的结果就是数组首元素的地址。

  1. 字符指针变量中存放是字符串首字符的地址
int main()
{
	char* p2 = "abcdefg";
	printf("p2 = %p\n", p2);
	return 0;
}

这段程序是什么意思呢?是将整个字符串放在指针变量p2中码?我来看一下结果

打印的结果也是一个地址,该地址是字符串首字符的地址,也就是’a’的地址。
在了解字符指针变量的俩种用法后,我们来看下面一段代码:

#include 
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 等于 str2\n");
	else
		printf("str1 不等于 str2\n");

	if (str3 == str4)
		printf("str3 等于 str4\n");
	else
		printf("str3 不等于 str4\n");

	return 0;
}

str1和str2使用第一种用法,str3和str4使用第二种用法。
来看运行结果

结果是str1不等于str2,str3等于str4,这是什么原因呢?

首先,srt1和str2是俩个数组,虽然它们的内容是相同的,但是它们在内存空间中的地址是不同的,所以数组名也就不相同。
再看,str3和str4这俩个字符指针变量中的值都是字符串首字符的地址。
由于这个字符串是字符常量,被存放再代码只读区,它不可以被修改,而且只有一份,所以str3和str4中的内容是相同的。

😹指针数组

我们知道有很多类型的数组

int类型数组——int类型元素的集合
char类型数组——char类型元素的集合
float类型的数组——float类型元素的集合
那么便可以推断出
指针数组——指针变量元素的集合

指针数组它是一个数组,里面的元素都是指针变量。

int main()
{
	//创建3个一维数组
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };

	int* parr[3] = { arr1,arr2,arr3 };

	return 0;
}

我们知道,一维数组的数组名就是首元素地址,我们创建了3个一维数组,将它们的数组名(是指针变量类型)存放在一个指针数组parr中。如此一个指针数组便创建好了。

int main()
{
	//创建3个一维数组
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };

	int* parr[3] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);//parr[i]是一维数组的数组名
									  //parr[i][j]就是一维数中具体元素
		}
		printf("\n");
	}

	return 0;
}

上面这段代码是在使用指针数组

它的结果和二维打印出来的一样。

使用指针数组可以实现类似于二维数组的 *** 作。

我们也可以创建其他类型的指针数组

char* arr2[4]; //一级字符指针的数组

还可以创建二级指针数组

char** arr3[5];//二级字符指针的数组

二级指针数组中存放的数据类型是char类型的二级指针,即每个元素都是一个char*指针的地址。

😹数组指针 🦊数组指针的定义

我们已经了解到了很多指针类型,有

char类型指针——指向char类型数据的指针
int
类型指针——指向int类型数据的指针
float*类型指针——指向float类型数据的指针
由此可以推断出
数组指针——指向数组类型数据的指针

数组指针是一个指针变量,它是用来存放数组的地址的。
本喵的文章数组名的实质讲到过对数组名取地址(&数组名)取出的是整个数组的地址,但是它表现的形式是数组首元素的地址。

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	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;
}


我们可以看到,数组名和数组名取地址的结果是相同的,但是数组名加1后增加了4个字节,也就是1个int类型的数据,而&数组名加1后增加了40个字节,也就是10个int类型的数据,也就是逃过了一个数组arr的所有数据。

int (*p)[10] = &arr;

如此一来我们就创建了一个数组指针变量,p中放的是整个数组的地址

数组指针的创建规则:
int (*p)[10] = &arr;语句中
变量名p先和 * 号结合,结合后变成了 * p,说明p是一个指针变量。
再与后面后面的[]结合,说明它是一个数组指针,[]中的10表示该指针指向的数组中有10个元素。
最前面的int表示这10个元素的数据类型是int类型。

🦊数组指针的使用

上代码:

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


结果就是打印数组中的内容,但是一般都不这样用,因为单纯的访问数组中的元素有更简单的办法。

这段代码本喵主要是想告诉大家:
数组指针变量的解引用得到的是整个数组,而整个数组是通过数名表示的,所以数组指针变量的解引用得到的就是数组名。

接下来我们来看一下数组指针变量的真正的使用场景。

void print(int(*p)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(p + i) + j));
			//printf("%d ", p[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
	print(arr, 3, 5);
	return 0;
}


结果就是将二维数组打印出来。

数组指针变量真正的使用场景是在传参的时候,形参是一个数组指针变量的时候。比如二维数组传参,实参传的是数组名。
本喵的文章数组名的实质讲到过二维数组的数组名的实质是首行元素的地址,是一个一维数组的地址。

上面的程序是通过数组指针变量访问到二维数组中的每一个元素的。

注意

printf("%d ", *(*(p + i) + j));

是可以写成

printf("%d ", p[i][j]);

*(指针变量+i)就可以写成 :指针变量[i]。
指针变量+i的结果解引用与
指针变量[i]加上下标引用 *** 作符,再去掉解引用 *** 作符
是等价的。

此时我们便可以很轻易的识别出下面这些代码的意思:

	int arr[5];//int类型的数组,有5个元素
	int* parr1[10];//指针数组,有10个元素,每个元素是int*类型的
	int(*parr2)[10];//数组指针,parr2是数组指针变量名,指向的数组有10个元素,每个元素是int类型的
😹数组指针数组

按照我们上面推断的方法可以推断出,数组指针数组是一组数组指针变量类型元素的集合。

int(*parr3[10])[5];

这便是数组指针数组代码的样子。

创建规则:
parr3先和[]结合,结合后就成了一个数组,[]中的10表示该数组有10个元素。
我们知道,数组是一种自定义类型,去掉数组名,剩下的就是它的类型。
去掉parr3[10]后剩下的就是该数组中元素的类型,所以该数组中元素的类型是int (*)[5],是数组指针类型,该指针指向的数组中有5个元素,每个元素的类型是int类型。

结合指针数组,数组指针,数组指针数组是不是发现有种像套娃的感觉,按照这样的规则是不是还有指向数组指针数组的指针?

是的确实有,但是这种类型的变量或者数组一般是用不到的,只需要了解就可以,当你遇到的时候知道它是什么。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1331246.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存