スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

C言語における配列とポインタによる文字列定義の違い

C言語では、変数の型に文字列型というものが存在しません。C言語で文字列を定義するには、主に二つの方法があります:

  • char型の配列を使用する
  • char型のポインタを使用する

この記事では、両者の違いによる注意点を述べたいと思います。

配列による定義とポインタによる定義との違い

文字定数・文字列・文字列リテラル

文字定数は、「'a'」のように、文字をアポストロフィ(')で囲んで表記します。文字定数のなかには、改行を表す「'\n'」のように、エスケープ記号(\)を使って表される特殊な文字定数があります。それをエスケープシーケンスといいます。C言語では、文字定数は、その文字に対応した文字コード(整数値)を値にとります。

C言語における文字列は、文字の並びと、その終端にヌル文字と呼ばれるエスケープシーケンス「'\0'」を付け加えたものからなります。ヌル文字「\0」は文字列の終端を識別するために必要です。ヌル文字の付加されていない文字の並びを文字列として扱うと、プログラムの誤動作につながる可能性があります。

C言語では、文字の並びを「"abcde"」のようにダブルクォーテーション(")で囲んだものを文字列リテラルあるいは文字列定数といいます。文字列リテラルは、文字列が格納されたメモリ領域へのポインタを返します。つまり、char*型の値をとります。

文字列の定義方法

C言語では、文字列を定義する方法が二種類あります:

  • char型の配列によって定義する
  • char型のポインタによって定義する

char型の配列によって定義する場合:

/* 以下の4つは同等 */
char s[]  = {'a', 'b', 'c', 'd', 'e', '\0'};  /* 文字定数を使う */
char t[6] = {'a', 'b', 'c', 'd', 'e', '\0'};  /* 文字定数を使う */
char u[]  = "abcde";  /* 文字列リテラルを使う */
char v[6] = "abcde";  /* 文字列リテラルを使う */

char型のポインタによって定義する場合:

char *p = "abcde";

ポインタ変数に対して、以下の定義の仕方はエラーになります:

char *q = {'a', 'b', 'c', 'd', 'e', '\0'};  /* コンパイル時エラー */

許される操作の違い

C言語では、文字列を配列によって定義した場合と、ポインタによって定義した場合とで、許される操作が異なります。

文字列を配列によって定義した場合、要素ごとに読み書きができます。

char s[] = "abcde";

s[1] = 'B';
printf("%s\n", s);  /* => aBcde */

一方、文字列をポインタによって定義した場合、ポインタが指す文字列の実体を直接書き換える操作は許されていません。

char *p = "abcde";  /* const修飾子をつけないと危ない? */

p[1] = 'B';  /* 動作は不定(コンパイル時エラーが出ない) */

C言語の文字列リテラル"~"は、文字列が格納されたメモリ領域へのポインタを返すので、char*型の値をとります。それゆえ、通常の変数へ代入するのは不正な操作です。

char s[] = "abcde";

s[1] = "B";  /* 誤動作(コンパイル時エラーが出ない) */

C言語では、等号(=)によって配列全体の内容を一括変更することはできません。

char s[6];

s = "abcde";  /* コンパイル時エラー */
char s[6] = "abcde";
char t[6];

t = s;  /* コンパイル時エラー */

その代わり、配列全体のコピーには、memcpy関数、memmove関数が使えます。また、文字列のコピーであれば、strcpy関数、strncpy関数、sprintf関数、snprintf関数も使えます(バッファオーバーランの問題については、この記事では触れません)。もちろん、for文によるループで要素ごとにコピーすることも可能です。

#include <stdio.h>
#include <string.h>  /* memmove */

int main(void)
{
  char s[] = "abcde";
  char t[10];

  memmove(t, s, sizeof(s));
  printf("%s\n", t);  /* => abcde */

  return 0;
}

一方、ポインタ変数に対する以下の操作は正常です。ある文字列リテラルを指すポインタが格納されたポインタ変数に、別の文字列リテラルを指すポインタを代入しています。

char *p = "abcde";

p = "ABCDEFG";
printf("%s\n", p);  /* => ABCDEFG */

配列全体へのポインタについて

上では、ポインタによって定義した文字列が配列として扱えないという話をしました。しかし、それと、配列全体へのポインタを操作することとは別の話です。念のため注意しておきます。

#include <stdio.h>

int main(void)
{
  char s[] = "abcde";
  char *p;

  p = s;  /* p に配列 s 全体へのポインタを代入 */
  p[1] = 'B'; 

  printf("%s\n", p);  /* => aBcde */

  return 0;
}

sizeof演算子の注意点

C言語におけるsizeof演算子の注意点

文字列からなる配列

C言語における文字列からなる配列

【theme : プログラミング
【genre : コンピュータ

プロフィール

よしいず

Author:よしいず
MATHEMATICS.PDFというウェブサイトを運営しています。

管理の都合上、トラックバックとコメントはオフにしてあります。ブログ経験者なら分かっていただけると思いますが、スパム(アダルトやその他の宣伝)ばかりなのが現実です。

リンクは自由です。当サイトの記事に対する間違いの指摘・意見・感想などを述べた記事からのリンクは歓迎です。ただし、ブログ記事アップ直後はミスが多く、頻繁に修正します。場合によっては削除する可能性もあります。その際、何も断りもなく修正・削除しますがご了承ください。内容を参考にする場合には投稿後一週間ほど様子を見てからにしてください(笑)。

記事の間違いを指摘するときは、その具体的箇所、理由(仕様に反するなど)・根拠(参考にした文献など)、代替案(同じ結果を得るための正しいやり方)も教えてください。そうしないと、(指摘される側および第三者はその時点では無知の状態なので、)どこが間違いなのか分かりませんし、本当に間違っているのかどうかが判断・検証できません。実際、間違いだと指摘されたことが結局は正しかったというケースもありますので。

このブログのタイトル一覧

リンク
月別アーカイブ
カテゴリ
最新記事
検索フォーム
RSSリンクの表示
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。