C语言-----指针进阶(上)

目录

1、指针与数组 

1.1 指针与数组 

1.2 数组名作为指针

1.3 指针与多维数组 

1.4 指针与数组参数 

1.5 指针数组 

1.6 数组指针


1、指针与数组 

1.1 指针与数组 

访问数组元素有两种方法:①使用下标实现。②使用指针实现(已知数组首元素地址,通过对地址的增减偏移量,得到各个元素的地址,从而访问到数组的各个元素)。

例、分别使用下标和指针访问数组元素。

#include<stdio.h.>
int main()
{
	int i = 0;
	int sz = 0;
	int array[5] = { 1,2,3,4,5 };
	sz = sizeof(array) / sizeof(array[0]);/*计算array数组元素个数*/
	int* parray = array;/*将数组首元素地址存放入parray,也可以这样写: int* parray=&array[0];*/
	printf("使用下标访问数组元素:\n\n");
	for (i = 0; i < sz; i++)/*下标访问*/
	{
		printf("数组 array 的第 %d 个元素的值为: %d\n", i, array[i]);
	}
	printf("\n\n使用数组地址(指针)访问数组元素:\n\n");
	for (i = 0; i < sz; i++)/*指针访问*/
	{
		printf("数组 array 的第 %d 个元素的值为: %d\n", i, *(parray+i));
	}
	return 0;
}

 运行结果,可看到访问数组元素,使用了两种方法:①直接法,使用数组下标实现。②间接法,通过使用指针实现。

本例子中,指针变量 parray 首先指向的是 array 数组下标为 0 的元素的地址,第2个for 循环 *(parray+i) 表达式的含义是:指针从数组首地址偏移 i 个存储单元(由于 int 数据占4个字节,所以每次偏移 4*i 个字节),访问该单元中的数据。根据数组的存储特点,依次访问了数组每个地址中的数据。下标和指针符号的具体含义见下表。 

数组元素下标符号指针符号
元素0array[0]*(p+0)
元素1array[1]*(p+1)
元素2array[2]*(p+2)
元素3array[3]*(p+3)
元素4array[4]*(p+4)

 注意:*(parray+i) 表达式中的括号是必须有的,不能省略。如果遗漏了小括号,将变成 *parray+i(*parray 是指对 parray 进行解引用操作,取出 parray 所指向的存储单元的数值),是指在 parray 所指向的存储单元的数值基础上加 ,始终没有改变指针 parray 的指向。

1.2 数组名作为指针

数组名作为指针(地址)。对于每个创建的数组,数组名就成为编译器为这个数组所创建的指针(地址)常量名称,存储的是数组第 1 个元素的起始地址,也就是数组首地址。上个例子中,“ int* parray = array; ”和 “ int* parray = &array[0]; ”是等效的。数组名表示数组首元素地址,除了以下两种情况: 

 (1)&数组名,取出的是数组的地址。&数组名,数组名表示整个数组。如:(&array - &数组名 - 数组名不是首元素地址 - 数组名表示整个数组。&数组名:取出的是整个数组的地址。)

(2)sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数 组。如:(sizeof(array) - sizeof(数组名) - 数组名表示的是整个数组。sizeof(数组名):计算的是整个数组的大小。)

(3)除此(1)、(2)两种情况之外,所有的数组名都表示数组首元素的地址。

例、使用数组名作为指针访问数组元素。

#include<stdio.h.>
int main()
{
	int i = 0;
	int sz = 0;
	int array[5] = { 1,2,3,4,5 };
	sz = sizeof(array) / sizeof(array[0]);/*计算array数组元素个数*/
	int* parray = array;/*将数组首元素地址存放入parray,也可以这样写: int* parray=&array[0];*/
	printf("使用数组名访问数组元素:\n\n");
	for (i = 0; i < sz; i++)/*使用数组名*/
	{
		printf("数组 array 的第 %d 个元素的值为: %d\n", i, *(array+i));
	}
	printf("\n\n使用数组地址(指针)访问数组元素:\n\n");
	for (i = 0; i < sz; i++)/*使用指针变量*/
	{
		printf("数组 array 的第 %d 个元素的值为: %d\n", i, *(parray+i));
	}
	return 0;
}

 分析:本例中分别使用了数组名和指针访问数组元素,结果一致,使用数组名访问元素和使用指针变量一样。

分析下列代码: 

(1)

array = array + 1;

 结果错误。原因:array 是数组名,是编译器为 array 数组创建的指针常量,常量不能赋值。

(2)

int* parray = array;
parray = parray + 1;

结果正确。原因:parray 是指针变量,初始值是 array 数组的首地址,+1 修改了 parray 的值,也改变了指针变量 parray 的指向。

1.3 指针与多维数组 

数组在内存中使用的是连续的存储区域。以二维数组 array[2][3] 为例,这 6 个数据在内存中按照Z形排列。如:尝试打印二维数组的每个元素的地址。

#include<stdio.h.>
int main()
{
	int i = 0;
	int j = 0;
	int array[2][3] = { {1,2,3},{3,4,5} };
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("array[%d][%d]的地址为;> %p\n", i, j, &array[i][j]);
		}
	}
	return 0;
}

 通过结果我们可以分析到,其实二维数组在内存中也是连续存储的。在内存中的存储形式如下:

也可如下: 

 二维数组 array[2][3],我们可以理解成数组是 array[2],有两个元素,每个元素类型为 int[3]。意思就是把第一行看成第一个元素,第二行看成第二个元素。每一行是一个一维数组,每一个一维数组有 3 个int类型的元素。可以把二维数组想象成一维数组的数组(二维数组array[2][3] --> 一维数组 array[2] 有两个元素 --> 每个元素是一个一维数组,该一维数组有三个元素)。

多维数组的数组名也被编译器创建为常量,用来存储数组的首地址。二维数组数组名,表示首元素的地址(即第一行的地址)。如:数组名 array 存储的是 array[0] 的地址,也就是存储的值是&array[0](第一行的地址,指的是数组下标为 0 的那一行的首地址)。

array[2][3] 是一个两行三列的二维数组,把二维数组数 array 想象成一维数组,每一行作为一个元素。如:第一行是第一个元素,第二行是第二个元素,依次类推;每一行(每一个元素)又是一个一维数组,每一个一维数组有三个元素;二维数组的首元素是二维数组的第一行:array[0];array 相当于第一行的地址,是一个一维数组的地址;可以数组指针来接收。(array[0]==array)

多维数组也可按照首地址加偏移量的形式,从而使指针指向多维数组中的任何一个元素,实现多维数组和指针的关联。

为了便于理解,以数组 array[2][3] 为例子,下列将逐步的进行分析:

array[1][1]    /*指的是数组 array 第一行第一列的元素*/
&array[1][1]    /*指的是 array[1][1] 元素的地址*/

 ②

array    /*指的是数组下标为 0 的那一行的首地址*/
array+1    /*指的是数组下标为 1 的那一行的首地址*/
&array[1]    /*指的是数组下标为 1 的那一行的首地址*/

 ③

array[1]    /*等价 array[1]+0,指的是 array[1][0] 元素的地址*/
array[1]+1    /*指的是 array[1][1] 元素的地址*/
*(array+1)+1    /*指的是 array[1][1] 元素的地址*/

 ④

*(*(array+1)+1)    /*指的是元素 array[1][1] 的值*/
*(array[1]+1)    /*指的是元素 array[1][1] 的值*/

例:使用指针访问二维数组。

#include<stdio.h>
int main()
{
	int array[2][3] = { {1,2,3},{4,5,6} };
	int i = 0;
	int j = 0;
	int* parray = NULL;
	parray = array;/*指针变量 parray 存储首元素(第一行)的地址*/
	printf("使用下标访问数组元素\n");
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("array[%d][%d] = %d\n", i, j, array[i][j]);
		}
	}
	printf("使用数组名访问数组元素\n");
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("array[%d][%d] = %d\n", i, j,*(*(array + i) + j));
		}
	}
	printf("使用指针名访问数组元素\n");
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
		{
			/*数组存储地址的计算:二维数组A[M][N],假设起始下标从0开始(首地址),
			按行存储 (总共有M行,N列):A [i] [j]=A + (i*N+j)*L。(L是数据类型的字节大小)*/
			printf("array[%d][%d] = %d\n", i, j, *(parray + i*3 + j));
		}
	}
	return 0;
}

 分析:第 1 个for 循环使用下标形式直接访问了二维数组元素;第 2 个 for 循环使用了数组名的地址形式间接访问了二维数组元素;第 3 个 for 循环使用指针访问了二维数组元素,因为 parray 是一个指针变量,它的初值是数组的首地址,因此通过改变偏移量,可达到指针 parray 指向不同地址的目的,从而实现输出数组元寨的目的。

首先指针 parray 指向数组 array 的首地址就是 array [0] 的地址,因为数组 array 是由 2 行 3 列组成的,所以 3*i+j 表示第 i 行第 j 列元素对应的下标,*(parray+3*i+j)就是数组 array[il[j]。

1.4 指针与数组参数 

指针作为参数传递给函数,用到的指针分两种情况,一种是指针变量,另一种是数组指针。下列对指针与数组参数进行分析。

一维数组传参

(1)实参是数组,形参也是数组。 

主调函数:

int array[10]={ 0 };
function(array);

被调函数:

function(int array[])
{
    //everything
}

(2)实参是数组,形参是指针。

主调函数:

int array[10]={ 0 };
function(array);

被调函数:

function(int* parray)
{
    //everything
}

(3)实参是指针,形参也是指针。

主调函数:

int array[10]={ 0 };
int* parray = array;
function(parray);

被调函数:

function(int* parray)
{
    //everything
}

(4)实参是指针,形参是数组。

主调函数:

int array[10]={ 0 };
int* parray=array;
function(parray);

被调函数:

function(int array[])
{
    //everything
}

(5)指针数组传参

#include <stdio.h>

void test1(int *array[5])//指针数组接收指针数组没有问题
{
    //everything
}

void test2(int**array)//二级指针接收一级指针没有问题
{
    //everything
}

int main()
{
    int* array[5]={ 0 };//指针数组 -- 存放指针的数组
    test1(array);
    test2(array);
    return 0;
}

 二维数组传参

#include<stdio.h>

void test1(int arr[3][5])
{
    //everything
}
void test2(int arr[][5])/*二维数组传参,函数形参的设计只能省略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素(也就是列数)。*/
{
    //everything
}
void test3(int (*arr)[5])/*传递的 arr,其实相当于第一行的地址,是一维数组的地址,用数组指针接收*/
{
    //everything
}
int main()
{
    //二维数组
    int arr[3][5] = {0};
    test1(arr);
    test2(arr);
    test3(arr);
    return 0;
}

例:统计字符

串中从 a 到 z 的26个小写字母各自出现的次数。 

#include<stdio.h>

void test(char* pchar,int arr[])
{
	int i = 0;
	while (*pchar)
	{
		if (*pchar >= 'a' && *pchar <= 'z')/*数组元素判断*/
		{
			/* *pchar 与字母 a 的差值刚好就是数组 arr 下标,得知数组下标就可以访问数组的元素,
			对数组元素值进行++,最后各个元素值得大小表示各个字母出现的次数。
            如:arr[0]等于多少就表示字母 a 出现的次数*/
			arr[*pchar - 'a']++;
		}
		pchar++;
	}
}

int main()
{
	char s[] = "abcdefgabcdeabc";
	int array[26] = { 0 };
	int i = 0;
	puts(s);/*打印字符串*/
	test(s, array);
	for (i = 0; i < 26; i++)/*打印 26 个字母分别出现的次数*/
	{
		printf("%d ", array[i]);
	}
	return 0;
}

 分析:本例子中实参和形参的类型是需要注意的,另外很巧妙的是使用两个字母的ASCII值的差值作为数组元素的下标,语句 arr[*pchar-'a'];实现了此功能。

1.5 指针数组 

指针数组是数组还是指针呢?指针数组是数组,是存放指针(地址)的数组。指针数组是指数组是由指针(地址)类型的元素组成。

//指针数组 --> 是数组 --> 用来存放指针(地址)的。

int array[5] = { 0 };//整型数组
char ch[5] = { 0 };//字符数组
int* parray[5];//存放整型指针的数组 -- 指针数组
char* pch[5];//存放字符指针的数组 -- 指针数组

指针数组是如何存放指针(地址)的?,如:

#include<stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	int e = 50;
	int* parray[5] = { &a,&b,&c,&d,&e }; /* 将a、b、c、d、e的地址存放到了指针数组 parray */
	int i = 0;
	for (i = 0; i < 5; i++)/*打印出a、b、c、d、e的值*/
	{
		printf("%d ", *(parray[i]));/* parray[0] 是一个指针变量,存储着 a 的地址,依次类推*/
	}
	return 0;
}

通过上例子,知道了指针数组的数组元素是用来存放指针(地址)的,这里就不对结果进行打印。

 如果指针数组的用法是如上例那种用法的话,大可不必多此一举。接下来看看指针数组的用法,如:

#include<stdio.h>

int main()
{
	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[] = { arr1,arr2,arr3 };/* 将arr1、arr2、arr3的首地址存放入指针数组parr中 */
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			/* parr[i]拿到第 i 个数组的首地址, + j意思是拿到第 i 个数组的首地址后对该地址进行运算,可找到该数组各个元素的地址;如: parr[0] 拿到的是数组 arr1 的首地址,parr[0]+j,找到数组 arr1 第 j 元素的地址,对地址进行解引用操作,访问该地址指向的存储空间的值(内容)*/
			printf("%d ", *(parr[i] + j));
			
		}
		printf("\n");
	}
	return 0;
}

 分析:将三个整型数组的首地址放入整型指针数组内,parray[i] 是一个指针变量,存储着 各个数组 的地址。分析图如下。

1.6 数组指针

 数组指针是指针还是数组呢? 答案是:指针。数组指针 - 指向数组的指针 - 存放数组的地址。

整型指针: int * pint; 能够指向整型数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。
数组指针:是指向数组的指针。
数组指针:int (*parray)[5];
解释:parray 先和 * 结合,说明 parray 是一个指针变量,然后指着指向的是一个大小为 5 的整型数组。所以 parray 是一个指针,指向一个数组,叫数组指针。这里要注意:[ ]的优先级要高于 * 号的,所以必须加上()来保证 parray 先和 * 结合。  

 数组指针是如何定义的?如何存放数组的地址呢?如下例子:

/* 数组指针 - 指向数组的指针 - 存放数组的地址(存放整个数组的的地址)。*/
#include<stdio.h>
int main()
{
	int array[5] = { 0 };
	int(*parray)[5] = &array;/* &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。*/
	char* ch[5] = { 0 };
	char* (*pch)[5] = &ch;
	return 0;
}

数组指针书写分析:如下图。

看一个简单例子,理解数组指针的使用: 

#include<stdio.h>
int main()
{
	int array[5] = { 1,2,3,4,5 };
	int(*parray)[5] = &array;//把整个数组的地址给了parray
	int i = 0;

	/*第一种写法*/
	//for (i = 0; i < 5; i++)
	//{
	//	//parray 是数组的地址,*parray(解引用)拿到了一维数组的数组名(数组的首元素地址),首地址 (*parray)[i](方块i)是什么意思呢?意思是:数组名 + 下标 i ,就可以访问数组的元素。也可以理解为:访问的是以 parray 为起始地址,数组下标为 i 的元素
	//	printf("%d ", (*parray)[i]);
	//}

	/*第二种写法*/
	for (i = 0; i < 5; i++)
	{
		// *parray == array; array数组名-->首元素地址,对首元素地址进行运算找到各个元素的地址
		printf("%d ",*(*parray + i));
	}
	return 0;
}

 分析:使用上面两种方法都可打印出数组 array 的元素。第一种写法:parray 是数组的地址,*parray(解引用)拿到了一个数组的数组名,数组名加下标就可访问数组的元素。第二种写法:*parray == array;  array 数组名 --> 数组名就是首元素地址,对首元素地址进行运算找到数组的各个元素的地址。

上例是简单的理解数组指针的使用,一般情况下数组指针的使用,至少是二维数组以上,才方便一些:如:

#include<stdio.h>
//参数是数组的形式
void print1(int arr[3][5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
//参数是指针的形式
void print2(int (*parray)[5],int row,int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)    /*四种打印方法*/
		{
			/*/  parray 是指向第一行的地址,parray+i 是跳过 i 行的地址,*(parray + i)(解引用)
             拿到这一行的数组名(首地址),找到这一行了,要找到这一行的某个元素,
			*(parray + i)+j 找到第 i 行第 j 列元素的地址  /*/
			printf("%d ",*(*(parray + i)+j));
			/*/ parray+i,找到第 i 行的地址,*(parray + i)(解引用)拿到第 i 行的数组名(第 i 行的首元素地址),
			访问的是以 parray+i 为起始地址,下标为 j 的元素 /*/
			printf("%d ", (*(parray + i))[j]);
			/*/ *(parray + i) 等价于 parray[i] /*/
			printf("%d ", *(parray[i] + j));
			/*/ *(parray + i) 等价于 parray[i]。*(parray[i] + j) 等价于 parray[i][j];/*/
			printf("%d ", parray[i][j]);
		}
		printf("\n");
	}
}
int main()
{
    //二维数组
	int array[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print1(array, 3, 5);//数组传参,数组接收
	//二维数组数组名 array,表示首元素的地址,把 array 想象成一维数组,第一行是第一个元素,第二行是第二个元素,依次类推。每一行又是一个一维数组
	//所以二维数组的首元素是二维数组的第一行:array[0];
	//所以这里传递的 array,其实相当于第一行的地址,是一个一维数组的地址
	//可以数组指针来接收
	print2(array, 3, 5);//数组指针的使用
	return 0;
}

分析:对于 “ printf("%d ", (*(parray + i))[j]); parray+i,找到第 i 行的地址,解引用拿到这一行的地址,访问的是以 parray+i 为起始地址,下标为 j 的元素 ”,为什么可以如此写呢?下面来看一个一维数组的例子:

#include<stdio.h>
int main()
{
	int array[5] = { 1,2,3,4,5 };
	int* parray = array;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d \n", *(parray + i));
		printf("%d \n", *(array + i));
		printf("%d \n", array[i]);//array[i] == *(array+i) == *(parray+i) == parray[i]
		printf("%d \n", parray[i]);//首地址 parray[i](方块i)是什么意思呢?意思是:访问的是以 parray 为起始地址,下标为i的元素
	}
	return 0;
}

分析:array[i] == *(array+i) == *(parray+i) == parray[i];首地址 parray[i](方块i)是什么意思呢?意思是:访问的是以 parray 为起始地址,下标为 i 的元素。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值