『C程序设计』读书笔记系列文章之第九章 指针
指针说白了就是地址。指针变量就是用来存放指针(地址)的变量。
1. 变量的指针和指向变量的指针变量
读起来很拗口,说白了就是变量的地址和用来存放变量地址的地址变量。因为一个变量在编译的时候系统要为它分配一个地址,假如再用一个变量来存放这个地址,那么这个变量就叫做指向变量的指针变量,也就是用来存放变量地址的这么一个变量。所谓"指向"就是指存放××的地址,如指向变量的指针变量,"指向"就是指用来存放变量的地址,再如指向数组的指针变量,"指向"就是指存放数组的地址。只要理解了这个,指针也就不难了。另外,还有指向字符串的指针变量,指向函数的指针变量,指向指针的指针变量等。
1) 指针变量的定义
形式:类型标识符 *标识符 如:int *pointer;
要注意两点:*表示pointer是个指针变量,在用这个变量的时候不能写成*pointer, *pointer是pointer指向的变量。一个指针变量只能指向同一个类型的变量。如上面
pointer只能指向int型变量。
2)指针变量的引用
两个有关的运算符:
& 取地址运算符 &a 就代表变量a的地址
* 指针运算符 *a 就代表变量a的值
2. 数组的指针和指向数组的指针变量
数组的指针指数组的起始地址,数组元素的指针指数组元素的地址。
1)指向数组元素的指针变量的定义与赋值
定义和指向变量的指针变量定义相同,c规定数组名代表数组的首地址,即第一个数组元素地址。
2)通过指针引用数组元素
我们通常引用数组元素的形式是a[i],如果用指针可以这样引用,*(a+i),或定义一个指针变量p,将数组a的首地址赋给p,p=a;然后用*(p+i)引用。
注意:指针变量p指向数组a首地址,则p++指向数组a的下一元素地址,即a[1]的地址。
3)数组名作函数参数
形参数组和实参数组之间并不是值传递,而是共用同一段地址,所以在函数调用过程中如果形参的值发生变化,则实参的值也跟着变化。
4)指向多维数组的指针和指针变量
以二维数组为居多。假设定义了一个二维数组a[3][4],那么
a代表整个二维数组的首地址,也代表第0行的首地址,同时也是第0行第0列的元素的首地址。a +0和a[0]代表第0行首地址,a+1和a[1]代表第一行的首地址。
假设a是一个数组的首地址,那么如果a是一维的,a+I代表第I个元素的地址,如果a是二维的,则a+I代表第I行的首地址。
那么第一行第二列的元素地址如何表示呢?a[1]+2或&a[1][2]或*(a+1)+2。
我们只要记住:在二维数组中a代表整个数组的首地址,a[I]代表第I行的首地址,a[I]与*(a+I)等价就行了。只要运用熟练了就没什么复杂的了。
5)指向由m个整数组成的一维数组的指针变量
如:int (*p)[4],p是一个指向包含4个元素的一维数组,如果p先指向a[0],则p+1指向a[1],即p的增值是以一维数组的长度为单位的,这里是4,举个例子:
假设a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23},p先指向a[0]也就是数组a的首地址,那么p+1就是a[1]的首地址即元素9的地址,因为在定义p时int (*p)[4],定义一维数组长度为4,所以p+1就等于加了一个一维数组的长度4。
3. 字符串的指针和指向字符串的指针变量
1)字符串的表示形式
c中字符串有两种表示形式:一种是数组,一种是字符指针
char string[]="I love c!";
char *str="I love c!";
其实指针形式也是在内存中开辟了一个数组,只不过数组的首地址存放在字符指针变量str中,千万不要认为str是一个字符串变量。
2)字符串指针作函数参数
实际上字符串指针就是数组的首地址。
3)字符指针变量与字符数组的区别
① 字符数组由若干元素组成,每个元素存放一个字符,而字符指针变量只存放字符串的首地址,不是整个字符串
② 对数组初始化要用static,对指针变量不用。
③ 对字符数组赋值,只能对各个元素赋值,不能象下面这样:
char str[14];
str="I love c!";
对指针变量可以,
char *str;
str="I love c!";
注意:此时赋给str的不是字符,而是字符串首地址。
④ 数组在定义和编译时分配内存单元,而指针变量定义后最好将其初始化,否则指针变量的值会指向一个不确定的内存段,将会破坏程序。如:
char *a;
scanf( "%s", a );这种方法是很危险的,应该这样:
char *a, str[10];
a = str;
scanf( "%s", a );这样字符指针就指向了一个确定的内存段。
⑤ 指针变量的值是可以改变的,而字符数组名所代表的字符串首地址却是不能改变的。
4. 函数的指针和指向函数的指针变量
一个函数在编译时被分配一个入口地址,这个入口地址就称为函数的指针。函数名代表函数的入口地址,这一点和数组一样。我们可以用一个指针变量来存放这个入口地址,然后通过该指针变量调用函数。如:假设有一个求两者较大的函数如下:int max( int x, int y );
当我们调用这个函数时可以这样:
int c;
c=max( a, b );这是通常调用方法,其实我们可以定义一个函数指针,通过指针来调用,如:
int (*p)(); //注意指向函数指针变量的定义形式
p=max; //此句就是将函数的入口地址赋给函数指针变量p
c=(*p)( a, b );
有些朋友可能对(*p)()不大理解,其实它的意思就是定义一个指向函数的指针变量p,p不是固定指向哪个函数的,而是专门用来存放函数入口地址的变量。在程序中把哪个函数的入口地址赋给它,它就指向哪个函数。但要注意,p不能象指向变量的指针变量一样进行p++,p-等无意义的操作。
既然p是一个指针变量,那么就可以作为函数的参数进行传递。其实函数的指针变量最常用的用途之一就是作为函数参数传递到其它函数。这也是c语言中应用的比较深入的部分了。
5. 返回指针值的函数
我们知道,一个函数可以带回一个整型值、字符值、实型值等,函数还可以带回一个指针型的数据,即地址。这种函数的定义形式如下:
类型标识符 *函数名(参数表) 如:int *a(x,y)返回一个指向整型的指针
使用这种函数的时候要注意:在调用时要先定义一个适当的指针来接收函数的返回值。这个适当的指针其类型应为函数返回指针所指向的类型。
这样的函数比较难于理解,其实只要把它当做一般的函数来处理就容易了。当我们觉得指针难于理解的时候,就把它暂时当做整型来看,就好理解多了。
6. 指针数组
指针数组无疑就是数组元素为指针,定义形式为: 类型标识 *数组名[数组长度]
如:int *p[4],千万不要写成int (*p)[4],这是指向一维数组的指针变量。指针数组多用于存放若干个字符串的首地址,注意一点,在定义指针数组时初始化,如下:
static char *name[]={"Li jing","Wang mi","Xu shang"};
不要以为数组中存放的是字符串,它存放的是字符串首地址,这一点一定要注意。
7. 指向指针的指针
说的明白一点,将一个指针再用一个变量来存放,那么这个变量就是指向指针的指针。定义如:char * *p;
8. 指针数组作main()函数的参数
函数形式为
main( int argc, char *argv[] ){}
main函数的参数是从命令行得到的,argc指命令行参数个数,注意命令名也算一个参数,命令行参数都是字符串,他们的首地址构成一个指针数组argv。Main函数的形参用argc和argv只是一个习惯,也可以定义成别的名字。
9. 指针小结
1)有关指针的数据类型
定 义 | 含 义 |
Int I; | 定义一个整型变量I |
Int *p; | P为指向整型数据的指针变量 |
Int a[n]; | 定义整型数组a,它有n个元素 |
Int *p[n]; | 定义指针数组p,它有n个指向整型的指针元素 |
Int (*p)[n]; | P为指向含有n个元素的一维数组的指针变量 |
Int f(); | F为返回整型值的函数 |
Int *p(); | P为返回值为指针的函数,该指针指向整型数据 |
Int (*p)(); | P为指向函数的指针,该函数返回一个整型值 |
Int **p; | 定义一个指向指针的指针变量 |
2)ANSI新增了一种void *指针类型,即定义一个指针变量,但不指向任何数据类型,等用到的时候再强制转换类型。如:
char *p1;
void *p2;
p1 = (char *)p2;
也可以将一个函数定义成void *型,如:
void *fun( ch1, ch2 )
表示函数fun返回一个地址,它指向空类型,如果需要用到此地址,也要对其强制转换。如(假设p1为char型):
p1=(char *)fun( c1,c2 );
指针应该说是c语言中比较重要的概念,也是c语言的精华,它有很多优点,但用不好也会带来严重性的错误,这就需要我们多用,多练,慢慢的积累经验。