指针,恐怕是C里面最令人头疼的东西了。
一般来讲,指针是一个其数值为地址的变量(或更一般的说是一个数据对象)。
ptr = &pooh;
// 把pooh的地址赋给ptr,称为ptr指向pooh
// ptr和&pooh的区别在于,ptr是一个变量,后者是一个常量
间接运算符 *
假定ptr指向bah,如下所示:
ptr = &bah;
这时就可以使用间接(indirection)运算符*(也称取值 dereferencing 运算符)
val = * ptr; // 得到ptr指向的值
ptr = &bah;
val = *ptr;
//上面2句等同于:val = bah;
取地址运算符:&,后面跟一个变量名时,&给出该变量的地址。
9.7.2 指针声明
申明指针变量时,还需要说明指针所指向变量的类型,因为不同的变量类型占用的存储空间大小不同,而有些指针操作需要知道变量类型所占用的存储空间,同时,程序也需要了解地址中存储的是何种数据,例如long和float两种类型的数值可能使用相同大小的存储空间,但是它们的存储方式完全不同。
int * pi;
char * pc;
float *pf, *pg;
//类型标识符表明了被指向变量的类型,而星号表示该变量为一指针
//int *pi;的意思是pi是一个指针,而*pi是int类型的。那么pi是什么类型呢?是“指向int的指针”类型,ANSI C专门为指针提供了%p输出格式。
9.7.3 使用指针在函数间通信
#include <stdio.h>
void interchange(int * u, int * v);
int main(void){
int x = 5, y = 10;
printf("Originally x = %d and y = %d.\n", x, y);
interchange(&x, &y);//向函数传递地址
printf("now x = %d and y = %d.\n", x, y);
return 0;
}
void interchange(int * u, int * v){
int temp;
temp = *u;
*u = *v;
*v = temp;
}
10. 数组和指针
10.3 指针和数组
数组名同时也是该数组元素的地址。即如果flizny是一个数组,那么:
flizny = &flizny[0];
dates + 2 == &dates[2] //相同的地址
*(dates + 2) == dates[2] //相同的值
ar[n] = *(ar+n),寻址到内存中的ar,然后移动n个单位,再取出数值。
间接运算符*的优先级高于+,因此*date+2
等价于(*date)+2
声明数组 参量:
由于数组名就是数组首元素的地址,所以如果实际参数是一个数组名,那么形式参数必须是与之相匹配的指针。在(而且仅在)这种场合中,C对于int ar[]和int *ar作出同样解释,即ar是指向int的指针。由于原型允许省略名称,因此下面4个是等价的:
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);
//定义函数时,名称不可以省略:
int sum(int *ar, int n){}
int sum(int ar[], int n){}
10.5 指针操作
#include <stdio.h>
int main(void){
int urn[5] = {100,200,300,400,500};
int * ptr1, * ptr2, * ptr3;
ptr1 = urn;
ptr2 = &urn[2];
printf("pointer value, dereferenced pointer, pointer address:\n");
printf("ptr1=%p,*ptr1=%d,&ptr1 = %p\n",ptr1,*ptr1,&ptr1);
//指针加法
ptr3 = ptr1 + 4;
printf("\n adding an int to a pointer:\n");
printf("ptr1 + 4 = %p, *(ptr1+3)=%d\n",ptr1+4,*(ptr1+3));
ptr1++;//递增指针
printf("\n values after ptr1++\n");
printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p \n", ptr1, *ptr1, &ptr1);
}
增加指针的值——可以通过一般的加法或增量运算符来增加一个指针的值。对指向某数组元素的指针做增量运算,可以让指针指向该数组的下一个元素。因此,ptr1++运算把ptr1加上数值4(我们的系统上int为4字节),使ptr1指向urn[1],现在ptr1的值是0x0012ff3c(下一个数组元素的地址),*ptr的数值为200(urn[1]的值),请注意ptr1本身的值仍是0x0012ff34.因为变量不会因为它的值的变化而移动位置。
不能对未初始化的指针取值
int *pt;
*pt = 5;//可怕的错误
为什么这样的代码危害很大?爹行表示把数值5存到pt所指向的地址,但由于pt没有被初始化,因此它的值是随机的,不知道5会被存储到什么位置。
切记:当创建一个指针时,系统只分配了用来存储指针本身的内存地址,并不分配用来存储数据的内存地址。因此,在使用指针之前,必须给它赋予一个已分配的内存地址。
10.6.1 对形参使用const
int sum(const int ar[], int n);
int sum(const int ar[], int n){
int i;
int total = 0;
for(i = 0; i < n; i++){
total += ar[i];
}
return total;
}
10.6.2 const 数组常量 指针常量 指向常量的指针
指向常量的指针不能用于修改数值:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double * pd = rates;
pd声明为指向const double 的指针,这样,就不可以使用pd来修改它所指向的数值。
*pd = 29.89; //不允许
pd[2] = 222.22;//不允许
rates[0] = 99.99; //允许
pd++; //让pd指向rates[1],允许
通常把指向常量的指针用作函数参量,以表明函数不会用这个指针来修改数据。
如:
void show_array(const double *ar, int n);
关于指针赋值和const有一些规则:首先,将常量或非常量数据的地址赋给指向常量的指针是合法的:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double locked[4] = {0.08, 0.075, 0.0725, 0.07};
const double * pc = rates; //合法
pc = locked; //合法
pc = &rates[3]; //合法
//只有非常量数据的地址才可以赋给普通指针
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double locked[4] = {0.08, 0.075, 0.0725, 0.07};‘
double * pnc = rates; //合法
pnc = locked; //非法
pnc = &rates[3]; //合法
// 这样的规则是合理的,否则,您就可以使用指针来修改被认为是常量的数据。
还可以用const来声明并初始化指针,以保证指针不会指向别处,关键在于const的位置:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
double * const pc = rates;
pc = &rates[2]; //不可以
*pc = 92.99; //可以
这样的指针仍然可用于修改数据,但它只能指向最初赋给它的地址。
最后,可以使用两个const来创建指针,这个指针既不可以更改所指向的地址,又不可以修改所指向的数据。
10.7 指针和多维数组
int zippo[4][2];
数组名zippo同时也是数组首元素的地址(这里zippo是包含2个int的数组的地址)。
- 因为zippo是数组首元素的地址,所以zippo的值和&zippo[0]相同。zippo[0]的值同其首元素(一个整数)的地址&zippo[0][0]相同。
- 对一个指针(也就是地址)加1,会对原来的数值加上一个对应类型大小的数值,zippo所指向对象的大小是2个int,zippo所指向的对象的大小是一个int,因此zippo+1和zippo[0]+1的结果不同。
- 对一个指针(也就是地址)取值(使用运算符或者带有索引的[]运算符)得到的是该指针所指向对象的数值。因为zippo[0]是其首元素zippo[0][0]的地址,所以(zippo[0])代表存储在zippo[0][0]中的数值,即一个int数值。同样,*zippo代表其首元素zippo[0]的值,但是zippo[0]本身就是一个int型的地址,即&zippo[0][0],因此*zippo是&zippo[0][0].对这两个表达式同时应用取值运算符将得到
** zippo 等价于 *&zippo[0][0] == zippo[0][0]
zippo == &zippo[0]
*(zippo[0]) == zippo[0][0]
*zippo == zippo[0]的值 == &zippo[0][0]
*zippo == &zippo[0][0] == zippo[0][0]
简言之,zippo是地址的地址,需要2辞去之词可以得到通常的数值。
10.7.1 指向多为数组的指针
int (*pz)[2]; //pz指向一个包含2个int值的数组:pz先和*
结合,从而创建一个指向包含2个int值的数组的指针
int *pz[2];//pz是包含2个某种元素的数组,与*
结合,表示pz是2个指针组成的数组,最后用int来定义,表示pz是由2个指向int值的指针构成的数组。这种声明会创建2个指向单个int值的指针。
#include <stdio.h>
int main(void){
int zippo[4][2] = {{2,4},{6,8},{1,3},{5,7}};
int (*pz)[2];
pz = zippo;
printf("pz = %p, pz+1=%p \n",pz,pz+1);
printf("pz[0]=%p,pz[0]+1 = %p\n",pz[0],pz[0]+1);
printf("*pz=%p, *pz+1 = %p \n",*pz, *pz+1);
printf("pz[0][0] = %d\n",pz[0][0]);
}
10.7.2 指针兼容性
int n = 5;
double x;
int *pi = &n;
double *pd = &x;
x = n;
pd = p1;//出错
int * pt;
int (*pa)[3];
int ar1[2][3];
int ar2[3][2];
int **p2; //指向指针的指针
有如下结论:
pt = &ar1[0][0]; //指向int
pt = ar1[0]; //指向int
pt = ar1; //非法
pa = ar1; //指向int[3]
pa = ar2; //非法
p2 = &pt; //指向int *
*p2 = ar2[0]; //指向int
p2 = ar2; //非法