C--五、指针

什么是指针?

指针 == 地址

什么是指针变量

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

C语言中访问变量的方式

  1. 变量名(直接访问)
  2. 地址(间接访问:即将变量i的地址存放在另一变量中,然后通过该变量来找到变量i的地址,从而访问i变量。)

访问的方式可以与生活中类比,比如我们找一个餐馆,如果知道它的名字,可以上地图上去查。或者我们知道它在某个区某个街道某某号我们也可以找到这个餐馆。

&与*运算符

&的用法

取地址运算符

*的用法
  1. 取值运算符
  2. 作为标识符,在声明指针变量时,起标识作用
int main()
{
	int a = 10;
	int *p;//此处的*是一个标识符,表明p是一个指针变量
	p = &a;
	
	printf("a的地址:%p\n",&a);
	printf("a的值:%d\n",*(&a));//此处的*表示取值运算符,把后面所跟地址中的数据取出来
	printf("p的地址:%p\n",&p);
	printf("p存放的地址(即a的地址):%p\n",p);
	printf("p存放的地址中的内容(即a的内容):%d",*p);	
}

既然指针变量存放的是地址,为什么要区分类型?

指针类型决定了指向空间的内存大小,也决定了增量

int main()
{
	int a = 0x1234;
	int *p = &a;
	char *q = &a;
	
	//p,q中存放的地址是相同的
	printf("p = %p\n",p);
	printf("q = %p\n\n",q);
	
	//由于指针类型不同导致了p,q指向内存空间的大小不同
	printf("*p = %x\n",*p);	
	printf("*q = %x\n\n",*q);
	
	//由于指针类型不同p,q指向内存空间的增量不同
	printf("++p = %p\n",++p);	
	printf("++q = %p",++q);
	
}

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

注意:p,q所占内存空间大小是相同的。

为什么要用指针?

封装一个函数,对两个数进行交换。

void changeData1(int data1,int data2){
	//1.错误的,交换了两个局部变量的值
	int temp;
	temp = data1;
	data1 = data2;
	data2 = temp;
}

void changeData(int *data1,int *data2){
	
	//2.正确的,直接通过指针修改值 
	int temp;
	temp = *data1;
	*data1 = *data2;
	*data2 = temp;
	
	//3.错误的 交换了两个局部变量的地址
	/*int *temp;
	temp = data1;
	data1 = data2;
	data2 = temp;*/
}

int main(){
	int data1 = 10;
	int data2 = 20;
	
	changeData(&data1,&data2);	
	
	printf("data1 = %d\n",data1);
	printf("data2 = %d\n",data2);
	return 0;
}

通过指针引用数组

int main (){
	int arr[3]= { 1,2,3};
	int *p;
	
	//数组的首地址就是首个元素的地址&arr[0];
	//数组名就是数组的首地址
	p = arr[0];
	printf(""首元素是:%d\n" ,*p);
	return o;
}

指针增量与数组的关系:

在这里插入图片描述

访问数组元素的方式
  1. 下标法
  2. 指针法
    1. 先偏移
    2. 然后取内容

问题一:我们可以使用数组名+i来进行偏移,也可以使用指针存储数组名的方式来偏移。那么
数组名和指针的区别在哪呢?

  1. 指针是存储地址的变量,而数组名可以理解为一个指针常量。也就是说数组名本身的值是不能改变的,数组名不能做自增,自减操作
  2. 使用sizeof关键字时,数组名和存放了数组名地址的指针大小不同。

问题二:两种访问数组元素的方式谁的效率更高?

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

指针和二维数组

父子数组是二维数组中特有的说法。
二维数组本质上还是数组,与一维数组不同的是其数组元素是也是一个数组。

那么a,a[0],a[0][0],*a,*a[0]

二维数组

数组指针

声明:
dataType (* pointerName)[Length];
(),[]运算符优先级都是1,并且结合方向是从左到右的,以使用运算符结合性优先级来理解,p先与()内的指针标识符*结合,故而其是一个指针。

#include <stdio.h>

int main()
{
	int arr[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
	//此处会出现warning 因为数据类型不同,p是int ,与arr增量的跨度不同
	int *p = arr;
	
	//能不能定义一个指针让其偏移量与arr相同?
	//数组指针,定义一个指针指向一个数组
	//数组指针才真正等同于二维数组名
	int (*p1)[4] = arr;
	
	int i,j;
	for(i = 0;i < 3;i++){
		for(j = 0;j < 4;j++){
			//下列方式也可以成功输出,但这相当于一维数组的方式
			//等价于3*4个元素的一维数组
			//printf("%d\n",*p++);

			printf("%d\n",*(*(p1+i)+j));
		}
	}
	
	return 0;
}

函数指针

声明:
returnType (*pointerName)(param list);

函数地址

如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针。

与数组名类似,数组名是地址,函数名也是地址

#include <stdio.h>

void demo1(){
	printf("%d\n",10); 
}

int demo2(int data){
	return data;
}

int main()
{
	void (*p)() = demo1;
	int (*p1)(int) = demo2;
	
	(*p)();
	printf("%d",(*p1)(20));
	return 0;
}

函数回调案例:

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

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

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

int gSum(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 data1 = 10;
	int data2 = 20;
	int cmd;
	int ret;
	int (*pfunc)(int,int);
	
	puts("输入指令:1(取最大值),2(取最小值),3(求和)");
	scanf("%d",&cmd);
	switch(cmd){
		case 1:
			pfunc = gMax;
			break;
		case 2:
			pfunc = gMin;
			break;
		case 3:
			pfunc = gSum;
			break;
		default:
			puts("输入错误!@1(取最大值),2(取最小值),3(求和)");
			exit(-1);
	}
	
	printf("结果为:%d\n",dataHandler(data1,data2,pfunc));
	
	return 0;
}

指针数组

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

#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	int size;
	
	int *arr[] = {&a,&b,&c,&d};
	int i;
	size = sizeof(arr)/sizeof(int *);
	for(i = 0;i < size;i++){
		//内层的*:取出指针数组中的元素
		//外层的*:取出元素中的值
		printf("%d\n",*(*(arr+i)));
	}
	return 0;
}

注意指针数组与数组指针的区别。

指针函数

一个函数可以返回–个整型值、字符值、实型值等,也可以返回指针型的数据,即地址。其概念与以前类似,只是返回的值的类型是指针类型而已。
例如“int * a(int x,int y);”,a是函数名,调用它以后能得到一个int型(指向整型数据)的指针,即整型数据的地址。x和y是函数a的形参,为整型。
请注意在
a两侧没有括号,在a的两侧分别为* 运算符和()运算符。而()优先级高于* ,因此a先与()结合,显然这是函数形式。这个函数前面有一个* ,表示此函数是指针型函数(函数值是指针)。最前面的int表示返回的指针指向整型变量。

#include <stdio.h>
#include <stdlib.h>
#define row 3
#define column 4

//使用malloc申请空间的指针p
//对指针p进行赋值
//sizeof(p) 的值为一个指针实际所占字节数
//不是申请时的大小

/*
	a个学生,b科成绩
	找出有不及格课程的学生及其学生号
	用指针函数实现
*/
void printPos(int *arr){
	int size = 2;
	int i;
	for(i = 0;i < size;i++){
		printf("%d	",*arr++);
	}
}

int *findFailStuNum(int (*p)[column]){
	int *posStu = (int *)malloc(sizeof(int) * row);
	//int temp[row] = {};
	int index = 0;
	
	int i,j;
	for(i = 0;i < row;i++){
		for(j = 0;j < column;j++){
			if(*(*(p + i)+j) < 60){
				*(posStu + index++) = i;
				break;
			}
		}
	}
	
	return posStu;
}

int main()
{
	int arr[row][column] = {{11,22,33,44},{99,66,77,88},{33,44,55,100}};
	int *posStu;
	
	posStu = findFailStuNum(arr);
	puts("不及格学生序号为:");
	printPos(posStu);
	free(posStu);
	
	return 0;
}

二级(多级)指针

dataType ** pointerName;
int **p;
与一级指针几乎一致,不同的是二级指针保存的是一级指针的地址。

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

/*
	a个学生,b科成绩
	输入学生序号,打印成绩
	用二级指针实现
*/
void getPos(int pos,int (*p)[4],int **posStu)
{
	*posStu = *(p+pos);
}

void printPos(int len,int *arr){
	int i;
	for(i = 0;i < len;i++){
		printf("%d	",*(arr+i));
	}
}

int main()
{
	int arr[3][4] = {{11,22,33,44},{55,66,77,88},{33,44,55,100}};
	int pos;
	int row;
	int column;
	int *posStu;
	
	row = sizeof(arr)/sizeof(*arr);
	column = sizeof(*arr)/sizeof(int);
	
	puts("输入学生序号(1,2,3):");
	scanf("%d",&pos);
	
	if(pos > row || pos <= 0){
		printf("输入学生序号不存在\n");
		exit(-1);
	}
	
	getPos(pos,arr,&posStu);
	printPos(column,posStu);
	
	return 0;
}

注意:二级指针和二维数组没有什么必然联系,二级指针不能直接指向二维数组。

总结

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值