此篇文章为本人自主学习,根据自身理解,整理的学习笔记(若侵权,请联系本人删文)
各类指针理解
目录
各类指针理解
巧记
以数组指针以及指针数组作为案列
数组指针 (*p)[n]
指针数组 *p[n]
以函数指针以及指针函数作为案列
1.指针函数
2.函数指针
以指针常量以及常量指针作为案列
1、指针常量——指针类型的常量(int *const p)
2、常量指针——指向“常量”的指针(const int *p, int const *p)
引用文献:数组指针和指针数组_mick_hu的博客-CSDN博客
(3条消息) 指针函数和函数指针函数指针和指针函数惟肖肖肖的博客-CSDN博客
(3条消息) 指针常量和常量指针_qq_36132127的博客-CSDN博客
巧记
1.数组指针--------指向数组的指针(可以当二维数组使用 int (*p)[N])
2.指针数组--------装着指针的数组(就是数组里面装的元素是指针即地址 int*p[N])
3.函数指针--------指向(调用)函数的指针
(就是通过指针调用函数的地址,从而达到调用函数 int(*p)(int,int) 可以调用形如 int get_sum(int a,int b)的函数)
4.指针函数--------返回指针的函数
(就是函数的返回值是一个指针 int *get_sum(int a,int b).....int *sum......return sum;)
5.常量指针--------指向“常量”的指针(指针指向的值不能改变 const int *p )
6.指针常量--------指针类型的常量(p地址不能改变 int *const p)
以数组指针以及指针数组作为案列
参考自:数组指针和指针数组_mick_hu的博客-CSDN博客
首先,理解一下数组指针和指针数组这两个名词:
“数组指针”和“指针数组”,只要在名词中间加上“的”字,就知道中心了——
数组的指针:是一个指针,什么样的指针呢?指向数组的指针。
指针的数组:是一个数组,什么样的数组呢?装着指针的数组。
然后,需要明确一个优先级顺序:()>[]>*,所以:
(*p)[n]:根据优先级,先看括号内,则p是一个指针,这个指针指向一个一维数组,数组长度为n,这是“数组的指针”,即数组指针;
p[n]:根据优先级,先看[],则p是一个数组,再结合,这个数组的元素是指针类型,共n个元素,这是“指针的数组”,即指针数组。
根据上面两个分析,可以看出,p是什么,则词组的中心词就是什么,即数组“指针”和指针“数组”。
int *p1[5]; int (*p2)[5];
首先,对于语句“intp1[5]”,因为“[]”的优先级要比“”要高,所以 p1 先与“[]”结合,构成一个数组的定义,数组名为 p1,而“int*”修饰的是数组的内容,即数组的每个元素。也就是说,该数组包含 5 个指向 int 类型数据的指针,如图 1 所示,因此,它是一个指针数组。
图1
其次,对于语句“int(p2)[5]”,“()”的优先级比“[]”高,“”号和 p2 构成一个指针的定义,指针变量名为 p2,而 int 修饰的是数组的内容,即数组的每个元素。也就是说,p2 是一个指针,它指向一个包含 5 个 int 类型数据的数组,如图 2 所示。很显然,它是一个数组指针,数组在这里并没有名字,是个匿名数组。
图2
由此可见,对指针数组来说,首先它是一个数组,数组的元素都是指针,也就是说该数组存储的是指针,数组占多少个字节由数组本身决定;而对数组指针来说,首先它是一个指针,它指向一个数组,也就是说它是指向数组的指针,在 32 位系统下永远占 4 字节,至于它指向的数组占多少字节,这个不能够确定,要看具体情况。
数组指针 (*p)[n]
数组指针:是指针——指向数组的指针。
看下面的例子进行理解
#include "stdafx.h" int main() { //一维数组 int a[5] = { 1, 2, 3, 4, 5 }; //步长为5的数组指针,即数组里有5个元素 int (*p)[5]; //把数组a的地址赋给p,则p为数组a的地址,则*p表示数组a本身 p = &a; //%p输出地址, %d输出十进制 //\n回车 //在C中,在几乎所有使用数组的表达式中,数组名的值是个指针常量,也就是数组第一个元素的地址,它的类型取决于数组元素的类型。 printf("%p\n", a); //输出数组名,一般用数组的首元素地址来标识一个数组,则输出数组首元素地址 printf("%p\n", p); //根据上面,p为数组a的地址,输出数组a的地址 printf("%p\n", *p); //*p表示数组a本身,一般用数组的首元素地址来标识一个数组 printf("%p\n", &a[0]); //a[0]的地址 printf("%p\n", &a[1]); //a[1]的地址 printf("%p\n", p[0]); //数组首元素的地址 printf("%d\n", **p); //*p为数组a本身,即为数组a首元素地址,则*(*p)为值,当*p为数组首元素地址时,**p表示首元素的值1 printf("%d\n", *p[0]); //根据优先级,p[0] 表示首元素地址,则*p[0]表示首元素本身,即首元素的值1 printf("%d\n", *p[1]); //为一个绝对值很大的负数,不表示a[1]...表示什么我还不知道 //将二维数组赋给指针 int b[3][4]; int(*pp)[4]; //定义一个数组指针,指向含4个元素的一维数组 pp = b; //将该二维数组的首地址赋给pp,也就是b[0]或&b[0],二维数组中pp=b和pp=&b[0]是等价的 pp++; //pp=pp+1,该语句执行过后pp的指向从行b[0][]变为了行b[1][],pp=&b[1] int k; scanf_s("%d", &k); return 0; }
根据上面二维数组可以得出,数组指针也称指向一维数组的指针,所以数组指针也称行指针。
指针数组 *p[n]
指针数组:是数组——装着指针的数组。
看下面的例子进行理解:
#include "stdafx.h" int main() { int a = 1; int b = 2; int *p[2]; p[0] = &a; p[1] = &b; printf("%p\n", p[0]); //a的地址 printf("%p\n", &a); //a的地址 printf("%p\n", p[1]); //b的地址 printf("%p\n", &b); //b的地址 printf("%d\n", *p[0]); //p[0]表示a的地址,则*p[0]表示a的值 printf("%d\n", *p[1]); //p[1]表示b的地址,则*p[1]表示b的值 //将二维数组赋给指针数组 int *pp[3]; //一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2],所以要分别赋值 int c[3][4]; for (int i = 0; i<3; i++) pp[i] = c[i]; int k; scanf_s("%d", &k); return 0; }
最后,从上文来看:
数组指针是一个指针变量,占有内存中一个指针的存储空间;
指针数组是多个指针变量,以数组的形式存储在内存中,占有多个指针的存储空间。
了解指针数组和数组指针二者之间的区别之后,继续来看下面的示例代码:
int arr[5]={1,2,3,4,5}; int (*p1)[5] = &arr; /*下面是错误的*/ int (*p2)[5] = arr;
不难看出,在上面的示例代码中,&arr 是指整个数组的首地址,而 arr 是指数组首元素的首地址,虽然所表示的意义不同,但二者之间的值却是相同的。那么问题出来了,既然值是相同的,为什么语句“int(p1)[5]=&arr”是正确的,而语句“int(p2)[5]=arr”却在有些编译器下运行时会提示错误信息呢(如在 Microsoft Visual Studio 2010 中提示的错误信息为“a value of type"int"cannot be used to initialize an entity of type"int()[5]"”)?
其实原因很简单,在 C 语言中,赋值符号“=”号两边的数据类型必须是相同的,如果不同,则需要显示或隐式类型转换。在这里,p1 和 p2 都是数组指针,指向的是整个数组。p1 这个定义的“=”号两边的数据类型完全一致,而 p2 这个定义的“=”号两边的数据类型就不一致了(左边的类型是指向整个数组的指针,而右边的数据类型是指向单个字符的指针),因此会提示错误信息。
以函数指针以及指针函数作为案列
参考自:(3条消息) 指针函数和函数指针函数指针和指针函数惟肖肖肖的博客-CSDN博客
1.指针函数
(1)本质是一个函数,不过它的返回值是一个指针。其声明的形式:类型名 *函数名(函数参数列表);
int * pfun(int, int);
由于“*”的优先级低于“()”的优先级,因而pfun首先和后面的“()”结合,也就意味着,pfun是一个函数;
接着再和前面的“*”结合,说明这个函数的返回值是一个指针。由于前面还有一个int,也就是说,pfun是一个返回值为整型指针的函数。
(2)返回类型可以是任何基本类型和复合类型。返回指针的函数的用途十分广泛。事实上,每一个函数,即使它不带有返回某种类型的指针,它本身都有一个入口地址,该地址相当于一个指针。比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,不过这时的变量是函数本身而已,而整个函数相当于一个“变量”。
(3)void型指针
void指针是一种不明确类型的指针,任何指针都可转换为void指针。 指针有两个非常重要的信息:
指针的值(指针目标对象的内存首地址) 指针指向对象的类型 注意点:void指针只保存了 指针的值 并没有记录 指针指向对象的类型。因此在用到对void指针解引时,需要先把void指针转换成原本的数据类型。
int n = 500; //定义一个int变量 int * p = &n; //定义int类型指针 void * pv = p; //定义void指针,只保存了p的值(即n的内存首地址) //错误的写法 printf("%d\n", *pv); //这里会报错,因pv指针没有明确数据类型,因此也不知道需要取多少字节的数据 //正确写法 printf("%d\n", *( (int*)pv ) ); //先把pv指针转为int类型指针,再对其解引
(4)举例:
//打印第m个学生的成绩 #include <stdio.h> float *find(float(*pionter)[4],int n);//函数声明 int main(void) { static float score[][4]={{60,70,80,90},{56,89,34,45},{34,23,56,45}};//3个学生的成绩 float *p; //定义float类型的指针 int i,m; printf("Enter the number to be found:"); scanf("%d",&m); printf("the score of NO.%d are:\n",m); p=find(score,m-1); //将find函数返回的地址给p for(i=0;i<4;i++) printf("%5.2f\t",*(p+i)); //打印(score+n)行i列数据 return 0; } float *find(float(*pionter)[4],int n)/*定义指针函数*/ { float *pt; //定义float类型的指针 pt=*(pionter+n); //将(pionter+n)行的首地址给pt return(pt); //返回float类型的地址 }
共有三个学生的成绩,函数find()被定义为指针函数,其形参pointer是指针指向包含4个元素的一维数组的指针变量(即简称数组指针)。
补充:为什么*(point+n)是表示地址?而不是取值。
*(point+1)单独使用时表示的是第 1行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址。因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针;就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。
main()函数中调用find()函数,将score数组的首地址传给pointer。
2.函数指针
(1)函数指针是指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。
C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数的参数。
(2)函数指针的声明方法为:
返回值类型 ( * 指针变量名) ([形参列表]);
注1:“返回值类型”说明函数的返回类型,“(指针变量名 )”中的括号不能省,括号改变了运算符的优先级。若省略整体则成为一个函数说明,说明了一个返回的数据类型是指针的函数,后面的“形参列表”表示指针变量指向的函数所带的参数列表。例如:
int func(int x); /* 声明一个函数 */ int (*f) (int x); /* 声明一个函数指针,是一个指向返回值为int的函数的指针 */ f=func; /* 将func函数的首地址赋给指针f */
或者使用下面的方法将函数地址赋给函数指针:
f = &func;
赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。
(3)举例:
#include<stdio.h> int max(int x,int y){return (x>y? x:y);} //返回两个数的最大值 int main() { int (*ptr)(int, int); //定义指向函数的指针变量 int a, b, c; ptr = max; //将max函数的地址给ptr scanf("%d%d", &a, &b); c = (*ptr)(a,b); //ptr和max函数地址相同,相当于调用max函数 printf("a=%d, b=%d, max=%d", a, b, c); return 0; }
ptr是指向函数的指针变量,所以可把函数max赋给ptr,作为ptr的值,即把max函数的入口地址赋给ptr,以后就可以用ptr来调用该函数,实际上ptr和max都指向同一个入口地址。不同就是ptr是一个指针变量,不像函数名称那样是死的,它可以指向任何函数,就看你想怎么做了。在程序中把哪个函数的地址赋给它,它就指向哪个函数。而后用指针变量调用它,因此可以先后指向不同的函数。不过注意,指向函数的指针变量没有++和--运算,用时要小心。
(4)补充
函数指针可作为参数 函数指针可作为返回值
#include <stdio.h> int add(int num1,int num2) { return num1+num2; } int sub(int num1,int num2) { return num1-num2; } int fun(int (*fp)(int,int),int num1,int num2) //函数指针做参数 { return (*fp)(num1,num2); } int (*select(char c))(int,int) //函数指针作为返回值 { switch(c) { case '+': return add; case '-': return sub; } }
int main() { int num1,op,num2; int (*fp)(int,int); printf("请输入一个表达式,比如(1+3):\n"); scanf("%d%c%d",&num1,&op,&num2); fp=select(op); //返回的函数指针赋予fp printf("%d%c%d=%d\n",num1,op,num2,fun(fp,num1,num2)); /* printf("3+5=%d\n",fun(add,3,5)); 函数名就是函数的首地址 printf("3-5=%d\n",fun(sub,3,5)); */ return 0; }
int (*add)(int, int);定义了一个函数指针add,用于指向返回值为int,并且有两个int参数的函数
int (*select(char c))(int,int) 分解如下:
select与后面的(char c)结合,说明是一个函数,即select(char c) 在和结合,说明select函数的返回值是一个指针,即(select(char c)) 在和后面的(int,int)结合,说明select函数返回的指针指向函数,不是指向int类型,即int ((*select(char c)))(int,int) 总结:返回值为select函数指针,该返回值指向一个 返回值为int,两个参数为int类型的函数 (5)使用typedef关键字
int (*PF)(int *, int); PF是一个函数指针变量,用于指向返回值为int,一个int类型参数的函数。
当使用typedef声明后,则PF就成为了一个函数指针类型,即 typedef int (*PF)(int *, int); 这样就定义了返回值的类型。
再用PF作为返回值来声明函数:PF func(int); // func(int)就是一个返回值为函数指针,一个int类型参数的函数
再用PF来声明:PF phead; //phead就是一个函数指针
(6)地址直接操作函数和指针
if(((vu32)(0X20001000+4))&0xFF000000)==0x08000000)
其中:
(vu32)(0X20001000+4))== ((__IO uint32_t)(0X20001000+4))==((volatile unsigned int)(0X20001000+4))
((vu32)(0X20001000+4)) 通过内存寻址访问地址为(0x20001000 + 4)中的值
(0X20001000+4)只是一个常量;
(volatile unsigned int*)(0X20001000+4) 将0x20001000 + 4这个常量强制转化成volatile unsigned int类型的指针;
((volatile unsigned int)(0X20001000+4)) 相对于取0x20001000 + 4地址处的值。
为什么要将0x20001000 + 4这个常量强制转化成volatile unsigned int类型的指针呢?
假设定义*P , 取地址符 &P 得到P地址,这个地址是随机的系统分配空闲地址。我们不能直接给P赋地址值,因为那样是不合法的,我们只能给指针赋值为NULL。
但是现在必须让指针指向一个已知地址(0X20001000+4),必须转换类型,在地址前面加上指针转换的类型我们这里用的volatile unsigned int*,如果不转换,不成功。
假如使用一个32位处理器,要对一个32位的内存地址进行访问,可以这样定义:
#define RAM_ADDR (*(volatile unsigned long *)0x01234567) 然后就可以用C语言对这个内存地址进行读写操作了。 读:tmp = RAM_ADDR; 写:RAM_ADDR = 0x55;
这里使用 volatile关键字的好处就是:
1.volatile是一个类型修饰符(typespecifier);
2.volatile关键字声明的变量,编译器对访问该变量的代码就不再进行优化;
3.volatile 关键字声明的变量,对变量的存取不能缓存到寄存器,每次使用时需要在内存中重新存取。
(7)使用typedef定义函数指针
typedef的意义 typedef int a[10]; // a 类型是 int[10];(存放int型数据的数组) a arr; // 定义一个数组:int arr[3];
typedef void (*p)(void); //p 类型是void ( * )void p A; //是指void(A)(void); 语法上typedef属于存储类声明说明符。 a[10]不是int的别名,(p)(void)不是void的别名。 上面的语句把a声明为具有10个int元素的数组的类型别名,p是一种函数指针的类型别名。
函数指针对象赋值用法 两种用法
typedef void (*IapFun)(void); //定义函数指针 void func(void); //定义函数 IapFun fun = func; //为函数指针对象赋值 fun(); //这里的fun()其实就相当于跳转到了func()里 typedef void (* IapFun)(void); //定义函数指针 IapFun jump2app; //定义函数指针对象 jump2app=(IapFun) * (vu32*)(appxaddr+4); //为函数指针对象赋值 appxaddr为函数指针地址,例如0x08000000 jump2app(); //调用函数
以指针常量以及常量指针作为案列
参考自:(3条消息) 指针常量和常量指针_qq_36132127的博客-CSDN博客
1、指针常量——指针类型的常量(int *const p)
本质上一个常量,指针用来说明常量的类型,表示该常量是一个指针类型的常量。在指针常量中,指针自身的值是一个常量,不可改变,始终指向同一个地址。在定义的同时必须初始化。用法如下:
int a = 10, b = 20; int * const p = &a;
*p = 30; // p指向的地址是一定的,但其内容可以修改
2、常量指针——指向“常量”的指针(const int *p, int const *p)
常量指针本质上是一个指针,常量表示指针指向的内容,说明该指针指向一个“常量”。在常量指针中,指针指向的内容是不可改变的,指针看起来好像指向了一个常量。用法如下:
int a = 10, b = 20; const int *p = &a;
p = &b; // 指针可以指向其他地址,但是内容不可以改变 3、例题 (1)
int main() { int m = 10; const int n = 20; // 必须在定义的同时初始化 const int *ptr1 = &m; // 指针指向的内容不可改变 int * const ptr2 = &m; // 指针不可以指向其他的地方 ptr1 = &n; // 正确 ptr2 = &n; // 错误,ptr2不能指向其他地方 *ptr1 = 3; // 错误,ptr1不能改变指针内容 *ptr2 = 4; // 正确 int *ptr3 = &n; // 错误,常量地址不能初始化普通指针吗,常量地址只能赋值给常量指针 const int * ptr4 = &n; // 正确,常量地址初始化常量指针 int * const ptr5; // 错误,指针常量定义时必须初始化 ptr5 = &m; // 错误,指针常量不能在定义后赋值 const int * const ptr6 = &m; // 指向“常量”的指针常量,具有常量指针和指针常量的特点,指针内容不能改变,也不能指向其他地方,定义同时要进行初始化 *ptr6 = 5; // 错误,不能改变指针内容 ptr6 = &n; // 错误,不能指向其他地方 const int * ptr7; // 正确 ptr7 = &m; // 正确 int * const ptr8 = &n; *ptr8 = 8; return 0; }
(2)判断下面程序对错,并说明理由
int main() { char * const str = "apple"; * str = "orange"; cout << str << endl; getchar(); }
错误
"apple"是字符串常量放在常量区,str指向"apple",那么str指向的是字符串常量"apple"的首地址,也就是字符a的地址,因此str指向字符a,str就等于字符a,对str的修改就是对字符串首字符a的修改,但"apple"是一个字符串常量,常量的值不可修改。
根据字符串赋值规则,可以修改整个字符串,方法是对指向字符串的指针str进行赋值,如下:
str = "orange"; 但依旧是错误的,在该赋值语句中,系统会在常量区一块新的空间写入字符串"orange"并返回其首地址,此时str由指向字符串常量"apple"的首地址变为指向字符串常量"orange"的首地址,str指向的地址发生了变化,但str是指针常量不能被修改,所以错误。
如果想要程序编译通过,就不能将str声明为指针常量,否则str在初始化之后就无法修改。因此将const修饰符去掉,并修改字符串赋值语句,修改后程序如下:
int main() { char * str = "apple"; str = "orange"; cout << str << endl; getchar(); }