从零开始学编程---第一步-C语言(二十二)

时间:2014-03-08 22:47    点击:

指针变量存储着地址
int a;
int *pa=&a
如果输入可以用如下两种
scanf("%d",&a),或scanf("%d",pa)
pa代表着指向变量的地址,*pa代表着指向变量的值

指针的间接访问到底有什么好处?
比如,*pa可以代表任何变量的值,只需要改变它的指向
进行多个变量操作时,可以用指针作为代理操作
如果有10对变量要比较大小,我只需要定义一对指针,而我只需要记住指针名,而不用不记这10对变量名
一对指针只要修改地址(也就是指向),就可以代替不同的变量进行比较,而名字可以不变
也不需要变量本身(只是名字本身)进行直接操作,指针作为代理代替它们操作

这些方便之处你会慢慢感受到的,指针更多的好处还在后面

下面说说数组跟指针的关系
其实数组名就是一个指针,即地址
int a[5] 这是一个int类型的数组
它有5个元素,分别为a[0],a[1],a[2],a[3],a[4],这5个都是变量,和普通变量没多大区别,就名字是规矩的
这个数组的名字是a,可别弄成a[5]了
a是一个指针常量(地址无法改变,不能指向其他变量), 它只存储着数组第一个元素的地址,即a[0]的地址
所以我们用scanf()输入字符串的时候不需要&
char a[5]
scanf("%s",a)
为什么只存储着第一个元素的地址,那其他元素怎么办?
C语言中,数组的内存分配是连续的,也就是连在一起的
如果数组第一个元素的地址是1000,那第2个则是1001
所以知道了第一个元素的地址,就可以知道其他元素的地址

一个变量有一个地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。
一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的。每个数组元素按其类型不同占有几个连续的内存单元。一个数组元素的首地址也是指它所占有的几个内存单元的首地址。

下面看程序
#include <stdio.h>

main()
{
     int a[5],*p;
     p=a;   //或者p=&a[0]

}

这里同时定义一个数组和一个指针,并把指针指向数组(也就是指向数组第一个元素)
这里初始化也可为int a[5],*p=a;
C语言规定,数组名代表数组的首地址,也就是第0号元素的地址。因此,下面两个语句等价:
p=&a[0];
p=a;

这里p,a,&a[0]3个都是同一地址

C语言还规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。
引入指针变量后,就可以用两种方法来访问数组元素了。
如果p的初值为&a[0],则:
1)     p+i和a+i就是a[i]的地址,或者说它们指向a数组的第i个元素。
p+1或a+1 这2句是等价的,p+1或a+1这里绝对不是地址+1,而是指向数组的下一位元素的地址
比如p是指向a[0],那么p+1就是指向a[1],p+1就相当于 p+1=&a[1]
p+i则是指向a[i],i是几,数组元素就对应是几

2)     *(p+i)或*(a+i)就是p+i或a+i所指向的数组元素,即a[i]。例如,*(p+5)或*(a+5)就是a[5]。
3)     指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价。
就是说如果指针变量指向数组,p指向数组a,那么数组名就可由p代替,即p[5],就好比普通变量可以换个名字出现(就是指针),这里数组也可换个名字,但a[5]还是a[5],并没有被替代
根据以上叙述,引用一个数组元素可以用:
1)     下标法,即用a[i]形式访问数组元素。在前面介绍数组时都是采用这种方法。
2)     指针法,即采用*(a+i)或*(p+i)形式,用间接访问的方法来访问数组元素,其中a是数组名,p是指向数组的指针变量,其处值p=a。

所以打印数组第1个元素可以用3种方法
printf("%d",a[0]);
printf("%d",*a);
printf("%d",*(a+0));

上面a本身就是一个指针,为什么我还要定义个p来指向?
因为a是指针常量(不可改变的),而p是指针变量
p可以p++(这里和p+1不同,p是自身发生了改变,即p=p+1,它开始指向数组第2个元素),而a不可以a++,a它只能指向数组第一个元素,而p可以指向数组任何一个元素

一定要熟记数组的各种形式输出
#include <stdio.h>

main()
{
     int a[5]={1,2,3,4,5},*p,i;
     p=a; 
     puts("输入一个数(0—4)");
     scanf("%d",&i);
     printf("%d\n",a[i]);
     printf("%d\n",p[i]);
     printf("%d\n",*(a+i));
     printf("%d\n",*(p+i));
     printf("%d\n",*a);
     printf("%d\n",*p);


}

下面说说指针变量的自增自减,这里只说自增,自增明白了,自减也就会明白
之前说过
a++   ++a       ++在后是先运算后自增(先执行后自增) ++在前是先自增后运算(执行)
下面看这个程序
#include <stdio.h>

main()
{
     int a[5]={10,20,30,40,50},*p=a;
     printf("%d\n",*(p++));
     printf("%d\n",*p);
     p=a;
     printf("%d\n",*(++p));
     printf("%d\n",*p);
     p=a;
     printf("%d\n",*p++);
     printf("%d\n",*p);
     p=a;
     printf("%d\n",++*p);
     printf("%d\n",*p);
     p=a;
     printf("%d\n",(*p)++);
     printf("%d\n",*p);
}

p=a这里是指针复位,重新指向a存储的地址,并非a的地址啊
首先是*(p++),就是指针自增指向数组下一位元素的地址,在取内容,由于++在后,所以先执行,就是打印*p 等于10 随后p++ 再打印20
*(++p),这个先自增后执行,所以先打印20,最后还是打印20
*p++等价于*(p++),不用重复了
++*p这里不等价于*(++p),*(++p)等价*++p,这里是*p的值先自增,所以先打印11,最后也打印11,指针变量p没任何变化
(*p)++表示p所指向的元素值加1,也就是*p自增,由于a[0]已经被++*p改变成11,所以这里先打印11,后打印12,指针变量p没任何变化


有点头晕吧,这些需要慢慢来,多用了几次也就慢慢知道怎么回事了

提醒一点,定义了指针最好给他初始化,否则不注意的时候会出错
#include <stdio.h>

main()
{
     int a,*p;
     a=100;
     *p=a;
     printf("%d",*p);
}

p的值是不确定的,它可能指向某一重要位置,突然改写它会导致出错

现在做一道题,颠倒数组里的数字
例如数组a 元素1,2,3,4,5,颠倒元素5,4,3,2,1再存储到数组a里

相信颠倒着打印大家应该会把,现在我们是把数组改变颠倒

在此之前先做把数组a{1,2,3,4,5}颠倒元素再存储到数组b里
这个很简单,就是把数组a的最后一个元素存储到b数组的第一个元素,a数组倒数第2个元素存储到b数组第2个元素,以此类推
代码如下:
#include <stdio.h>

main()
{
     int a[5]={1,2,3,4,5},b[5],i;
     for(i=0;i<5;i++)
     {
         b[4-i]=a[i];
     }
     for(i=0;i<5;i++)
         printf("%d ",b[i]);
}

现在用指针做一次,如下
#include <stdio.h>

main()
{
   int i,a[5],b[5],*p;
   p=a;
   for(i=1;i<=5;i++)
   *p++=i;
   *p--;
   for(i=0;i<5;i++)
   b[i]=*p--;

   puts("数组a:");
   for(i=0;i<5;i++)
   printf("%d ",a[i]);
   putchar('\n');
   puts("数组b:");
   for(i=0;i<5;i++)
   printf("%d ",b[i]);

  
}

现在解释下这一段
int i,a[5],b[5],*p;
   p=a;
   for(i=1;i<=5;i++)
   *p++=i;
   *p--;
   for(i=0;i<5;i++)
   b[i]=*p--;


指针p指向数组名a,也就是p=&a[0]
循环i 1 2 3 4 5
*p++=i; *p++等价*(p++) p++表示指向数组元素下一位 *是取当前地址的内容
*p++=i i等于1 *p++ 是++在后,先执行后自增,它一开始就是指向数组第一位,先执行*p=i,然后p++,数组第一位被赋值1,然后指针p指向第2位,后面就清楚了把
把1,2,3,4,5赋值给a数组后,后面来个*p--;这是为什么?
因为赋值5的时候,由于是先执行后自增,p最后又自增的一次,此时它指向a数组第6位(第6位数据是未知的)
所以这里*p--;让p重新指向第5位
然后从第5位开始倒序把p指向地址的值赋值给b数组
b[i]=*p--;

不知道我这样说明白了没有?

现在回归正题,让数组a{1 2 3 4 5}变成5,4,3,2,1
现在在一个数组里实现数字颠倒
就是把最后一位跟第一位交换,倒数第2位跟第2位交换。。。
例如
1,2,3,4,5
5跟1交换 5,2,3,4,5
4跟2交换 5,4,3,2,1
3不动,总共交换两次即可
那么如果是N个数字,怎么知道要交换多少次呢
其实交换次数就是N的一半
比如5个数需要交换2次,3个数交换1次,6个数交换3次。。
奇数除以2不需要管余数,偶数正常除以2

代码如下
#include <stdio.h>

main()
{
     void diandao(int[],int);
     int a[5]={1,2,3,4,5};
     diandao(a,5);
     for(int i=0;i<5;i++)
         printf("%d ",a[i]);
     printf("\n");
}

void diandao(int a[],int n)
{
     int i,j,t,m=n/2;
     for(i=0;i<m;i++)
     {
         j=n-1-i;
         t=a[i];
         a[i]=a[j];
         a[j]=t;
     }
    

}

需要理解的就是这里
int i,j,t,m=n/2;
     for(i=0;i<m;i++)
     {
         j=n-1-i;
         t=a[i];
         a[i]=a[j];
         a[j]=t;
     }

m是交换次数   n是有多少个数字
j=n-1-i; 这是得到数组的最后一个下标 别说不知道-1的含义啊

t=a[i];
a[i]=a[j];
a[j]=t;

t是作为交换的中间变量
这里是最后面下标数组元素依次跟最前面下面数组元素交换
循环条件为交换次数

for(i=0;i<m;i++)
     {
         j=n-1-i;
         t=a[i];
         a[i]=a[j];
         a[j]=t;
     }
也可改为
     for(i=1;i<=m;i++)
     {
         j=n-i;
         t=a[i-1];
         a[i-1]=a[j];
         a[j]=t;
     }


上面题目稍做改动就可以用指针来解

#include <stdio.h>

main()
{
      void diandao(int*,int);
      int a[5]={1,2,3,4,5},*p;
      p=a;
      diandao(p,5);
      for(int i=0;i<5;i++)
          printf("%d ",a[i]);
      printf("\n");
}

void diandao(int *p,int n)
{
      int i,j,t,m=n/2;
      for(i=0;i<m;i++)
      {
          j=n-1-i;
          t=p[i];
          p[i]=p[j];
          p[j]=t;
      }
    

}


另外也可以改成颠倒字符
#include <stdio.h>

main()
{
     void diandao(char[],int);
     char a[50];
     puts("输入一个字符串");
     for(int i=0;i<50;i++)
     {     a[i]=getchar();
         if(a[i]=='\n')
         {break;
         i--;}
     }
     diandao(a,i);
     for(int j=0;j<=i;j++)
         putchar(a[j]);

}

void diandao(char a[],int n)
{
     int i,j,t,m=n/2;
     for(i=0;i<m;i++)
     {
         j=n-1-i;
         t=a[i];
         a[i]=a[j];
         a[j]=t;
     }
    

}

注意
if(a[i]=='\n')
         {break;
         i--;}
因为i值的数组下标是回车字符,所以i--回到前一个数组下标

上面程序实际就是字符颠倒


两个指针变量可以进行运算,但只能运行减法运算
两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址) 相减之差再除以该数组元素的长度(字节数)。

看代码
#include <stdio.h>

main()
{
     int a[5]={1,2,3,4,5},*p1,*p2;
     p1=a,p2=&a[4];
     printf("%d",p2-p1);
}

p1指向数组第1位元素,p2指向数组最后一位元素
它们相减会得到4,因为它们相距4个元素
如果是p1减p2则是-4

指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。例如:
p1==p2表示p1和p2指向同一数组元素
p1>p2表示p1处于高地址位置
p1<p2表示p1处于低地址位置

值得一提的是,两个指针变量之间的运算只有指向同一数组的两个指针变量之间才能进行运算, 否则运算毫无意义。


前面有个程序是颠倒字符串,现在我们来写个复制字符串的程序
例如字符数组a是hello 把hello复制到字符数组b里
其实有个C语言自带的strcat()函数可以实现这个功能,详见本贴第40楼
但是我们现在用指针的方法来实现这功能
这个道理很简单,就是把a数组元素依次用循环赋值给b数组的各个元素
程序如下:
#include <stdio.h>


main()
{
     void fuzhi(char *a,char *b);
     char a[]="hello world",b[20],*p;
     p=b;
     fuzhi(a,p);
     puts(a);
     puts(b);
    
    
}
void fuzhi(char *a,char *b)
{
     while((*b=*a)!='\0')
     {
         a++;
         b++;
     }
}


注意void fuzhi(char *a,char *b)
可以改成void fuzhi(char a[20],char b[20])
但是数组必须给个大小,由此可见,第一种很方便,以后就用第一种拉
while((*b=*a)!='\0')这里怎么理解?
把a数组元素的每个值赋给b数组的元素 最后赋值到\0的时候停止循环
为什么这里可以a++,b++,不是说数组名是个常量,不可改变的吗
因为这里的a,b并不是数组名本身,它们只是形参,它们在这里就相当于是指针变量

此程序还可简化,如下
#include <stdio.h>

main()
{
     void fuzhi(char *a,char *b);
     char a[]="hello world",b[20];
     fuzhi(a,b);
     puts(a);
     puts(b);
    
    
}
void fuzhi(char *a,char *b)
{
     while(*b++=*a++);
}


现在我们做个求一个数字数组的最大数和最小数
可以用冒泡的方法,但这里不用
先上代码
#include <stdio.h>

main()
{
     void max_min(int[],int);
     int a[5];
     puts("输入五个数");
     for(int i=0;i<5;i++)
     scanf("%d",&a[i]);
     max_min(a,5);
    
}

void max_min(int a[],int n)
{
     int max,min;
     max=min=a[0];
     for(int i=1;i<5;i++)
         if(max<a[i]) max=a[i];
         else if(min>a[i]) min=a[i];

     printf("max=%d,min=%d\n",max,min);
    

}

关于这里
max=min=a[0];
for(int i=1;i<5;i++)
if(max<a[i]) max=a[i];
else if(min>a[i]) min=a[i];

是把a[0]作为最大数和最小数来看,程序里是可以这样的
等于就是假设a[0]既是最大数又是最小数
假如a[0]=1,就是max=1 min=1 
然后a[0]依次和后面数组元素比较
假如后面的数大于a[0],则max就等于那么数
假如后面的数小于a[0],则min就等于那么数
最后就是max和min比较所有数

这里for循环语句没加大括号后面却跟了2条语句,为什么
因为for如果只跟一条语句可以不加大括号,也就是只能执行一条语句
上面两条语句,只有一条可以执行,所以可以这样的
但是判断条件是同时判断的
就是说比较大和小是同时比较的,不是说先比完了大的再去比小的

稍微改下就是指针解了,其实一样,只是你可能不太习惯这种用法
#include <stdio.h>

main()
{
     void max_min(int[],int);
     int a[5];
     puts("输入五个数");
     for(int i=0;i<5;i++)
     scanf("%d",&a[i]);
     max_min(a,5);
    
    
        

}

void max_min(int a[],int n)
{
     int max,min,*p;
     p=a;
     max=min=*p;
     for(int i=1;i<5;i++)
         if(max<*(p+i))
         max=*(p+i);
         else if(min>*(p+i))
         min=*(p+i);

     printf("max=%d,min=%d\n",max,min);
    

}


 

来源:幻想编程//所属分类:站长原创/更新时间:2014-03-08 22:47
顶一下
(8)
100%
踩一下
(0)
0%
上一篇:从零开始学编程---第一步-C语言(二十一)
下一篇:从零开始学编程---第一步-C语言(二十三)
相关内容