指针是c语言的一个重要概念,指针类型是c语言最有特色的数据类型:
*利用指针编写的程序可使调用函数共享变量或数据结构,实现双向数据通信;
*可以实现内存空间的动态存储分配;可以提高程序的编译效率和执行速度。
(1)指针的基本概念及指针变量的定义
1.基本概念
计算机的内存是以字节为单位的连续的存储空间,每个字节都有一个编号,这个编号称为地址。由于内存的存储空间是连续的,因此编号地址也是连续的。
变量名与内存中的一个地址相对应。
直接按变量的地址存取变量值的方式称为直接存取方式。
先通过一个变量a得到另一个变量b的地址,然后再进行存取变量值的方式得到b的值的方式称为间接存取方式。
2.定义方法
指针变量定义的一般形式
类型说明符 *标识符
*指针变量定义形式中的星号*不是变量名的一部分,它的作用是用来说明该变量是指针变量。
*如果一个表达式的值是指针类型的,即是内存地址,则称这个表达式是指针表达式。数组名代表数组的地址,是地址常量,也是指针表达式。
(2)指针运算
1.赋值运算
功能:将指针表达式的值赋给指针变量,即用指针表达式的值取代指针变量原来存储的地址值。
赋值运算符右侧的指针表达式指向的数据类型和左侧指针变量所指向的数据类型必须相同。
2.取地址运算
&标识符
功能:执行该表达式后返回"&"符后面名为"标识符"的变量(或数组元素)的地址值。
标识符只能是一个除register类型之外的变量或者数组元素。
int *e;int d;e=&d;
scanf("%d",e)等于scanf("%d",&d);
3.取内容运算
*指针表达式
"*指针表达式"的功能与"*"号后面"指针表达式"所指向的变量和数组元素等价。
取内容运算符"*"是单目运算符,也称为指针运算符或者间接访问运算符。
#include<stdio.h> int main(){
int x,*p;
x=;
p=&x;
printf("%d %p\n",x,p);//输出x的值和p的值(也就是x的地址)
x=;
//*p=x;这个语句可有可无
printf("%d %d",x,*p);
return ;
}
4.指针表达式与整数相加相减运算
p+n或p-n
表达式p+n的值=p的值+p所指向的类型长度*n;
表达式p-n的值=p的值-p所指向的类型长度*n;
只有p+n和p-n都指向连续存放的同类型数据区域,例如数组,指针加、减整数才具有实际意义。
5.自增自减运算
++p;p++;--p;p--;
都是使p指向下一个数据。
6.同类指针相减运算
得到两个同类型指针之间数据元素的个数
7.关系运算
比较指针变量所存的地址值的大小关系
#include<stdio.h> int main(){
int s=,a[],*p,*q;
for(p=a+,q=a;p>=q;p--){
scanf("%d",p);
}
q=a+;
p=a;
while(p<q){
s+=*p;
++p;
}
printf("%d\n",s);
return ;
}
8.强制类型转换运算
(类型说明符*)指针表达式
功能:将"指针表达式"的值转换成"类型说明符"类型的指针
float q,*i=&q;
int *p;
p=(int *)i+;//是将i的值转换成与p同类型的指针。如果i的值是无符号十进制数,
//则(int *)i+1指在整数基础上加上一个float型长度值
9.空指针
在没有对指针变量赋值之前,指针变量存储的地址值是不确定的,它存储的地址值可能是操作系统程序在内存中占据的地址空间中的一个地址,也可能是某一常驻内存的系统应用程序所占据的一个地址,还可能是内存中还没有分配使用的一块空间中的地址。因此,没有对指针变量赋初值而直接使用指针变量p进行scanf("%s",p);和*p表达式;形式的赋值运算可能会产生不可预料的后果,甚至会导致系统无法正常运行。
为了避免上述问题,通常会给指针变量付一个初值0.并把它成为空指针变量。
p='\0';p=0;和p=NULL;三个语句等价。'\0'的ASCII码值为0;NULL是在studio.h文件中定义的常数,其值为0。
(3)指针变量与一维数组
1.指针变量与一维数组之间的联系与区别
相同点:指针变量与一维数组不仅都可以用来处理内存中连续存放的一系列数据,而且采用统一的地址计算方法访问内存。因此任何使用下标变量完成的操作都可以使用指针变量来实现。数组名代表数组的首地址,其值为数组第一个元素的地址,一个一维数组名就是一个指向该数组第一个元素的指针。
不同点:指针变量是地址变量,可以改变其本身的值;而除了作为形参的数组名外,其它数组名是地址常量,地址值不能改变,不可以给除了作为形参的数组名之外的其他数组名赋值;用数组存取内存中的数据是通过其每个元素来实现的,而用指针变量存取内存中的数据是通过连续地改变指针的指向来实现的。
若有定义int a[5],*p=a,i;则有以下等价形式:
p[i]、*(p+i)、*(a+i)、a[i]四个表达式等价,都表示数组元素a[i];
&p[i]、p+i、a+i与&a[i]四个表达式等价,都表示数组元素a[i]的地址。
#include<stdio.h> int main(){
char s1[],s2[],*p1=s1,*p2=s2;
scanf("%s,",s1);
for(;*p1!='\0';p1++,p2++){
*p2=*p1;
}
printf("%s\0",s2);
return ;
}
上述代码会出现这种问题:
原因应该是s2的数组的结尾没有默认为'\0',导致后续的继续输出的乱码。
解决方法:
1).先将s2数组所有元素初始为'\0'。
#include<stdio.h> int main(){
char s1[],s2[],*p1=s1,*p2=s2;
for(int k=;k<;k++){
s2[k]='\0';
}
scanf("%s,",s1);
for(;*p1!='\0';p1++,p2++){
*p2=*p1;
}
printf("%s\0",s2);
return ;
}
2).将s2与s1相同的位置加上'\0'。
#include<stdio.h> int main(){
char s1[],s2[],*p1=s1,*p2=s2;
scanf("%s,",s1);
for(;*p1!='\0';p1++,p2++){
*p2=*p1;
}
*p2='\0';
printf("%s\0",s2);
return ;
}
gets(s)函数与scanf("%s:",&s)/* scanf("%s",s) */相似,但不完全相同,使用scanf("%s",&s);函数输入字符串时存在一个问题,就是如果输入了空格会认为字符串结束,空格后的字符将作为下一个输入项处理,但gets()函数将接收输入的整个字符串直到遇到换行为止。
2.字符串指针与字符串
下面介绍指针的方法定义和引用字符串
有两种方法将一个字符型指针变量指向一个字符串:
1)用赋初值的方式:
char *p="C Language";它是将存放字符串常量的存储区(或称无名数组)的首地址赋给指针变量p,使p指向了字符串中第一个字符c所在的存储单元,并将字符串的字符依次存入首地址开始的连续的存储单元中,系统在最后一个字符e的后面加上'\0'.
2)用赋值运算的方式
char *p;p="C Language";这与赋初值的结果完全相同。
可以改变指针变量p中的地址而使p指向另外的字符串,另外的字符串的长度不受限制,一旦p指向另外的字符串,并且没有另外的指针指向"C language",则此字符串将失踪,再也无法找到。
无论用赋初值还是用赋值运算的方式,利用字符型指针变量指向字符串常量,系统都是把字符串常量存储在只读存储区,不允许对字符串进行修改。
注意:在数组赋初值时,语句char s[11]="C language";不能等价于程序段char s[11];s[]="C Language";即数组可以在定义时整体赋初值。不可以利用赋值语句对数组整体赋值。
用指针变量打印一个字符串:
#include<stdio.h> int main(){
char *p="C language";
char *q="I am a Student!";
for(;*p!='\0';){
putchar(*++p);
}
printf("\n");
int i=;
while(q[i]){
printf("%c",p[i++]);
}
return ;
}
求输入的字符串的长度:
#include<stdio.h> int main(){
char s[],*p=s;
printf("Please input a string:\n");
gets(s);
while(*++p);
printf("Length of the string is %d\n",p-s);
return ;
}
用字符之指针指向格式字符串:
#include<stdio.h> int main(){
char *p,a='A',b='B';
p="A的ASCII码为%d,B的ASCII码为%d。\n";
printf(p,a,b);
return ;
}
(4)指针与函数
1.指针作为函数参数
函数可以有指针类型的参数。定义函数的指针类型参数与定义指针类型变量的方法类似。
#include<stdio.h>
//函数dis1和dis2达到相同的效果,即传入一个指向字符串的指针,输出这个字符串
void dis1(char *a){
printf("%s\n",a);
}
void dis2(char *a){
while(*a){
printf("%c",*a++);
}
printf("\n");
}
//dispaly1函数和display2函数具有相同的效果,利用字符数组名作为形参,这里字符数组名可看做字符型指针变量。
void display1(char *t){
char s[]="display1定义的字符串";
printf("%s\n",t);
t=s;
printf("%s\n",t);
}
void display2(char t[]){
char s[]="display2定义的字符串";
printf("%s\n",t);
t=s;
printf("%s\n",t);
}
int main(){
//1.用两种方法输出一个字符串
char *p="I am MenAngel";
dis1(p);
dis2(p); //2.用字符指针作形参和字符数组名作为形参
char *q="主函数中定义的字符串!";
display1(q);
printf("\n");
printf("%s\n",q);
printf("\n");
display2(q);
return ;
}
由本例的执行结果可以看出,在display函数中的t与主函数中的p都是指针变量,分别存在内存的不同区域,变量p与t存储的信息可以不同,变量p(实参)存储的值传递给变量t(形参)之后,变量t存储的值得变化对变量p存储的值没有影响。
指针作为函数的参数,在调用时传递的时地址,传递地址的方式有四种:
1)形参和实参都是数组名;
2)形参和实参都用指针变量;
3)形参用数组名,实参用指针变量;
4)形参用指针变量,实参用数组名。
由于c编译系统将形参数组名当作指针变量来处理,因此在子函数体内可以将形参数组名作为指针变量使用,可以在子函数体内给形参数组名赋值。
实例:将字符串中的字符按照逆序输出:
#include<stdio.h>
#include<string.h>
#define M 80
void rever(char *q){
int length,k;
char *p;
length=strlen(q);
printf("The length of this string is %d。\n",length);
for(p=q+length-;q<p;q++,p--){
k=*q;
*q=*p;
*p=k;
}
}
int main(){
char str[M];
printf("Enter a string which is less than 80 characters:\n");
scanf("%s",str);
rever(str);
printf("revers string is :%s\n",str);
return ;
}
这里传入的是形参,但是通过形参改变了原来实参的指向的数据内容,与前一个例子不同的是形参可能会改变实参指向的内容,但形参的指向改变,实参的指向并不会同时发生改变。
2.返回指针的函数
返回指针函数定义的一般形式:
类型说明符 *函数名(){}
实例:
1)将给定字符串的第一个字母变成大写字母,其他字母变成小写字母
#include<stdio.h>
#include<string.h> char *str(char *s){
int i=;
if(*s>='a'&&*s<'z'){
*s=*s-;
}
while(*(s+i)!='\0'){
if(*(s+i)>='A'&&*(s+i)<='Z'){
*(s+i)=*(s+i)+;
}
i++;
}
return s;
}
int main(){
char str1[];
printf("original string is:\n");
gets(str1);
printf("%s\n",str1);
printf("correct string is:%s\n",str(str1));
return ;
}
2)在给定的字符串中寻找一个特定的字符x,若找到x,则返回x在s中第一次出现的地址,并把s中该字符和该字符之前的字符按照逆序输出:
#include<stdio.h>
#include<string.h> int main(){
char *str(char *,char);
char s[];
char *p,x;
gets(s);
x=getchar();
p=str(s,x);
if(*p){
printf("%c",*p);
while(p-s){
p--;
printf("%c",*p);
}
}else{
printf("char %c not found",x);
}
return ;
} char *str(char *s,char x){
int c=;
while(x!=s[c]&&s[c]!='\0'){
c++;
}
return (&s[c]);
}
3.函数的指针和指向函数的指针变量
函数的名字有值,其值等于该函数存储的首地址,即等于该函数的入口地址,在编译时分配给函数的这个入口地址就称为函数的指针。
指向函数的指针变量定义的一般形式:
类型说明符(*标识符)(形式参数表);例如 int (*p)(int a,int b);其中p即为指向函数的指针变量。
*定义指向函数的指针变量时,形式参数表只写出各个形式参数的类型即可,也可以与函数原型的写法相同,还可以将形式参数表省略不写。
*指向函数的指针变量允许的操作:
1)将函数名或者指向函数的指针变量的值赋给指向同一类型函数的指针变量;
2)函数名或者指向函数的指针变量作为函数的参数。
3)可以利用指向函数的指针变量调用函数,调用函数是:
(*变量名)(实参列表)
其调用结果是使程序的执行流程转移到指针变量所指向函数的函数体。函数的地址值赋给指向函数的指针变量以后,指针变量就指向了该函数。
实例:
1)求多项式x^4+x-1当x=1.5,2.5,3.5,4.5时的值。
#include<stdio.h>
#include<math.h> double f(double z){
double d;
d=pow(z,4.0)+z-;
return d;
} int main(){
int i;
double r,x,f(double),(*y)(double);
y=f;
for(i=;i<=;i++){
x=i+0.5;
r=(*y)(x);//可用r=y(x)代替r=(*y)(x);
printf("x=%f,y=%f\n",i+0.5,r);
}
return ;
}
2)当x=15度,30度,45度时,求函数y=2sinx-cos2x的值:
#include<stdio.h>
#include<math.h> double calculate(double (*p1)(double),double (*p2)(double),double q){
return (*(*p1)(q)-(*p2)(*q));
} int main(){
double x;
x=3.141592653/;
printf("x=15,y=%10.6f\n",calculate(sin,cos,*x));
printf("x=30,y=%10.6f\n",calculate(sin,cos,*x));
printf("x=45,y=%10.6f\n",calculate(sin,cos,*x));
return ;
}
将函数作为函数的参数,实现灵活的函数的使用。与C#中的委托相似。