可爱的指针(二)

可爱的指针(二)

在上一篇可爱的指针(一)中,我们了解了指针的基本内容。在这一篇中,我们主要了解指针与字符串、二维数组之间的操作。

字符串和指针

在具体讲字符串和指针之间的操作以前,我们首先回顾一下字符串。在C\C++中(抛开字符串string类),字符串是结尾为\0的一个字符数组。比如,我们可以用一下几个方式定义并初始化字符串:

char A[8] = "Beijing";
char B[] = "Beijing";
char c[10] = "include"; //虽然定义了长度为10的字符数组,但实际上最后3位均为‘\0’

分析以下程序:

#include<iostream>
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<<endl;
  return 0;
}

此程序的逻辑不难,主要的核心就是替换h[0]h[4]最后程序会打印出abc7cg。有意思的地方在cout<<h<<endl;。之前我们提到数组名字等价于指向这个数组的指针(数组第一个元素的地址),但是使用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: "<<a<<endl;
  cout<<"string b is: "<<b<<endl;
  return 0;
}

这个程序的核心在*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<<pc<<endl;
  pc++;
  cout<<pc<<endl;
  cout<<*pc<<endl;
  pc = buffer;
  cout<<pc<<endl;
  return 0;
}

这段程序主要考察使用cout输出指向字符串的指针。cout<<pc<<endl;可以直接打印出hello,在pc++之后,指针指向字符'e',所以再打印pc会输出ello。对于cout<<*pc<<endl;,只会打印单独的字符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<iostream.h>
void main(){
  int a[4] = {1,3,5,7};
  cout<<"a = "<<a<<endl;
  cout<<"&a[0] = "<<&a[0]<<endl;
  cout<<"a+1 = "<<a+1<<endl;
  cout<<"&a[0]+1 = "<<&a[0]+1<<endl;
  cout<<"&a = "<<&a<<endl;
  cout<<"&a+1 = "<<&a+1<<endl;
}

这个程序的运行结果是:

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+1a16

Example 4

#include<iostream.h>
void main(){
   int a[3][4] = {{1,3,5,7}, {9,11,13,15}, {17,19,21,23}};
   cout<<"a="<<a<<endl;
   cout<<"&a[0]="<<&a[0]<<endl<<endl;

   cout<<"a+1="<<a+1<<endl;
   cout<<"&a[0]+1="<<&a[0]+1<<endl;
   cout<<"&a[1]+1="<<&a[1]+1<<endl<<endl;
   cout<<"*a="<<*a<<endl;
   cout<<"a[0]="<<a[0]<<endl;
   cout<<"&a[0][0]="<<&a[0][0]<<endl<<endl;

   cout<<"*a+1="<<*a+1<<endl;
   cout<<"a[0]+1="<<a[0]+1<<endl;
   cout<<"&a[0][0]+1="<<&a[0][0]+1<<endl<<endl;

   cout<<"a[1]="<<a[1]<<endl;
   cout<<"*(a+1)="<<*(a+1)<<endl;
   cout<<"a[1]+1="<<a[1]+1<<endl;
   cout<<"*(a+1)+1="<<*(a+1)+1<<endl<<endl;

   cout<<"&a="<<&a<<endl;
   cout<<"&a+1="<<&a+1<<endl;
}

这个程序的输出结果如下:

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等价,比a16字节。&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]+1a[1]4字节,因为此时级别在a[1]这个数组,自增1只会跨越1个数。*(a+1)+1a[1]+1等价。
  • &a上浮一级,所以&a+1会跨越整个二维数组,比&a12*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<<setw(4)<<*(*(p+i)+j);
}

这段程序主要是要关注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<name+5;p++){
      cout<<*p<<endl;
   }
}

这里,char *name[]就是一个指针数组。

指向指针的指针

前面提到我们可以用指针变量来存放地址,每一个变量也是对应一个地址。所以一定存在一个指针,它指向一个指针变量,我们可以把它看成指向指针的指针。在Example 6中,char *name是一个指针数组,它的每一个元素都是一个指针。如果我们想通过一个指针来访问每一个指针数组的元素,我们可以使用指向指针的指针char **p=name;。这里,p指向指针数组的第一个元素(为“Follow me”的指针)。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容