四、指针(小结)

A.定义:

          一个地址即为一个指针,是一个常量,所谓指针变量是指一个来存储指针(地址)的变量;

ep1:int a=10;

        int *b=&a;

这里,&a就是一个指针,是一个常量,而b就是一个变量,用来存储a的地址的。

B.指针变量的定义和引用:

a定义:

         格式:   [存储类型]   数据类型符  *变量名;

         注意:1.*和变量名之间可以有若干空格;

                 2.int *p定义的是指针变量p,而不是*p;

b赋值(三种方法):

                 1.指针变量初始化        int a;int *p=&a;

                 2.赋值语句法              int a;int *p;p=&a;

                 3.转移  int a=20;int *p,*q;p=&a;q=p;

          注意:在第三种方法中,一般而言只能同类型之间相互赋值。如果=两边不同类型,则需要强制转换,但是强制转换后他们引用内存单元对应的值是不一样的;

          ep2:int a;

                  int pi;

                  char pc;

                  pi=&a;

                  pc=(char *)pi;

          如上,引用pi就相当于引用a的整个内存单元的值,而引用pc就相当于引用a的第一个字节所对应的值(在vc6.0中,a对应4个字节,从低到高,引用pi就是将a4个字节存储的内容全部引用,引用pc就是将a的地址最低的一个字节存储的内容引用);

C.零指针与空类型指针

    零指针:指针变量的值为零的值的指针

   int *p=0;

   int *p=NULL;

   这两种都可以表示零指针;

  空类型指针(void):使用时需进行强制转化

D:指针变量的引用

      注意:1.*在指针定义是表示类型说明符,在表达式中表示运算符,意为取内容;

              2.*是单目运算符,优先级同++、--一样。

              3.在vc6.0中指针变量需要4个字节的存储单元;

E:指针和地址运算

     指针可以进行简单的加减运算,但不能进行乘除法运算;

     加减运算规则:

    ptype a=值;

    ptype *p;

     p=&a;(假设&a的值为ADDR)

    则p+(-)n=ADDR+(-)n*sizeof(ptype)

   另外,指针变量之间可以进行==、>、<的判断;

F.指针与数组

1.数组的指针:数组的指针实际上就是数组在内存中的起始地址;

用两段代码来解释吧。

代码1:

int a[10];

int k;

for(k=0;k<10;K++)

   a[k]=k;

代码二:

int a[10];

int k;

for(k=0;k<10;K++)

     *(a+k)=k; 

代码一和代码二运行的结果是完全相同的;变量名a就是数组的起始地址,*(a+k)就是对a[k]的引用

      地址    数组元素    指针   

2000 a[0] ←a      
2004 a[1] ←a+1      
2008 a[2] ←a+2      
2012 a[3] ←a+3 注意:    
2016 a[4] ←a+4 a+k &a[k]
2020 a[5] ←a+5 *(a+k) a[k]
2024 a[6] ←a+6      
2028 a[7] ←a+7      
2032 a[8] ←a+8      
2036 a[9] ←a+9      

 

 

 

 

 

 

 

当然,你也可以单独设置一个指针变量指向数组的起始地址

int a[10];

int *p=&a;(int *p=&a[0];)

对应的关系就如下表:

  2000 ←指针p所占据的内存单元    
    地址 元素
2000 a[0] ←a p *a *p p[0]
2004 a[1] ←a+1 p+1 *(a+1) *(p+1) p[1]
2008 a[2] ←a+2 p+2 *(a+2) *(p+2) p[2]
2012 a[3] ←a+3 p+3 *(a+3) *(p+3) p[3]
2016 a[4] ←a+4 p+4 *(a+4) *(p+4) p[4]
2020 a[5] ←a+5 p+5 *(a+5) *(p+5) p[5]
2024 a[6] ←a+6 p+6 *(a+6) *(p+6) p[6]
2028 a[7] ←a+7 p+7 *(a+7) *(p+7) p[7]
2032 a[8] ←a+8 p+8 *(a+8) *(p+8) p[8]
2036 a[9] ←a+9 p+9 *(a+9) *(p+9) p[9]

 

 

 

 

 

 

 

 

 

这蛮简单的,通过这两组表就非常容易理解了;

2.二维数组的指针

在这个里面个人以为,要非常清晰的理解行指针与列指针的含义。

用一个表达式来概括:

*(*(a+i)+j)

a:第0行的地址;

a+i:第i行的地址;

*(a+i)第i行第0列的地址;

*(a+i)+j第i行第j列的地址;

*(*(a+i)+j)第i行第j列的元素;

这样就对指针表示二维数组的理解非常清楚了,再定义一个指针变量 int *p;,就可以对同一个数组元素有各种不同的表示方法了;

例如:

a[i][j]

*(a+i)[j]

*(*(a+j)+j)

*(a[i]+j)

G:指针数组与数组指针

其实懂了行指针与列指针,这也是蛮好理解的

指针数组(int *p[20]):列指针(就是列地址,比如a[0],a[1]等),定义一组数组元素全为指针变量的数组;

数组指针(int (*p)[20]):行指针(就是行地址),定义一个指向大小为20的int型数组的指针;

区别:指针数组是定义一组指针,而数组指针是定义一个指针。

F:指针与字符串

1.字符串给指针赋值有以下两种方式:

a:格式  char  *字符指针变量名=字符串常量;

            char  *pstr="I Love xiao qian!" ;

b: 格式  char  *字符指针变量名;

            字符指针变量名=字符串常量;

            char  *pstr;

             pstr="I Love xiao qian!" ;

2.字符串的引用也有以下两种方式:

a:逐个单字符引用:

1 #include <stdio.h>
2 
3 void main ()
4 {
5 char *pstr="I Love XiaoQian!";
6 for (;*pstr++!='\0';)
7      printf("%c",*(pstr-1));
8 }

b:整体引用:

1 #include <stdio.h>
2 
3 void main ()
4 {
5 char *pstr="I Love XiaoQian!";
6      printf("%s",pstr/*地址*/);
7 }

注意:如果一个指针没有指向一个有效内存就被引用,则其被称为“野指针”操作或空指针赋值;

一个比较好的例题:

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 void main ()
 5 {
 6 int i,j,k;
 7 char *pcity[]={"wuhan","beijing","shanghai","tianjin","guangzhou",""};
 8 char *ptemp;
 9 
10 for (i=0;strcmp(pcity[i],"")!=0;i++)
11 {
12     k=i;
13     for (j=i+1;strcmp(pcity[j],"")!=0;j++)
14         if (strcmp(pcity[k],pcity[j])>0)
15             k=j;
16     if (k!=i)
17         {
18         ptemp=pcity[i];
19         pcity[i]=pcity[k];
20         pcity[k]=ptemp;
21         }
22 }
23 for (i=0;strcmp(pcity[i],"")!=0;i++)
24     printf("%s  ",pcity[i]);
25 printf("\n");
26 }

H:指针与动态内存分配

void *malloc(unsigned int size);     说明:用于分配若干字节的内存空间,返回一个指向该区域地址的指针;若系统不能提供足够的内存单元,函数将返回一个空指针;注:n*sizeof(int)

void *calloc(unsigned int num,unsigned int size);     说明:用于给若干同一类型的数据项分配连续的存储空间,其中每个数据的长度单位为字节,且系统自动存储单元的值置为0;   

void *realloc(void *p,unsigned int size);    说明:将指针*p所指向的存储空间大小改为size个字节

void  free(void *block);   说明:动态内存分配之后必须用free()函数来释放;block是连续空间的首地址

动态内存分配遵循的规则:分配、判断、释放,三位一体;

I:多级指针

a:定义:一般来说,n级指针变量其内容是存放一个n-1级指针变量的地址(以下我们以二级指针为例)。

    定义格式:[存储类型]   数据类型符   **变量名;/*数据类型符是二级指针指向最终目的变量的存储类型*/

    **:在定义是表示说明符,**在表达式中表示运算符,这点同指针的定义时*的作用是一样的;

 1 main()
 2 {
 3  static int a[5]={1,3,5,7,9};
 4  int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]}; 
 5 
 6 int **p,i; p=num;
 7  for(i=0;i<5;i++)
 8 { printf("%d\t",**p);
 9  p++; 
10 } 
11 }

J:指针作为函数参数

      要实现函数的传址调用,在定义形参是就必须使用指针型函数。

 1 #include <stdio.h>
 2 #include <string.h>
 3 void alltrim(char *psstr,char *pdstr)
 4 {
 5     char *pstart,*pend;
 6     
 7     //将pstart指向左边非空格的第一个位置
 8     pstart=psstr;
 9     while(*pstart==' ')
10         pstart++;
11     //将pend指向右边非空格的第一个位置
12     pend=pstart+strlen(pstart)-1;
13     while(pend>pstart&&*pend==' ')
14         pend--;
15     //将pstart所指向的字符到pend所指向的字符复制到pdstr中
16     while(pstart<=pend)
17         *pdstr++=*pstart++;
18     *pdstr='\0';
19 }
20 int main( )
21 {
22     char *pstr,str[20];
23     pstr=   "Good Bye!  ";
24     printf("%s\n",pstr);
25     alltrim(pstr,str);
26     printf("%s",pstr);
27 
28 }

还有个比较经典的例子是书上P291的例9-13,我给出最经典的部分:

1 p=(int **)malloc(row*sizeof(int *));
2 for(i=0;i<row;i++)
3     p[i]=(int *)malloc(col*sizeof(int));

这是动态为二维数组分配内存,先分配row个行,然后在每个行的基础上为每个列分配动态内存空间

通过图像可知,行与行之间的地址可能是不连续的。

释放:

1 for(i=0;i<row;i++)
2     free(p[i]);
3 free(p);

先释放行,再释放列;

K:关于const

     const作用:为了避免程序员修改形参的值,可以将参数设定为常态形参。

对于指针型参数来说,常态有一下两种含义:

1.指形参本身是常态

1 void func(int * const p)
2 {
3     int a[10];
4     *p=5;//正确
5     p=a;//将引起编译错误
6 }

 

不能对指针变量本身做修改

2.指针所指的内存单元是常态

1 void func(const int *p)
2 {
3     int a[10];
4     *p=5;//将引起编译错误
5     p=a;//正确
6 }

 

另外还有一点特别值得注意的是:如果程序想通过函数为指针变量分配内存,则形参必须是一个指针的指针。

/*因为二级指针在函数中引用后是一级指针的地址被赋值,而一级指针在函数中引用后是目标变量内存的地址,是一个常量,不能被赋值*/

L:指向函数的指针——函数指针

  定义:一个函数在编译时,被分配了一个入口地址,用函数名来表示,这个地址就称为该函数的指针。

  定义格式:  函数类型 (*指针变量)([形参类型1],[形参类型2],[形参类型3],[形参类型4])

说明:1.函数类型为函数指针所指向函数的返回值类型;

        2.指针变量是专门存放函数入口地址,可指向返回值的类型相同的不同函数

        3.“*指针变量”外的括号不能缺,否则就成了返回指针值得函数了(指针函数)

    赋值:  函数指针=[&]函数名;

1 int max (int a,int b)
2 {
3     return(a>b?a:b);
4 }
5 int (*p)(int,int);
6 p=max;

调用格式:函数指针变量([实参类型1],[实参类型2],[实参类型3],[实参类型4]);或(*函数指针变量)([实参类型1],[实参类型2],[实参类型3],[实参类型4]);

ep:

     p(2,3)或(*p)(2,3);//等价于max(2,3)

M:指针作为函数的返回值——指针函数

定义格式:函数类型 *函数名([形参类型1],[形参类型2],[形参类型3],[形参类型4])

注意:1.其他函数调用指针函数获取指针后,要在适当的时候,调用free函数释放这个指针;

         2.如果一个函数返回一个指针,不能返回auto型局部变量的地址,但可以返回static型变量的地址(返回的指针对应的内存空间不能因该指针的返回二被释放掉)。

            一般而言有以下三种:a、函数中动态内存分配的首地址;

                                        b、函数中静态变量或全局变量对应的的存储单元的首地址;

                                       c、通过指针形参所获得的实参的有效地址;

N:带参数的main函数

main (int argc,char *argv[])

命令行格式:命令名  参数1  参数2  参数3....(DOS提示符下命令行的一般形式为:    C:\>可执行文件名 参数 参数 ……;)

注意:1.参数之间以一个或多个空格隔开

         2.argc参数表示了命令行中参数的个数(注意:文件名本身也算一个参数),argc的值是在输入命令行时由系统按实际参数的个数自动赋予的。