C语言:随笔5--指针1

指针虽然是一个重要的概念,但是一点也不复杂。但他的确是C语言的精华。

正确而灵活的运用它,可以有效的表示复杂的数据结构;能动态的分配内存;能方便的使用字符串,有效而方便的使用数组。

为了说清楚什么是指针,必须弄清楚数据在内存中是如何存储的,又是如何读取的。

那就从一个美丽的故事开始吧。。。。

比如,昨天小A去图书馆借一本“我是美女”这本书的时候,在路上不小心碰到了一个帅哥,然后小A就非常勇敢的冲了上去一把掏出手机,文了该帅哥加的电话号码和地址,然后帅哥觉得小A实在长相一般不肯告诉他家的电话号码,就只是说了他家的地址,他是住在单身公寓的102号房,那这样小A就可以去找他了哈哈,因为小A已经知道了他家的地址了,那这就跟我们的内存挂钩了,我们知道内存有两个属性,第一个内存是一个线性的存储空间,有两个属性一个是地址,另一个是地址上边存放的数据。OK,内存是用来存放数据的,就像我们的地址,每一个门牌号码都有一个家,家里面就会放帅哥,就是一样的道理。我们通过地址才能够索引到某个我们要索引的内存,如果我们不知道他们的地址门牌号我们应该怎么找呢,不能一个个挨家敲门吧。所以,我们的内存都是有编好地址的,所以只要在固定的地址里面它会有对应的数据。我们存数据就是靠这个地址来索引的,那我就知道我的东西存在哪里了,也知道我的东西要去哪里拿。

内存区的每一个字节都有一个编号,这就是“地址”。如果在程序中定义了一个变量,在对程序进行编译时,系统就会自动的给这个变量分配内存单元。这叫做局部变量。

在C语言中,对变量的访问有两种方式,直接访问和间接访问。

举个例子就是:

为了开一个A抽屉,有两种方法:

(1)一种方法是将A钥匙带在身上,需要时直接找出该钥匙打开抽屉,取出所需的东西。-----直接访问

直接访问如:a=5;

系统在编译时,已经对变量分配了地址,例如若变量a分配的地址是2000,则该语句的作用就是把常数5保存到地址为2000的单元。(我们说内存中有两个属性嘛,一个是地址,然后和地址对应的空间存放的一个数据,那他地址就是2000存放的数据就是5,这个变量呢我们称之为a。)

(2)另外一种方法是:为安全起见,将A钥匙放到另外一个抽屉B中锁起来。如果需要打开A抽屉,就需要先找出B钥匙,打开B抽屉,取出A钥匙,再打开A抽屉,取出A抽屉中之物。-----间接访问

间接访问如:scanf("%d",&a);//这个函数的作用读取一个整形的变量,把他的值赋值给a

当我们调用这个函数的时候,把变量a的地址传递给函数scanf,函数首先把该地址保存到一个单元中,然后把从键盘接收的数据通过所存储的地址保存到变量a中。

1、初识指针

在C语言中指针是一种特殊的变量,它存放的是地址(一般变量存放的是值嘛,例如刚才a=5,变量a存放的是5;但是我们的指针他是一个特殊的变量它是存放地址的,叫做指针变量)。假设我们定义了一个指针变量,int *i_pointer用来存放整型变量i的地址。可以通过语句i_pointer=&i;(取得i的地址把他赋值给i_pointer这个指针变量)

如上图:假设变量i,变量j,变量k他们的地址分别是2000,2002,2004;它里边存放的数据分别为3、6、9;而我们定义的这个指针变量呢它里边存放的是一个地址,我们这里通过这一句i_pointer=&i,使得变量i的地址存放到了B处,所以B处存放的是i的地址,指针变量也是变量,数组变量也是变量,它跟变量是一样的道理,只是指针变量存放的是一个地址,而我们普通变量存放的是一个值。我们只需要把他灵活的应用就很NB了。

将i的地址(2000)存放到i_point中。这时,i_pointer的值就是(2000)即变量i所占用单元的起始地址。(因为不同类型在内存中占的字节数是不同的)

要存取变量i的值,可以采用间接方式:先找到存放“i的地址”的变量i_pointer,从中取出i的地址(2000),然后取出i的值3。见下图:直接的话,直接i=3;就取出来了。

*:叫做取值操作符;

&:叫做取地址操作符;(后边会详细分析这两个操作负要怎么用,目前只是基本使用方法)

int i=2000;//i的值为2000
int *pointer;//我们声明一个指针pointer//此处的*号并不是取值操作符,这个是申明它为指针的一个特征
pointer=&i;//在这里取出i的地址,比如说他的地址是1000,就把他的地址给了变量pointer//这个是取址
printf("%d\n",*pointer)//那我通过这个*号,取出指向这个地址的里边的值,也就是打印出来的值应该是2000//这个才是取值操作

知道了一个变量的地址,就可以通过这个地址来访问这个变量,因此又把变量的地址称为该变量的”指针“。指针指向的是一个地址。而指针变量呢,C语言中定义一类特殊的变量这些变量专门用来存放变量的地址,称为指针变量。(所以地址和变量是完全不同的两回事。)

注意:指针变量的值(即指针变量中存放的值是地址(即指针))要区分“指针(是一个地址)”和“指针变量(是一个变量)”这两个概念。

//定义一个指针变量
float *pointer_3;//pointer_3是指向float型变量的指针变量//pomiter_3是变量名而不是*pointer_3//*号只是起到表明它是指针
//*表示定义,*可以表示声明定义一个指针,如果不在声明的情况下,它是取值操作符

//下边可以使用赋值语句使一个指针变量得到另一个变量的地址,从而使他指向一个该变量。如:

首先我们的pointer_1里面存放的是i的地址,所以它指向的是i这个变量;pointer_2存放的是j的地址,所以指向的是j这个变量。那么如果有如下语句:

pointer_1=pointer_2;//就是将pointer_2这个变量里面的内容把他存放覆盖了pointer_1这个里面的内容。所以此时pointer_1里边存放的不再是i的地址了,而是j的地址了。而此时pointer_1指向了j。当然pointer_2没有发生改变还是指向的j;也就是说现在i被人抛弃了而两个都直奔j了。

指针变量的前面的*号表示改变量的类型是指针变量。即类型说明符 *变量名;

在定义指针变量时,必须指定基类型。特别注意的是:只有整型变量的地址才能放到指向整型变量的指针变量中。

请牢记,指针变量中只能存放地址(指针),不要将一个整数(或任何其他非地址类型的数据)赋给一个指针变量,否则编译器也会把该值当成一个地址来处理。

&变量名;//地址运算符&来表示变量的地址。

2、指针做函数参数

输入a,b两个整数,按大小顺序输出

#include<stdio.h>
//交换a,b的值
void swap(int *p1,int *p2);//行参定义的指向整数型的指针变量

void main()
{
   int a,b;
   int *pointer_1, *pointer_2;
   scanf("%d %d",&a,&b);
   pointer_1=&a;//pointer_1该指针变量指向的是a。
   pointer_2=&b;
   if(a<b)
   {
       swap(pointer_1,pointer_2);//所以实参需要传入的是????指针呢?还是指针变量。pointer_1叫做指针变量
   }
   printf("\n%d > %d\n",a,b);    
}
void swap(int *p1,int *p2)
{
   int temp;
   printf("I'm swap....\n");
   temp=*p1;
   *p1=*p2;
   *p2=temp;
}

输入a,b,c三个整数,按大小顺序输出

#include<stdio.h>
//交换a,b的值
void exchange(int *q1,int *q2,int q3);//使得a>b>c。

void main()
{
   int a,b,c;
   int *p1, *p2,*p3;
   scanf("%d %d %d",&a,&b,&c);
   p1=&a;//p1指针指向a的地址
   p2=&b;
   p3=&c;
   exchange(p1,p2,p3);//与上边定义相比,是做了int *q1=p1;这个操作。(行参和实参的一个交换就相当于一个赋值操作,他们是通过栈来完成的)
   printf("%d %d %d\n",a,b,c);    
}
void exchange(int *q1,int *q2,int *q3)
{
   void swap(int *pt1,int *pt2);//用于交换
   if(*q1<*q2)//p1和q1都是指向a变量的//即如果a<b的话交换
   { 
      swap(q1,q2);
   }
   if(*q1<*q3)//a如果小于c的话c,交换
   { 
      swap(q1,q3);
   }
   if(*q2<*q3)//b如果小于c的话再交换
   { 
      swap(q2,q3);
   }

}
void swap(int *pt1,int *pt2)
{
   int temp;
   temp=*pt1;
   *pt1=*pt2;
   *pt2=temp;
}

3、数组与指针

(因为数组和指针都是指向了一个地址,数组是比较稳定的,而指针是随时可变的;数组在定义的时候就占用了空间,而指针是一个变量,指针的空间随时可以消除,而数组不能)

一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元(而且他们是连续的),他们都有相应的地址。

指针变量(里边存放的是变量的地址)既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)

所谓数组元素的指针就是数组元素的地址。

定义一个指向数组元素的指针变量的方法,与以前介绍的指向变量的指针变量相同。

int a[10];//定义a为包含10个整型数据的数组
int *p;//定义p为指向整型变量的指针变量 
//应当注意,如果数组为int型,则指针变量的基类型也应为int型
p=&a[2];//指针指向数组的第三个元素;把a[2]元素的地址赋给指针变量p,也就是说把p指向a数组的第2号元素。
p++;//表示

引用数组元素,可以用下标法也可以用指针法:

(1)下标法:a[i]的形式;

(2)指针法:*(a+1)或者*(p+i);//因为a是第一个元素的地址,其中的加i就是指向的是第i个元素,不是说地址加i,因为要看什么类型。

其中a是数组名,p是指向数组元素的指针变量,其初值p=a即p=&a[0];

注意:数组名(在编译器是编译为一个地址的)即“编译成数组的第一个元素的地址。”(这就是上边说的相似的地方)

用数组名做函数的参数--------

如:

void f(int arr[],int n)
{
    ......
}
void main()
{
   int arr[10];
   ... ...
   f(arr,10);//数组名相当于该数组的首地址
}

上述中的,f(int arr[],int n),在编译时是将arr按照指针变量处理的,相当于将函数f的首部写成f(int *arr,int n)所以以上这两种写法是等价的。

需要说明的是:C语言调用函数时虚实结合的方法,都是采用值传递方式,当用变量名作为函数参数时,传递的是变量的值;当用数组名作为函数参数时,由于数组名代表的是数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。

例子:将数组a中n个整数按相反顺序存放。

#include<stdio.h>
void reverse(int x[],int n)//形参x是数组名
void main()
{
   int i, a[10]={3,5,7,8,1,0,2,6,9,4};
   printf(" The original array:\n");
   for(i=0,i<10,i++)
   {
       printf("%d",a[i]);
   }
   printf("\n");
   reverse(a,10);
   printf("The Array has been inverted:\n");
}
void reverse(int x[],int n)
{
   int temp,i,j,m;
   m=(n-1)/2;
   for(i=0;i<=m;i++)
   {
      j=n-1-i;
      temp=x[i];
      x[i]=x[j];
      x[j]=temp;
   }
}

改变一下用指针做函数的参数:

#include<stdio.h>
void reverse(int *x,int n)//形参x为指针变量
void main()
{
   int i, a[10]={3,5,7,8,1,0,2,6,9,4};
   printf(" The original array:\n");
   for(i=0,i<10,i++)
   {
       printf("%d",a[i]);
   }
   printf("\n");
   reverse(a,10);
   printf("The Array has been inverted:\n");
}
void reverse(int *x,int n)//形参x为指针变量
{
   int *p,temp,*i,*j,m;
   m=(n-1)/2;//中间序号
   i=x;//前边的序号//i指向数组的第一个元素
   j=x-1+n;//后边的序号//j指向数组的最后一个元素
   p=x+m;//指向中间配对
   for(;i<=p;i++,j--)
   {
      
      temp=*i;
      *i=*j;
      *j=temp;
   }
}

用指针做参数------

例子:从10个数中找出最大值和最小值。

#include<stdio.h>
int max,min;//全局变量
void max_min_values(int array[],int n);
void main()
{
   int i,number[10];
   printf("Enter 10 integer numbers:\n");
   for(i=0;i<10;i++)
   {
       scanf("%d",&number[i]);
   }
   max_min_values(number,10);
   printf("\nmax=%d,min=%d\n,"max,min);
}
void max_min_values(int array[],int n)
{
   int *p,*array_end;
   array_end=array+n;
   max=min=*array;
   for(p=array+1;p<array_end;p++)
   {
      if(*p>max)
      {
          max=*p;
      }
      else if(*p<min)
      {
          min=*p;
      }
   }

}

总结:如果有一个数组,想在函数中(子程序)改变此数组中的元素值,实参与形参的对应关系有以下四种:

(1)形参和实参都用数组名:

void main()
{
   int a[10];
   f(a,10)
}
void f(int [],int n)
{
   ......
}

(2)实参采用数组名,形参用指针变量。如:

void main()
{
   int a[10];
   f(a,10);
}
void f(int *a,int n)
{
   ......
}

(3)实参和行参都用指针变量。如:

void main()
{
   int a[10];
   int *p=a;//指针变量p指向数组的首地址
   f(p,10);//传指针相当于传a数组的地址
}
void f(int *x,int n)//int *x指针相当于指向a的地址,相当于传值的时侯x=p,p指向a那么x也指向a
{
   ......
}

(4)实参为指针变量,行参为数组名。如:

void main()
{
   int a[10];
   int *p=a;
   f(p,10);//1这里把数组的首地址当作实参传过去,
}
void f(int x[],int n)//2定义数组来接收首地址,说明位置起始点一样指向了同一组数组。
{
   ......
}

例子:对一个数组中的元素按照从大到小的顺序排列一下。

#include<stdio.h>
int max,min;//全局变量
void sort(int array[],int n);
void main()
{
   int *p,i,a[10]={3,7,4,9,2,0,5,8,1,6};
   printf("The origial array:\n");
   for(i=0;i<10;i++)
   {  
         printf("%d",a[i]);
   }
   printf("\n");
   p=a;
   sort(p,10);
   printf("The result is:\n");
   for(p=a;i=0;i<10;i++)
   {
       printf("%d",*p);
       p++;
   }
   printf("\n");
}
void sort(int x[],int n)
{
   int i,j,k,t;
   for(i=0;i<n-1;i++)
   {
      k=i;//假设第一个是最大的,如果接着的那个比他大那就把他调过来
      for(j=i+1;j<n;j++)
      {
         if(x[j]>x[k])
            {
                t=x[j];
                x[j]=x[k];
                x[k]=t;
            }
      }
   }

}

猜你喜欢

转载自blog.csdn.net/m0_37957160/article/details/108512339