相信大家对下面的代码不陌生:
- int i = 2;
- int * p;
- p = &i;
这是最简单的指针应用,也是最基本的用法。再来熟悉一下什么是指针:首先指针是一个变量,它保存的并不是平常的数据,而是变量的地址。如上代码,指针 p 中保存的是整型变量 i 的地址信息。
接下来看如何定义一个指针,既然指针也是一个变量,那么它的定义也和其它变量一样定义:如:int 是间接寻址或间接引用运算符。上例中我们还看到了一个特别的运算符 &,它是一个取地址运算符(在其他合适场合 & 也是按位运算运算符,&& 为取交集运算符)。
在上面的指针定义中,我们看到了定义的是一个整型指针,难道指针还有类型吗?答案是肯定的,指针只能指向某种特定类型的对象,也就是说,每个指针都必须指向某种特定的数据类型(唯一的例外:指向 void 类型的指针可以存放指向任何类型的指针,但它不能间接引用其自身。)。比如,int 类型的指针绝对不能指向 char 类型的变量。
下面我们给出一个完整的例子来说明指针的简单应用:
- #include < stdio.h > voidmain() {
- int a,
- b,
- c,
- *p;
- a = 1;
- b = 3;
- p = &a;
- b = *p + 1;
- c = *(p + 1);
- printf("%d %d %d %d /n", a, b, c, *p + 3);
- }
运行结果为: 1 2 -858993460 4
这是个完整的例子,可以自己在机器上调试一下,现在很多人用的都是微软的 Visual Studio 开发环境,有人就不知道在该开发环境中怎么写 C 程序以及调试 C 程序,具体境况可以参考附录。
在上面例子中,看到了这样两个表达式 b= (p+1); 前者的意思是 p 所指的地址里的内容加 1 再赋给 b,相当于 b=a+1;,后者是 p 所指的地址加 1 再把(p+1)所指的地址赋给 c,当然我们不知道 p 的下一个地址里放的是什么,所以输出了一个随机值(这样的操作时很危险的,切记不要使用不确定的内存地址)。
数组大家应该都很熟悉了,用途非常广泛。
int a[4]={2,4,5,9};
此语句定义一个 4 个空间大小的整型数组 a 并为它进行了初始化。
数组的基础知识可以参考其他相应的教材,我们在这主要讨论指针和数组的结合应用。
我们再来看个完整的例子:
- #include < stdio.h > voidmain() {
- int a[4] = {
- 2,
- 4,
- 5,
- 9
- };
- int * p;
- p = a; * p = *p++;
- printf("%d %d %d/n", *p, *p + 6, *(p + 1));
- }
运行结果:4 10 5
分析:语句 p=a; 表示把数组 a 的第 0 个元素的地址赋给指针 p,数组名 a 代表的是数组 a 的第 0 个元素的地址。
a[i] 表示数组 a 的第 i 个元素,如果定义一个指针 p,那么语句 p=&a[0]; 表示可以将指针 p 指向数组 a 的第 0 个元素,也就是说 p 的值为数组元素 a[0] 的地址。那么 (p+i)引用的是数组元素 a[i] 的内容。对数组元素 a[i] 的引用也可以写成 (p+i)也是等价的。
虽然数组和指针有这么多通用的地方,但我们必须记住,数组名和指针之间有一个不同之处。指针是一个变量,因此语句 p=a 和 p++ 都是合法的。但数组名不是变量,因此,类似于 a=p 和 a++ 形式的语句是非法的。
下面来看一个我们常用的函数 strlen(char *s):
- int strlen(char * s) {
- int n;
- for (n = 0; * s != '/0'; s++) n++;
- return n;
- }
因为 s 是一个指针,所以对其执行自增运算是合法的。执行 s++ 运算不会影响到 strlen 函数的调用者中的字符串,它仅对该指针在 strlen 函数中的私有副本进行自增运算。在函数定义中,形式参数 char s[] 和 char *s 是等价的。
我们再来看一下地址算术运算:如果 p 是一个指向数组中某个元素的指针,那么 p++ 将对 p 进行自增运算并指向下一个元素,而 p+=i 将对 p 进行加 i 的增量运算,使其指向指针 p 当前所指向元素之后的第 i 个元素。同其他类型的变量一样,指针也可以进行初始化。通常,对指针有意义的初始化值只能是 0 或者是表示地址的表达式,对后者来说,表达式所表达的地址必须是在此之前已定义的具有适当类型的数据的地址。任何指针与 0 进行相等或者不相等的比较运算都有意义。但是指向不同数组的元素的指针之间的算术或比较运算没有意义。指针还可以和整数进行相加或相减运算。如 p+n 表示指针 p 当前指向的对象之后第 n 个对象的地址。无论指针 p 指向的对象是何种类型,上述结论都成立。在计算 p+n 时,n 将根据 p 指向的对象的长度按比例缩放,而 p 指向的对象的长度则取决于 p 的声明。例如,如果 int 类型占 4 个字节的存储空间,那么在 int 类型的计算中对应的 n 将按 4 的倍数来计算。
指针的减法运算也是有意义的,如果 p 和 q 指向相同数组中的元素,且 p<q,那么 q-p+1 就是位于 p 和 q 指向的元素之间的元素的数目。我们来看一下 strlen(char *s) 的另一个版本:
- int strlen(char * s) {
- char * p = s;
- while ( * p != '/0') p++;
- return p - s;
- }
程序中,p 被初始化为指向 s,即指向该字符串的第一个字符,while 循环语句将依次检查字符串中的每个字符,直到遇到标识字符数组结尾的字符'/0'为止。由于 p 是指向字符的指针,所以每执行以此 p++,p 就将指向下一个字符的地址,p-s 则表示已经检查过的字符数,即字符串长度。
总结:有效的指针运算包括相同类型指针之间的赋值运算;指针和整数之间的加减运算;指向相同数组中元素的两个指针间的减法或比较运算;将指针赋值为 0 或指针与 0 之间的比较运算。其他所有形式的指针运算都是非法的。
再来看两条语句:
- char a[] = "I am a boy";
- char * p = "I am a boy";
a 是一个仅仅足以存放初始化字符串以及空字符'/0'的一维数组。数组中的单个字符可以进行修改,但 a 始终指向同一个存储位置。而 p 是一个指针,其初值指向一个字符串常量,之后它可以被修改以指向其他地址,但如果试图修改字符串的内容,结果是没有定义的。
为了更容易理解数组和指针的关系,我们再来看一个函数:
- void strcpy(char * s, char * t) {
- int i;
- i = 0;
- while ((s[i] = t[i]) != '/0') i++;
- }
因为参数是通过值传递的,所以在 strcpy 函数中可以以任何方式使用参数 s 和 t。
下面是指针实现的几个版本:
- void strcpy(char * s, char * t) {
- while (( * s = *t) != '/0') {
- s++;
- t++;
- }
- }
最简版本:
- voidstrcpy(char * s, char * t) {
- while ( * s++=*t++);
- }
这里,s 和 t 的自增运算放到了循环的测试部分中。表达式 * t++ 的值是执行自增运算之前 t 所指向的字符。后缀运算符 ++ 表示在读取该字符之后才改变 t 的值。同样,在 s 执行自增运算之前,字符就被存储到了指针 s 指向的旧位置。上面的版本中表达式同'/0'的比较是多余的,因为只需要判断表达式的值是否为 0 即可。
这两个词次听起来挺新颖的,到底是什么意思呢?
由于指针本身也是变量,所以它们也可以像其他变量一样存储在数组中。这一点很容易理解。
- #include < stdio.h > #include < string.h > voidmain() {
- int i;
- char b[] = {
- "wustrive_2008"
- };
- char * a[1]; * a = b;
- for (i = 0; i < strlen(b); i++) printf("%c", *(a[0] + i));
- printf("/n");
- }
运行结果:wustrive_2008
这里库函数 strlen,strlen 为 string 类的标准库函数,所以要包含 #include
下面我们来自己写一个 strlen 函数,我们把上面的例子该成这样:
- #include < stdio.h > int strlen(char * s) {
- char * p = s;
- while ( * p != '/0') p++;
- return p - s;
- }
- void main() {
- int i;
- char b[] = {
- "wustrive_2008"
- };
- char * a[1]; * a = b;
- for (i = 0; i < strlen(b); i++) printf("%c", *(a[0] + i));
- printf("/n");
- }
这个运行结果和上个例子一样,不一样的只是我们自己实现了 strlen 函数,我们再编程时使用的库函数,都是语言的开发者或者系统为我们写好了的函数,其实我们也可以自己写。
这个例子很好的演示了指针数组的用法,指针数组 a 的值 a[1] 是一个指针,指向字符数组第一个字符。
指针的指针也很好理解,就是一个指针里放的是另一个指针的地址,而另一个指针可能指向一个变量的地址,还可能指向另一个指针。
看两个定义语句:int a[5][10]; int *b[5];
从语法角度讲,a[3][4] 和 b[3][4] 都是对一个 int 对象的合法引用。但 a 是一个真正的二维数组,它分配了 50 个 int 类型长度的存储空间。但 b 定义仅仅分配了 5 个指针,并且没有初始化,它们必须进行显示的初始化,假设 b 的每个元素都指向一个有 10 个元素的数组,那么编译器就要为它分配 50 个 int 类型长度的存储空间以及 5 个指针存储空间。指针数组的一个重要优点在于,数组的每一行长度可以不同,也就是说,b 的每个元素不必都指向一个有 10 个元素的向量。
在 C 语言中,函数虽然不是变量,但可以定义指向函数的指针。这种类型的指针可以被赋值,存放在数组中,传递给函数以及作为函数的返回值等。
如果下面的语句为一个函数的参数,表示什么意思:
int (,void *)
它表明 p 是一个指向函数的指针,该函数具有两个 void p)(v[i],v[left])<0) 中,p 的使用和其声明是一致的,p 是一个指向函数的指针, p(void) 则表明 p 是一个函数,该函数返回一个 int 类型的指针。
下面来看两个声明:
- int * f(); //f是一个函数,它返回一个指向int类型的指针
- int( * pf)(); //pf是一个指向函数的指针,该函数返回一个int类型的对象。
来源: http://www.tuicool.com/articles/BVbURrF