指针是一种保存变量地址的变量,声明方式如下:
double *dp, atof(char *);
表明,*dp和atof(char *)的值都是double类型的。该声明中atof的参数是指向char类型的指针,指针必须指向特性类型的对象,但是void类型的指针可以存放指向任何类型的指针。
数组和指针的关系:
数组名所代表的指针就是该数组最开始的一个元素的地址。在计算数组元素a[i]的值时,C语言实际上去先将其转换为*(a + i)的形式在进行计算的,因为pa[i] 和 *(pa + i)是等价的。需要注意的是,pa[i]虽然在语法上是合法的,但是如果(p + i)不在数组范围内,则引用边界之外的对象是非法的。一般而言,用指针编写的程序比用数组下标编写的程序执行速度快。
#include <stdio.h>
int strlen(char *s)
{
int n;
for (n = 0; *s != '\0'; s++)
n++;
return n;
}
main()
{
char arr[] = {"hello,world"};
char *ptr = "hello,world";
printf("%d\n", strlen("hello,world"));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(ptr));
return 0;
}
以上代码的三个printf的结果都是一样的。
观察这三种调用strlen函数的方式,实际上他们在这里都是等价的,都传递第一个元素的地址。
字符串常量是一个字符数组,所以字符串常量可以通过一个指向其第一个元素的指针访问。
有效的指针运算:
- 相同类型指针之间的赋值运算。
- 指针同整数之间的加法或减法运算。
- 指向相同数组中元素的两个指针间的减法或比较运算。
- 将指针赋值为0或指针与0之间的比较运算(符号常量等价于常量0)。
除了以上类型的运算,其他所有形式的指针都是非法的,例如两个指针间的加法、乘法、除法、移位或屏蔽运算,指针同float或double类型之间的加法运算;不经强制类型转换而直接将指向同一类型对象的指针赋值给指向另一种类型对象的指针的运算(两个指针之一是void *类型的情况除外)。
#include <stdio.h>
#define N 100
#define printVal(val) printf(#val " = %d\n", val)
int pInt[N];
double pDouble[N];
int main()
{
int* p = malloc(sizeof(int) * N);
int* test = malloc(5);
printVal(pInt);
printVal(pDouble);
printVal(p);
// pInt++; 非法,因为pInt不是左值。
// pDouble++; 同上
p++;
printVal(pInt + 1);
printVal(pDouble + 1);
printVal(p);
return 0;
}
观察以上程序的输出可以发现,指针加上一个整数,该整数会根据指针指向对象的长度按比例缩放。
由此,函数strlen()可以写成如下形式:
int strlen(char *s)
{
char *p = s;
while (*p != '\0')
p++;
return p - s;
}
现在我们看看数组和指针的区别。
char amessage[] = "now is the time";/* 定义一个数组*/
char *pmessage = "now is the time"; /* 定义一个指针*/
上面的定义有很大差别。
上面声明中,amessage是一个仅仅足以存放初始化字符串以及空字符'\0'的一维数组。数组中的单个字符串可以进行修改,但amessage始终指向同一存储位置。另一方面,pmessage是一个指针,其初值指向一个字符串常量,之后可以被修改以指向其他地址。但如果试图修改字符串的内容,结果是没有定义的。
书中向我们展示了strcpy(s,t)(把指针t指向的字符串复制到指向指针指向的位置)4中不同的实现版本。这几个简短的程序不仅有助于理解指针,而且有益于我们学习C风格的编程。
void strcpy(char *s, char *t)
{
int i = 0;
while ((s[i] = t[i]) != '\0')
i++;
}
void strcpy(char *s, char *t)
{
while ((*s = *t) != '\0')
{
s++;
t++;
}
}
void strcpy(char *s, char *t)
{
while ((*s++ = *t++) != '\0')
;
}
void strcpy(char *s, char *t)
{
while (*s++ = *t++)
;
}
当然,最后一个版本的strcpy(s, t)是最好的。它利用指针而且更进一步精炼程序。在ASCLL码中,'\0'的ASCLL码值是0。
如果在函数声明中,除数组的第一维(下标)可以不指定大小外,其余各维都必须明确指定大小。
指针数组:
先看看指针数组是怎么初始化的:
char *color[] = {
"red",
"blue",
"black",
"white",
};
观察以上代码,可以发现,指针数组有一个重要的优点:每一行长度可以不同。
再将指针数组和二维数组做个比较:
char *name[] = {"Illegal month", "Jan", "Feb", "Mar"};
char aname[][15] = {"Illegal month", "Jan", "Feb", "Mar"};
name是一个存放char *的数组,数组每个元素指向一个字符串。
而二维数组是每一行存放一个对应的字符串。可以直接修改字符串的值。
函数指针的声明方法为:
例如:
int func(int x); /* 声明一个函数 */
int (*f) (int x); /* 声明一个函数指针 */
f=func; /* 将func函数的首地址赋给指针f */
赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。
#include <stdio.h>
void swap(int v[], int i, int j)
{
int temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
int main()
{
int num[] = {1, 2};
void (*pSwap)(int [], int , int);
pSwap = swap;
pSwap(num, 0, 1);
printf("%d\n%d\n", num[0], num[1]);
return 0;
}
在C语言中,指针的声明可以很复杂。以下是书中对一些声明的解释:
char **argv;//argv: pointer to pointer to char
int (*daytab)[13]; //daytab: pointer to array[13] of int
int *daytab[13]; //daytab: array[13] of point to int
void *comp(); //comp: function returning pointer to void
void (*comp)(); //comp: pointer to function returning void
char (*(*x()[]))(); //x: function returning pointer to array[] of
//pointer to function returning char
char (*(*x[3])())[5];//x: array[3] of pointer to function returning
//pointer to array[5] of char