赞
踩
目录
(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 字节,至于它指向的数组占多少字节,这个不能够确定,要看具体情况。
数组指针:是指针——指向数组的指针。
看下面的例子进行理解
#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;
}
根据上面二维数组可以得出,数组指针也称指向一维数组的指针,所以数组指针也称行指针。
指针数组:是数组——装着指针的数组。
看下面的例子进行理解:
#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)本质是一个函数,不过它的返回值是一个指针。其声明的形式:类型名 *函数名(函数参数列表);
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。
(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博客
本质上一个常量,指针用来说明常量的类型,表示该常量是一个指针类型的常量。在指针常量中,指针自身的值是一个常量,不可改变,始终指向同一个地址。在定义的同时必须初始化。用法如下:
int a = 10, b = 20;
int * const p = &a;
*p = 30; // 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();
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。