C指针
感谢b站up主江科大自化协的讲解:https://www.bilibili.com/video/BV1Mb4y1X7dz/?spm_id_from=333.999.0.0
该学习笔记也是基于up课程做的笔记,我的个人能力及理解的局限,不足之处还望大佬指正~
9.1 指针简介
指针(Pointer)是C语言的一个重要知识点,其使用灵活、功能强大,是C语言的灵魂
指针与底层硬件联系紧密,使用指针可操作数据的地址,实现数据的间接访问
计算机存储机制
int a = 0x12345678;
short b = 0x5A6B;
char c[ ] = {0x33, 0x34, 0x35};
注意:
小端分配(Little Endian)是一种数据存储方式,它将多字节数据类型的最低有效字节存储在内存的最低地址处,而最高有效字节存储在最高地址处。这种存储方式的名称源于计算机处理信息的方式与书写语言的差异。在西方书写中,我们习惯从左到右进行书写,而在计算机内部处理数据时,数据通常是从右到左进行处理的。因此,在小端分配中,数据的低位部分在存储器中的地址是比高位部分的地址更小的。
例如,对于一个 32 位整数变量,其值为 0x12345678,该变量在小端分配存储方式下的存储方式如下:
地址 | 0x4000 | 0x4001 | 0x4002 | 0x4003 |
---|---|---|---|---|
数据内容 | 0x78 | 0x56 | 0x34 | 0x12 |
可以看到,在小端分配中,该变量的最低有效字节 0x78 存储在内存地址 0x4000 处,而最高有效字节 0x12 则存储在地址 0x4003 处。
定义指针
指针即指针变量,用于存放其他数据单元(变量/数组/结构体/函数等)的首地址。若指针存放了某个数据单元的首地址,则这个指针指向了这个数据单元,若指针存放的值是0,则这个指针为空指针
定义一个指针变量:
解释:
这里的 x 表示指针类型的大小,通常用于指示指针所占用的内存空间大小。
在一个 16 位系统中,指针通常占用 2 个字节的内存空间,即 x = 2。
在一个 32 位系统中,指针通常占用 4 个字节的内存空间,即 x = 4。
在一个 64 位系统中,指针通常占用 8 个字节的内存空间,即 x = 8。
需要注意的是,这里的指针类型的大小与数据类型的大小不一定相同。例如,在一个 32 位系统中,int 类型通常占用 4 个字节的内存空间,但指针类型也是 4 个字节。这是因为不同的数据类型在内存中的存储方式和占用的内存空间大小不同,而指针类型所占用的内存空间大小通常是与系统位数相关的固定值。
例子:
#include <stdio.h>int main(void)
{int a;int *p;
// char a;
// char *p;printf("%d\n",sizeof(a)); //int 4, char 1printf("%d\n",sizeof(p)); //int 8, char 8return 0;
}
指针的操作
若已定义:int a; //定义一个int型的数据int *p; //定义一个指向int型数据的指针
则对指针p有如下操作方式:
操作方式 | 举例 | 解释 |
---|---|---|
取地址 | p=&a; | 将数据a的首地址赋值给p |
取内容 | *p; | 取出指针指向的数据单元 |
加 | p++; | 使指针向下移动1个数据宽度 |
加 | p=p+5; | 使指针向下移动5个数据宽度 |
减 | p–; | 使指针向上移动1个数据宽度 |
减 | p=p-5; | 使指针向上移动5个数据宽度 |
例子:(取地址和取内容)
#include <stdio.h>int main(void)
{char a = 0x66;char *p;p = &a;
// char *p = &a;printf("%x\n",a); //66printf("%x\n",p); //62fe47printf("%x\n",*p); //66return 0;
}
例子:(使指针向下移动1个数据宽度)
#include <stdio.h>int main(void)
{int a = 0x66;int *p;p = &a;printf("%x\n",a); //66printf("%x\n",p); //62fe44printf("%x\n",*p); //66p++;printf("%x\n",p); //62fe48(加了一个数据宽度,int4字节)return 0;
}
注意:
通常用于数组,单独变量没有意义。
2.数组与指针
数组是一些相同数据类型的变量组成的集合,其数组名即为指向该数据类型的指针。数组的定义等效于申请内存、定义指针和初始化。
例如: char c[ ] = {0x33, 0x34, 0x35};
等效于: 申请内存定义 char *c = 0x4000;初始化数组数据利用下标引用数组数据也等效于指针取内容。
例如: c[0]; 等效于: *c;c[1]; 等效于: *(c+1);c[2]; 等效于: *(c+2);
例子:
#include <stdio.h>
#include <stdlib.h>int main(void)
{
// char a[] = {0x33, 0x34, 0x35};int *a;a = malloc(3*4);*a = 0x33;*(a+1) = 0x34;*(a+2) = 0x35;// char *p;
// p = a;printf("a[0]=%x\n",a[0]); //33printf("a[1]=%x\n",a[1]); //34printf("a[2]=%x\n",a[2]); //35printf("*a=%x\n",*a); //33printf("*(a+1)=%x\n",*(a+1)); //34printf("*(a+2)=%x\n",*(a+2)); //35return 0;
}
3.注意事项
在对指针取内容之前,一定要确保指针指在了合法的位置,否则将会导致程序出现不可预知的错误
同级指针之间才能相互赋值,跨级赋值将会导致编译器报错或警告
二级指针是指一个指向指针的指针,也可以说是一个指针的指针。它可以用于实现多级数据结构,例如二维数组、链表等。在 C 语言中,二级指针的定义形式如下:
int **pp; // 定义一个指向 int 类型指针的指针
其中,int *
表示指向 int 类型的指针,int **
表示指向指针的指针,即二级指针。
函数指针是指向函数的指针,它可以用于回调函数、动态绑定等场景。在 C 语言中,函数指针的定义形式如下:
int (*p)(int, int); // 定义一个指向返回类型为 int,参数为两个 int 类型的函数指针
其中,int (*)
表示函数指针类型,p
是函数指针变量名,(int, int)
表示函数指针所指向函数的参数类型。在使用函数指针时,需要注意使用函数指针绑定函数时要保证函数的返回类型、参数类型和个数与函数指针类型一致。
例如,下面的代码定义了一个函数指针变量 p
,并将其绑定到了名为 add
的函数上:
int add(int x, int y) {return x + y;
}int main() {int (*p)(int, int) = add;printf("%d\n", p(1, 2)); // 输出 3return 0;
}
在这段代码中,p
是一个函数指针变量,它被初始化为指向 add
函数。在调用函数指针时,可以直接使用 p
变量,并传递参数。
9.2 指针的应用
传递参数1)使用指针传递大容量的参数,主函数和子函数使用的是同一套数据,避免了参数传递过程中的数据复制,提高了运行 效率,减少了内存占用2)使用指针传递输出参数,利用主函数和子函数使用同一套数据的特性,实现数据的返回,可实现多返回值函数的设计
传递返回值1)将模块内的公有部分返回,让主函数持有模块的“句柄”,便于程序对指定对象的操作
值传递:(虽然安全,但是耗内存)
#include <stdio.h>void fun(int param)
{printf("%x\n",param);
}int main(void)
{int a = 0x66;fun(a);return 0;
}
指针传递:(指针传递大容量的参数)
#include <stdio.h>int FindMax(const int *array,int Count) //用const,只读
{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,3,5,4,3};int Max;Max = FindMax(a,6);printf("Max=%d\n",Max);return 0;
}
指针传递:(指针传递输出参数,可实现多返回值函数的设计)
#include <stdio.h>void FindMaxAndCount(int *max,int *count,const int *array,int length) //用const,只读
{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[] = {1,2,5,5,4,3};int Max;int Count;FindMaxAndCount(&Max,&Count,a,6);printf("Max=%d\n",Max); //5printf("Count=%d\n",Count); //2return 0;
}
传递返回值:(将模块内的公有部分返回,让主函数持有模块的“句柄”,便于程序对指定对象的操作)
#include <stdio.h>/****************/
int Time[]={23,59,55};
int *GetTime(void)
{return Time;
}
/****************/int main(void)
{int *pt;pt = GetTime(); //通过指针间接访问printf("pt[0]=%d\n",pt[0]);printf("pt[1]=%d\n",pt[1]); printf("pt[2]=%d\n",pt[2]);return 0;
}
传递参数和传递返回值
#include <stdio.h>int main(void)
{//用指针传递参数和体现模块的“句柄”
// FILE *f = fopen("F:\\test.txt","w");
// fputc('A',f); //在f写个字符A
// fputs('HelloWorld!',f); //在f写个HelloWorld!char a;char s[10];FILE *f = fopen("F:\\test.txt","r");a = fgetc(f); //普通的值传递fgets(s,15,f); //返回的输出参数fclose(f);printf("%c",a);peintf(s);return 0;
}
9.3 指针在单片机的运用
直接访问物理地址下的数据1)访问硬件指定内存下的数据,如设备ID号等2)将复杂格式的数据转换为字节,方便通信与存储
1)访问硬件指定内存下的数据,如设备ID号等
main.c文件
#include <REGX52.H>
#include "LCD1602.h"void main()
{
// unsigned char *p; //定义一个指针变量unsigned char code *p; //定义一个指针变量(加code,访问程序存储空间)LCD_Init();LCD_ShowString(1,1,"Hello");//直接读取ID号存放的RAM区
// p = (unsighne 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 = (unsighne 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;unsigned char DataSend[]={0x12,0x34,0x56,0x78};float num = 12.345;unsigned char *p;p = (unsigned char *)#SendData(p,4);/*****************************/SendData(DataSend,4);printf("\nAirData=");for(i=0;i<20;i++){printf("%x ",AirData[i]);} /*****************************/unsigned char DataReceive[4];float *fp;ReceiveData(DataReceive,4);fp = (float *)DataReceive;printf("\nnum=%f",*fp);/*****************************/return 0;
}
补充:结构体指针
结构体指针是指一个指向结构体的指针,它可以用于访问和修改结构体中的成员变量。在 C 语言中,结构体指针的定义和初始化形式如下:
struct person {char* name;int age;
};struct person p; // 定义结构体变量
struct person *pp; // 定义结构体指针变量pp = &p; // 将结构体指针变量设置为结构体变量的地址
pp->name = "Tom"; // 使用 -> 运算符访问结构体指针成员变量
pp->age = 20;
其中,&p
表示取结构体变量 p
的地址,pp
表示指向结构体 person
的指针,pp->name
表示访问结构体指针成员变量 name
,pp->age
表示访问结构体指针成员变量 age
。
另外,在定义结构体指针时,也可以在指针类型前加上关键字 typedef
,以便更方便地使用结构体指针:
typedef struct {char* name;int age;
} person;person p; // 定义结构体变量
person *pp = &p; // 定义结构体指针变量并初始化为结构体变量的地址
pp->name = "Tom"; // 访问结构体指针成员变量
pp->age = 20;
在这段代码中,person
是一个结构体类型别名,它等价于上面定义的 struct person
。定义结构体变量时可以直接使用别名 person
,定义结构体指针时也可以直接使用 person *
。