指针
1. 指针变量定义
在计算机内部每一个字节单元都有一个编号,称为地址。
内存单元的地址称为指针,存放指针的变量称为指针变量。
上面这两句话揭示了指针和指针变量的区别。
请记住上面两句话,因为这就是指针的本质。
计算机位数对应于地址的字宽,32位计算机的字宽就是32位的,也就是4个字节。(所有类型(int*,char*,struct*,数组等)的指针都是32位的,即4个字节的
)
为什么呢?因为计算机的位数代表了其寻址能力,因为指针变量存放的是指针,也就是地址。而我们的计算机需要利用32位二进制去给内存编址,也就是4个字节。所以在遇到别人让你求sizeof(指针变量)时,这个问题就变得很简单了,现在我们知道了32位的机器下面,这个值是4,64位机器下面这个值是8。好了,下次别人再问你这个问题,你可以反问他:你说的是多少位的机器?
指针定义形式如下:
类型说明符* 变量名
- *表示一个指针变量,类型说明符表示指针所指向的数据类型。
- 虽然所有的指针都等长,但是还要指明数据宽度,因为对指针变量的其他操作都涉及都涉及指针变量的数据宽度。(即指针所指向数据的类型)
2. 指针变量的赋值
指针变量定义以后需要赋给具体的值,否则·不能随便使用。指针变量的值只能是其所指向的变量的地址。不能是其他数据。
C语言中变量的地址是有编译系统分配的,用户不知道的。但是变量的地址可以用&运算符来查看变量的地址。形式如下:
&变量名
// & —>取地址符
3. 指针变量的引用
- *------>指针运算符(间接存取运算符)
- &------>取地址运算符
指针变量P的目标变量m,因此*P,m,*(&m)表达式的含义相同。
- P-----指针变量,它的内容是地址
- *P-----指针所指向的对象,它的内容是数据(即其所指向的变量的值)
- &P------指针变量所在的存储区域的地址(注意这里的说法,也就是指针变量的地址)
4. 指针的运算
指针运算是以指针变量所存放的值(地址量)作为运算量来进行的运算。指针的运算是指就是地址的计算。定义指针变量P:
-
p+n---->指针向地址方向大的方向移动n个数据
-
p-n----->指针向地址方向小的地方移动n个数据
-
+±----->++p,p++
-
– ------>–p,p–
-
p-q--------->两个指针之间相隔数据元素的个数
p + x---->实际内存单元的地址量:p+x*sizeof(p指向的数据类型)
-
注意:两个指针相减的结果值不是地址量,而是两个指针间的元素个数。
指针的关系运算:
- 具有不同数据类型的指针之间的关系运算没有意义。指向不同数据区域的两个指针之间的关系运算也没有意义。
两个重要的表达式:
- *p++----->两个运算符属于同一优先级,且结合性都是自右向左,相当于*(p++),表达式的作用是在原p的值的基础上进行间接操作,再将p值加1。表达式的值是提取原p的内容。(++p,因为p位于*的右边,++的左边,所以p先与右边的*结合,p先参与运算*P,再把p值后移一个数据宽度,即再执行p++的操作)
- *++p ------>相当于*(++p),先将p的值移动一个数据单位,再取当前p所指向的内存单元中的数据
5. 空指针
空指针即指针指向了零号地址,在程序中可以为指针赋初值0,所以可以用:int *p = 0;
C 中一般用NULL指针来代表0,它可以表明一个指针当前并未指向任何变对象。
比如:int *p = NULL;
这就代表**指针p指向了0号地址,**因为我们知道指针变量是用来存放指针也就是地址的,其中的值一定是存放的某一个地址。假设我们定义指针却没有初始化,其实此时指针变量中也是有值的,只不过我们不知道它指向哪里。为了安全起见,C标准就规定了NULL代表0号地址即0x0000 0000
也就是说NULL实际上是与0是等效的。
口说无凭,给个例子:
这是为什么我们在一些编程中会经常拿指针变量去和0比较来判断它是否为非空的原因了。
在定义指针时,最好将指针初始化为0,否则我们无法清楚指针具体指向了哪里,该指针也就变成了野指针。
6. 指针和数组
数组名是一个地址,指针的一次相加是以其所指向的对象的数据宽度为单位进行移动。数组名 +i ---->数组名[i]
指针的效率要高于数组下标。
[]是变址运算符,*(a+i)和a[i]无条件等价
假设指针P指向数组a的首元素,那么素组元素以下几种表达方式是等价的:a[i]<—>*(a+i)<—>p[i]<—>*(p+i)(前提:令p = a)
数组名是地址常量,指针是地址变量,数组名可以被作为指针参与运算,但是不能被赋值
根据上面提到的特性我们可以推出以下几个表达式是等价的:
```c
/*前提*/
int *p,a[10];
p = a;
/*等价关系*/
a <----->&a[0] //数组首地址
*a <----->a[0]
*(a+i)+b <------> a[i] + b
*a++ <-------> a[i++]
*++a <-------> a[++i]
*a-- <-------> a[i--]
*--a <-------> a[--i]
7. 指针与二维数组
二维数组可以看做是以一维数组为元素的特殊的一维数组。即多维数组就是低维数组组合而成。多维数组参与运算时以行为单位进行移动,也被称为行地址。
我们依旧以指针来理解多维数组:
- a[]—>第一行第一列元素的地址
- a---->第一行的首地址,也等于第一行第一列元素的地址
- a+1—>第二行第一列元素的地址
基于上面三点,我们可以得出以下对等关系:
a[1][1]<->*(&(a[1][0])+1)<->*(a[1]+1)<->*(*(a+1)+1)
还可以得到:a[1] = *(a+1)
可能这个表示不是很好理解,下面我来仔细分析一下:-
a是以行为单位进行移动的,就是代表行地址。而我们多维数组又可以看做是由一维数组一行一行的排列而成,所以我们可以得到。然后我们可以得出行指针是指向这一行一维数组的首地址的指针,也就相当于二级指针。
-
而数组中的表示符号’[]'表示偏移加取值(我们可以联想到一维数组中的利用数组下标取值)。所以a[1]就很好理解了,二维数组第一行数组的地址偏移一行,也就是第二行,再进行取值操作。前面有提到二维数组的数组名相当于二级指针,对它进行取值后得到的就是一级指针。也就是第二行数组的首(元素)地址。
-
经过上面两点的分析,我们已经可以理解了,为什么a+1后还要进行取值操作了。目的当然是为了得到一级指针,也就是第二行数组首(元素)地址。
理解了上面这个表达式后,我们对多维数组中的任何表达式都能轻车熟路了,当然了再总结一下:
-
当偏移量前的元素单元是整个素组时,偏移的单位是行,当偏移量前的元素单元是行时,偏移的单位是行中的元素。
8. 多级指针
指向一级指针变量的指针称为二级指针。同理可推:n级指针也就是指向n-1级指针变量的指针。
二级指针: <存储类型> <数据类型> **<指针名>
int m = 100,*p = NULL;
int **q = NULL;
p = &m;
q = &p; //等价于*q = p,**q = m
9. 指针数组
指由若干个具有相同存储类型和数据类型指针变量构成的集合。
定义形式:<储存类型> <数据类型> *<指针变量名> [<大小>]
不够生动和具体,我们来举个例子:
int *a[5];
-
上面就定义了一个指针数组,我们来分析一下:
- 因为’[]‘优先级比’*‘要高,所以a先与’[]'结合,组成一个一维数组。
- 然后这个移位数组再与’*'结合,此时数组中的每一个元素就都变成了一个指针变量。
- 于是每一元素都是指针变量的数组,就是指针数组。
- 我们的指针数组常用来存储多个字符串。
-
其实我们可以看出来,指针数组名实际为二级指针。
-
但是指针数组,我们还会经常和数组指针一个概念搞混淆。
下面来看下什么是数组指针:
存储类型 <数据类型> (*数组指针名) [大小]
比如:int (*p) [4];//这里必须加’()‘因为’[]'优先级更高
分析:这就是定义了一个指针变量p,它指向了含有四个元素的一维数组。
从名字上就可以看出,数组的指针,也就是指向数组的指针。是不是很熟悉,提醒一下前面讲到的二维数组时提到的指向一维数组的指针,比如数组a[2][3],a就是指向一行一维数组的指针,a+1就是指向第二行一维数组的指针。这么一说就显得很简单了。
数组指针其实就是用来存储数组地址的指针。其本质是一个指针
10. const与指针
const关键字的作用是使得变量常量化,即限制变量为只读。若用来使指针变量常量化。
下面来分析一下,以下几个表达式的不同之处:
const int *a;
//限制了通过指针去修改指针所指向的目标,但是该变量可以通过修改自身的值进行改变。
int const *a;
//同上面的一样。
int *const a;
//指针变量存储的地址值不能被修改。但是可以*a去修改指针的目标值。
const int *const a;
//常量化指针变量和其表达式,即不可以修改指针变量的地址,也无法通过*a去修改目标值。
前两个位限制指针变量a所指向的变量的值不能被修改;第三种形式时限定指针变量a的不能被改动。第四种为限制指针变量的值及其所指向的变量的值都不能修改。
-
总结:
- 1.指针变量名与const之间没有任何修饰符,限制指针变量为只读。即限定指针变量存储的地址值不能被改变
- 2.指针指向的目标值为只读,const修饰(int )或者修饰*,不能通过p去修改。
11. void指针
- void类型的指针变量是一种不确定数据类型的指针变量,它可以通过强制类型转换,才能让指针变量指向其他数据类型。
- *在没有强转之前void 类型的指针不能进行任何数据运算。
12. 字符指针
-
字符指针就是存储字符变量的地址
-
利用字符指针对字符串进行遍历
-
虽然字符数组名是字符串的首地址,但是数组名是一个常量,其值是不能改变的(不能进行自加、自减、赋值等操作),但如果把字符数组的首地址赋值给一个字符指针变量,就可以移动这个字符指针变量来访问每一个字符。
字符指针数组:
-
若数组中存储了若干个字符串的地址,则这个数组就叫做字符指针数组。例如:
char s1[] = {"welcime"}; char s2[] = {"to"}; char s3[] = {"wuhan"}; char *a1[] = {s1,s2,s3}; char *a2[] = {"welcome","to","wuhan"};
上面这段代码中,a1和a2都是字符指针数组。a1,a2存储的是一维指针的地址,所以,a1,a2也是二级指针,也是我们前面提到的指针数组。
-