C语言核心之数组和指针详解

指针

相信大家对下面的代码不陌生:


  1. int i=2;                                            
  2. int *p;                                             
  3. p=&i; 

这是最简单的指针应用,也是最基本的用法。再来熟悉一下什么是指针:首先指针是一个变量,它保存的并不是平常的数据,而是变量的地址。如上代码,指针p中保存的是整型变量i的地址信息。

接下来看如何定义一个指针,既然指针也是一个变量,那么它的定义也和其它变量一样定义:如:int p;是间接寻址或间接引用运算符。上例中我们还看到了一个特别的运算符&,它是一个取地址运算符(在其他合适场合&也是按位运算运算符,&&为取交集运算符)。

在上面的指针定义中,我们看到了定义的是一个整型指针,难道指针还有类型吗?答案是肯定的,指针只能指向某种特定类型的对象,也就是说,每个指针都必须指向某种特定的数据类型(唯一的例外:指向void类型的指针可以存放指向任何类型的指针,但它不能间接引用其自身。)。比如,int
类型的指针绝对不能指向char 类型的变量。

下面我们给出一个完整的例子来说明指针的简单应用:


  1. #include<stdio.h> 
  2. void main() 
  3.     int a,b,c,*p; 
  4.     a=1; 
  5.     b=3; 
  6.     p=&a; 
  7.     b=*p+1; 
  8.     c=*(p+1); 
  9.     printf("%d %d %d %d /n",a,b,c,*p+3); 

运行结果为: 1 2 -858993460 4

这是个完整的例子,可以自己在机器上调试一下,现在很多人用的都是微软的Visual Studio 开发环境,有人就不知道在该开发环境中怎么写C程序以及调试C程序,具体境况可以参考附录。

在上面例子中,看到了这样两个表达式b=p+1;和c=(p+1);前者的意思是p所指的地址里的内容加1再赋给b,相当于b=a+1;,后者是p所指的地址加1再把(p+1)所指的地址赋给c,当然我们不知道p的下一个地址里放的是什么,所以输出了一个随机值(这样的操作时很危险的,切记不要使用不确定的内存地址)。

数组

数组大家应该都很熟悉了,用途非常广泛。


  1. int a[4]={2,4,5,9}; 

此语句定义一个4个空间大小的整型数组a并为它进行了初始化。

数组的基础知识可以参考其他相应的教材,我们在这主要讨论指针和数组的结合应用。

我们再来看个完整的例子:


  1. #include<stdio.h> 
  2. void main() 
  3.     int a[4]={2,4,5,9}; 
  4.     int *p; 
  5.     p=a; 
  6.     *p=*p++; 
  7.     printf("%d %d %d/n",*p,*p+6,*(p+1)); 

运行结果:4 10 5

分析:语句p=a;表示把数组a的第0个元素的地址赋给指针p,数组名a代表的是数组a的第0个元素的地址。

a[i]表示数组a的第i个元素,如果定义一个指针p,那么语句p=&a[0];表示可以将指针p指向数组a的第0个元素,也就是说p的值为数组元素a[0]的地址。那么(p+1)引用的是数组元素a[1]的内容,p+i是数组元素a[i]的地址,(p+i)引用的是数组元素a[i]的内容。对数组元素a[i]的引用也可以写成(a+i)。可以得出结论:&a[i]与a+i的含义相同,p[i]与(p+i)也是等价的。

虽然数组和指针有这么多通用的地方,但我们必须记住,数组名和指针之间有一个不同之处。指针是一个变量,因此语句p=a和p++都是合法的。但数组名不是变量,因此,类似于a=p和a++形式的语句是非法的。

下面来看一个我们常用的函数strlen(char *s):


  1. int strlen(char *s) 
  2.     int n; 
  3.     for(n=0;*s!='/0';s++) 
  4.           n++; 
  5.     return n; 

因为s是一个指针,所以对其执行自增运算是合法的。执行s++运算不会影响到strlen函数的调用者中的字符串,它仅对该指针在strlen函数中的私有副本进行自增运算。在函数定义中,形式参数char s[]和char *s是等价的。

我们再来看一下地址算术运算:如果p是一个指向数组中某个元素的指针,那么p++将对p进行自增运算并指向下一个元素,而p+=i将对p进行加i的增量运算,使其指向指针p当前所指向元素之后的第i个元素。同其他类型的变量一样,指针也可以进行初始化。通常,对指针有意义的初始化值只能是0或者是表示地址的表达式,对后者来说,表达式所表达的地址必须是在此之前已定义的具有适当类型的数据的地址。任何指针与0进行相等或者不相等的比较运算都有意义。但是指向不同数组的元素的指针之间的算术或比较运算没有意义。指针还可以和整数进行相加或相减运算。如p+n表示指针p当前指向的对象之后第n个对象的地址。无论指针p指向的对象是何种类型,上述结论都成立。在计算p+n时,n将根据p指向的对象的长度按比例缩放,而p指向的对象的长度则取决于p的声明。例如,如果int类型占4个字节的存储空间,那么在int类型的计算中对应的n将按4的倍数来计算。

指针的减法运算也是有意义的,如果p和q指向相同数组中的元素,且p<q,那么q-p+1就是位于p和q指向的元素之间的元素的数目。我们来看一下strlen(char *s)的另一个版本:


  1. int strlen(char *s) 
  2.    char *p=s; 
  3.    while(*p!='/0') 
  4.           p++; 
  5.    return p-s; 

程序中,p被初始化为指向s,即指向该字符串的第一个字符,while循环语句将依次检查字符串中的每个字符,直到遇到标识字符数组结尾的字符’/0’为止。由于p是指向字符的指针,所以每执行以此p++,p就将指向下一个字符的地址,p-s则表示已经检查过的字符数,即字符串长度。

总结:有效的指针运算包括相同类型指针之间的赋值运算;指针和整数之间的加减运算;指向相同数组中元素的两个指针间的减法或比较运算;将指针赋值为0或指针与0之间的比较运算。其他所有形式的指针运算都是非法的。

再来看两条语句:

char  a[]=”I  am  a  boy”;  char *p=”I  am  a  boy”;

a是一个仅仅足以存放初始化字符串以及空字符’/0’的一维数组。数组中的单个字符可以进行修改,但a始终指向同一个存储位置。而p是一个指针,其初值指向一个字符串常量,之后它可以被修改以指向其他地址,但如果试图修改字符串的内容,结果是没有定义的。

为了更容易理解数组和指针的关系,我们再来看一个函数:


  1. void strcpy(char *s,char *t) 
  2.    int i; 
  3.    i=0; 
  4.    while((s[i]=t[i])!='/0') 
  5.           i++; 

因为参数是通过值传递的,所以在strcpy函数中可以以任何方式使用参数s和t。

下面是指针实现的几个版本:


  1. void strcpy(char *s,char *t) 
  2.    while((*s=*t)!='/0'){ 
  3.           s++; 
  4.           t++; 
  5.    } 

最简版本:


  1. void strcpy(char *s,char *t) 
  2.    while(*s++=*t++) 
  3.           ; 

这里,s和t的自增运算放到了循环的测试部分中。表达式*t++的值是执行自增运算之前t所指向的字符。后缀运算符++表示在读取该字符之后才改变t的值。同样,在s执行自增运算之前,字符就被存储到了指针s指向的旧位置。上面的版本中表达式同’/0’的比较是多余的,因为只需要判断表达式的值是否为0即可。

指针数组和指向指针的指针

这两个词次听起来挺新颖的,到底是什么意思呢?

由于指针本身也是变量,所以它们也可以像其他变量一样存储在数组中。这一点很容易理解。


  1. #include<stdio.h> 
  2. #include<string.h> 
  3. void main() 
  4.    int i; 
  5.    char b[]={"wustrive_2008"}; char *a[1]; *a=b; for(i=0;i<strlen(b);i++) printf("%c",*(a[0]+i)); printf("/n"); 

运行结果:wustrive_2008

这里库函数strlen,strlen为string类的标准库函数,所以要包含#include。

下面我们来自己写一个strlen函数,我们把上面的例子该成这样:


  1. #include<stdio.h> 
  2. int strlen(char *s) 
  3.    char *p=s; 
  4.    while(*p!='/0') 
  5.           p++; 
  6.    return p-s; 
  7. void main() 
  8.    int i; 
  9.    char b[]={"wustrive_2008"}; char *a[1]; *a=b; for(i=0;i<strlen(b);i++) printf("%c",*(a[0]+i)); printf("/n"); 

这个运行结果和上个例子一样,不一样的只是我们自己实现了strlen函数,我们再编程时使用的库函数,都是语言的开发者或者系统为我们写好了的函数,其实我们也可以自己写。

这个例子很好的演示了指针数组的用法,指针数组a的值a[1]是一个指针,指向字符数组第一个字符。

指针的指针也很好理解,就是一个指针里放的是另一个指针的地址,而另一个指针可能指向一个变量的地址,还可能指向另一个指针。

指针和多维数组

看两个定义语句:int a[5][10]; int *b[5];

从语法角度讲,a[3][4]和b[3][4]都是对一个int对象的合法引用。但a是一个真正的二维数组,它分配了50个int类型长度的存储空间。但b定义仅仅分配了5个指针,并且没有初始化,它们必须进行显示的初始化,假设b的每个元素都指向一个有10个元素的数组,那么编译器就要为它分配50个int类型长度的存储空间以及5个指针存储空间。指针数组的一个重要优点在于,数组的每一行长度可以不同,也就是说,b的每个元素不必都指向一个有10个元素的向量。

指向函数的指针:

在C语言中,函数虽然不是变量,但可以定义指向函数的指针。这种类型的指针可以被赋值,存放在数组中,传递给函数以及作为函数的返回值等。

如果下面的语句为一个函数的参数,表示什么意思:


  1. int (p)(void ,void *) 

它表明p是一个指向函数的指针,该函数具有两个void类型的参数,其返回值类型为int。语句if((p)(v[i],v[left])<0)中,p的使用和其声明是一致的,p是一个指向函数的指针,p代表一个函数。如果写成这样:int p(void ,void )则表明p是一个函数,该函数返回一个int类型的指针。

下面来看两个声明:

int  *f();      //f是一个函数,它返回一个指向int类型的指针
int   (*pf)();     //pf是一个指向函数的指针,该函数返回一个int类型的对象。

作者:wustrive

来源:51CTO

时间: 2024-08-02 02:58:50

C语言核心之数组和指针详解的相关文章

C/C++数组和指针详解

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:wdzxl198@163.com    /*  转载请注明本文出处: *  http://blog.csdn.net/wdzxl198/article/details/9087497 /***********************

C语言 数组指针详解及示例代码_C 语言

数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element).数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存.以int arr[] = { 99, 15, 100, 888, 252 };为例,该数组在内存中的分布如下图所示: 定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素.在C语言中,我们将第 0 个元素的地址称为数组的首地址.以上面的数组为例,下图是 arr 的指向: 下面的例子演示了如何以指针的方

C语言 字符串指针详解及示例代码_C 语言

C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中,这在<C语言字符数组和字符串>中已经进行了详细讲解,这里不妨再来演示一下: #include <stdio.h> int main(){ char str[] = "http://c.biancheng.net"; int len = strlen(str), i; //直接输出字符串 printf("%s\n", str); //每次输出一个字符 for(i=0; i<

C语言中的数组和指针汇编代码分析实例

  这篇文章主要介绍了C语言中的数组和指针汇编代码分析实例,本文用一则C语言例子来得到对应的汇编代码,并一一注解每句汇编代码的含义,需要的朋友可以参考下 今天看<程序员面试宝典>时偶然看到讲数组和指针的存取效率,闲着无聊,就自己写了段小代码,简单分析一下C语言背后的汇编,可能很多人只注重C语言,但在实际应用当中,当出现问题时,有时候还是通过分析汇编代码能够解决问题.本文只是为初学者,大牛可以飘过~ C源代码如下: 代码如下: #include "stdafx.h" int

「C语言回顾之旅」第一篇:指针详解

说明:     最近学校课程开设了<数据结构>的课程,无疑,数据结构的重要性在IT生涯的进阶路上是不可置疑的,也常说,数据结构是专业性与非专业性的分界线.所以无论以后走的是什么方向,毕竟是读计算机专业的,所以必须学好数据结构的.虽然目前我给自己定的方向是走运维/系统架构方向的,可有句话说得好,不懂开发的运维注定会被淘汰,在IT这一行,要让自己变得更加强大.最近也一直在学Python,感觉还不错,学数据结构相信对自己也肯定有好处的,对一些较为底层的知识有些了解和理解,这样才能走得更远!     

数组 指针-c语言二维数组的指针问题

问题描述 c语言二维数组的指针问题 在WinTC下运行如下代码 #include void main() { char a1[][5]={"ab","cd","ef","ghi","sfs"}; printf("%sn",*(a1+3)); //语句1 printf("%cn",**(a1+3)); //语句2 } 运行结果为: ghi g 但如果将上述语句1中的%s

C迷途指针详解_C 语言

本文较为详尽的讲述了C语言的迷途指针,分析了其概念.原理与检测方法.分享给大家供大家参考.具体如下: 一般来说,在计算机编程领域中,迷途指针,或称悬空指针.野指针,指的是不指向任何合法的对象的指针. 当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称迷途指针.若操作系统将这部分已经释放的内存重新分配给另外一个进程,而原来的程序重新引用现在的迷途指针,则将产生无法预料的后果.因为此时迷途指针所指向的内存现在包含的已经完全是不同的数

C语言 二级指针详解及示例代码_C 语言

指针可以指向一份普通类型的数据,例如 int.double.char 等,也可以指向一份指针类型的数据,例如 int *.double *.char * 等. 如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针. 假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量,它们的关系如下图所示: 将这种关系转换为C语言代码: int a =100; int *p1 = &a; int **p2 = &p1; 指针变量也是一种变量

PHP 数组基本操作方法详解_php实例

数组的概念 数组(array)是 PHP 中一个非常重要的概念,我们可以把数组看做一系列类似的数据的集合,实际上数组是一个有序图. PHP 还提供了超过 70 个内建函数来操作数组. 创建数组 使用 array() 语言结构创建数组: <?php $arr_age1 = array(18, 20, 25); // 或者: $arr_age2 = array("wang"=>18, "li"=>20, "zhang"=>25