可爱的指针(二)
在上一篇可爱的指针(一)中,我们了解了指针的基本内容。在这一篇中,我们主要了解指针与字符串、二维数组之间的操作。
字符串和指针
在具体讲字符串和指针之间的操作以前,我们首先回顾一下字符串。在C\C++中(抛开字符串string类),字符串是结尾为\0
的一个字符数组。比如,我们可以用一下几个方式定义并初始化字符串:
char A[8] = "Beijing";
char B[] = "Beijing";
char c[10] = "include"; //虽然定义了长度为10的字符数组,但实际上最后3位均为‘\0’
分析以下程序:
#include
using namespace std;
int main(){
char h[] = "Peking";
h[0] = 'a'; h[1] = 'b';
h[2] = 'c'; h[3] = '7';
h[4] = 'c';
cout<
此程序的逻辑不难,主要的核心就是替换h[0]
到h[4]
最后程序会打印出abc7cg
。有意思的地方在cout<
cout
语句却没有打印出地址,而是字符串的内容(直到找到\0
)。这就是cout
对字符数组特殊的处理。
简单介绍过字符串之后,我们来看看指向字符串的指针。其实和常规数组的指针一样,指向字符串的指针变量可以这样定义:char a[10]; char *p; p = a;
。
Example 1
int main(){
char a[] = "How are you?", b[20];
char *p1, *p2;
for(p1 = a, p2 = b; *p1 != '\0'; p1++, p2++)
*p2 = *p1;
*p2 = '\0';
cout<<"string a is: "<
这个程序的核心在*p2 = *p1;
,即把所有a
字符串的内容赋值给b
。所以程序会打印出:
string a is: How are you?
string b is: How are you?
Example 2
int main(){
char buffer[10] = "ABC";
char *pc;
pc = "hello";
cout<
这段程序主要考察使用cout
输出指向字符串的指针。cout<
hello
,在pc++
之后,指针指向字符'e',所以再打印pc
会输出ello
。对于cout<<*pc<
e
,因为*pc
此时等价于hello
中第二个字符(数组的名字是指向数组的第一个元素的指针,在pc
自增后,指向第二个元素)。pc = buffer;
意味着我们可以将另一个字符串指针赋值给pc
,因为pc
是指针变量,可以进行更新。综上,程序的运行结果是:
hello
ello
e
ABC
二维数组与指针
之前我们提到的指针与数组(无论是普通数组还是字符数组),都是考虑的一维的情况。在指针指向一维数组的情况时,指向数组的指针等价于指向数组第一个元素的指针。当指针指向二维数组时,这个规律依旧成立。我们先回顾一下二维数组的相关概念。
- 定义二维数组:
int a[3][4]
,表示定义了三个存放a[4]
型数据的存储单元。他们的名字分别为a[0], a[1], a[2]
。 - 二维数组
a[3][4]
包含三个元素:a[0], a[1], a[2]
。每个元素都是一个“包含四个整型元素”的数组。
[图片上传失败...(image-66b17d-1606053716702)]
这里,二维数组a[3][4]
中的a
代表指向第一个元素a[0]
的指针(注意这里的表述,虽然指向a[0]
的指针和指向a[0][0]
的指针的值是相同的,但如果把这两个指针分别加一,指针变量的变化是不一样的)。
总结一下,定义一个二维数组:int a[3][4] = {{1,3,5,7}, {9,11,13,15}, {17,19,21,23}};
- 由对一维数组的分析可知:数组名是指向数组第一个元素的指针。
- 且二维数组的第一个元素是
a[0]
(a[0]
是一个包含四个整型元素的一维数组)。 - 则可以做出判断:(1)
a
与&a[0]
等价;a[0]
与&a[0][0]
等价;(2)a[0]
与*a
等价;a[0][0]
与**a
等价。
乍一看可能有些晕。没关系,我们先看一下一维数组的情况,然后通过一维数组扩展到二维数组。
给定一个一维数组int a[4] = {1,3,5,7};
-
a
是指向数组第一个元素的指针,即a
等价于&a[0]
。如果我们将a
自加1,则a
会指向a[1]
,跨越4
字节。 -
*a
是数组的第一个元素a[0]
,即*a
等价于a[0]
。这里我们可以看出*a
相当于“下沉”了一级。 -
&a
是指向数组的指针,&a+1
将跨越16
字节。所以,&a
相当于“上浮”了一级。
Example 3
#include
void main(){
int a[4] = {1,3,5,7};
cout<<"a = "<
这个程序的运行结果是:
a = 0x7fffd5f41480
&a[0] = 0x7fffd5f41480
a+1 = 0x7fffd5f41484
&a[0]+1 = 0x7fffd5f41484
&a[1] = 0x7fffd5f41484
&a = 0x7fffd5f41480
&a+1 = 0x7fffd5f41490
原因如下:
- 因为
a
是指向数组第一个元素的指针常量,所以a
与&a[0]
等价,故第一行和第二行打印的值相同。 - 第三、第四、第五行打印的值相同,因为
a+1
为指向数组第二个元素的指针,和&a[0]+1
与&a[1]
等价。 -
&a
相当于“上浮”了一级。虽然&a
的值与a
和&a[0]
相同,但&a+1
会从a
跨越16个字节,所以&a+1
比a
大16
。
Example 4
#include
void main(){
int a[3][4] = {{1,3,5,7}, {9,11,13,15}, {17,19,21,23}};
cout<<"a="<
这个程序的输出结果如下:
a=0x7fffca660460
&a[0]=0x7fffca660460
a+1=0x7fffca660470
&a[0]+1=0x7fffca660470
&a[1]+1=0x7fffca660480
*a=0x7fffca660460
a[0]=0x7fffca660460
&a[0][0]=0x7fffca660460
*a+1=0x7fffca660464
a[0]+1=0x7fffca660464
&a[0][0]+1=0x7fffca660464
a[1]=0x7fffca660470
*(a+1)=0x7fffca660470
a[1]+1=0x7fffca660474
*(a+1)+1=0x7fffca660474
&a=0x7fffca660460
&a+1=0x7fffca660490
有了一维数组的结果做铺垫,二维数组的例子就更好理解了。
-
a
与&a[0]
等价。 - 由于
a
为指向数组第一个元素的指针,并且数组第一个元素占16
字节,所以a+1
和&a[0]+1
等价,比a
大16
字节。&a[1]+1
同理。 - 由于
*a
下沉了一级,所以*a
代表a[0]
。a[0]
等价于&a[0][0]
。*a+1
等价于a[0]+1
(比a[0]
的地址大4
字节,只跨越了1个数)。&a[0][0]+1
等价于a[0]+1
。 -
*(a+1)
下沉一级,所以等价于a[1]
。a[1]+1
比a[1]
多4
字节,因为此时级别在a[1]
这个数组,自增1只会跨越1个数。*(a+1)+1
与a[1]+1
等价。 -
&a
上浮一级,所以&a+1
会跨越整个二维数组,比&a
大12*4=48
个字节。
Example 5
利用指针变量引用多维数组中的数组
- 输入i,j; 输出
a[i][j]
void main(){
int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};
int (*p)[4], i,j;
p = a;
cin>>i>>j;
cout<
这段程序主要是要关注int (*p)[4];
的定义。这里,p
是一个指向有四个元素的数组的指针。*(p+i)
等价于a[i]
,*(p+i)+j
等价于a[i]+j
,即&a[i][j]
。再在最前面加上*
,*(*(p+i)+j)
为a[i][j]
。
指针数组
数组中各个数组元素均为指针类型的数据,组成的数组就是指针数组。int *pointer[10];
指针数组最频繁的用途就是存放很多字符串,比如下面这个例子:
Example 6
void main(){
char *name[] = {"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer Design"};
char **p = name;
for(;p
这里,char *name[]
就是一个指针数组。
指向指针的指针
前面提到我们可以用指针变量来存放地址,每一个变量也是对应一个地址。所以一定存在一个指针,它指向一个指针变量,我们可以把它看成指向指针的指针。在Example 6中,char *name
是一个指针数组,它的每一个元素都是一个指针。如果我们想通过一个指针来访问每一个指针数组的元素,我们可以使用指向指针的指针char **p=name;
。这里,p
指向指针数组的第一个元素(为“Follow me”的指针)。