指针
指针简介
  指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; 能很方便地使用数组和字符串; 并能象汇编语言一样处理内存地址,从而编出精练而高效的程序。所以说学好指针基本上就学好了大半C语言但是,指针也是C语言中最为困难的一部分,一般初学者都会在此止步不前,不仅仅是难的问题,还有就是很复杂。说句实话,我觉得我的指针学的也不是很扎实,所以我也就只能多多借鉴别人资料了。力求方便大家理解。
  指针的基本概念
在计算机中,所有的数据都是存放在存储器中的。 一般把存储器中的一个字节称为一个内存单元, 不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等, 在第二章中已有详细的介绍。为了正确地访问这些内存单元, 必须为每个内存单元编上号。 根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。 可以用一个通俗的例子来说明它们之间的关系。我们到银行去存取款时, 银行工作人员将根据我们的帐号去找我们的存款单, 找到之后在存单上写入存款、取款的金额。在这里,帐号就是存单的指针, 存款数是存单的内容。对于一个内存单元来说,单元的地址即为指针, 其中存放的数据才是该单元的内容。在C语言中, 允许用一个变量来存放指针,这种变量称为指针变量。因此, 一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。图中,设有字符变量C,其内容为“K”(ASCII码为十进制数 75),C占用了011A号单元(地址用十六进数表示)。设有指针变量P,内容为011A, 这种情况我们称为P指向变量C,或说P是指向变量C的指针。 严格地说,一个指针是一个地址, 是一个常量。而一个指针变量却可以被赋予不同的指针值,是变。 但在常把指针变量简称为指针。为了避免混淆,我们中约定:“指针”是指地址, 是常量,“指针变量”是指取值为地址的变量。 定义指针的目的是为了通过指针去访问内存单元。
  既然指针变量的值是一个地址, 那么这个地址不仅可以是变量的地址, 也可以是其它数据结构的地址。 数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址, 也就找到了该数组或函数。这样一来, 凡是出现数组,函数的地方都可以用一个指针变量来表示, 只要该指针变量中赋予数组或函数的首地址即可。这样做, 将会使程序的概念十分清楚,程序本身也精练,高效。在C语言中, 一种数据类型或数据结构往往都占有一组连续的内存单元。 用“地址”这个概念并不能很好地描述一种数据类型或数据结构, 而“指针”虽然实际上也是一个地址,但它却是一个数据结构的首地址, 它是“指向”一个数据结构的,因而概念更为清楚,表示更为明确。 这也是引入“指针”概念的一个重要原因。
大家千万搞明白:变量的地址称为指针,所以指针是地址。指针变量就是存放地址的变量。一定要记清楚别搞混了。千万千万,我见过不少朋友都是弄错了概念,导致程序总是出错。
指针变量的类型说明
对指针变量的类型说明包括三个内容:
(1)指针类型说明,即定义变量为一个指针变量;
(2)指针变量名;
(3)变量值(指针)所指向的变量的数据类型。
  其一般形式为: 类型说明符 *变量名;
  其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。
  例如: int *p1;表示p1是一个指针变量,它的值是某个整型变量的地址。 或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量, 应由向p1赋予的地址来决定。
  再如:
staic int *p2; /*p2是指向静态整型变量的指针变量*/
float *p3; /*p3是指向浮点变量的指针变量*/
char *p4; /*p4是指向字符变量的指针变量*/ 应该注意的是,一个指针变量只能指向同类型的变量,如P3 只能指向浮点变量,不能时而指向一个浮点变量, 时而又指向一个字符变量。
指针变量的赋值
  指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址, 决不能赋予任何其它数据,否则将引起错误。在C语言中, 变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。 C语言中提供了地址运算符&来表示变量的地址。其一般形式为: & 变量名; 如&a变示变量a的地址,&b表示变量b的地址。 变量本身必须预先说明。
还是请大家牢记:指针变量只能存放地址(指针),万万不要把一个整数或是其他的非地址类型的数据赋值给指针变量,我们还是来看个例题:
p_1=100; 这样的赋值就是错了,因为p_1十指针变量能将100赋值与它,被赋值的指针变量前不能再加“*”说明符,如写为*p=&a 也是错误的
<1>指针变量初始化赋值,如:
int a;
int *p=&a;
<2>把一个变量的地址赋予指向相同数据类型的指针变量。例如:
int a,*pa;
pa=&a; /*把整型变量a的地址赋予整型指针变量pa*/
<3>把一个指针变量的值赋予指向相同类型变量的另一个指针变量。如:
int a,*pa=&a,*pb;
pb=pa; /*把a的地址赋予指针变量pb*/
由于pa,pb均为指向整型变量的指针变量,因此可以相互赋值。
<4>把数组的首地址赋予指向数组的指针变量。
例如: int a[5],*pa;
pa=a; (数组名表示数组的首地址,故可赋予指向数组的指针变量pa)
也可写为:
pa=&a[0]; /*数组第一个元素的地址也是整个数组的首地址,
也可赋予pa*/
当然也可采取初始化赋值的方法:
int a[5],*pa=a;
<5>把字符串的首地址赋予指向字符类型的指针变量。例如: char *pc;pc=”c language”;或用初始化赋值的方法写为: char *pc=”C Language”; 这里应说明的是并不是把整个字符串装入指针变量, 而是把存放该字符串的字符数组的首地址装入指针变量。 在后面还将详细介绍。
<6>把函数的入口地址赋予指向函数的指针变量。例如: int (*pf)();pf=f; /*f为函数名*/

指针变量的算术运算
  对于指向数组的指针变量,可以加上或减去一个整数n。设pa是指向数组a的指针变量,则pa+n,pa-n,pa++,++pa,pa–,–pa 运算都是合法的。指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。应该注意,数组指针变量向前或向后移动一个位置和地址加1或减1 在概念上是不同的。因为数组可以有不同的类型, 各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。
例如:
int a[5],*pa;
pa=a; /*pa指向数组a,也是指向a[0]*/
pa=pa+2; /*pa指向a[2],即pa的值为&pa[2]*/ 指针变量的加减运算只能对数组指针变量进行, 对指向其它类型变量的指针变量作加减运算是毫无意义的。(3)两个指针变量之间的运算只有指向同一数组的两个指针变量之间才能进行运算, 否则运算毫无意义。
<1>两指针变量相减
两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址) 相减之差再除以该数组元素的长度(字节数)。例如pf1和pf2 是指向同一浮点数组的两个指针变量,设pf1的值为2010H,pf2的值为2000H,而浮点数组每个元素占4个字节,所以pf1-pf2的结果为(2000H-2010H)/4=4,表示pf1和 pf2之间相差4个元素。两个指针变量不能进行加法运算。 例如, pf1+pf2是什么意思呢?毫无实际意义。
<2>两指针变量进行关系运算
指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。例如:
pf1==pf2表示pf1和pf2指向同一数组元素
pf1>pf2表示pf1处于高地址位置
pf1<pf2表示pf2处于低地址位置
下面的这段代码大家自己体会哈:
main(){
int a,b,c,*pmax,*pmin;
printf(“input three numbers:\\n”);
scanf(“%d%d%d”,&a,&b,&c);
if(a>b){
pmax=&a;
pmin=&b;}
else{
pmax=&b;
pmin=&a;}
if(c>*pmax) pmax=&c;
if(c<*pmin) pmin=&c;
printf(“max=%d\\nmin=%d\\n”,*pmax,*pmin);
}
指针与数组
在前面函数的学习中我们知道,数组名作函数的实参和形参的问题,可能当时大家不是很容易去理解,那么现在学习了指针后就会发现理解就容易多了。 数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址, 形参得到该地址后也指向同一数组。 这就好象同一件物品有两个彼此不同的名称一样。同样,指针变量的值也是地址, 数组指针变量的值即为数组的首地址,当然也可作为函数的参数使用。是不是很简单了呢?
其实本来我们的这个教程十不想做指针的,因为在文本格式下很难做表格,尔指针又是要做了表格才好理解,特别是对地址的阐述的时候,但是我们这个教程因为是针对初学者和非计算机专业的朋友,所以我不会讲的太深,所以还是给了大家有关指针的学习,因为没有图表,所以我会以代码来做说明,我们这里只是讨论一维和二维数组,后面的多位数组又兴趣的朋友可以自己去学习,或者有时间我在做个专题。
一级指针变量与一维数组的关系
int  *p   与  int  q[10]         
数组名是指针(地址)常量
p=q;   p+i 是q的地址
数组元素的表示方法:下标法和指针法,   即若p=q,                    则       p ? q ? *(p+i) ? *(q+i)
形参数组实质上是指针变量,即int  q[ ] ? int *q
在定义指针变量(不是形参)时,不能把int  *p  写成int  p[];
系统只给p分配能保存一个指针值的内存区(一般2字节);而给q分配2*10字节的内存区 我们还是来看段代码吧:
例题1:将数组a中的n个整数按相反顺序存放      
void inv(int  x[], int n)
{   int t,i,j,m=(n-1)/2;
    for(i=0;i<=m;i++)
    {    j=n-1-i;
  t=x;  x=x[j];  x[j]=t;
    }
}
main()
{   int i,a[10],*p=a;
     for(i=0;i<10;i++,p++)
       scanf(“%d”,p);
     p=a;      inv(p,10);
    printf(“The array has been reverted:\\n”);
    for(p=arr;p<arr+10;p++)
       printf(“%d “,*p);
}
因为这段代码相对的简单,我就不做说明分析了但是要是所分析就给大家一个提示“实参用指针变量,形参用数组”。相信通过这么久的学习,大家一定在代码的阅读方面有了很大的提高。要想学好一门语言多读代码还是必要的哦~

**数组的指针变量
把二维数组a 分解为一维数组a[0],a[1],a[2]之后,设p为指向二维数组的指针变量。可定义为: int (*p)[4] 它表示p是一个指针变量,它指向二维数组a 或指向第一个一维数组a[0],其值等于a,a[0],或&a[0][0]等。而p+i则指向一维数组a。从前面的分析可得出*(p+i)+j是二维数组i行j 列的元素的地址,而*(*(p+i)+j)则是i行j列元素的值。
二维数组指针变量说明的一般形式为: 类型说明符 (*指针变量名)[长度] 其中“类型说明符”为所指数组的数据类型。“*”表示其后的变量是指针类型。 “长度”表示二维数组分解为多个一维数组时, 一维数组的长度,也就是二维数组的列数。应注意“(*指针变量名)”两边的括号不可少,如缺少括号则表示是指针数组(本教程不再做介绍,有兴趣的朋友可以自己看书自学),意义就完全不同了。二维数组形参实际上是一维数组指针变量,如 int  x[ ][10] 等价于int  (*x)[10]。
还是看例题哈:
[Explain]main()
{
static int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int(*p)[4];
int i,j;
p=a;
for(i=0;i<3;i++)
for(j=0;j<4;j++) printf(“%2d “,*(*(p+i)+j));
}
代码分析:Expain字符串指针变量的说明和使用字符串指针变量的定义说明与指向字符变量的指针变量说明是相同的。只能按对指针变量的赋值不同来区别。 对指向字符变量的指针变量应赋予该字符变量的地址。如: char c,*p=&c;表示p是一个指向字符变量c的指针变量。而: char *s=”C Language”;则表示s是一个指向字符串的指针变量。把字符串的首地址赋予s。
再看一例
请看下面一例。
main()
{
char *ps;
ps=”C Language”;
printf(“%s”,ps);
}
运行结果为:
C Language
上例中,首先定义ps是一个字符指针变量, 然后把字符串的首地址赋予ps(应写出整个字符串,以便编译系统把该串装入连续的一块内存单元),并把首地址送入ps。程序中的: char *ps;ps=”C Language”;等效于: char *ps=”C Language”;输出字符串中n个字符后的所有字符。
现在是不是清楚多了呢?
我们再来做个前面做的例题:
例题1:把3个人的名字按照字母顺序输出。
还记得我们开始这么设计这个程序吗?
我们开始用的十二维数组做的,当时的代码是这样写的:
void main()
{
char st[20],cs[3][20];
int i,j,p;
printf(“input people\’s name:\\n”);
for(i=0;i<3;i++)
gets(cs);/*输入三个人名字符串*/
printf(“\\n”);
for(i=0;i<3;i++)
{
  p=i;strcpy(st,cs);
  for(j=i+1;j<3;j++)
  if(strcmp(cs[j],st)<0) {p=j;strcpy(st,cs[j]);}
  if(p!=i)
   {
    strcpy(st,cs);
    strcpy(cs,cs[p]);
    strcpy(cs[p],st);
   }
  puts(cs);
}
printf(“\\n”);
}
我们现在来用指针+二维数组写,那代码会这么写呢?请看下面的代码:
#include”string.h”
main(){
void sort(char *name[],int n);
void print(char *name[],int n);
static char *name[]={ “zx”,”xz”,”yy”};
int n=3;
sort(name,n);
print(name,n);
}
void sort(char *name[],int n){
char *pt;
int i,j,k;
for(i=0;i<n-1;i++){
k=i;
for(j=i+1;j<n;j++)
if(strcmp(name[k],name[j])>0) k=j;
if(k!=i){
pt=name;
name=name[k];
name[k]=pt;
}
}
}
void print(char *name[],int n){
int i;
for (i=0;i<n;i++) printf(“%s\\n”,name);
}
代码分析:这个程序中定义了两个函数,一个是sort用来完成排序, 其形参为指针数组name,即为待排序的各字符串数组的指针。形参n为字符串的个数。另一个函数是print,是在排序后字符串的输出,其形参与sort的形参相同。主函数main中,定义了指针数组name 并作了初始化赋值。然后分别调用sort函数和print函数完成排序和输出。值得说明的是在sort函数中,对两个字符串比较,采用了strcmp 函数,strcmp函数允许参与比较的串以指针方式出现。name[k]和name[ j]均为指针,因此是合法的。字符串比较后需要交换时, 只交换指针数组元素的值,而不交换具体的字符串。等到大家学了数据结构以后就发现第二种程序设计的时间复杂度远小于第一种

1 对 “C 简明教程 (九)”的想法;

评论被关闭。