C语言指针

目录

1.指针与地址

2.指针变量

3.参数传递

4.指针与数组
  4.1指向一维数组的指针
  4.2数组名作为函数参数小秘密
  4.3二维数组的地址
  4.4数组指针

5.函数指针

6.指针数组

7.指针函数

8.二级指针

1.指针与地址

在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的 一个字节 称为 一个内存单元不同的数据类型所占用的内存单元数不等。为了正确地访问这些内存单元,为每个内存单元编上号,根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址

  前面埋过一个坑,变量有四个要素:变量类型,变量名,变量值,存储单元。前面三个已经解释过了,只剩下了存储单元没解释,在定义变量时,程序会申请一块内存单元,那么该内存单元会获得一个编号,也就是地址。
  既然如此,我们可以通过两种方式得到变量的值一个是通过地址,另一个就是用变量名。
例如:

#include <stdio.h>

int main()
{
	int a = 10;
	printf("变量名访问a的值为%d\n",a);
	printf("a的地址为%p\n",&a);
	printf("地址访问a的值为%d\n",*(&a));
}

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

&是取地址符号前面讲过,这里的 “ * ” 是取值运算符(这里不是乘号,在两个数中间才是乘号)作用是将把后面的地址里的数据“取出来”,利用星号“ * ”通过指针,得到内存中的数据称为解引用。 C语言里面地址就是指针,在上面的例子中a的地址(&a)就是指针

总之,学习指针前,你就记住地址就是指针,指针就是地址。

2.指针变量

在前面学习过程中,遇到整型变量,字符变量,它们用来保存整型数的变量和字符型的变量。那么指针变量就是用保存地址(指针)的变量,指针变量的定义格式如下:
  类型 * 指针变量名;

说明:
类型 : 是指指针变量所指单元中存放数据的类型,如int ,float, char等。
星号“ * ”:向系统说明定义的是指针变量,这里的 * 不是运算符。

可以通过指针来获取变量的值,例如:

#include <stdio.h>

int main()
{
	int data = 10;
	int *addr = &data;
	printf("变量名访问data的值为%d\n",data);
	printf("data的地址为%p\n",&data);
	printf("地址访问data的值为%d\n",*(&data));
	printf("指针访问变量data的值为%d\n",*addr);
}

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


为指针变量赋值(让其指向某一地址,也称为指针变量初始化)时,需要注意一下情况:

   1.定义指针时赋值格式为 类型 * 指针名 = &变量名;一定不要忘记取地址符号“&”,指针变量只能存放地址。

  2.定义指针变量,没有初始化,后面赋值时,指针名不要加“ * ”,也不要忘记“&”。基本样式为指针名 = &变量名;

示例:

int main()
{
	int data = 10;
	int *addr;
	addr = &data;
	return 0;
}

  3.指针变量没有指向明确指向地址时,不要直接解引用赋值。

示例:

#include "stdio.h"

int main()
{
        int *addr;
        *addr = 8;//解引用
        printf("%d\n",*addr);
        return 0;
}

运行结果:
在这里插入图片描述
并没有打印出结果8,对指针变量初始化时,赋予的不是地址和上面这种情况,此时指针称为野指针。野指针的一个特点,就是在指针初始化时没有指向明确的地址

  4.指针类型要与变量类型要一致。如果不一致,后面通过指针取值时,值会不一样。

示例:

#include <stdio.h>

int main()
{
	int data = 0x1234;
	int *addr1 = &data;
	char *addr2 = &data;
	
	puts("int *类型:");
	printf("地址:%p\n",addr1);
	printf("data = %d\n",*addr1);
	printf("\n");
	puts("char *类型:");
	printf("地址:%p\n",addr2);
	printf("data = %d\n",*addr2);
	
	return 0;
}

编译时,会弹出下面这样的警告:
在这里插入图片描述
这是类型不匹配,不用管它,继续运行程序。

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

最后的运行结果,char和int型指针保存的地址都是一样的,但通过取值运算符得到的变量data的值不一样。是因为取值运算符 * 会根据指针类型访问不同类型空间(int型占四个字节,访问四个字节,char型占一个字节,访问一个字节),所以使用指针变量保存变量地址时,指针类型和变量类型要一致。

3.参数传递

例如:封装一个函数,实现交换两个变量的值

#include <stdio.h>

void ChangeData(int a,int b)
{
	int temp;
	printf("传入时:a=%d,b=%d\n",a,b);
	temp = a;
	a = b;
	b = temp;
	printf("经过交换后:a=%d,b=%d\n",a,b);
}
int main()
{
	int a,b;
	puts("请输入两个整数:");
	scanf("%d%d",&a,&b);
	printf("main函数里交换前:%d,%d\n",a,b);
	printf("\n");
	puts("ChangeData函数里:");
	ChangeData(a,b);
	printf("\n");
	printf("main函数里交换后:%d,%d\n",a,b);
	return 0;
}

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

上面程序运行结果,发现调用时ChangeData函数里面两个整数的值已经交换了,但是回到main函数里面a和b的值还是没有交换。这是为什么了?

是因为调用函数ChangeData后面传递的是实参值,而函数ChangeData形参只是接受到实参的值。会另外形参分配内存单元保存实参的值,交换也是交换形参的值。而main函数里面的实参内存单元,在定义变量结束时就已经分配完成。对不同存储单元的a,b进行操作当然不可能成功。

这种实参传递方式称为值传递,主调函数调用会给形参分配内存单元,此时形参内存单元保存的是实参复制给形参的值,这时改变形参值不会影响到实参的值,是因为修改的是另一个存储单元的值,在调用结束后形参的存储单元就被释放

那么怎样才能使它们交换成功了?接着看:
代码:

#include <stdio.h>

void ChangeData(int *a,int *b)
{
	int temp;
	printf("传入时:a=%d,b=%d\n",*a,*b);
	temp = *a;
	*a = *b;
	*b = temp;
	printf("经过交换后:a=%d,b=%d\n",*a,*b);
}
int main()
{
	int a,b;
	puts("请输入两个整数:");
	scanf("%d%d",&a,&b);
	printf("main函数里交换前:%d,%d\n",a,b);
	printf("\n");
	puts("ChangeData函数里:");
	ChangeData(&a,&b);
	printf("\n");
	printf("main函数里交换后:%d,%d\n",a,b);
	return 0;
}

运行结果:
在这里插入图片描述
这时候两个数的值,经过函数调用成功交换!此时实参传递的内容是a和b的地址。也就是说此时形参接收到的是实参的存储单元,在函数ChangeData里是对同一个存储单元的‘a’和‘b’进行操作。

这种传参数递方式称为“地址传递”,主调函数调用时也会给形参分配一块内存单元,只不过时此这块内存单元保存的是实参复制给形参的地址。此时可以形参指针就可修改这块内存的内容,从而达到影响实参的值。

4.指针与数组

4.1指向一维数组的指针

先来看一个示例:
代码:

#include <stdio.h>

int main()
{
	int nums[4] = {0,1,2,3};
	printf("数组地址:%p\n",nums);
	printf("数组首元素地址:%p\n",&nums[0]);
	return 0;
}

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

为什么输出数组地址可以不用取地址符号“&”?并且数组地址和数组首元素地址一摸一样?

C语言规定,数组名就代表了该数组的首地址,而且是该数组首元素地址。在前面数组一章中提到数组元素地址是连续的,也就说整个数组是以首地址开头往后开辟一块连续的内存单元

在上面代码中,数组nums[4]内存示意图如下:
在这里插入图片描述

数组 nums的首地址为 0x61FE00,首元素nums[0]地址也为 0x61FE00。则数组名 nums 就代表这个首地址,因此在 nums 前面不能再加地址运算符&。

通过前面学习我们知道数组元素的访问方式是用数组名 [下标]进行访问。我们也可以通过指针变量进行访问。利用指针变量遍历整个数组,示例:

#include <stdio.h>

int main()
{
	int nums[4] = {0,1,2,3};
	int *addr = nums;
	for(int i = 0;i < 4;i++)
	{
		printf("第%d个元素为:%d\n",i,*addr++);
	}
	return 0;
}

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

指针变量addr不是在只保存了一个数组首地址元素吗?为什么使addr自增以后,经过取值运算符会是下一个数组元素?我们可以将它每次自增的地址打印出来,示例:
代码:

#include <stdio.h>

int main()
{
	int nums[4] = {0,1,2,3};
	int *addr = nums;
	for(int i = 0;i < 4;i++)
	{
		printf("第%d个元素地址:%p\n",i,addr++);
	}
	return 0;
}

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

通过上面程序运行,可以发现指针变量每次自增,指向的地址和数组的地址一样,而且都是增加四个字节。我们将指针类型切换成字符型看一下地址的变化,示例:

#include <stdio.h>

int main()
{
	int nums[4] = {0,1,2,3};
	char *addr = nums;
	for(int i = 0;i < 4;i++)
	{
		printf("第%d个元素地址:%p\n",i,addr++);
	}
	return 0;
}

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

通过两次程序运行结果对比,可以看出,指针变量每次的自增不是“单纯的加一”,是根据指针类型符所占字节大小往后进行偏移(前提是指针变量必须明确指向地址)。


见怪不怪的写法:

1.将指针变量名当数组名使用,示例:

#include <stdio.h>

int main()
{
	int array[4] = {4,11,35,98};
	int *addr = array;
	for(int i = 0;i < 4;i++)
	{
		printf("第%d个元素:%d,地址为:%p\n",i,addr[i],&addr[i]);
	}
	return 0;
}


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

注意:此时指针变量名当作数组名,打印数组里面元素值不用“ * ”,取地址时要用“&”。

2.将指针名或数组名利用“+”进行地址偏移,实现访问数组。示例:

#include <stdio.h>

int main()
{
	int array[4] = {4,11,35,98};
	int *addr = array;
	puts("数组名:");
	for(int i = 0;i < 4;i++)
	{
		printf("第%d个元素:%d,地址为:%p\n",i,*(array+i),array+i);
	}
	puts("\n指针名:");
	for(int j = 0;j < 4;j++)
	{
		printf("第%d个元素:%d,地址为:%p\n",j,*(addr+j),addr+j);
	}
	return 0;
}

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

此情况也是对地址进行偏移,加1等于该地址往后偏移1次,加2等于该地址往后偏移2次,以此类推。
注意:数组名不能自增或自减,因为数组名是指针常量,常量没有自增。

4.2数组名作为函数参数小秘密

来看一下究竟有什么小秘密,封装一个函数,将数组里面元素打印出来,示例:

#include <stdio.h>

void PrintfArry(int nums[])
{
	int size = sizeof(nums)/sizeof(nums[0]);
	for(int i = 0;i<size;i++)
	{
		printf("%d\n",nums[i]);
	}
}
int main()
{
	int arry[] = {1,2,3,4,5,6,7};
	PrintfArry(arry);
	return 0;
}

中间会弹出下面这个警告,不用理会。
在这里插入图片描述

运行结果:
在这里插入图片描述
为什么最后只打印了数组前两个元素,后面的没有答应,难道是因为形参有问题吗?可以先将形参所占字节数打印出来,示例:

#include <stdio.h>

void PrintfArry(int nums[])
{
	printf("形参nums所占字节数:%d\n",sizeof(nums));
	/*int size = sizeof(nums)/sizeof(nums[0]);
	for(int i = 0;i<size;i++)
	{
		printf("%d\n",nums[i]);
	}*/
}
int main()
{
	int arry[] = {1,2,3,4,5,6,7};
	PrintfArry(arry);
	return 0;
}

运行结果:
在这里插入图片描述
明明是传递的一个数组arry,里面有7个元素,一共是28个字节数才对啊。为什么形参只有8个字节数?是因为中括号里面没有写具体的元素个数吗?试一下:

#include <stdio.h>

void PrintfArry(int nums[7])
{
	printf("形参nums所占字节数:%d\n",sizeof(nums));
	/*int size = sizeof(nums)/sizeof(nums[0]);
	for(int i = 0;i<size;i++)
	{
		printf("%d\n",nums[i]);
	}*/
}
int main()
{
	int arry[] = {1,2,3,4,5,6,7};
	PrintfArry(arry);
	return 0;
}

在这里插入图片描述

结合几次程序运行,可以得出,数组作为函数参数时,传递的并不是整个数组。并且传递过后,形参所占字节数为8。事实上,数组名作为函数参数传递,传递的是数组首地址,这“8个字节”的是在“windows”操作系统下,一个地址所占字节数为8,不会根据根据形参数组里面有多少个元素而该变。

既然数组作为函数参数时,传递的是地址,那么我们以后将形参里面的“数组”写成指针形式,这样以后就不会弹出上面类型不匹配的警告,为了方便对数组的操作也要把数组长度也传递过去,示例:

#include <stdio.h>

void PrintfArry(int *nums,int numsSize)
{
	for(int i = 0;i < numsSize;i++)
	{
		printf("%d\n",nums[i]);
	}
}
int main()
{
	int arry[] = {1,2,3,4,5,6,7};
	int size = sizeof(arry)/sizeof(arry[0]);
	PrintfArry(arry,size);
	return 0;
}

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

4.3二维数组地址


定义以下二维数组:int a [3][4];,二维数组a由3个元素组成:a[0],a[1],a[2],每个元素a[i]各包含4个元素,分别为:a [i] [0],a [i][1], a[i] [2], a[i][3],如下图所示:
在这里插入图片描述

对于二维数组int a[3][4]有:

1.a — 二维数组首地址,即第0行首地址,也是该数组第一个元素。

示例:

#include <stdio.h>

int main()
{
	int a[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
	printf("数组名a:%p\n第一个元素地址:%p",a,&a[0][0]);
	return 0;
}

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

2.a+i — i行的首地址,也是 i 行第一个元素的首地址。

示例:

#include <stdio.h>

int main()
{
	int a[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
	for(int i = 0;i < 3;i++)
	{
		printf("a[%d]:%p\n第%d行首元素地址:%p\n\n",i,a+i,i,&a[i][0]);
	}
	return 0;
}

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

仔细观察程序运行结果可以发现,每一行首元素地址相差16个字节,而一行里面有个四个元素,每个元素都是整数型,一个整型数数据占四个字节。二维数组名使用加运算符和减运算符进行运算时,是进行地址偏移,每次地址偏移的大小是一行元素所占的字节大小。

3.a[i] —是i行第0列元素地址,等价于*(a+i)和&a[i]。(这是二维数组,不能把&a[i]理解为元素 a[i]的地址,不存在元素 a[i]。)

示例:

#include <stdio.h>

int main()
{
	int a[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
	for(int i = 0;i < 3;i++)
	{
		printf("a[%d]:%p\n&a[%d]:%p\n第%d行0列元素地址:%p\n\n",i,a[i],i,&a[i],i,&a[i][0]);
	}
	return 0;
}

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

4.a[i]+j—第i行j列元素地址,等价于*(a+i)+j

示例:

#include <stdio.h>

int main()
{
	int a[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
	for(int i = 0;i < 3;i++)
	{
		for(int j = 0;j < 4;j++)
		{
			printf("a[%d]+%d:%p\n",i,j,a[i]+j);
			printf("第%d行%d列的地址:%p\n\n",i,j,&a[i][j]);
		}
	}
	return 0;
}

运行结果:
在这里插入图片描述
上面案例,可以分析一下。把这个二维数组看成一维数组,里面的元素是a[0],a[1],a[2]。把这些元素使用加运算和减运算符进行运算,每次地址的偏移大小是a[i]里面单个元素的所占字节。

5.取二维数组里面元素值有三种方法:*(a[i] + j) <=> *( *(a+i)+j) <=>a[i][j]

#include <stdio.h>

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

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

4.4数组指针


前面我们定义了一个指针变量指向一维数组。假如定义一个指针变量指向二维数组会怎么样了?我们来看一下:
示例:

#include <stdio.h>

int main()
{
	int a[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
	int *p = a;
	for(int i = 0;i < 12;i++)
	{
		printf("%d\n",*p++);
	}
	return 0;
}

运行时肯定又有个警告:
在这里插入图片描述
运行结果:
在这里插入图片描述
在程序编译时会弹出一个警告,也是类型不匹配。。而且提醒我们使用“int (*)[4]”类型,也就是使用数组指针。不能直接定义“int ( * )[4]”这样的数组指针,标准定义格式为:

    类型符 (* 指针变量名) [数组长度]

说明:1.“类型符”要与数组类型一致
   2.“* 指针变量名”一定要在括号里面,星号后面的变量是定义的指针变量。
   3.这里的“数组长度”指的是二维数组第二维长度

示例:

#include <stdio.h>

int main()
{
	int a[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
	int *p[4] = a;
	for(int i = 0;i < 3;i++)
	{
		for(int j = 0;j < 4;j++)
		{
			printf("%d	",*(p[i]+j));
		}
		printf("\n");
	}
	return 0;
}

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

注意:数组指针中括号里面的长度要和指向的二维数组第二维长度一致!如果不用指针数组,用指针变量指向二维数组地址,然后用指针取值时,不能用二维数组三种取值方法!

错误示例:

#include <stdio.h>

int main()
{
	int a[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
	int *p = a;
	for(int i = 0;i < 3;i++)
	{
		for(int j = 0;j < 4;j++)
		{
			printf("%d	",*(p[i]+j));
			printf("%d	",p[i][j]);
			printf("%d	",*(*(p+i)+j));
		}
		printf("\n");
	}
	return 0;
}

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

5.函数指针


在《指针与数组》小节中,我们知道了数组是一块连续的内区存,数组名就是数组的首地址,并且可以通过指针进行访问。同样的函数也是一块连续的内区存,函数名就是该函数的首地址,也可以通过指针进行访问。函数指针变量定义的一般形式为:
   类型说明符 ( * 指针变量名) ();

说明:1.“类型说明符”表示被指函数的返回值的类型
   2.“()”空括号表示指针变量所指的是一个函数

利用指针形式实现对函数调用,示例:
有两个整数a和b,由用户输人1,2或3。如输入1,程序就给出a和b中大者,输入2,就给出a和b中小者,输入3,则求a与b之和。
代码:

#include <stdio.h>

int GetMax(int x,int y)
{
	return x>y ? x:y;
}

int GetMin(int x,int y)
{
	return x<y ? x:y;
}

int GetAdd(int x,int y)
{
	return x + y;
}
int main()
{
	int a = 5,b = 8;
	int cmd,ret;
	int (*p)(int a,int b);
	puts("提示:1-取最大值,2-取最小值,3-求和");
	puts("请输入一个数:");
	scanf("%d",&cmd);
	switch(cmd){
		case 1:
			p = GetMax;
		break;
		
		case 2:
			p = GetMin;
		break;
		
		case 3:
			p = GetAdd;
		break;
		
		default:
			puts("输入错误");
	}
	ret = (*p)(a,b);
	printf("ret = %d\n",ret);
	
	return 0;
}

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

从上述程序可以看出用,函数指针变量形式调用函数的步骤如下:
  1.先定义函数指针变量,上面程序中,定义 p为函数指针变量。
  2.把被调函数的入口地址(函数名)赋予该函数指针变量,本案例是让用户选择其中一个函数作为函数指针的值。
  3.函数的一般形式为:
     (*指针变量名) (实参表)
补充:
  定义函数指针时,后面的空括号里。可以写被指向的函数形参列表,参数名不影响程序运行,可以与任何变量名一样。也可以不用参数名,直接写参数类型但一旦写了,为函数指针赋值时一定要和被指向的函数的形参列表一致(参数数量一致,参数类型一致,顺序一致)。

错误示例:

#include <stdio.h>

int Add(int x,char y)
{
	return x + y;
}

int main()
{
	int (*p)(int);
	int (*p1)(char,int);
	int (*p2)(int,int);
	
	p = Add;//数量不一致
	p1 = Add;//顺序不一致
	p2 = Add;//参数类型不一致
		
	return 0;
}

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

6.指针数组


前面学习过数组指针,我们在中间加一个“的”字,就是数组的指针,该数据类型是一个指针,指向的地址为数组首地址。同样的本小节介绍的指针数组,中间加一个“的”字,就是指针的数组,数据类型是数组类型,里面的元素都为指值类型。指针数组的定义格式为:
  类型符 * 数组名 [ 数组长度 ]

说明:类型符要与数组里面元素类型一致。
注意:*和数组名不要括号括起来,否则就是数组指针。

示例:

#include <stdio.h>

int main()
{
	int a = 1,b = 2,c = 3,d = 4;
	int *p[4] = {&a,&b,&c,&d};
	for(int i = 0;i < 4;i++)
	{
		printf("%d\n",*p[i]);
	}
	return 0;
}

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

补充:指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。

可以定义一个函数指针数组用来储存多个函数指针,将函数指针里面第一个例子修改一下,示例:

#include <stdio.h>

int GetMax(int x,int y)
{
	return x>y ? x:y;
}

int GetMin(int x,int y)
{
	return x<y ? x:y;
}

int GetAdd(int x,int y)
{
	return x + y;
}

int main()
{
	int a = 3,b = 4;
	int (*p[3])() = {   //函数指针数组
		GetMax,
		GetMin,
		GetAdd,
	};
	for(int i = 0;i < 3;i++)
	{
		printf("%d\n",(*p[i])(a,b));
	}
	return 0;
}

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

7.指针函数


之前在《函数》一章说到,在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数。定义格式为:
   函数返回类型 * 函数名(形参表)
  {
     //函数体;
   }

其中函数名之前加了“*”号表明这是一个指针型函数,返回值是一个指针。

示例: 有a个学生,每个学生有b门课程的成绩。要求在用户输人学生序号以后,能输出该学生的全部成绩。用指针函数来实现。

代码:

#include <stdio.h>

int *GetPosPerson(int pos,int (*pstu)[4])//实参参数里有一个二维数组,形参要用数组指针。
{
	int *p = (int *)(pstu+pos);//需要转换类型
	return p;
}

int main()
{
	int socres[3][4] = {
		{45,96,32,83},
		{30,59,66,81},
		{72,88,46,75},
	};
	int *poss;
	int pos;
	puts("请输入你需要查看的学习成绩:0,1,2");
	scanf("%d",&pos);
	poss = GetPosPerson(pos,socres);
	for(int i = 0;i < 4;i++)
		printf("%d	",poss[i]);
	
	return 0;
}

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

8.二级指针


如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量,也称为二级指针。定义格式为:
  类型符 **指针变量名

说明:只是比指针变量多了一个“ * ”。
示例:可以看一下用一级指针来保存指针变量的地址
代码:

#include <stdio.h>

int main()
{
	int data = 10;
	int *addr = &data;
	int *addr_1=&addr;
	
	printf("data的地址:%p\n",&data);
	printf("addr保存的地址:%p,内容:%d\n",addr,*addr);
	printf("addr的地址:%p\n",&addr);
	printf("addr_1保存的地址:%p,内容:%p",addr_1,*addr_1);
	
	return 0;
}

编译时会弹出下面这个警告,不用管它:
在这里插入图片描述
运行结果:
在这里插入图片描述

对指针变量addr_1用取值运算符不能发现,取出的内容是addr保存的地址,也就是data的地址。如果,我想得到data的值是不是可以在addr_1用两个取值运算符就可以了?来试一试

代码:

#include <stdio.h>

int main()
{
	int data = 10;
	int *addr = &data;
	int *addr_1=&addr;
	
	printf("利用addr_1的data的值:%d\n",**addr_1);
	return 0;
}

运行结果:
在这里插入图片描述
这个错误不能不管了,提醒我们用二级指针,用二级指针看一下程序运行情况。
代码:

#include <stdio.h>

int main()
{
	int data = 10;
	int *addr = &data;
	int **addr_1=&addr;
	
	printf("data的地址:%p\n",&data);
	printf("addr保存的地址:%p,内容:%d\n\n",addr,*addr);
	printf("addr的地址:%p\n",&addr);
	printf("addr_1保存的地址:%p,*addr_1:%p,**addr_1:%d",addr_1,*addr_1,**addr_1);
	
	return 0;
}

运行结果:
在这里插入图片描述
为什么要使用用二级指针呢?通过函数调用修改变量a,b的值,我们需要传递a和b的地址。那通过函数调用,修改指针变量的值,是不是也需要传递这个指针变量的地址。形参不能用一级指针去接受实参一级指针的地址值。

可以将函数指针的例子修改一下。
错误示例:

#include <stdio.h>

void GetPosPerson(int pos,int (*pstu)[4],int *poss)//不用指针函数,定义一个普通函数
{
	poss = (int *)(pstu+pos);//修改指针变量poss
	
}

int main()
{
	int socres[3][4] = {
		{45,96,32,83},
		{30,59,66,81},
		{72,88,46,75},
	};
	int *poss = NULL;
	int pos;
	puts("请输入你需要查看的学生成绩:0,1,2");
	scanf("%d",&pos);
	GetPosPerson(pos,socres,&poss);
	for(int i = 0;i < 4;i++)
		printf("%d	",*poss++);
	
	return 0;
}

编译时会弹出下面这个警告,继续运行。
在这里插入图片描述

运行结果:
在这里插入图片描述
可以看到并没有打印出学生的成绩,而且指针变量poss没有指向明确的地址,是个野指针,程序运行时产生了一个段错误,编译时还弹出警告。

正确示例:
代码:

#include <stdio.h>

void GetPosPerson(int pos,int (*pstu)[4],int **poss)//使用二级指针
{
	*poss = (int *)(pstu+pos);
	
}

int main()
{
	int socres[3][4] = {
		{45,96,32,83},
		{30,59,66,81},
		{72,88,46,75},
	};
	int *poss = NULL;
	int pos;
	puts("请输入你需要查看的学生成绩:0,1,2");
	scanf("%d",&pos);
	GetPosPerson(pos,socres,&poss);
	for(int i = 0;i < 4;i++)
		printf("%d	",*poss++);
	
	return 0;
}

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值