C/C++のポインタの機能--配列との関係

沖林正紀
2008-03-28 17:00:00
  • このエントリーをはてなブックマークに追加

ポインタ変数と配列との関係

 ポインタ変数と配列との深い関係を表す例を示そう。それは、配列の変数名をポインタ変数のように扱えるということだ。

    int n[] = { 7, 3, 5, 9, 2 };
    printf( "%d\n", *n );  /*  7 と出力される  */

 上記は、nを5つのint型の要素を持つ配列として宣言した後、すぐに*nの値を表示させている。この結果は7と出力される。つまり、*nは配列の最初の要素(n[ 0 ])の値を表していることになる。

 では、n[ 1 ]以降の値を*nを使って表すにはどうしたらよいだろうか。そのためには以下のように記述する。

    int n[] = { 7, 3, 5, 9, 2 };
    printf( "%d\n", *(n + 1) );   /* 3 と出力される */
    printf( "%d\n", *(n + 2) );   /* 5 と出力される */

 こうすると、*(n + 1)がn[ 1 ]の値を表し、*(n + 2)がn[ 2 ]の値を表すことがお分かりいただけるだろう。

ポインタ変数と文字列との関係

 C/C++においては、文字列は文字の配列として扱われるので、ポインタ変数との関係はほとんど上記のint型配列の場合と同じなのだが、異なっている点もある。以下に例を示そう。

    char s[] = "Hello";
    printf( "%c\n", *s );         /* H と出力される */
    printf( "%c\n", *(s + 1) );   /* e と出力される */
    printf( "%c\n", *(s + 2) );   /* l と出力される */
    printf( "%s\n", s );          /* Hello と出力される */

 配列sをHelloという文字列で初期化した後、*sを書式%cで出力すると、最初の文字Hが表示される。これはs[ 0 ]の値だ。同じ書式で*(s + 1)の値を出力すると、eが表示される。これはs[ 1 ]の値だ。ここまでのところは、書式を%cにしている以外はint型の場合と同じだ。

 int型と異なるのは書式%sのときだ。ここでは*sではなくsと記述している。以前にもこのような処理については説明しているが、%sは桁数の指定がない時は文字列の終端('\0')までを*sから順に*(s + 1)の値、*(s + 2)の値、と出力することを表す。その結果、文字列が出力されることになるわけだ。

 ここで、文字へのポインタを使って違う書き方をしてみよう。以下に例を示す。前述の配列の場合と処理の仕方に変わりがないことがおわかりいただけるだろう。

  char *p = "Hello";
  printf( "%c\n", *p );       /* H と出力される */
  printf( "%c\n", *(p + 1) ); /* e と出力される */
  printf( "%s\n", p );        /* Hello と出力される */

 しかし、前述の配列では、配列変数sを指定された文字列で「初期化」していたのに対し、今回のポインタ変数pには、"Hello"という文字列を指すポインタ(アドレス)が代入されているという違いがある。配列は、まるごと“代入”することができないので、sとvという同じ型の配列があるとき s = v;とは書けないが、p と q という同じ型のポインタであればp = q; と書くことはできる。

 たとえば、ポインタ変数pに別の文字列へのポインタを代入したいのであれば、変数を初期化した後でも p = "Welcome"; とすることができる。なお、文字列(文字の配列)以外に、任意の型の配列定数を表現することはできないので、初期化時に int n[] = { 1, 2, 3 }; と書くことはできても、int *p; に対して p = { 1, 2, 3 }; と書くことはできない。

  • コメント(12件)
#1 επιστημη   2008-03-29 10:05:24
> ポインタ変数と配列との深い関係を表す例を示そう。
> それは、配列の変数名をそのままポインタ変数名として扱えるということだ。

違います。n に値を代入できません(左辺値になれない)から、配列の変数名はポインタ"定数"扱いです。
#2 επιστημη   2008-03-29 10:14:39
[2page冒頭]
> さきほど*sで1つの文字列を表していたが、

違います。*sは"文字列"ではなく(先頭の)"文字"です。
文字列を表すのは*のつかない s です。
#3 がる   2008-03-31 00:20:21
んと…とりあえず筆者の方に伺ってみたいのですが。
char s[] = "Hello";

char *s = "Hello";
との違いが「どのあたりにある」と感じられてますでしょうか?
この違いがわかれば「配列の変数名をそのままポインタ変数名として扱える」といった暴論には繋がらないのでは無かろうか、と思います。

ポインタとか配列とかやるのであれば、まずはこのあたりの「メモリレベルでの差異」をきちんと理解してから、になさったほうがよろしいように思うのですが如何なものでしょうか。
#4 bright   2008-04-06 11:12:53
配列の変数名をそのままポインタ変数名として扱えるのではなく、配列のデータアクセスと、ポインタ経由でのデータアクセスに同じ表現が使えるだけのことです。この記述方法は便利ですが、初学者を混乱させてしまう仕様であることも事実だと思います。

初学者に誤った知識を与えないよう、早く説明を修正された方が良いでしょう。
#5 リュート   2008-04-17 21:27:08
気になったことがあるのですが,コメントの書き方で
char s[] = "Hello";

char s[] = {'H', 'e', 'l', 'l', 'o', '\0'};
が同じ内容のように読み取れるので,
「文字列は,s[]ではなく*sに直接代入する記述も可能」
という文は,
char *s = {'H', 'e', 'l', 'l', 'o', '\0' };
も可能である,と言っているように思えます.更にその直後の文字列のみに有効で,
int *n = {1, 2, 3};
という記述は不可能,という説明がその誤解を深めているように思います.
しかし,実際には
char *s = {'H', 'e', 'l', 'l', 'o', '\0'};
も不可能だと思います.
少なくとも,コメントの中の/* char s[] = {'H', ... */ の部分は削除した方が良いのではないでしょうか.
#6 がる   2008-04-19 01:33:55
ところで。「C/C++のポインタの機能--配列との関係」が未修正のまま大分時間が経過しているように見えるのですが。
いつ頃修正されるご予定なのでしょうか?
まぁ「レビュー中」と先頭に書かれているので大丈夫だとは思うのですが…少々きになりましたもので。
#7 tomita   2008-04-21 13:59:01
builder編集部の冨田です。
連載の修正が遅れており、ご迷惑をおかけしております。申し訳ございません。

いついつまでに修正を完了するとのお約束は致しかねる状況なのですが、
現在、再発防止策も合わせて検討しております。

今しばらくご辛抱頂ければ幸いです。
#8 オワタ   2008-05-13 10:42:55
この筆者の方はIなのですか。。。
Iのレベルも落ちたもんだ。。。
#9 tomita   2008-06-03 18:29:19
builder編集部の冨田です。

本記事を6月3日の18時に修正しました。
修正が多岐に渡っているため、修正個所を逐次記載するのではなく、
前回掲載分を下記にそのまま転載いたします。
#10 tomita   2008-06-03 18:29:41
1ページ目

ポインタ変数と配列との関係

 ポインタ変数と配列との深い関係を表す例を示そう。それは、配列の変数名をそのままポインタ変数名として扱えるということだ。

int n[] = { 7, 3, 5, 9, 2 };
printf( "%d\n", *n ); /* 7 と出力される */

 上記は、nを5つのint型の要素を持つ配列として宣言した後、すぐに*nの値を表示させている。この結果は7と出力される。つまり、*nは配列の最初の要素(n[ 0 ])の値を表していることになる。

 では、n[ 1 ]以降の値を*nを使って表すにはどうしたらよいだろうか。そのためには以下のように記述する。

int n[] = { 7, 3, 5, 9, 2 };
printf( "%d\n", *(n + 1) ); /* 3 と出力される */
printf( "%d\n", *(n + 2) ); /* 5 と出力される */

 こうすると、*(n + 1)がn[ 1 ]の値を表し、*(n + 2)がn[ 2 ]の値を表すことがお分かりいただけるだろう。
ポインタ変数と文字列との関係

 C/C++においては、文字列は文字の配列として扱われるので、ポインタ変数との関係はほとんど上記のint型配列の場合と同じなのだが、異なっている点もある。以下に例を示そう。

char s[] = "Hello"; /* s[] = { 'H', 'e', 'l', 'l', 'o', '\0' } */
printf( "%c\n", *s ); /* H と出力される */
printf( "%c\n", *(s + 1) ); /* e と出力される */
printf( "%c\n", *(s + 2) ); /* l と出力される */
printf( "%s\n", s ); /* Hello と出力される */

 配列sにHelloという文字列を代入した後、*sを書式%cで出力すると、最初の文字Hが表示される。これはs[ 0 ]の値だ。同じ書式で*(s + 1)の値を出力すると、eが表示される。これはs[ 1 ]の値だ。ここまでのところは、書式を%cにしている以外はint型の場合と同じだ。

 int型と異なるのは書式%sのときだ。ここでは*sではなくsを記述している。以前にもこのような処理については説明しているが、実は%sは特別な書式で、桁数の指定がない時は文字列の終端('\0')までを*sから順に*(s + 1)の値、*(s + 2)の値...と出力することを表す。その結果、文字列が出力されることになるわけだ。

 ちなみに文字列は、s[]ではなく*sに直接代入する記述も可能だ。以下に例を示す。両者とも処理の仕方は特に変わりないことがお分かりいただけるだろう。これは文字列のみに有効な記述であり、数値型でint *n = { 1, 2, 3 };などと記述することはできない。

char *s = "Hello";
printf( "%c\n", *s ); /* H と出力される */
printf( "%c\n", *(s + 1) ); /* e と出力される */
printf( "%s\n", s ); /* Hello と出力される */
#11 tomita   2008-06-03 18:30:04
2ページ目

ポインタ変数の配列

 ポインタ変数も配列を宣言できる。これは文字列の配列を表す場合によく用いられる。以下にその例を示す。

char *s[] = { "Japanese", "English", "Chinese" };
printf( "%s\n", s[ 0 ] ); /* Japanese と出力される */
printf( "%s\n", s[ 1 ] ); /* English と出力される */
printf( "%s\n", s[ 2 ] ); /* Chinese と出力される */
printf( "%s\n", *s ); /* Japanese と出力される */
printf( "%s\n", *(s + 1) ); /* English と出力される */
printf( "%s\n", *(s + 2) ); /* Chinese と出力される */

 さきほど*sで1つの文字列を表していたが、この例では*s[]によって複数の文字列を要素とする配列を表している。ただ、さきほどs[]でも*sでも1つの文字列を表すことができ、処理するにあたって特に変わりないことを説明したところだ。

 それなら、*s[]を**sのように記述したらどのように処理されるだろう。以下の例をご覧いただきたい。

char *s[] = { "Japanese", "English", "Chinese" };
printf( "%c\n", **s ); /* J と出力される */
printf( "%c\n", **(s + 1) ); /* E と出力される */
printf( "%c\n", **(s + 2) ); /* C と出力される */

 s は*s[]の最初の文字列の頭文字であるJを表している。同様に(s + 1)は2番目の文字列の頭文字E、*(s + 2)は3番目の文字列の頭文字Cを表していることが分かる。それ以外の文字をポインタ変数で表すには、どうしたら良いのだろう。

 次回は、この続きを説明していきたい。
#12 tomita   2008-06-03 18:30:39
以上です。
読者の皆様には大変ご迷惑をおかけしましたこと、深くお詫び申し上げます。
  • 新着記事
  • 特集
  • ブログ
このサイトでは、利用状況の把握や広告配信などのために、Cookieなどを使用してアクセスデータを取得・利用しています。 これ以降ページを遷移した場合、Cookieなどの設定や使用に同意したことになります。
Cookieなどの設定や使用の詳細、オプトアウトについては詳細をご覧ください。
[ 閉じる ]