C语言指针基础用法讲解

前言

本篇文章旨在复习指针的基础用法,对于指针的原理并不涉及

用法讲解

前两个用法是常用的基础用法,最后一个用法是在嵌入式开法中的应用。此外,特别说明,本文介绍的知识基础用法,更高级的用法如:函数指针、二级指针、结构体指针等并不涉及。

传递参数

使用指针作为函数的参数,此时该参数称为实参。
指针传参两大特点:

  1. 执行效率高,不必再多拷贝一份数据
  2. 通过地址访问数据,主函数与子函数均可以访问数据

传递大容量参数

使用指针传递大容量的参数,主函数和子函数使用的是同一套数据, 避免了参数传递过程中的数据复制,提高了运行效率,减少了内存占用

如何理解普通传参与指针传参:
有一个学霸和一个学渣,两个人是关系很好的朋友,所以学渣的作业都是抄学霸的。
普通传参可以理解为:学霸担心直接把作业给学渣,学渣会破坏它的作业,所以就另外找一张纸把答案再抄一份给学渣,这就是普通传参,计算机会临时开辟内存空间存放参数,在调用函数时会将数据拷贝到这片内存空间中,真正的数据并没有被访问,子函数只是拿到了数据的拷贝,就像学渣并没有拿到学霸的作业,只是拿到了拷贝的版本。

对应代码展示如下:

#include <stdio.h>

void fun(int x)
{
    x = 1;//试图在子函数中修改变量的值
}

int main()
{
    int m;
    m = 2;//给m赋值
    printf("%d\n", m);//输出m的值,应为2
    fun(m);//调用fun函数
    printf("%d\n", m);//输出m的值,仍为2
    return 0;
}
//上述代码说明在子函数中修改变量的值并没有成功,原因是局部变量在子函数执行完后就会被销毁,所以当子函数执行完后,x就会被销毁


指针传参可以理解为:学渣想要抄学霸的作业,但是作业是一片作文,如果此时学霸再拷贝一份的话,会非常麻烦,所以学霸只好把作文的原本交给学渣,此时学渣拿到了学霸的作业,这就是指针传参,在调用函数时,子函数拿到了数据的地址,通过该地址可以直接访问到数据,就像学渣拿到了学霸的作业一样。

对应代码如下:

#include <stdio.h>

void fun(int *x)
{
    *x = 1;//在子函数中修改变量的值
}

int main()
{
    int m;
    m = 2;//给m赋值
    printf("%d\n", m);//输出m的值,应为2
    fun(&m);//调用fun函数
    printf("%d\n", m);//输出m的值,此时输出值为1
    return 0;
    //m的值被改变,原因是在子函数中通过m的地址访问到了m,故可以完成对m的修改
}

下面的代码为指针能够提高效率的体现:
在数据量非常大时,如果用形参传参,则会非常浪费内存,并且效率非常低,所以采用指针传参,直接访问数据,而不进行拷贝。

#include <stdio.h>

//找一个数组中的最大值
//数组的长度很长,那么如果再使用形参传参,效率很低
//使用指针传参
int FindMax(int* array)
{
	int max = 0;
	for (int i = 0; array[i] != 0; i++)
	{
		if (max < array[i])
		{
			max = array[i];
		}
	}
	return max;
}
int main()
{
	int max = 0;
	int num[] = { 12,23,34,45,65,78,97,56,53,5242,24,24,224,24,2,5,5,5,45,35,345,35,4,4,45,0 };//[]内可以不写,因为数组名就是存储数组首地址的指针
	//末尾用0作为结束标志
	printf("%d", FindMax(num));
	//关于数组和指针,两者虽然不能说完全相同,但在许多情况下都是等价的,可以互相混用
	//即elemtype array等价于elemtype array[]等价于elemtype *array,都是数组的**首地址**,elemtype代表某个数据结构的类型
	//那么从另一个角度来理解数组下标为什么要从零开始:array为首元素的地址,指向首元素
	//而(array+i)为指针向后偏移i位,此时指针指向第i-1个元素,即指向第i个元素应该偏移i-1位,那么指向首元素偏移0位
	//array[i]与(array+i)时等价的,也就是说:array[i]也是让指针偏移i-1位,所以数组下标从零开始
	//则上述子函数中的array[i]也可以写为array+i
}          

前面说到,学霸是因为担心学渣破坏他的作业才将作业拷贝一份的,那么此时将作业直接交给学渣,这种风险仍然存在,但此时学霸不希望学渣破坏他的作业。换言之,在用指针传参时,可以在子函数内修改数据,但是我们此时不希望数据被修改,因为会导致错误,有没有什么方法呢?

先来看数据会被修改的代码:

#include <stdio.h>


int FindMax(int* array)
{
	int max = 0;
	*(array + 9) = 1;
	for (int i = 0; array[i] != 0; i++)
	{
		if (max < array[i])
		{
			max = array[i];
		}
	}
	return max;
}
int main()
{
	int max = 0;
	int num[] = { 12,23,34,45,65,78,97,56,53,5242,24,24,224,24,2,5,5,5,45,35,345,35,4,4,45,0 };
	//末尾用0作为结束标志
	printf("%d ", FindMax(num));
	printf("%d", num[9]);
}          

执行结果如下图:

在这里插入图片描述
显然最大值被我们在子函数中修改为1

那么防止值被修改的方法为:
使用const进行修饰,被const修饰过的称为常量,如果此时再修改,将会报错。

对应代码如下:

#include <stdio.h>


int FindMax(const int* array)
{
	int max = 0;
	*(array + 9) = 1;
	for (int i = 0; array[i] != 0; i++)
	{
		if (max < array[i])
		{
			max = array[i];
		}
	}
	return max;
}
int main()
{
	int max = 0;
	int num[] = { 12,23,34,45,65,78,97,56,53,5242,24,24,224,24,2,5,5,5,45,35,345,35,4,4,45,0 };
	//末尾用0作为结束标志
	printf("%d ", FindMax(num));
	printf("%d", num[9]);
}          

执行结果为:

在这里插入图片描述
编译器会报错,警告我们不能修改数据

事实上,许多的库函数都会加上const修饰,为了确保数据不会被乱改:

在这里插入图片描述
在这里插入图片描述

传递输出参数

使用指针传递输出参数,利用主函数和子函数使用同一套数据的特性,实现数据的返回, 可实现多返回值函数的设计

由于指针传参的缘故,使子函数与主函数都可以访问数据,而在上面的例子中,我们不希望其被修改,但事物都是具有两面性的,其可以修改数据的作用在某些情况又是我们所需要的,比如说,使一个子函数有多个返回值,该返回值实际上就是子函数的参数

#include <stdio.h>

void FindMax(const int* array,int *count,int *max)
{
	for (int i = 0; array[i] != 0; i++)
	{
		if (*max < array[i])
		{
			*max = array[i];
			*count = 1;
		}
		else if (*(max) == array[i])
		{
			(*count)++;//这里要注意后置++的优先级高于*,而我们要使(*count),所以要加括号,不能写为*count++
		}
	}
}

int main()
{
	int max = 0;
	int cnt = 0;
	int num[] = {1,2,3,4,5,5,5,5,5,0};
	FindMax(num, &cnt,&max);
	printf("%d %d",max,cnt);
}

输出结果为:
在这里插入图片描述
显然通过指针我们做到了让函数可以返回两个值,实现了多返回值
传递输出参数的意思就是将子函数的参数作为返回值

传递返回值

将模块内的公有部分返回,让主函数持有模块的**“句柄”**,便于程序对指定对象的操作

句柄:可以将句柄理解为把手,只要握住了这个把手,就可以对其进行访问

通常我们会对某个代码块进行封装,而传递返回值常用来访问封装内的部分(注意:该部分必须是全局变量,因为局部变量在函数结束后会被销毁

对应代码如下:

#include <stdio.h>


/********************///封装的定时器
int Time[3] = { 1,2,3 };//全局变量

int* GetTime()
{
	return Time;//返回指向数组的指针
}
/********************/


int main()
{
	int* ptr;
	ptr = GetTime();//使用指针ptr接受GetTime返回的指向Time数组的指针
	for (int i = 0; i < 3; i++)
	{
		printf("%d\n", ptr[i]);
	}
}

运行结果如下:
在这里插入图片描述
成功访问到了Time数组的值

类似的操作还有C语言的文件操作,fopen等函数的返回值都是指针,此处不多赘述

直接访问物理地址下的数据

访问硬件指定内存下的数据

访问硬件指定内存下的数据,如设备ID号等

单片机的ID号,使用手册有详细介绍

对应代码如下:

#include <REGX52.H>
#include "LCD1602.h"

void main()
{
	unsigned char *p;
	
	LCD_Init();
	
	p=(unsigned char *)0xF1;//0xF1为整型数据,而p是无符号字符型指针,在C语言的语法中是不能跨级别或者跨类型赋值的,所以此处需要强制类型转换
	
	LCD_ShowHexNum(2,1,*p,2);
	
	while(1)
	{
		
	}
}

将复杂格式的数据转换为字节

复杂格式数据转换为字节,方便通信与存储
这一个功能是我们最常用的功能
我们想使用串口或无线模块向电脑发送数据,但是串口或无线模块发送数据只能以字节为单位,但是我们所发送的数据形式是多种多样的(整型、浮点型、字符型、结构型等等),所以就需要使用指针进行数据转换

转换方法:使用指针unsigned char *ptr
原理:数据都是以字节存储的,我们所看到的复杂数据只不过是对其按照一定的码制进行了编码,我们只需进行反编码即可,反编码也需要遵循一定的规则,而这个规则就是unsigned char *ptr,因为unsigned char恰好是一个字节,所以用其进行强制转换可以将数据转换为字节。换言之,我们将复杂数据看作是一个unsigned char类型的数据,然后再用一个指向该数组的指针就可以将数据转换为字节形式。体现在代码中就是将elemtype*的指针转换为unsigned char * 类型的指针,因为unsigned char 是一个字节一个字节的读取.反之我们接收到的以字节形式发送而来的数据,只要提前知道该数据的类型elemtype,再使用elemtype 进行强制转换(将unsigned char的指针转换为elemtype*类型的指针)*,即对发送来的数据进行编码,将数据还原。

对应代码如下:

#include <stdio.h>


/********************///封装的无线模块
unsigned char Data[20];//使用char Data[]为数据发送的地点或位置

void SendData(const unsigned char *data,unsigned char count)//data为待发送数据,待发送的数据不能被修改,所以要加const修饰
{
	unsigned char i;
	for (i = 0; i < count; i++)
	{
		Data[i] = data[i];//向char Data[]发送数据
	}
}


void ReceiveData(const unsigned char* data, unsigned char count)
{
	unsigned char i;
	for (i = 0; i < count; i++)
	{
		data[i] = Data[i];//接收发送在Data中的数据
	}
}

/********************/


int main()
{
	unsigned char i;

	unsigned char DataSend[] = { 0x12,0x34,0x56,0x78 };

	float num = 12.345;//待发送的复杂数据

	unsigned char* p;

	p = (unsigned char*)&num;//将float*的指针转换为unsigned char * 类型的指针,
	//因为unsigned char *是一个字节一个字节的读取

	SendData(DataSend, 4);

	/************/ // 查看发送的数据
	printf("\nData=");
	for (i = 0; i < 20; i++)
	{
		printf("%x ", Data[i]);
	}
	/************/

	unsigned char DataReceive[4];

	float *fp; //将unsigned char*的指针转换为float*类型的指针,还原数据

	ReceiveData(DataReceive, 4);

	fp = (float*)DataReceive;

	printf("\nDataReceive=");
	for (i = 0; i < 4; i++)
	{
		printf("%x ", DataReceive[i]);
	}
}

以上内容就是对指针基础应用的总结。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值