前言:在用C编程时,数组指针,指针数组,函数指针,指针做返回值和形参等常常困扰自己,现在做一点小总结!
一.数组指针
数组指针,即指向数组的指针,实质是指向数组首元素地址的指针,。从定义上我们可以看出,数组指针的本质是一个指针,但是它是一个二级指针(指向地址的指针(或者称之为:指向指针的指针)),更进一步说,二级指针变量存放的是其它指针变量的地址。
1.指向一维数组的数组指针
数组指针指向一个一维数组。指向一个数组的实质是:指向数组首元素的地址。
首先说一下操作符的优先级和结核性,操作符(),[],*的优先级:()==[]>*。可是()和[]的结合性是从左到右的,所以编译器遇到()和[]时会先从表达式的左边开始结合运算。在判断是数组指针还是指针数组声明时,就看变量名先和那个操作符结合,先和[]结合则为指针数组,先和*结合则是数组指针。
所以 int (*p)[5]是一个数组指针 ,而 int* p[5]是一个指针数组,指针数组和数组指针有着本质的差别:数组指针是一个二级指针;指针数组是一个数组,只是里面存放的元素是指针
int (*p)[5]中的5表示所指数组的长度。
首先通过一个例子来简单说明一下指向一维数组的指针
void main()
{
int a[3]={1,2,3};
int (*p)[3];//指向数组的指针(其实是指向数组首地址的指针)是一个二级指针
int i;
p=&a;
for(i=0;i<3;i++)
{
printf("%d,%d\n",i,(*p)[i]);
}
}
这里值得注意的是,p的赋值,因为p是一个二级指针,"p=a"是错误的(把数组a的首元素的地址赋给p,这里提醒大家一下:a是一个一级指针,本身是一个常量,其值为数组a首元素的地址,a=&a[0],所以a无法进行自增自减等元素,"a++,a--"都是错误的),应该是"p=&a"才能完成对二级指针的赋值操作。
2.指向二维数组的指针
其实所谓指向数组,就是指向数组的首地址,这与指向数组元素是有明显差别的,自然是一个二级指针概念。
为了能更好地理解数组指针,与普通指针及二级指针的区别,下面举例说明一下。
例如:{int a[4][5];int (*p)[5]=a;}这里a是个二维数组的数组名,相当于一个二级指针常量;
p是一个指针变量,它指向包含5个int元素的一维数组,此时p的增量以它所指向的一维数组长度为单位;
*p+i是二维数组a[0][i]的地址;
*(p+2)+3表示a[2][3]地址(第一行为0行,第一列为0列),*(*(p+2)+3)表示a[2][3]的值。
void main()
{
int c[][3] = {1,2,3,4,5,6};
int (*p)[3];//所指数组的长度
int i,j;
p = c;
for(i=0; i<2; i++)
for (j=0;j<3;j++)
printf("%p,%d\n ",*(p+i), *(*(p+i)+j));
printf("\n ");
}
注意:*(p+i)输出的是第i行首元素的地址,*(*(p+i)+j))是c[i][j]。
第二个例子我们联系着讲一下二维数组的初始化和数组指针的运用。
void main()
{
char c[][8] = {"chengdu","panda!"};
char (*p)[8];//指向数组长度,最好与二维数组的第二维相同
int i,j;
p = c;
for(i=0; i<2; i++)
for (j=0;j<8;j++)
printf("%p,%c\n ",*(p+i), *(*(p+i)+j));
printf("\n ");
}
*(p+i)等价于p[i],对于二维数组来言,p[i]只是第i行的首地址。
二维数组其实也可以看出一维数组,只是数组的元素还是个一维数组。其中数组a由{a[0],a[1]}组成,这里a[0],a[1]分别表示第0行和第1行的首地址(&a[0][0],&a[1][0])数组a[1]由{a[0][0],a[0][1],a[0][2]}组成
二维数组即可以通过int a[2][3]={{1,2,3},{4,5,6}};分行初始化,也可以通过int a[2][3]={1,2,3,4,5,6};不分行进行初始化。
对于字符数组还可以 char c[][8] = {"chengdu","panda!"};进行初始化。
二.指针数组
指针数组是数组元素为指针的数组(例如 int *p[3],定义了p[0],p[1],p[2]三个指针),其本质为数组。
我们以字符串为例,首先讲下初始化一个存放字符串的字符数组和一个指向字符串的指针(指向字符串的指针其实就是指向字符串的第一个字符)
char heart[]="I Love Shanghai!";//char heart[]={"I Love Shanghai!"}等价
char * head="I Love Shanghai!";
首先我们讲下二者的不同之处,1.heart是一个常量,head是一个变量 2.这点最值得注意,"I Love Shanghai!"作为字符串存储在常量区,假设现在有一个函数function(char *a),我们把head作为实参传递给函数的形参a
时,如果function(char *a)里有要改变指针所指内容时,会报错,因为"I Love Shanghai!"作为字符串存储在常量区,是不能进行更改的(指向常量数据的指针,所指内容是只读的,常量字符串不能修改其值),但是把heart作为实参传递给形参a时则不会报错,所以当我们要改变字符串的字符时,得用数组的形式定义初始化。
void function(char *a)
{
int length=strlen(a);
int i=0,k=0,j;
char b[]="chengdu";
for( ;i<length-1;i++)
{
if(a[i]=='S')
j=i;
}
for(k,j;j<length;j++,k++)
{
a[j]='0';
a[j]=b[k];
if (k>=strlen(b))
a[j]='\n';
}
}
void main()
{
char heart[]="I Love Shanghai";//char *heart="I Love Shanghai"则会报错
function(heart);
printf("%s",heart);
}
数组指针的定义和初始化,注意:字符串在赋值语句中做右值时,传递的是指针(第一个字符的地址)。
char *p[2]={"chengdu","panda"};
void main()
{
char *p[2]={"chengdu","panda"};
int i,j;
for(i=0;i<2;i++)
for(j=0;j<8;j++)
if(*(p[i]+j)!=NULL)
printf("%p,%c\n",p[i],*(p[i]+j));
}
或者
void main()
{
char *p[2]={"chengdu","panda"};
int i;
for(i=0;i<2;i++)
printf("%s\n",p[i]);
}
从上面的例子,应该可以看出指针数组的初始化,以及调用操作了,下面再举一个例子强化一下认识。
void main()
{
int c[][3] = {1,2,3,4,5,6};
int *p[2]={c[0],c[1]};
int i,j;
for(i=0; i<2; i++)
for (j=0;j<3;j++)
printf("%p,%d\n ",p[i], *(p[i]+j));
printf("\n ");
}
总结: 对于int *p[2],p[i]代表的是数组p第i个元素, 但是这个元素本身是一个指针(地址), 通过char c[][3] = {1,2,3,4,5,6};char *p[2]={c[0],c[1]};(c[0],c[1]是地址)初始化之后,可以用 *(p[i]+j) 来操作c[i][j].
三.函数指针
int max(int x,int y)
{
int z;
z=x>y?x:y;
return z;
}
void main()
{
int a=3,b=2,z;
int (*p)(int ,int);//带参数列表和返回数据类型
p=max;
z=(*p)(a,b);
printf("%d\n",z);
}
int (*p)( int,int ); 用来定义 p 是一个指向函数的指针变量,该函数有两个整形参数,函数值为整形。注意 *p 两侧的括号不可省略,表示 p 先与 * 结合,是指针变量,然后再与后面的 ( ) 结合,表示此指针变量指向函数,这个函数值 (即函数的返回值) 是整形的。如果写成 int *p ( int,int ) ,由于( )的优先级高于 *,它就成了声明一个函数P( 这个函数的返回值是指向整形变量的指针)。
赋值语句 p = max ; 作用是将函数 max 的入口地址赋给指针变量p。和数组名代表数组首元素地址类似,函数名代表该函数的入口地址。这时 p 就是指向函数 max 的指针变量,此时 p 和 max都指向函数开头,调用 *p 就是调用 max 函数。但是p作为指向函数的指针变量,它只能指向函数入口处而不可能指向函数中间的某一处指令处,因此不能用 *(p + 1)来表示指向下一条指令。
注意:
(1) 指向函数的指针变量的一般定义形式为:
数据类型 (*指针变量名)(函数参数列表),这里数据类型就是函数返回值的类型
(2) int (* p) ( int,int ); 它只是定义一个指向函数的指针变量 p, 它不是固定指向哪一个函数的,而只是表示定义这样一个类型的变量,它是专门用来存放函数的入口地址的。在程序中把哪一函数(该函数的值应该是整形的,且有两个整形参数)的地址赋给它,他就指向哪一个函数。在一个函数中,一个函数指针变量可以先后指向同类型的不同函数。
(3) p = max; 在给函数指针变量赋值时,只需给出函数名而不必给出函数参数,因为是将函数的入口地址赋给 p ,而不涉及 实参和形参的结合问题,不能写成 p = max(a,b);
(4) z= (*p)(a,b) 在函数调用时,只需将( *p ) 代替函数名即可,后面实参依旧。
(5) 对于指向函数的指针变量,像 p++ ,p+n.....是无意义的。
函数指针变量通常的用途之一就是把指针作为参数传递到其他函数。函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量,也可以是指向函数的指针也可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。
示例:指向函数的指针作为形参
int max(int x,int y)
{
int z;
z=x>y?x:y;
return z;
}
int min(int x,int y)
{
int z;
z=x>y?y:x;
return z;
}
int sub(int (*fun1)(int,int),int(*fun2)(int,int),int a,int b,int c)//定义指向函数的指针时,一定要带指向函数的参数列表和返回类型,形参的名字可以随意取
{
int z1,z2;
z1=(*fun1)(a,b);
z2=(*fun2)(b,c);
return(z1*z2);
}
void main()
{
int a=2,b=5,c=4;
int z;
z=sub(max,min,a,b,c);
printf("%d\n",z);
}
四.指针作为形参和返回值的注意事项
1.指针作为返回值
内存可以分为:堆内存(程序员自己开辟,释放),栈内存(存放局部变量,系统自己管理,当作用域结束之后,系统将之收回),全局静态区(存放全局变量(常量,字符串等)
当指针作为返回值返回时,一定要保证该指针值是一个有效的指针。
case1.返回值指向全局变量(全局变量存放在全局静态区,函数调用结束后依然存在于内存中)
long score[5]={1,2,3,4,5};//定义一个全局变量
long *getmax(long *a,int size)
{
long temp=score[0];
int i,pos;
for(i=0;i<size;i++)
{
if(a[i]>temp)
{temp=a[i];pos=i;}
}
return &score[pos];
}
void main()
{
long* p=getmax(score,5);
printf("%p\n",p);
}
下面的例子,与之对比,返回一个局部变量则是错误的。原因是这里的数组score是一个局部变量,函数调用结束后该数组所占用的内存空间将被释放,因此,return &score[pos]虽然返回了数组元素的指针,但该数组元素已经不存在了,这个指针也就是一个无效的指针,(需要特别指明的是,当返回指向局部变量的指针时,虽然用printf也可以打印出指针来,但是此时的指针时所指的是垃圾,没有任何意义,会给代码挖坑)这样的代码可能导致程序的异常。
long *getmax(long *a,int size)
{
long score[5]={1,2,3,4,5};//定义一个局部变量
long temp=score[0];
int i,pos;
for(i=0;i<size;i++)
{
if(a[i]>temp)
{temp=a[i];pos=i;}
}
return &score[pos];
}
void main()
{
long* p=getmax(score,5);
printf("%p\n",p);
}
case2 返回动态内存(堆内存),用malloc建立
int* getadress(int size)
{
int *p;
p=(int *)malloc(sizeof(int)*size);
return p;
}
void main()
{
int * p=getadress(5);
printf("%p\n",p);
}
case3.返回指针指向静态局部变量
int* getadress()
{
static int i=10;
int *p=&i;
return p;
}
void main()
{
int * p=getadress();
printf("%p\n",p);
}
case4.返回形参列表中的指针变量(因为p是从主调函数传过来的指针,所以当func结束时依然存在,所以作为返回值)
int* getadress(int *a)
{
int *p=a;
return p;
}
void main()
{
int i;
int *p=getadress(&i);
printf("%p\n",p);
}
case5 .返回指向字符串常量的指针
char * getadress()
{
char *p="shanghai";//这个可以用,字符串常量放在全局静态内存区,但是返回的指针并不能改变字符串,只能读
//char p[]="shanghai";//错的,指针乱指,数组存放在栈上
return p;
}
void main()
{
char *p=getadress();
printf("%p,%s\n",p,p);
}
以上是我整理的5种case,可以作为返回值。
2.指针作为形参
函数返值有两种方式:1.通过返回值return函数来给出返回值 2.通过形参指针来改变原来实参指针所指向变量的值,这就要求函数体内的程序必须修改形参指针所指内容,而不是修改形参指针的值,修改形参指针的值并不会改变原来实参指针所指向变量的值。
编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p所指内容(即_p所指变量的值),就导致参数p的内容作相应的修改,但是改变_p的值时没有用的,这样并不会导致参数p所指内容发生修改。
case1.在函数体内改变指针形参的值,并不会改变指针实参的值
void MyMemory(int *s)
{
s=(int*)malloc(sizeof(int));
}
void main()
{
int *p=NULL;
printf("%p\n",p);
MyMemory(p);
printf("%p\n",p);
}
case2.在函数体内改变指针形参的值
第一个例子:改变指针变量的值的方式(采用二级指针)
void MyMemory(int **s)
{
*s=(int*)malloc(sizeof(int));
}
void main()
{
int *p=NULL;
printf("%p\n",p);
MyMemory(&p);
printf("%p\n",p);
}
第二个例子:改变指针变量所指变量的值
void fun(int *p)
{
*p=100;
}
void main()
{
int a=10;
int *b=&a;
printf("%-d\n",a);
fun(b);
printf("%-d\n",a);
}
总结:要改变指针变量的值须用二级指针作形参,要改变指针变量所指变量的值,则须在函数体内改变形参指针所指的值。
PS:本人本科硕士读的都是核工程,刚毕业转行作算法,弄C时间太短,希望大家轻虐。本文所有程序均run过,但是编译头没有给出!