一、细说指针
int p;
//这是一个普通的整型变量
int *p;
//首先从P 处开始,先与*结合,所以说明P 是一个指针,
//然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针
int p[3];
//首先从P 处开始,先与[]结合,说明P 是一个数组,
//然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组
int *p[3];
//首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,
//然后再与*结合,说明数组里的元素是指针类型,
//然后再与int 结合,说明指针所指向的内容的类型是整型的,
//所以P 是一个由返回整型数据的指针所组成的数组
int (*p)[3];
//首先从P 处开始,先与*结合,说明P 是一个指针
//然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,
//然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针
int **p;
//首先从P 开始,先与*结合,说是P 是一个指针,
//然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.
//由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.
int p(int);
//从P 处起,先与()结合,说明P 是一个函数,
//然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据
int (*p)(int);
//从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,
//然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,
//所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3];
//可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,
//然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,
//然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,
//然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.
//所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.
说到这里也就差不多了,我们的任务也就这么多,理解了这几个类型,其它的类型对我们来说也是小菜了,不过我们一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用,这上面的几种类型已经足够我们用.
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区,让我们分别说明。
1.指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]
怎么样?找出指针的类型的方法是不是很简单?
2.指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
(1)int*ptr; //指针所指向的类型是int
(2)char*ptr; //指针所指向的的类型是char
(3)int**ptr; //指针所指向的的类型是int*
(4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
(5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C 越来越熟悉时,你会发现,把与指针搅和在一起的”类型”这个概念分成”指针的类型”和”指针所指向的类型”两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。
3.指针的值—-或者叫指针所指向的内存区或地址
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?(重点注意)
4 指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32 位平台里,指针本身占据了4 个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。
二、数组和指针的关系
例1:指针数组
char *str[3]={
"Hello,thisisasample!",
"Hi,goodmorning.",
"Helloworld"
};
char s[80];
strcpy(s,str[0]); //也可写成strcpy(s,*str);
strcpy(s,str[1]); //也可写成strcpy(s,*(str+1));
strcpy(s,str[2]); //也可写成strcpy(s,*(str+2));
上例中,str 是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str 当作一个指针的话,它指向数组的第0 号单元,它的类型是char **
,它指向的类型是char *。
*str
也是一个指针,它的类型是char *,它所指向的类型是char,它指向的地址是字符串”Hello,thisisasample!”的第一个字符的地址,即’H’的地址。注意:字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量.
str+1 也是一个指针,它指向数组的第1 号单元,它的类型是char**,它指向的类型是char*。
*(str+1)
也是一个指针,它的类型是char*,它所指向的类型是char,它指向”Hi,goodmorning.”的第一个字符’H’
例二:数组指针
int array[10];
int (*ptr)[10];
ptr=&array;
上例中ptr 是一个指针,它的类型是int(*)[10],他指向的类型是int[10] ,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array 代表数组本身。
那么sizeof(指针名称)测出的究竟是指针自身类型的大小呢,还是指针所指向的类型的大小?
答案是前者。例如:
int(*ptr)[10];
则在32 位程序中,有:
sizeof(int(*)[10])==4
sizeof(int[10])==40
sizeof(ptr)==4
实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。
三、指针和函数的关系
例3:指向函数的指针
int fun1(char *,int);
int (*pfun1)(char *,int);
pfun1=fun1;
int a=(*pfun1)("abcdefg",7); //通过函数指针调用函数。
可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参
一个函数指针的简单用法:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
double Add(double x, double y) {return x + y;}
double Sub(double x, double y) {return x - y;}
double Mul(double x, double y) {return x * y;}
double Div(double x, double y) {return x / y;}
//申明一个具有5个函数指针的数组
//这里用了typedef便于管理,等同于
//double (*funcTable[5])(double, double) = {Add, Sub, Mul, Div, pow};
typedef double func_t(double, double);
func_t *funcTable[5] = {Add, Sub, Mul, Div, pow};
//申明1个字符串数组,用于数组
char *msgTable[5] =
{"Sum", "Difference", "Product", "Quotient", "Power"};
int main()
{
int i;
double x = 6, y = 2;
for(i = 0; i < 5; i++)
printf("%10s: %6.2f\n", msgTable[i], funcTable[i](x, y));
return 0;
}
四、指针和结构类型的关系
可以声明一个指向结构类型对象的指针
struct MyStruct
{
int a;
int b;
int c;
};
struct MyStruct ss={20,30,40};
//声明了结构对象ss,并把ss 的成员初始化为20,30 和40。
struct MyStruct *ptr=&ss;
//声明了一个指向结构对象ss 的指针。它的类型是
//MyStruct *,它指向的类型是MyStruct。
int *pstr=(int*)&ss;
//声明了一个指向结构对象ss 的指针。但是pstr 和
//它被指向的类型ptr 是不同的。