感谢江科大!该文章是基于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的首地址编码成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;
}