以上定义语句中,p和t都是合法用户标识符,在每个变量前的星号(*)是一个类型说明符,用来标识该变量是指针变量。
变量前的星号不可省略,若省略了星号说明符,就变成了把p和t定义为整型变量(int是类型名)。在这里,说明了p和t是两个指向整型(int 类型)变量的指针,也就是说变量p和t中只能存放int类型变量的地址,这时我们称int是指针变量p和t的“基类型“。基类型用来指定该指针变量可以指向的变量的类型。 例如: int *p1;
表示p1是一个指针变量,它的值是某个整型变量的地址。或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量,应由向p1赋予的地址来决定。 再如:
int *p2; /*p2是指向整型变量的指针变量*/ float *p3; /*p3是指向浮点变量的指针变量*/ char *p4; /*p4是指向字符变量的指针变量*/
应该注意的是,一个指针变量只能指向同类型的变量,如P3 只能指向浮点变量,不能时而指向一个浮点变量,时而又指向一个字符变量。
2.2 指针变量的引用
指针变量中只能存放地址(指针),将一个整型变量(或任何其他非地址类型的数据)赋给一个指针变量是不允许的。 如:
int *p; /*定义一个指向整型变量的指针*/ p=300 ; /*300为整数*/ 是不合法的赋值。
与指针相关的两个运算符是“&“(取地址运算)和“*“(指针运算符)。 1) &:取地址运算符。
2) *:指针运算符(或称“间接访问” 运算符)。 C语言中提供了地址运算符&来表示变量的地址。 其一般形式为: &变量名; 如:
int i,*p; P=&i;
2.3 指针变量作为函数参数
前面的章节中介绍过,函数参数可以是整型、实型、字符型等数据,指针类型数据同样也可以作为函数参数来进行传递。它的作用是将一个变量的地址传送到另一个函数中,参与该函数的运算。 如果想通过函数调用从而得到n个要改变的值,可以采用如下方法: (1)在主调函数中设n个变量,分别用n个指针变量指向它们。
(2)然后将各个指针变量作为实参,即将这n个变量的地址传给所调用的函数的形参。 (3)通过形参指针变量的改变,从而改变这n个变量的值。 (4)主调函数中就可以使用这些改变了值的变量。
形参指针变量的值的改变不能使实参指针变量的值发生改变。
考点3. 数组与指针
一个数组包含若干个元素(变量),在定义时被分配了一段连续的内存单元。因此,可以用一个指针变量来指向数组的首地址,通过该首地址就可以依次找到其他数组元素,同样指针变量也可以指向数组中的某一个元素。所谓数组的指针是指数组的起始地址,数组元素的指针是各个数组元素的地址。
3.1 指向数组元素的指针
C语言规定数组名代表数组的首地址,也就是数组中第0号元素的地址。有如下语句: int c[10]={0};
int *p; p=c;
则语句p=c;与语句p=&c[0];是等价的。 数组c不代表整个数组。上述“p=c;“的作用是把数组c的首地址赋值给指针变量p,而不是把数组c中各元素的值赋给p。
定义指向数组元素的指针变量的方法,与定义指向变量的指针变量相同。例如: int c[10],*p; p=&c[5];
指针变量p指向了数组c中下标为5的那个元素,即p用来保存c[5]的地址。
3.2 通过指针引用数组元素
按C语言的规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素(而不是将p的值简单加1),这里的加1是指增加一个长度单位(与数组基类型所占存储单元相同)。例如,数组元素是浮点型,每个元素占4个字节,则p+1意味着使p的值(是一个地址)加4个字节,以使它指向下一个元素。
将++和--运算符用于指针变量是十分有效的,可以使用指针变量自动向前或向后移动,指向下一个或上一个数组元素。不过要小心利用,否则会导致内存错误。
3.3 用数组名作为函数参数
数组名可以用做函数的形参和实参。当数组名作为参数被传递时,若形参数组中各元素发生了变化,则原实参数组各元素的值也随之变化。因为数组名作为实参时,在调用函数时是把数组的首地址传送给形参,因此实参数组与形参数组共占一段内存单元。而如果用数组元素作为实参的情况就与用变量作为实参时一样,是“值传递“方式,单向传递,即使形参数组元素值发生了变化,原实参的数组元素值也不会受影响。
3.4 指向多维数组的指针和指针变量
有如下定义: int c[3][4],*p;
c是该二维数组的数组名,它代表了整个数组的首地址,也就是第0行的首地址,c+1代表第1行的首地址,即c+1是[c1]的地址。c[0]、c[1]、c[2]既然是一维数组名,而C语言又规定了数组名代表数组的首地址,因此c[0]代表第0行的一维数组中第0列元素的地址,即&c[0][0]。同样地,c[1]的值是&c[1][0]。
第0行第1列元素的地址怎么表示呢?可以用c[0]+1来表示。此时“c[0]+1“中的1代表1个列元素的字节数,即两个字节。如c[0]的值是2020,则c[0]+1的值是2022。
根据上面的介绍,c[i]和*(c+i)等价,因此,c[0]+1和*(c+0)+1的值都是&c[0][1],都是用来表示第0行第1列的元素的地址。c[i]从形式上看是数组c中第i个元素。如果c是二维数组名,则c[i]代表一维数组名,c[i]本身并不占实际的内存单元,它也不存放数组中各个元素的值,它只是一个地址。c、c+i、c[i]、*(c+i)+j、c[i]+j都是地址,*(c[i]+j)、*(*(c+i)+j)是元素的值。
考点4. 字符串与指针
4.1 字符串的表示形式
(1)用字符数组存放一个字符串,然后输出该字符串。 例如:
char str[]=“I am a student??“; printf(“%s\\n“,str);
(2)用字符指针指向一个字符串。
可以不定义数组,而定义一个字符指针,用字符指针指向字符串中的字符。 例如:
char *str=“I am a student??“;
/*定义str为指针变量,并指向字符串的首地址*/ printf(“%s\\n“,str);
在这里没有使用字符数组,而是在程序中定义了一个字符指针变量str,并使该指针变量指向一个字符串的首地址。C语言对字符串常量是按字符数组进行处理的,在内存中开辟了一个字符数组来存放字符串常量。程序在定义字符指针变量str时,把字符串的首地址赋给str。str只能指向一个字符变量或其他字符类型数据,不能同时指向多个字符数
据,更不能理解为把字符串中的全部字符存放到str中(指针变量只能存放地址)。在输出时,利用字符型指针str的移动来控制输出,直到遇到字符串结束标志'\\0'为止。
通过字符数组名或字符指针变量可以一次性输出的只有字符数组(即字符串),而对一个数值型的数组,是不能企图用数组名输出它的全部元素的,只能借助于循环逐个输出元素。
显然,用%s可以控制对一个字符串进行整体的输入输出。对字符串中字符的存取,与操作其他数组的方法相同,既可以用下标方法,又可以用指针方法。
4.2 字符串指针作函数参数
将一个字符串从一个函数传递到另一个函数,可以用地址传递的办法,即用字符数组名作为参数或用指向字符串的指针变量作为参数,进行传递。
字符串指针变量作为函数实参,形参可以是字符指针变量,同样也可以是字符数组名。当字符数组名作为函数实参时,形参可以是字符数组名,同样也可以是字符指针变量。
4.3 字符指针变量和字符数组的区别
虽然字符数组和字符指针变量都能实现对字符的存储和运算,但它们两者之间有如下区别:
(1)字符数组是由若干个元素组成的,每个元素中存放一个字符,而字符指针变量中存放的是地址(字符串的首地址),绝不是将字符串的内容存放到字符指针变量中。
(2)赋值方式。只能对字符数组各个元素赋值,不能用以下方法对字符数组赋值: char str[20]; str=“I am happy“;
而对字符指针变量,可以采用以下方法赋值: char *s;
s=“I am happy??“;
(3)字符数组可以在定义时对其整体赋初值(即初始化),但在赋值语句中不能完成整体赋值。下面的做法是不允许的:
char s[30];
s[]=“I am so happy“;
而字符指针变量既可以在定义时赋初值,也可以出现在赋值语句中,相对来说要比字符数组使用起来灵活一点。 (4)如果定义了一个字符数组,在编译时,系统会为它分配一段连续的内存单元,它的地址是确定的。而当定义了一个字符指针变量后,就要即时给该指针变量分配内存单元,该指针变量中可以存放一个地址值,也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋以一个地址值,则它并未具体指向一个确定的字符数据。如: char s[10]; scanf(“%s“,s);
是可以的。但用下面的方法是极其危险的: char *s;
scanf(“%s“,s);
因为编译时虽然给指针变量s分配了内存单元,s的地址(即&s)已经指定了,但s的值并未指定。在s单元中是一个不可预料的值,在执行scanf()函数时要求将一个字符串输入到s所指向的一段内存单元中,即以s的值(地址)开始的一段内存单元。而s的值如今却是不可预料的,它可能指向内存中空白的存储区(未用的用户存储区),这样固然可以,但它也有可能指向内存中已存放指令或数据的有用内存段。这就会破坏程序,甚至破坏系统,造成严重的后果。应当这样:
char *a,s[10]; a=s;
scanf(“%s“,a);
先使a有确定值,也就是使a指向一个数组的首地址,然后输入一个字符串,把它存放在以该地址开始的若干单元中。 (5)在程序中指针变量的值可以改变。例如: char *s=“china”; s=s+2;
指针变量s的值可以改变,当要输出字符串时,从s当前所指向的单元开始输出各个字符(本题中从字符i开始输出),直到遇到'\\0'为止。而数组名虽然代表了地址,但它的值是一个固定的值,是不能改变的。下面是错的: char str[]={“china”};
str=str+2;
考点5. 指向函数的指针
用函数指针变量调用函数
我们已经知道,可以用指针变量指向整型变量、字符型变量、字符串、数组,同样指针变量也可以指向一个函数。编译时,一个函数将被分配给一个入口地址,这个入口地址就称为该函数的指针。因此,可以通过使用一个指向函数的指针变量调用此函数。 说明:
(1)指向函数的指针变量的一般定义形式为: 数据类型 (*指针变量名)( );
如int(*s)();,“数据类型”指该函数返回值的类型。
(2)(*s)()表示定义了一个指向函数的指针变量,但目前它不是固定指向哪一个函数,而只是表示定义了这样一个类型的变量,它的作用是专门用来存放函数的入口地址。在程序中实现把某一个函数的地址赋给它,它就指向那一个函数,这样它的值也就确定了。在一个程序中,一个指针变量以先后指向不同的函数,也就是说指向函数的指针变量和普通指针变量一样,可以多次使用。
(3)在给函数指针变量赋值时,只需给出函数名而不必给出参数。如: s=fun; /* fun为已有定义的有参函数*/
因为是将函数入口地址赋给s,不涉及到参数的问题,不能写成: s=fun(a,b);
(4)用函数指针变量调用函数时,只需将(*s)代替函数名即可(s为已经定义过的指向函数的指针变量名),在(*s)之后的括号中根据需要写上实参。
(5)对指向函数的指针变量,有些运算,如++s、--s、s+3等都是没有意义的。
考点6. 返回指针的函数
返回指针值的函数
一个函数的返回值可以是一个整型值、字符型值、实型值等,同样地,函数的返回值也可以是指针类型的数据,即地址。这种返回指针值的函数,一般定义形式为: 类型名 *函数名(参数表); 例如:
int *fun(int a,int b);
fun是函数名,调用它可以得到一个指向整型数据的指针(地址)。a、b是两个整形变量,是函数fun()的形参。注意:*fun在两侧没有括弧,在fun的两侧分别为*运算符和()运算符。而()优先级高于*,因此fun先与()结合,显然这是函数形式。这个函数前面有一个*,表示此函数是指针型函数。
考点7. 指针数组和指向指针的指针
7.1 指针数组的概念
若在一个数组中,其元素均为指针类型数据,这样的数组称为指针数组,也就是说,指针数组中的每一个元素都相当于一个指针变量。一维指针数组的定义形式为: 类型名 *数组名[数组长度]; 例如:
int *a[10];
由于[]运算符的优先级比*运算符的优先级高,因此a先与[10]结合,形成a[10]形式,然后与前面的“*“结合,“*“表示此数组是指针类型的,每个数组元素都可指向一个整型变量。
指针数组的一个重要用途是可以用来指向若干个字符串。例如,在对库房物品进行管理时,想把物品名称放在一个数组中,然后对这些物品进行统计和查询,我们可以分别定义一些字符串来存放各物品名称,然后利用指针数组中的元素分别指向各字符串。如果还想对字符串排序,不必改动字符串的位置,只需改动指针数组中各元素的指向(即