C语言 -- 指针

本文详细介绍了C语言中的指针基础知识,包括指针的概念、指针变量、指针与数组、函数指针、指针数组和多级指针的使用。通过实例展示了如何通过指针进行数据交换、数组初始化、函数封装以及处理二维数组。强调了指针在函数参数传递和数组操作中的重要性,并探讨了指针在提高代码效率和灵活性方面的优势。
摘要由CSDN通过智能技术生成

提示:本文仅是对个人学习经历的一个记录 

前言:

作为一个小白,关于我的学习经历,我想在此做一个记录。

本文关于C语言指针部分内容。

PS:目前作者在学指针部分的内容,所以代码是基指针部分知识编写的。


1、指针

1.1、指针==地址

例如:int   a = 10(类型,变量名,内存地址,值)

访问变量的两种方式:

  • 变量名能访问
  • 通过地址也能访问 

& 取地址运算符        * 将地址内的值读出运算符

#include <stdio.h>

int main()
{
	int a = 10;
	
	printf("a = %d\n",a);    //输出a的值
	printf("a的地址是:%p\n",&a);    //输出a的地址
	printf("a = %d\n",*(&a));    //*取值运算符,把它后面跟的内存地址中的数据"取出来"
	
	return 0;
}

结果:

1.2、指针变量==存放地址的变量

1.2.1、定义一个指针变量(* 的标识作用)

  • 例如:int *p,这里的*是一个标识符,告诉系统我是一个指针变量,用来保存别人的地址
  • * 的标识作用,只产生在指针变量定义或声明的时候

1.2.2、使用一个指针变量(* 的运算作用)

  •         printf("地址访问:a = %d\n",*(&a));
  •         printf("指针变量访问:a = %d\n",*p);
#include <stdio.h>

int main()
{
	//什么是整型变量,存放整型数的变量
	//什么是字符变量,存放字符型数据的变量
	//什么是指针变量,存放指针的变量
	//什么是指针变量,存放地址的变量
	
	int a = 10;
	int *p;	//这里的*是一个标识符,告诉系统我是一个指针变量,用来保存别人的地址,与下面的运算符不同
	
	p = &a;
	
	printf("变量名访问:a = %d\n",a);    //输出a的值
	printf("a的地址是:0x%p\n",&a);    //输出a的地址
	printf("地址访问:a = %d\n",*(&a));    //*取值运算符,把它后面跟的内存地址中的数据"取出来"
	printf("指针变量访问:a = %d\n",*p);
	
	return 0;
}

结果:

1.3、指针变量区分类型

思考:既然指针变量是存放别人地址的变量,那什么要区分类型呢?

类型决定指向空间的大小,决定增量

#include <stdio.h>

int main()
{
	int a = 0x1234;
	int *p = &a;
	char *c = &a;
	
	printf("address:p = %p\n",p);
	printf("address:c = %p\n",c);
	
	putchar('\n');
	
	printf("pointer_p: a = %x\n",*p);
	printf("pointer_c: a = %x\n",*c);//取值的时候出了问题,取值运算符会根据
	                                 //指针变量类型访问不同大小的空间
	putchar('\n');
	
	printf("address:++p = %p\n",++p);//增量不同
	printf("address:++c = %p\n",++c);
	
	return 0;
}

结果:


2、为什么需要用指针

函数封装时使用。 

 练习1、封装一个函数,实现两个数的交换。

#include <stdio.h>

void changeData(int *data1,int *data2)
{
	int tmp;
	
	tmp = *data1;
	*data1 = *data2;
	*data2 = tmp;
}

int main()
{
	int data = 10;
	int data2 = 20;
	
	printf("交换前:data=%d | data2=%d\n",data,data2);
	
	changeData(&data,&data2);
	
	printf("交换后:data=%d | data2=%d\n",data,data2);
	
	return 0;
}

结果:


练习2、指针指向固定的区域

#include <stdio.h>

int main()
{
	int a = 10;
	printf("address of a is 0x%p\n",&a);
	volatile unsigned int  *p = (volatile unsigned int *)0x000000000061FE66;
	printf("p = 0x%p",p);
	
	return 0;
}

结果:


2.1、示例

例一、输入三个数a,b,c; 要求不管怎么输入,在输出的时候,a,b,c就是由大到小的顺序输出,用函数封装实现。

#include <stdio.h>

void changeData(int *data1,int *data2,int *data3)
{
	int tmp;
	
	if( *data1<*data2){
		tmp = *data1;
		*data1 = *data2;
		*data2 = tmp;
	}if( *data1<*data3){
		tmp = *data1;
		*data1 = *data3;
		*data3 = tmp;
	}if( *data2<*data3){
		tmp = *data2;
		*data2 = *data3;
		*data3 = tmp;
	}
}

void initDtat(int *data1,int *data2,int *data3)
{
	puts("请输入三个整数:");
	scanf("%d%d%d",data1,data2,data3);
}

int main()
{
	int a;
	int b;
	int c;
	
	initDtat(&a,&b,&c);
	
	puts("交换前:");
	printf("a = %d | b = %d | c =%d\n",a,b,c);
	
	changeData(&a,&b,&c);
	
	puts("交换后:");
	printf("a = %d | b = %d | c =%d\n",a,b,c);
	
	return 0;
}

结果:


3、通过指针引用数组

3.1、定义一个指针变量指向数组

//可以用一个指针变量指向一个数组元素。
//例如:

int a[10] = {1,3,5,7,9,11,13,15,17,19};    //定义a为包含10个整型数据的数组

int *p;    //定义p为指向整型变量的指针变量

p = &a[0];    //把a[0]元素的地址赋给指针变量p

//指针指向数组首元素的地址

在C语言中,数组名(不包括形参数组名,形参数组并不占据实际的内存单元)代表数组中首元素(即序

号为0的元素)的地址。因此,下面两个语句等价:

  • p = &a[0];        //p的值是a[0]的地址
  • p = a;        //p的值是数组a首元素(即 a[0])的地址

3.2、指针增量和数组的关系

#include <stdio.h>

int main()
{
	int arry[3] = {1,2,3};
	int *p;
	
	//p = &arry[0];//数组的首地址就是首个元素的地址
	p = arry;//数组名就是数组的首地址
	
	for( int i = 0;i<3;i++){
		printf("第%d元素是:%d |address:0x%p\n",i,*(p+i),(p+i));
	}
	/*
	printf("0元素是:%d\n",*p);
	printf("1元素是:%d\n",*(p+1));//p+1地址偏移
	printf("2元素是:%d\n",*(p+2));
	*/
	
	return 0;
}

结果:


3.3、通过指针引用数组元素

3.3.1、数组访问方法:

  • 下标法
  • 指针法

3.3.2、指针法 

1、偏移

#include <stdio.h>

int main()
{
	int arry[3] = {1,2,3};
	int *p;
	
	p = arry;
	
	for( int i = 0;i<3;i++){
		printf("%d ",*p++);//优先级,先*p在p++
	}
	putchar('\n');
	p = arry;//指针回调
	for( int i = 0;i<3;i++){//再次访问时
		printf("%d ",*p++);//优先级,先*p在p++
	}
	putchar('\n');
	
	return 0;
}

//	for( int i = 0,j = 0;i<3;i++)//i和j都是整形

结果:


2、取内容 

  • 见怪不怪:指针当作数组名,下标法访问
  • 见怪不怪:数组名拿来加
  • 数组和指针变量的大小
#include <stdio.h>

int main()
{
	int arry[3] = {1,2,3};
	int *p = arry;
	
	printf("size of arry is:%d\n",sizeof(arry));//3*4=12
	printf("size of   p  is:%d\n",sizeof(p));//os(操作系统),用8个字节来表示一个地址
	printf("size of int  is:%d\n",sizeof(int));
	printf("size of pointer is:%d\n",sizeof(int*));
	printf("size of pointer is:%d\n",sizeof(char*));
	
	printf("%d\n",*p);//指针访问
	printf("%d\n",p[2]);//指针当作数组名,下标访问
	printf("%d\n",*arry);
	
	for( int i = 0;i<3;i++){
		printf("%d ",p[i]);//指针当作数组名,下标访问
	}
	putchar('\n');
	
	for( int i = 0;i<3;i++){
		printf("%d ",*(p+i));//指针访问
	}
	putchar('\n');
	
	for( int i = 0;i<3;i++){
		printf("%d ",*(arry+i));//数组名拿来加
	}
	putchar('\n');
	
	for( int i = 0;i<3;i++){
		printf("%d ",*p++);//指针偏移访问
	}
	p = arry;//指针回调
	putchar('\n');
/*	
	for( int i = 0;i<3;i++){
		printf("%d ",*arry++);//编译不过,指针常量,arry就是arry[0],不能改变
	}
	putchar('\n');
*/	
	return 0;
}

结果:


3、数组名和指针区别

  • 指针常量,arry 是定死的,不能改变(数组名)
  • 指针变量 ,p 是变量,可以改变(指针)

3.3.3、两种方法效率对比

对于使用指针和数组下标的选择:

  • 系统在使用数组下标对数组成员变量进行访问时,开销比较大,指针的访问效率是远远大于数组名的访问效率的。但是只有在指针正确访问时,才成比下标法更有效率。
  • 下标法更加容易理解,在可读性方面,也更加的具有优势,具体怎么选择,也没有一定的说法。

3.4、实例练习

例1、函数封装数组初始化,遍历

#include <stdio.h>

void initArry(int *parr,int size)
{
	int i;
	for( i=0;i<size;i++){
		printf("请输入第%d个数据:\n",i+1);
		scanf("%d",parr++);
	}
	putchar('\n');
}

void printArry(int *parr,int size)
{
	int i;
	for( i=0;i<size;i++){
		printf("%d ",*parr++);
	}
	putchar('\n');
}

int main()
{
	int arry[5];
	int size = sizeof(arry)/sizeof(arry[0]);
	
	initArry(arry,size);//实际参数,数组的首地址: 名,首个元素的地址
	printArry(arry,size);
	
	return 0;
}

结果:


例2、将数组中的n个元素按逆序存放

#include <stdio.h>

void revangeArry(int *parr,int size)
{
	int i;
	int j;
	int tmp;
	
	for( i=0;i<size/2;i++){
		j = size-1-i;
		tmp = *(parr+i);
		*(parr+i) = *(parr+j);
		*(parr+j) = tmp;
	}
}

void initArry(int *parr,int size)
{
	int i;
	for( i=0;i<size;i++){
		printf("请输入第%d个数据:\n",i+1);
		scanf("%d",parr++);
	}
	putchar('\n');
}

void printArry(int *parr,int size)
{
	int i;
	for( i=0;i<size;i++){
		printf("%d ",*parr++);
	}
	putchar('\n');
}

int main()    //逆序输出
{
	int arry[5];
	int size = sizeof(arry)/sizeof(arry[0]);
	
	initArry(arry,size);
	printArry(arry,size);
	revangeArry(arry,size);
	printArry(arry,size);
	
	return 0;
}

结果:


4、指针和二维数组

补充:段错误检测方法:

gcc demo.c -g

gdb a.exe

r q y

4.1、多维数组元素的地址

为了说清楚指向多维数组元素的指针,先回顾一下多维数组的性质,以二维数组为例。设有一个二维

数组a,它有3行4列。它的定义为

  • int a[3][4] = {{1,3,5,7}, {9,11,13,15}, {17,19,21,23}};

a是二维数组名。a数组包含3行,即3个行元素:a[0],a[1],a[2]。而每一个行元素又是一个一维数组,

它包含4个元素(即4个列元素)。例如,a[0]所代表的一维数组又包含4个元素:

  • a[0][0], a[0][1],  a[0][2],  a[0][3]。

可以认为二维数组是“数组的数组”,即二维数组a是由3个一维数组所组成的。二维数组本质还是数组,不同点是数组元素还是个数组。

a[0], a[1], a[2] 既然是一维数组名,而C语言又规定了数组名代表数组首元素地址,因此 a[0] 代表一维

数组a[0]中第0列元素的地址,即&a[0][0];也就是说 a[1] 的值是&a[1][0], a[2] 的值是&a[2][0]。

换种说法总结:

  • a表示父数组的地址,(a + 1),父数组(行)偏移一位,偏移了一个子数组
  • a[0],*a表示子数组的地址(名),(a[0] +1),子数组(列)偏移一位
  • a表示父数组地址,它存储的是子数组的地址,*a表示子数组地址
#include <stdio.h>

int main()
{
	int arry[3][3] = {{1,2,3},
	                  {4,5,6},
					  {7,8,9}};
	
	printf("arr是父数组地址:%p,偏移1后是:%p\n",arry,arry+1);
	printf("arr[0]是子数组地址:%p,偏移1后是:%p\n",arry[0],arry[0]+1);
	printf("arr[0]是子数组地址:%p,偏移1后是:%p\n",*(arry+0),*(arry+0)+1);
	
	return 0;
}

结果:

4.2、认知加深

  • 前已述及,a[0] 和 *(a+0) 等价,a[1] 和 *(a+1) 等价,a[i] 和 *( a+i) 等价。因此,a[0]+1 和 *(a+0)+1都是 &a[0][1] 。a[1]+2和*( a+1)+2 的值都是&a[1][2]。请注意不要将* (a+1)+2错写成*(a+1+2),后者变成*(a+3)了,相当于a[3]。
  • 进一步分析,欲得到a[0][1]的值,用地址法怎么表示呢?既然a[0]+1和*(a+0)+1是a[0][1]的地址,那么,*(a[0]+1)就是a[0][1]的值。同理,*( *(a十0)+1)或*( * a十1)也是a[0][1]的值。* (a[ i ]+j)或* ( *(a+i)+j)是 a[ i ][ j ]的值。务请记住* (a+i)和 a[ i ]是等价的。
  • 有些读者可能不理解,为什么a+1和*(a十1)等价呢?他们想,a+1(是地址)和* (a+1)(是内容)怎么都是同一个值呢?的确,二维数组中有些概念比较复杂难懂﹐要仔细消化,反复思考。
     

4.3、总结

#include <stdio.h>

int main()
{
	int arr[3][4] = {{11,12,13,14},
	                 {21,22,23,24},
					 {31,32,33,34}};
	int i;
	int j;
	
	for( i=0;i<3;i++){
		for( j=0;j<4;j++){
			printf("add:0x%p | data:%d \n",&arr[i][j],arr[i][j]);
			printf("add:0x%p | data:%d \n",arr[i]+j,*(arr[i]+j));
			printf("add:0x%p | data:%d \n",*(arr+i)+j,*(*(arr+i)+j));
			printf("========================================\n");
		}
		putchar('\n');
	}
	
	return 0;
}

结果:

 


5、数组指针

数组指针是指向数组的指针,也称为指向数组的指针变量。它是一种特殊的指针类型,可以用来表示数组的地址,通过数组指针可以访问数组的元素。

数组指针的定义方式和普通指针类似,只不过需要在类型说明符和指针符号 ' * ' 之间加上一对方括号 '[]' 表示这是一个指向数组的指针,例如:

int (*p)[3];  // 定义指向具有 3 个元素的 int 数组的指针

上述定义中的 'p' 是一个指向具有 3 个元素的 'int' 数组的指针,可以使用指针访问数组的元素,例如:

int a[2][3] = {{1,2,3}, {4,5,6}};
p = a;  // 将指针指向数组的首元素
printf("%d\n", (*p)[0]);  // 输出数组的第一个元素 1
printf("%d\n", p[1][2]);  // 输出数组的第五个元素 6

在上述示例中,先将数组 'a' 的首元素的地址赋给 'p',然后使用指针访问数组中的元素,可以使用 '(*p)[i]' 或 'p[i][j]' 的形式进行访问。需要注意的是,由于数组指针和数组名等价,所以也可以使用数组名进行访问,例如 '*a' 相当于 '(*p)'。

#include <stdio.h>

int main()
{
	int arr[3][4] = {{11,12,13,14},
	                 {21,22,23,24},
					 {31,32,33,34}};
	int i;
	int j;
	int *p;
	
//	p = &arr[0][0];
	//p = arr;
	//能不能定义一个指针,让指针偏移的时候,也偏移对应大小的数组?
	//数组指针,定义一个指针,指向一个数组!
	//数组指针才是真正等同于二维数组
	
	int (*p2)[4];    //指向一个含有4个元素的数组
	
	p2 = arr;
	
	printf("p2=%p\n",p2);
	printf("++p2=%p\n",++p2);
	
	for( i=0;i<3;i++){
		for( j=0;j<4;j++){
			//printf("%d\n",*p++);
		}
	}
	
	return 0;
}

结果:

 


例1、输出二维数组任意行列的数

#include <stdio.h>

void tipsInput(int *ph,int *pl)
{
	printf("请输入要取值的行列数:\n");
	scanf("%d%d",ph,pl);
	puts("done\n");
}

int getTheData(int (*p)[4],int hang,int lie)
{
	int data;
	
	data = *(*(p+hang)+lie);
	
	return data;
	//return p[hang][lie];
}

int main()
{
	int arr[3][4] = {{11,12,13,14},
	                 {21,22,23,24},
					 {31,32,33,34}};
	int hang,lie;
	int data;
	
	//1、提示输入行列
	tipsInput(&hang,&lie);
	//2、找出对应的数
	data = getTheData(arr,hang-1,lie-1);
	//3、输出结果
	printf("第%d行%d列的值是:%d\n",hang,lie,data);
	
	return 0;
}

结果:

 


6、函数指针

6.1、定义

如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起

始地址(又称入口地址)称为这个函数的指针。

  • 函数名就是地址

6.2、如何定义一个函数指针变量

函数指针变量是指向函数的指针,它可以存储函数的地址,以便在程序中通过指针调用相应的函数。定义函数指针变量的方式如下:

返回类型 (*函数指针变量名)(参数列表)

其中,`返回类型` 是所指向函数的返回值类型;`函数指针变量名` 是定义的函数指针变量的名称;`参数列表` 是所指向函数的参数列表类型。例如,以下代码定义了一个函数指针变量 `pfun`,它指向一个无参数无返回值的函数:

void (*pfun)();

上述代码中,`pfun` 是一个指向无参数无返回值 `void` 类型函数的函数指针变量。

定义好函数指针变量后,就可以将其指向相应的函数,以便通过指针调用该函数。假设有以下函数定义:

void fun() {
    printf("Hello, World!\n");
}

则可以使用以下代码将 `pfun` 指向 `fun` 函数,并通过 `pfun` 指针调用该函数:

pfun = fun;      // 将函数指针指向函数
(*pfun)();       // 通过指针调用函数

在上述代码中,首先将函数指针 `pfun` 指向 `fun` 函数,然后通过 `(*pfun)()` 的形式调用该函数。也可以直接使用 `pfun()` 的形式调用函数,因为函数指针在调用时会自动转换成相应的函数调用语句。

6.3、使用函数指针

#include <stdio.h>

int inCData(int data1)
{
	return ++data1;
}

void putWelcome()
{
	puts("欢迎来到我的世界!!");
}

int main()
{
	void (*p)();//定义一个函数指针变量
	int (*p2)(int data1);
	int data;
	
	p = putWelcome;//指向函数
	p2 = inCData;
	
	putWelcome();//函数名调用(直接)
	(*p)();//函数指针调用(间接)
	
	data = inCData(10);
	(*p2)(10);
	
	printf("p2测试:%d\n",data);
	printf("p2测试:%d\n",(*p2)(10));
	
	return 0;
}

结果:

 


6.4、好用之处

  • 根据程序运行过程的不同情况,调用不同的函数

例1、有两个整数a和 b,由用户输入1,2或3。如输人1,程序就给出a和b中大者,输入2,就给出a和b中小者,输入3,则求a与b之和。

#include <stdio.h>
#include <stdlib.h>

int getMax(int data1,int data2)
{
	return data1>data2 ? data1:data2;
}

int getMin(int data1,int data2)
{
	return data1<data2 ? data1:data2;
}

int getSum(int data1,int data2)
{
	return data1+data2;
}

int dataHandler(int data1,int data2,int (*pfunc)(int,int))
{
	int ret;
	
	ret = (*pfunc)(data1,data2);
	
	return ret;
}

int main()
{
	int a = 3;
	int b = 4;
	int cmd;
	int ret;
	int (*pfunc)(int,int);
	
	puts("请输入1(求Max),2(求Min),3(求Sum):");
	scanf("%d",&cmd);
	
	switch(cmd){
		case 1:
			pfunc = getMax;
			break;
		case 2:
			pfunc = getMin;
			break;
		case 3:
			pfunc = getSum;
			break;
		default:
			puts("输入错误!\n@请输入1(求Max),2(求Min),3(求Sum):");
			exit(-1);    //输入错误时pfunc是野指针
	}
	
	ret = dataHandler(a,b,pfunc);
	printf("ret = %d\n",ret);
	
	return 0;
}

结果:

 


7、指针数组

7.1、定义

  • 它是数组,数组的每一项都是一个指针变量

指针数组是一个数组,其中的每个元素都是一个指针。也就是说,指针数组是一个保存多个指向不同对象的指针的数组,它的元素都是指针类型,每个指针指向一个独立的对象。

定义指针数组的语法为:

数据类型 * 数组名称[元素个数]

其中,`数据类型` 是指针指向的数据类型;`数组名称` 是指针数组的名称;`元素个数` 是指针数组中的元素个数。类型名中应包括符号“*”,如“int * ”表示是指向整型数据的指针类型。

以下是定义一个指针数组的示例:

int a = 1, b = 2, c = 3;
int *ptr[3] = {&a, &b, &c};

上述代码定义了一个名为 `ptr` 的指针数组,它由 3 个 `int` 类型的指针数组成。定义时使用了大括号 `{}` 可以初始化指针数组的元素,将 `a`、`b`、`c` 的地址赋值给了指针数组的元素。

由于[]比*优先级高,因此ptr先与[3]结合,形成ptr[3]形式,这显然是数组形式,表示ptr数组有3个元素。然后再与ptr前面的“*"结合,“*”表示此数组是指针类型的,每个数组元素(相当于一个指针变量)都可指向一个整型变量。

注意不要写成:
int (*ptr)[3];    //这是指向一维数组的指针变量

可以通过下标访问数组元素,例如可使用 `ptr[0]` 访问 `a` 的内存地址,使用 `ptr[1]` 访问 `b` 的内存地址。若要访问指针数组元素所指向的值,则需要使用指针解引用运算符 `*`,例如,使用 `*ptr[0]` 就可以访问 `a` 存储的值 `1`。

7.2、实例

  • 函数指针数组
#include <stdio.h>
#include <stdlib.h>

int getMax(int data1,int data2)
{
	return data1>data2 ? data1:data2;
}

int getMin(int data1,int data2)
{
	return data1<data2 ? data1:data2;
}

int getSum(int data1,int data2)
{
	return data1+data2;
}


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

结果:

 


8、指针函数

  • 返回指针值的函数

8.1、概念

一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址。其概念与以前类似,只是返回的值的类型是指针类型而已。

指针函数是返回指针类型的函数,它的返回值是一个指针,该指针指向内存中的某个地址。指针函数可以接受参数,也可以不接受参数。使用指针函数可以有效地减少内存的开销,节省代码的编写时间,并且具有灵活性高和可重用性强等优点,在软件工程中应用广泛。

例如“int * a(int x,int y);”,a是函数名,调用它以后能得到一个int*型(指向整型数据)的指针,即整型数据的地址。x和y是函数a的形参,为整型。

请注意在* a两侧没有括号,在a的两侧分别为*运算符和()运算符。而()优先级高于* ,因此a先与()结合,显然这是函数形式。这个函数前面有一个* ,表示此函数是指针型函数(函数值是指针)。最前面的int表示返回的指针指向整型变量。

定义指针函数的语法为:

返回类型 *函数名称(参数列表)

其中,`返回类型` 是指针指向的数据类型;`函数名称` 是指针函数的名称;`参数列表` 是指针函数的参数列表类型。

以下是一个简单的例子,它定义了一个指针函数 `getMax`,该函数接受两个整型参数 `a` 和 `b`,并返回它们中的较大值:

int* getMax(int a, int b) {
    if (a >= b) {
        return &a;
    } else {
        return &b;
    }
}

在上述代码中,`getMax` 函数返回一个指向整型变量的指针,该指针指向 `a` 或 `b` 中较大的那个数。

我们可以通过以下代码调用 `getMax` 函数来获取两个整型数的最大值:

int a = 10, b = 20;
int *ptr = getMax(a, b);
printf("The max number is %d\n", *ptr);

在上述代码中,将 `a` 和 `b` 作为参数传递给 `getMax` 函数,获取返回值(一个指向整型数的指针),然后使用指针解引用运算符 `*` 获取指针指向的值,即找到了两个数中的最大值。在输出语句中使用 `%d` 格式化字符来输出整型数。

8.2、实例练习

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

#include <stdio.h>

int* getPosPerson(int data1,int (*pstu)[4])//指针函数,返回指针的函数
{
	int *data;
	
	data = (int *)(pstu+data1);//父数组偏移//强转成指针类型
	
	return data;
}

int main()
{
	int scores[3][4] = {{1,2,3,11},
						{4,5,6,59},
						{7,8,9,99}};
	int *ppos;
	int pos;
	
	puts("请输入你需要查看的学生的学号:(0,1,2)");
	scanf("%d",&pos);
	
	ppos = getPosPerson(pos,scores);
	
	for( int i=0;i<4;i++){
		printf("%d ",*ppos++);
	}
	putchar('\n');
	
	return 0;
}

结果:

 


例2、对例1中的学生,找出其中有不及格的课程的学生及其学生号。

#include <stdio.h>

int* bujige(int (*p)[4])
{
	int* pt = NULL;//对于及格的同学返回空
	
	for( int i=0;i<4;i++){
		if( *(*p+i)<60){//比较子数组元素
			pt = *p;//把子数组赋给返回值
		}
	}
	
	return pt;//返回有不及格的同学的学号(子数组)
}

int main()
{
	int scores[3][4] = {{88,82,73,90},
						{89,79,69,60},
						{76,82,91,39}};
	int* bujige(int (*p)[4]);//定义一个指针函数,参数是数组指针
	int* p;
	int i,j;
	
	for( i=0;i<3;i++){
		p = bujige(scores+i);//传入子数组(子数组就是父数组的元素),返回值赋给指针p
		if( p!=NULL){//如果返回值不为空,说明这个同学有不及格的成绩
			printf("学号为%d的同学有成绩不合格!\n",i);//打印学号(子数组号)
			for( j=0;j<4;j++){
				printf("%d ",*(p+j));//打印成绩子数组元素(成绩)
			}
		}
	}
	
	return 0;
}

结果:

 


9、二级(多级)指针

  • 认知考虑的时候,其实所有东西跟一级指针一样,差别就是保存的是指针变量的地址。 

9.1、概念

二级指针是指一个指针变量的指针,也就是说,指向指针的指针。二级指针也被称为指针的指针,它在程序中的作用是可以访问或修改指针的值。

定义二级指针的语法为:

数据类型 ** 变量名称

其中,`数据类型` 表示所指向的指针指向的数据类型;`变量名称` 是定义的二级指针变量的名称。例如,以下是一个定义二级指针的示例:

int a = 10;
int *p = &a;
int **pp = &p;

在上述代码中,`p` 是一个指向整型数 `a` 的指针,`pp` 是一个指向指针 `p` 的指针,也就是一个二级指针。可以使用 `**pp` 访问 `a` 存储的值。例如:

printf("%d\n", **pp);  // 输出 a 存储的值 10

在上述代码中,使用 `**pp` 解析出指针 `p` 指向的值 `&a`,然后再次使用指针解引用运算符 `*`,从而得到存储在 `a` 中的值 `10`。

二级指针在二维数组、链表等数据结构中常常使用,能够更方便、高效地访问或修改数据结构中指针的值。但是,使用二级指针需要注意指针的内存分配和释放,以免出现内存泄漏等错误。

9.2、实例练习

#include <stdio.h>

int main()
{
	int data = 100;
	int *p = &data;
	
	printf("data的地址是:%p\n",&data);
	printf("p保存data的地址:%p |内容是:%d\n",p,*p);
	
	printf("p的地址是:%p\n\n",&p);
/*	
	int *pp = &p;
	printf("pp保存p的地址:%p\n",pp);
	printf("*pp是:%p\n",*pp);
*/	
	int **p2 = &p;
	printf("p2保存p的地址:%p\n",p2);
	printf("*p2是:%p\n",*p2);
	printf("**p2来访问data:%d\n",**p2);
	
	int ***p3 = &p2;
	printf("p3保存p2的地址:%p\n",p3);
	printf("*p3是:%p\n",*p);
	printf("***p3来访问data:%d\n",***p3);
	
	return 0;
}

结果:

 


  • 当你通过函数调用来修改调用函数指针指向的时候
  • 就像通过函数调用修改某变量的值的时候一样
#include <stdio.h>

void getPosPerson(int data1,int (*pstu)[4],int **ppos)
{
	*ppos = (int *)(pstu+data1);
}

int main()
{
	int scores[3][4] = {{1,2,3,11},
						{4,5,6,59},
						{7,8,9,99}};
	int *ppos;
	int pos;
	
	puts("请输入你需要查看的学生的学号:(0,1,2)");
	scanf("%d",&pos);
	
	getPosPerson(pos,scores,&ppos);
	
	for( int i=0;i<4;i++){
		printf("%d ",*ppos++);
	}
	putchar('\n');
	
	return 0;
}

结果:

 

9.3、二级指针不能简单粗暴指向二维数组

#include <stdio.h>

int main()
{
	int scores[3][4]={
		{1,2,3,11},
		{4,5,6,59},
		{7,8,9,99}
	};//int (*p)[4];
	
//	int a[]={1,2,3}
//	int *p = a;
/*	
	int **p;
	p = scores;
//	*p ?

	printf("scores:%p\n",scores);
	printf("p = %p\n",p);
	printf("*p = %p\n",*p);//*p是一个野指针,不是我们认为的会变成列地址
	printf("*scores = %p\n",*scores);//*scores是列地址
	
	**p = 100;//段错误
	printf("done\n");//不会执行24行
*/	
	int (*p2)[4] = scores;
	
	int **p3 = &p2;
	
	**p3 = 100;
	printf("%d\n",scores[0][0]);
	
	return 0;
}

结果:


10、总结

各种指针的定义:
1,一个整形数: int a;
2,一个指向整形数的指针: int *a;
3,一个指向指针的指针,它指向的指针指向一个整形数: int **a;

4,一个有10个整形数的数组: int a[10];
5,一个有10个指针的数组,每个指针指向一个整形数: int *a[10];

6,一个指向有10个整形数的数组的指针: int (*a)[10];
7,一个指向指针的指针,被指向的指针指向一个有10个整形数的数组: int (**a)[10];

8,一个指向数组的指针,该数组有10个整形指针: int *(*a)[10];
9,一个指向函数的指针,该函数有一个整形参数并返回一个整形数: int (*a)(int);
10,一个有10个指针的数组,每个指针指向一个函数,该函数有一个整形参数并返回一个整形数:       int (*a[10])(int);
11,一个函教的指针,指向的函数的类型是有两个整形参数并且返回一个函数指针的函数,返回的函数指针计指向有一个整形参数且返回整形数的函款: int (*(*a)(int,int)) (int);


总结:

以上就是今天要讲的内容,本文仅仅简单介绍了基于指针部分的知识。

如有错漏,望批评指正。让我们共同进步吧!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值