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

寒假要开始猛刷《剑指offer》,先回顾一下C语言基础做个热身。

指针

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

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

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

接下来看如何定义一个指针,既然指针也是一个变量,那么它的定义也和其它变量一样定义:如: int *p; 。‘*‘ 是间接寻址或间接引用运算符。上例中我们还看到了一个特别的运算符&,它是一个取地址运算符(在其他合适场合&也是按位运算运算符,&&为取交集运算符)。在上面的指针定义中,我们看到了定义的是一个整型指针,难道指针还有类型吗?答案是肯定的,指针只能指向某种特定类型的对象,也就是说,每个指针都必须指向某种特定的数据类型(唯一的例外:指向void类型的指针可以存放指向任何类型的指针,但它不能间接引用其自身)。比如,int 类型的指针绝对不能指向char 类型的变量。

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

 1 #include<stdio.h>
 2
 3 int main()
 4 {
 5 int a,b,c,*p;
 6 a=1;
 7 b=3;
 8 p=&a;
 9 b=*p+1;
10 c=*(p+1);
11 printf("%d %d %d %d \n",a,b,c,*p+3);
12
13 return 0;
14 }

运行结果为: 1 2 6763280 4

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

数组

数组大家应该都很熟悉了,用途非常广泛。 int a[4]={2,4,5,9}; 此语句定义一个4个空间大小的整型数组a并为它进行了初始化。数组的基础知识可以参考其他相应的教材,我们在这主要讨论指针和数组的结合应用。
我们再来看个完整的例子:

 1 #include<stdio.h>
 2
 3 int main()
 4 {
 5     int a[4]= {2,4,5,9};
 6
 7     int *p;
 8
 9     p=a;
10
11     *p=*p++;
12
13     printf("%d %d %d\n",*p,*p+6,*(p+1));
14     return 0;
15 }

运行结果:2 8 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的含义相同,a[i]与*(a+i)也是等价的。虽然数组和指针有这么多通用的地方,但我们必须记住,数组名和指针之间有一个不同之处。指针是一个变量,因此语句p=a和p++都是合法的。但数组名不是变量,因此,类似于a=p和a++形式的语句是非法的。

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

1 int strlen(char *s)
2 {
3 int n;
4 for(n=0;*s!=‘\0‘;s++)
5 n++;
6 return n;
7 }

因为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 {
3 char *p=s;
4 while(*p!=‘\0‘)
5 p++;
6 return p-s;
7 }

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

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

再来看两条语句:

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

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

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

1 void strcpy(char *s,char *t)
2 {
3 int i;
4 i=0;
5 while((s[i]=t[i])!=‘\0‘)
6 i++;
7 }

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

1 void strcpy(char *s,char *t)
2 {
3 while((*s=*t)!=‘\0‘){
4 s++;
5 t++;
6 }
7 }

最简版本:

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

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

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

这两个词次听起来挺新颖的,到底是什么意思呢?由于指针本身也是变量,所以它们也可以像其他变量一样存储在数组中。这一点很容易理解。

 1 #include<stdio.h>
 2 #include<string.h>
 3
 4 int main()
 5 {
 6     int i;
 7     char b[]= {"wustrive_2008"};
 8     char *a[1];
 9     *a=b;
10     for(i=0; i<strlen(b); i++)
11         printf("%c",*(a[0]+i));
12     printf("\n");
13     return 0;
14 }

运行结果:wustrive_2008
这里库函数strlen,strlen为string类的标准库函数,所以要包含#include。

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

 1 #include<stdio.h>
 2
 3 int strlen(char *s)
 4 {
 5     char *p=s;
 6     while(*p!=‘\0‘)
 7         p++;
 8     return p-s;
 9 }
10
11 int main()
12 {
13     int i;
14     char b[]= {"wustrive_2008"};
15     char *a[1];
16     *a=b;
17     for(i=0; i<strlen(b); i++)
18         printf("%c",*(a[0]+i));
19     printf("\n");
20     return 0;
21 }

这个运行结果和上个例子一样,不一样的只是我们自己实现了strlen函数,我们再编程时使用的库函数,都是语言的开发者或者系统为我们写好了的函数,其实我们也可以自己写。这个例子很好的演示了指针数组的用法,指针数组a的值a[0]是一个指针,指向字符数组第一个字符。指针的指针也很好理解,就是一个指针里放的是另一个指针的地址,而另一个指针可能指向一个变量的地址,还可能指向另一个指针。

指针和多维数组

看两个定义语句: 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个元素的向量。

指向函数的指针

这里我想向大家推荐一个博友的博客,写的非常好。 http://www.cnblogs.com/liangyan19910818/archive/2011/08/19/2145270.html

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

下面来看两个声明:

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

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

int (*p)(void ,void *)

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

指针数组和数组指针

(1) 数组指针(也称行指针)

定义 int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
 p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
 p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。

(2)指针数组
定义 int *p[n];
[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,这样赋值也是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:
int *p[3];
int a[3][4];
for(i=0;i<3;i++)
p[i]=a[i];
这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。

这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]

优先级:()>[]>*

好了,数组和指针先复习到这里,C语言东西还是比较多的。下一步是在春节期间刷完《剑指offer》,想想就激动~  找工作需要靠运气,未必会刷掉渣渣,也未必就不刷学霸,但是肯定会刷掉一种人:连题都不刷的人,哈哈~~~~~~

时间: 2024-10-21 03:57:37

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

c语言数组与指针详解(上)

彻底搞懂c语言数组与指针 部分引用 c语言指针怎么理解 知乎 程序设计入门----c语言 (浙江大学翁恺) <c primer plus>第六版 基础知识 1. 指针基础 &:代表对变量取地址 int*或char*或者把这个星号紧贴着变量比如int *a = &b: 代表新建一个用来储存地址的变量,这也代表&b这个值的类型是int*. int *a, b 或 int* a, b 中只有a是int指针类型,b是int整型. 关于电脑大小端的讨论:大端是指是指数据的高字节,

c++核心-字符串、向量和数组及指针详解

关于STL的内容后面再详细介绍,string和vector都是标准模板库的内容. 数组是C的复合类型(可能只有我这么叫),一组数据,空间连续,所以由开始也有结束,从0开始,到n-1结束.注意别越界,因为是从0开始的. string和vector内部的保存数据的空间也是一个数组,所以空间也是连续的,可以看做数组的封装,同时提供了很多便利的操作,因为便利,所以也会一定程度减少自己犯错误的机会. 空间连续所以可以通过下标(随机,所谓随机,就是可以直接访问,不需要从头一个一个找下来才能找到)访问.因为跟

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

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

彻底搞定C语言指针详解

1.语言中变量的实质 要理解C指针,我认为一定要理解C中“变量”的存储实质, 所以我就从“变量”这个东西开始讲起吧! 先来理解理解内存空间吧!请看下图: 内存地址→ 6 7 8 9 10 11 12 13 ----------------------------------------------------------------- ... | | | | | | | |.. ------------------------------- ---------------------------

指针数组,数组指针,指针函数,函数指针,二级指针详解

先看个简单的:char *p,这定义了一个指针,指针指向的数据类型是字符型,char  *(p)定义了一个指针P: char *p[4], 为指针数组,由于[]的优先级高于*,所以p先和[]结合,p[]是一个数组,暂时把p[]看成是q,也就是char *(q),定义了一个指针q,只不过q是一个数组罢了,故定义了一个数组,数组里面的数据是char *的,所以数组里面的数据为指针类型.所以char *p[4]是四个指针,这四个指针组成了一个数组,称为指针数组,既有多个指针组成的数组. char(*p

C语言 二维数组与指针笔记

今天分析了C语言二维数组和指针的基本理解,感觉有点懵...代码记录一下,如果有大神临幸发现哪里有误,欢迎指正~~~ #include <stdio.h> #include <stdlib.h> #include <string.h> //void func(int p[][]) //这样写等同于void func(int **p) p++移动了四个字节,(*p)++移动了四个字节,不符合二维数组规律 //{ //} //列优先输出的函数(即竖着输出) void func

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

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

C语言学习_数组与指针2

数组其实是一种变相的指针,数组名同时也是指针,eg: CODE == &CODE[0]; 数组的加法: #include<stdio.h> #define SIZE 4 int main(void) { shortdates[SIZE]; short* pti; shortindex; doublebills[SIZE]; double* ptf; pti= dates;//把数组地址付给指针 ptf= bills; printf("%23s  %10s\n", &

C语言之不规则数组和指针

不规则数组是每一行的列数不一样的二维数组. 在了解不规则数组之前,先了解一下用复合字面量创建的二维数组.复合字面量是一种C构造,前面看起来像是类型转换操作,后面跟的是花括号括起来的初始化列表. (const int) {100} (int[3]) {1,2,3} 下面的声明把数组声明为整数指针的数组,然后用复合字面量语句块进行初始化,由此创建了数组arr. int (*(arr[])) = { (int[]) {0,1,2}, (int[]) {3,4,5}, (int[]) {6,7,8}};