(C语言)指针的详解与应用

本文详细介绍了C语言中的指针概念,包括指针与底层硬件的联系、定义、基本操作,以及在值传递、指针传递参数、数组与指针、单片机应用和数据转换中的作用。通过实例演示,帮助读者理解指针在编程中的关键作用。
摘要由CSDN通过智能技术生成

感谢江科大!该文章是基于b站up主江科大的C语言指针的讲解做的笔记,方便以后使用。

江科大b站视频链接:https://www.bilibili.com/video/BV1Mb4y1X7dz/?spm_id_from=333.999.0.0

1.指针简介

指针(Pointer)是C语言的一个重要知识点,其使用灵活、功能强大,是C语言的灵魂
指针与底层硬件(内存)联系紧密,使用指针可操作数据的地址,实现数据的间接访问

2.计算机存储机制 

 注意:目前计算机大多使用小端分配模式。数据的低字节存储在低地址空间,数据的高字节存储在高地址空间。但是数组里的数据是按顺序存的。

3.定义指针

  注意:无论是指向什么数据类型的指针,指针所占的字节都只与该系统的位宽有关。(1byte=8bit)

        在16位的系统中 1字(word)= 2字节(byte)=16(bit)
        在32位的系统中 1字(word)= 4字节(byte)=32(bit)
        在64位的系统中 1字(word)= 8字节(byte)=64(bit)

例:

#include <stdio.h>

int main(void)
{
//    int a;
//    int *p;
	char a;
	char *p;
    
    //sizeof关键字计算所占字节数 
    printf("%d\n",sizeof(a)); //int 4, char 1
    printf("%d\n",sizeof(p)); //int 8, char 8
    
    return 0; 
}

 4.指针的操作

例:

#include <stdio.h>

int main(void)
{
	char a=0x66; //a->1个字节 
	char *p;     //p->8个字节(只与系统位宽有关) 
	
	p=&a; //将数据a的首地址赋值给p(a只有一个字节,首地址即a的地址) 
	
	printf("%x\n",a);  //66 
	printf("%x\n",p);  //66fe17(p=a的地址,这个地址可能和电脑系统有关,可能会不一样)
	
	//指针是通过地址间接访问数据的 
	printf("%x\n",*p); //66(把p内存里的数据当作一个地址,打印这个地址所对应的数据,即a) 
	
	//指针加减通常用于数组 
	p--; //指针减,向下移动一个数据宽度(char->1字节 short->2字节 int->4字节) 
	
	printf("%x\n",p); //66fe16
	
	return 0;
} 

5.数组与指针 

例: 

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

int main(void)
{
//	int a[]={0x33,0x44,0x55};

	int *a; 
	//malloc返回一个void *(指向空型的指针),想选定指针类型的话,可以自己定义 
	//定义一个指向int数据类型的指针a,这样a就拿到了这个申请内存的首地址 
	a=(int *)malloc(3*4); //申请内存(3个变量,每个变量占4个字节)  
	
	//初始化数组数据
	*a=0x33;
	*(a+1)=0x44;
	*(a+2)=0x55; 
	
	printf("a[0]=%x\n",a[0]); //33
	printf("a[1]=%x\n",a[1]); //44
	printf("a[2]=%x\n",a[2]); //55
	
	//数组名即为指向该数据类型的指针(指针的加减通常用于数组) 
	printf("*a=%x\n",*a);         //33
	printf("*(a+1)=%x\n",*(a+1)); //44
	printf("*(a+2)=%x\n",*(a+2)); //55
	
	return 0;
}

6.注意事项 

7.指针的应用 

传递参数 

1)值传递参数和指针传递大容量参数

值传递参数

例: 

#include <stdio.h>

void fun(int param)
{
	param=0x88; 
	printf("%x\n",param); //88
}

int main(void)
{
	int a=0x66;
	
	fun(a);
	printf("%x\n",a); //66
	
	return 0;
}

(1)首先执行主函数int a=0x66,申请4个字节的内存。如图所示

(2)接着调用子函数fun(a),把a传递过去。实际上是经历了一个定义局部变量param(重新申请param4个字节的内存),把a中的数据复制到param的一个过程。如图所示

(3)在子函数运行结束之后,子函数中的局部变量将被销毁,保护了主函数中变量的安全

值传递中更改子函数的变量不影响主函数中的变量隔离了子函数和主函数中的数据保护了主函数中数据的安全,但是如果需要传递大容量的参数就比较费时费力了,这时可以采用指针传递。

指针传递大容量参数

例: 

#include <stdio.h>

int FindMax(int *array, int Count) //参数:数组 数组元素个数 
{
	int i;
	int max=array[0];
	for(i=1;i<Count;i++) //遍历数组 
	{
		if(array[i]>max)
		{
			max=array[i]; //刷新最大值 
		} 
	}
	return max;
}

int main(void)
{
	int a[]={1,2,5,9};
	int Max;
	
	Max=FindMax(a,4); //数组名即为指向该数据类型的指针(a即为该数组的首地址) 
	printf("Max=%d\n",Max); 
	
	return 0;
}

(1)首先执行主函数申请a数组的内存(16个字节)Max数据的内存(4个字节),a(数组名)即为该数组的首地址,如图所示

(2)接着调用FindMax子函数,并且把a的首地址给传过去了,子函数有一个新的局部变量int *array,所以会在程序中在申请一个内存array(8个字节),array的初始值为a,当引用array的时候,需要间接访问(数组传参时需要间接访问),array[0]即为array(或者a指针地址下,因为array初始值为a)当作指针地址下的第一个数据,这个数据是就是主函数中a数组的第0个数据a[0],所以array遍历的数据即为主函数中a的数据,如图所示。(参数Count为常见的值传递,见上方值传递)

(3)子函数的array只是创建了一个指针变量(8个字节),并没有把a数组中的数据复制,并且通过指针间接访问的还是主函数中的a数组,也就是说子函数和主函数共用一个数组

(4)在子函数运行结束之后,子函数中的局部变量(int *array)将被销毁,主函数将继续使用a数组

指针传递中主函数和子函数使用的是同一套数据避免了参数传递过程中的数据复制,提高了运行效率,减少了内存占用,但是指针传递时子函数可以修改主函数中的数据,这时可以用const关键字防止子函数修改主函数的数据。

指针传递大容量参数(防止子函数修改主函数中的数据)

例: 

#include <stdio.h>

//const是常量的意思,当用const修饰array指针时,在子函数中,array只能被读,不能被写
//当试图在子函数中修改array时,程序将会报错 
int FindMax(const int *array, int Count) //参数:数组 数组元素个数
{
	int max=array[0];
	
	array[1]=66; //修改主函数中的数据 
	
	return max;
}

int main(void)
{
	int a[]={1,2,5,9};
	int Max;
	
	Max=FindMax(a,4); //数组名即为指向该数据类型的指针(a即为该数组的首地址)
	printf("Max=%d\n",Max); 
	printf("a[1]=%d\n",a[1]); //66(主函数中的数据被修改)
	
	return 0;
}

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

例: 

#include <stdio.h>

//这里的max和count等同于主函数的Max和Count,实现了多返回值 
//参数:最大值 最大值出现的次数 数组 数组的长度(元素个数) 
void FindMaxAndCount(int *max,int *count,const int *array,int length)
{
    int i;
    *max = array[0];
    *count = 1;
    for(i=1;i<length;i++)    //遍历数组 
    {
        if(array[i]>*max)
        {
            *max = array[i]; //刷新最大值 
            *count = 1;
        }
        else if(array[i] == *max)
        {
            (*count)++;      //记录最大值出现的次数 
        }
    }
 }

int main(void)
{
    int a[] = {5,19,5,19,19,6};
    int Max;
    int Count;
    FindMaxAndCount(&Max,&Count,a,6); //同级指针才能相互赋值,故要取地址 
    printf("Max=%d\n",Max);		//19
    printf("Count=%d\n",Count); //3
  
    return 0;
}

传递返回值  

例:  

#include <stdio.h>

/***********************/
//指针很多时候是用于封装 
int Time[]={23,59,55};

int *GetTime(void) //返回一个指向int数据类型的指针 
{
    return Time;
}
/***********************/

int main(void)
{
    int *pt;
    
    //GetTime()返回Time[]的首地址,并赋值给pt       
    //等同于pt为Time[]的首地址,这样就拿到了Time[]的"句柄"
    pt = GetTime(); //通过指针间接访问 
    
    printf("pt[0]=%d\n",pt[0]); //23
    printf("pt[1]=%d\n",pt[1]); //59   
    printf("pt[2]=%d\n",pt[2]); //55
    
    return 0;
}

综合案例:指针传递参数和返回值 

#include <stdio.h>

int main(void)
{
	//体现指针传递大容量参数  体现模块的"句柄"	
	//FILE *fopen(char *filename, char *mode)   FILE *为返回值,
    //filename为文件名(包括文件路径),mode为打开方式,它们都是字符串。
/*    
    FILE *f = fopen("F:\\test.txt","w"); //"w"为只写方式 
    fputc('A',f);	                     //在f写个字符'A'
    fputs("HelloWorld!",f);              //在f写个字符串"HelloWorld!"
*/    
    char a;
	char s[10]; 
    
    //体现指针实现输出参数的返回 
    FILE *f = fopen("F:\\test.txt","r"); //"r"为只读方式
    a=fgetc(f);     //读取字符 普通的值传递 
    fgets(s,5,f);   //在f中读取的5个字符(包括字符串结尾'\0')保存在s数组里面
    
    fclose(f);      //关闭文件 
    printf("%c",a); //'A'
    printf(s);      //"AHell "(包括字符串结尾'\0')
    
    return 0;
}

 指针在单片机的应用

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

例:  读取51单片机的设备ID号

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

void main()
{
//	unsigned char *p;      //定义一个指针变量(访问内部RAM区)
	unsigned char code *p; //定义一个指针变量(加code,访问程序存储器)

	LCD_Init();
	LCD_ShowString(1,1,"HelloWorld!");

/*
	//直接读取ID号存放的RAM区(F1H-F7H)
	p = (unsigned char *)0xF1;      //跨级赋值,需要强制类型转换
	LCD_ShowHexNum(2,1,*p,2);
	LCD_ShowHexNum(2,3,*(p+1),2);
	LCD_ShowHexNum(2,5,*(p+2),2);
	LCD_ShowHexNum(2,7,*(p+3),2);
	LCD_ShowHexNum(2,9,*(p+4),2);
	LCD_ShowHexNum(2,11,*(p+5),2);
	LCD_ShowHexNum(2,13,*(p+6),2);
*/

	//ID号存放在程序区的地址读取
	p = (unsigned char code *)0x1FF9; //跨级赋值,需要强制类型转换
	LCD_ShowHexNum(2,1,*p,2);
	LCD_ShowHexNum(2,3,*(p+1),2);
	LCD_ShowHexNum(2,5,*(p+2),2);
	LCD_ShowHexNum(2,7,*(p+3),2);
	LCD_ShowHexNum(2,9,*(p+4),2);
	LCD_ShowHexNum(2,11,*(p+5),2);
	LCD_ShowHexNum(2,13,*(p+6),2);

	while(1)
	{

	}
}

2)将复杂格式的数据转换为字节,方便通信与存储

 例:

#include <stdio.h>

/*******************************************************************/
unsigned char AirData[20];

//参数:发送的数据 发送数据的个数 
void SendData(const unsigned char *data, unsigned char count)
{
    unsigned char i;
    for(i=0;i<count;i++)    //遍历数据 
    {
        AirData[i]=data[i]; //把数据发送到空气中(空气接收数据) 
    }
}

//参数:接收的数据 接收数据的个数 
void ReceiveData(unsigned char *data,unsigned char count)
{
     unsigned char i;
    for(i=0;i<count;i++)    //遍历数据
    {
        data[i]=AirData[i]; //把空气中的数据发送到电脑中(电脑接收数据)
    } 
}
/******************************************************************/

int main(void)
{
    unsigned char i;
    
    //用指针将复杂格式的数据转换为字节,方便通信与存储 
	float num=12.345;             //4个字节,把num当作一个数组,用指针发送 
	unsigned char *p; 
	
	p=(unsigned char *)&num;      //跨级赋值,需要强制类型转换(把num的首地址编码成unsigned char *型,获取num的首地址) 
    SendData(p,4);                //把p(num的首地址)当作一个数组发送过去 
    
    printf("AirData=");
    for(i=0;i<20;i++)	          //遍历打印发送到空气中的数据
    {
        printf("%x ",AirData[i]); //前四个字节为1f854541(p=nun的首地址) 
    }  
  
	unsigned char DataReceive[4]; 
	float *fp;
	
    ReceiveData(DataReceive,4);
	fp=(float *)DataReceive;      //跨级赋值,需要强制类型转换(把接收到的地址解码成float *型)
    printf("\nnum=%f ",*fp);      //打印这个地址下的数据(*fp->取内容) 
 
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值