C语言指针总结
C语言中的精华是什么,那当然是指针,是C语言的难点部分。C是对底层操作非常方便的语言,而底层操作中用到最多的就是指针,
这成就了优秀的C程序的效率几乎和汇编语言程序一样高的功绩。
本文介绍C指针的一些基础和高级知识。关键好是多写代码,这样才能更好的理解C的精华--指针。
1. 指针的概念
指针是一种数据类型,与其它的数据类型不同的是指针是一种“用来存放地址值的”变量。
首先搞懂这几个概念:
指针类型、指针指向数据类型、指针指向的内存区、指针在内存中占空间大小.
如:int *pn;
指针类型: 去掉变量名,所剩就是指针类型,可知 pn 的指针类型为 int * ;
指针所指数据类型: 去掉变量名及 *, int 即为指针指向数据的类型;
指针指向的内存区:指针指向数据类型为多大,那么指针指向的内存区大小就为多少。
指针在内存中所占空间大小:在 32位的系统上, 指针在内存中始终是一个占了4个字节的数据类型。
再介绍下C里面特殊类型的指针,void指针:
void的意思就是“无值”或“无类型”。void指针一般称为“通用指针”或“泛指针”, 使用void指针可以很容易地把void指针转换成其它数据类型的指针.
分析指针,可以从变量名处起,根据运算符优先级结合,一步一步分析.
下面举例分析:
int p;//这是一个普通的整型变量
int *p;//首先从P处开始,先与*结合,所以说明P是一个指针,然后再与int结合,说明指针所指向的内容的类型为int型.所以P是一个返回整型数据的指针
int p[3];//首先从P处开始,先与[]结合,说明P是一个数组,然后与int结合,说明数组里的元素是整型的,所以P是一个由整型数据组成的数组
int *p[3];//首先从P处开始,先与[]结合,因为其优先级比*高,所以P是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int结合,说明指针
所指向的内容的类型是整型的,所以是一个由返回整型数据的指针所组成的数组
int (*p)[3];//首先从P处开始,先与*结合,说明P是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,
然后再与int结合,说明数组里的元素是整型的.所以P是一个指向由整型数据组成的数组的指针
int **p;//首先从P开始,先与*结合,说明P是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int结合,说明该指针所指向的元素是整型数据.
所以P是一个返回指向整型数据的指针的指针
int p(int);//从P处起,先与()结合,说明P是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数然后再与外面的int结合,说明函数的返回值是一个
整型数据.所以P是一个有整型参数且返回类型为整型的函数
int (*p)(int);//从P处开始,先与指针结合,说明P是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int结合,说明函数有一个int型的参数,
再与最外层的int结合,说明函数的返回类型是整型,所以P是一个指向有一个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3];//从P开始,先与()结合,说明P是一个函数,然后进入()里面,与int结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的
是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int结合,说明指针指向的
内容是整型数据.所以P是一个参数为一个整数且返回一个指向由整型指针变量组成的数组的指针变量的函数
2.指针的算术运算
有效的指针运算包括相同类型指针之间的赋值运算、指针同整数之间的加法或减法运算、指向相同数组中元素的两个指针间的减法或比较运算、将指针赋
值为0或指针与0之间的比较运算。其它形式的指针运算都是非法的,例如两个指针间的加法、乘法等。
指针可以进行加或减运算,例如:
pnew=pold + n
一个指针 pold加(减)一个整数 n后,结果是一个新的指针pnew,其中pnew和 pold的类型相同及其所指向的类型也相同,pnew的值将比 pold的值增加(减少)
了n乘sizeof(pold所指向的类型)个字节。即pnew=pold+n
即 pnew表示指针pold当前指向的对象之后第n个对象(pold所指类型的对象)的地址。3. 运算符&和*
&是取地址运算符,*是...书上叫做“间接运算符”(c++叫解引用)。
&n的运算结果是一个指针,指针的类型是n的类型加个*,指针所指向的类型是n的类型,指针所指向的地址,那就是n的地址。
*p 的结果是 p所指向的东西。*p,它的类型是 p指向的类型,它所占用的地址是p所指向的地址。
int n=1;
p=&n;//&n的结果是一个指针,类型是int*,指向的类型是int,指向的地址是n的地址。
4. 指针表达式
一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表达式。
char *str[32];
char **pstr=str;//如果把str看作指针的话,str也是指针表达式
char *str;
str=*pstr;//*pstr是指针表达式
str=*(pstr+1);//*(pstr+1)是指针表达式
由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,
指针指向的内存区,指针自身占据的内存。
5. 数组和指针的关系
数组的数组名其实可以看作一个指针。
定义一个数组int A[]={1,2, 3};
则数A就有了两重含义:
第一,它代表整个数组,它的类型是 int [3];
第二,它是一个常量指针,该指针的类型是int*,该指针指向的类型是 int,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,
该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似 A++的表达式是错误的。
此外数组在作为函数参数时,数组名将蜕化为指针。
例如:
void fun(char arr[12]);
在编译时编译器会当成是:
void fun(char *arr);//你在这个函数中使用sizeof(arr)得到的值是4
6. 指针和结构类型的关系
struct My{ int a; int b;};
假设我们定义了上面的结构体,同时定义结构体的结构对象并初始化,struct My ss={10,20};
那么我们如何通过指针ptr来访问ss的三个成员变量呢?
答案就是,我们先定义一个指向结构对象ss的指针,struct MyStruct *ptr=&ss;然后,使用指向运算符->便可实现对结构对象ss成员的访问。
ptr->a;//或者可以这们(*ptr).a,建议使用前者
ptr->b;
7. 指针和函数的关系
可以把一个指针声明成为一个指向函数的指针,从而通过函数指针调用函数。
例如:
int fun(char *,int);
int (*pfun)(char *,int);
pfun=fun;
int a=(*pfun)("abc", 3);
例中,定义了一个指向函数fun的指针pfun,把pfun作为函数的形参。把指针表达式作为实参,从而实现了对函数fun的调用。
在这里不得不说,C里面的回调函数。
回调函数:就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,
我们就说这是回调函数。
使用回调函数原因:
可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被
调用函数。
举例:
typedef int (*Fun)(char *p); // 为回调函数命名,类型命名为Fun,参数为char *pint Afun(void *p){ // 方法 Afun,格式符合Fun 的格式 printf("CallBack:%s!/n", (char*)p); return 0;}void call(char *p, int (*f)(void *)){ f(p);//回调}int main(){ call("123", Afun); return 0;}
8. 指针类型转换
当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式,这就要求两边的类型一致,所指向的类型也一致,
如果不一致的话,需要进行强制类型转换。语法格式是:(TYPE *)p;
这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE *,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。要注意的是,原来的指针
p的一切属性都没有被修改。另外,一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也必须保证类型一致,否则需要强
制转换。
9.指针应用
int (*a)[10] 和 int *a[10] 什么区别
int *a[10] :数组指针。数组a里存放的是10个int型指针
int (*a)[10] :a是指针,指向一个数组。此数组有10个int型元素
int *a[10]
先找到声明符a,然后向右看,有[]说明a是个数组,再向左看,是int *,说明数组中的每个元素是int *。所以这是一个存放int指针的数组。
int(*a)[10]
先找到声明符a,被括号括着,先看括号内的(优先级高),然后向右看,没有,向左看,是*,说明a是个指针,什么指针?在看括号外面的,先向右看,有[] 是个数组,
说明a是个指向数组的指针,再向左看,是int,说明数组的每个元素是int。所以,这是一个指向存放int的数组的指针。
例:
int *a[10];int (*b)[10];printf("*a[10]: %d/n ", sizeof(a));printf("(*b)[10]: %d/n ", sizeof(b));
结果是:
*a[10]: 40 //说明a是一个数组名
(*b)[10]: 4 //说明b是一个指针
10.右左法:应用分析指针的复杂申明
上面第9 说明了指针的简单应用,下面介绍复杂的应用。
右左法则:
“The right-left rule:Start reading the declaration from the innermost parentheses, go right, andthen go left. When you encounter parentheses, the direction
should be reversed.Once everything in the parentheses has been parsed, jump out of it. Continue tillthe whole declaration has been parsed.”
翻译后为:
首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过
程直到整个声明解析完毕。
对于这句话应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有
一个。
如:
Int (*f)(int n)
确定括号的含义是分析这个声明的重要步骤,这个声明有两个括号,每个括号作用不同。
首先找到未定义的标识符f,它向右是括号,然后向左,是 *, 即 f是个指针。然后向右为(int n), 这个括号为函数调用操作符。所以f是个指向函数的指针,然后再向左,
碰到int 即为“这个函数的返回类型为整型”,把上面的解析连起来即 f为一个包含参数int n的函数指针,它所指向的函数返回一个整型值。
int (*f[5])(int*p);
f右边是一个[]运算符,说明f是一个具有5个元素的数组,f的左边有一个*,说明f的元素是指针,要注意这里的*不是修饰 f的,而是修饰f[5]的,原因是[]运算符优先级比*高,
f先跟[]结合,因此*修饰的是f[5]。跳出这个括号,看右边,也是一对圆括号,说明f数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。
int(*(*func)[5])(int *p);
func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明
这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的元素是函
数指针,这些指针指向具有int*形参,返回值为int类型的函数。
参考:
http://harborwan.blog.sohu.com/19248423.html
http://blog.csdn.net/megaboy/article/details/482771
<
- 09-29如何通过wrap malloc定位C/C++程序的内存泄漏
- 02-25打车软件大战升级,补贴还能维持多久?
- 12-23BMP文件右旋90度[c语言]
- 12-23寻找直方图中面积最大的矩形(C语言版)
- 12-23[ndk,2]ndk开发案例和错误处理
- 12-23[ndk,1]ndk开发,C语言入门讲解
- 12-23C语言连续存储实现队列机制
- 12-23Objective-c 数据类型
- 01-11全球最受赞誉公司揭晓:苹果连续九年第一
- 12-09罗伯特·莫里斯:让黑客真正变黑
- 12-09谁闯入了中国网络?揭秘美国绝密黑客小组TA
- 12-09警示:iOS6 惊现“闪退”BUG
- 11-28Bossjob宣布上线AI翻译功能
- 11-28腾讯应用宝电脑版推小宝AI助手 部分功能已
- 11-28周鸿祎亲自上阵演短剧,将于发布会上播出
- 11-28机构:2024第三季度全球NAND闪存产业营收增
- 11-18LG新能源宣布与Bear Robotics达成合作,成为