指针虽然是一个重要的概念,但是一点也不复杂。但他的确是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;
}
}
}
}