C言語講座 第3回

第2回では、文字列の宣言と出力方法の話が出てきました。今回は、もう少し詳しく文字列の扱いを説明していきましょう。今回はポインタの話も出てきます。

まずは、次のプログラムを入力してください。このプログラムは、20種類のアミノ酸を1文字表記で出力します。

#include <stdio.h>
#include <string.h>

int main()
{
   int   i;    /* i: loop counter */
   char  one[] = "ACDEFGHIKLMNPQRSTVWY";

   for (i = 0; i < strlen( one ); i++) {
       printf("%c ", one[i]);
   }
   printf("\n");
   return 0;
}

amino1.cという名前で保存して、コンパイルし、実行しましょう(プログラム名はamino1)。

 $ cc -o amino1 amino1.c
 $ ./amino1

結果は、

 A C D E F G H I K L M N P Q R S T V W Y

と出力されます。

1行目および3〜5行目までは、これまでと同じですが、2行目に新しい分が付け加わりました。この2行目は9行目で用いられているstrlen()関数の定義が記述されている“string.h”を読み込むための行です。この行がないと、strlen()関数が正しく働きません。

6行目ではint型の変数“i”を宣言しています。その後にある/* */で囲まれた部分はコメント文で、プログラムの内容には直接は関わりません。Pythonの時のように、変数の意味などの注釈をつけておく時によく用いられます。なお、C言語のコメント文は入れ子構造(コメント文の中にコメント文がある状態、つまり、“/* comment 1 /* comment 2 */ */”のような形)にはできないことを覚えておいてください。話を戻して、“i”はコメント文にもあるように繰り返しを数えるために使います。

7行目は、第2回でも出てきた、文字列の宣言および初期化のための行です。char型の配列変数oneを宣言しており、この変数をアミノ酸の1文字表記で初期化しています。この行においてone[0]に’A’、one[1]に’C’、…one[19]に’Y’が代入された“one”という文字列が作られます。

9〜11行目は、配列変数oneを出力しています。C言語におけるfor文は、Pythonでの使い方と少し異なり、for (式A;式B;式C) {命令}という形で使われます。for文に到達すると、まず式Aが実行されます。次に、式Bを評価します。もし式Bが成立していれば、“{命令}”の部分を実行後、式Cを実行してもう一度式Bを評価します。式Bが成立しなくなった場合にはfor文が終了します。8〜10行目を読み下すと、まず変数iに0を代入します。次に、i < strlen( one )を評価します。strlen()関数は、char型の配列変数を引数として渡すと、その配列変数に入っている文字列の長さを返します。この場合は配列変数oneが20個の文字からできているので、20が返されます。iの中身が0なので式が成立し、9行目を実行します。これは、i番目の文字列oneの中身の表示、つまりone[0]で指定される変数の中身(この場合は'A')の表示です。次にi++(iに1を足す)を実行します。またi < strlen(one)を評価し...と続きます。結局変数iは0から19まで変化するので、one[0], one[1], ..., one[19]の順番に文字が出力、つまり、'A', 'C', ...'Y'が出力されることになります。 このプログラムにおいて7行目を、 [c light="true"] char *one = "ACDEFGHIKLMNPQRSTVWY"; [/c] と記述してもほぼ同じ結果になります。oneの前の"*"がポインタを示す記号です。配列による文字列の宣言+初期化とポインタによる文字列の宣言+初期化には細かい違いがありますが、現時点では同じことだと思っても問題はありません。とにかく、配列でもポインタでも同じようなことが表現できることを覚えておいて下さい。 次は、20種類のアミノ酸を3文字表記で出力するプログラムです。 [c] #include <stdio.h> int main() { int i; /* i: loop counter */ char three[20][4] = { "Ala", "Cys", "Asp", "Glu", "Phe", "Gly", "His", "Ile", "Lys", "Leu", "Met", "Asn", "Pro", "Gln", "Arg", "Ser", "Thr", "Val", "Trp", "Tyr" }; for (i = 0; i < 20; i++) { printf("%s ", three[i]); } printf("\n"); return 0; } [/c] amino3.cという名前で保存し、プログラム名はamino3としてコンパイルしましょう。実行すると、

 Ala Cys Asp Glu Phe Gly His Ile Lys Leu Met Asn Pro Gln Arg Ser Thr Val Trp Tyr

と出力されます。

基本的なプログラムの流れは、最初のプログラムと同じです。大きな違いは6〜9行目になります。

このプログラムでは、アミノ酸配列の3文字表記を表示するために、3つの文字からなる文字列を20個用意する必要があります。このような場合に使われるのが2次元配列です。

6〜9行目は、char型の配列変数threeを宣言しており、この変数をアミノ酸の3文字表記で初期化しています。“char three[20][4] =”という部分は、4つの文字を入れられる文字列が20個できるようにchar型の2次元配列変数を宣言し、それを“=”以下の文字列で初期化するという意味になります。複数の文字列で初期化する場合は、””で囲まれた文字列を“,”で区切り、それを“{ }”で囲います。

3文字分しか代入しないのになぜ4文字分の配列を用意するか疑問に思うかもしれません。これは、第2回でも出てきたように、文字列の最後には必ず”\0″をつけなければいけないためです。初期化する命令文では特に明記していませんが、コンパイルされる時に4文字目に”\0″が付け加わっています。

11〜13行目では、配列変数threeの中身を表示しています。第2回で、文字列を出力する時はprintf()関数において”%s”を使いました。2次元配列の場合は、1次元目だけ(three[0], three[1], …, three[19])を指定して”%s”を使うと、それに対応する文字列(”Ala”, “Cys”, …”Tyr”)が出力されます。

なお、このプログラムの6〜9行目は、下記のように書いてもほぼ同じ意味になります。

   char  *three[] = { "Ala", "Cys", "Asp", "Glu", "Phe",
                      "Gly", "His", "Ile", "Lys", "Leu",
                      "Met", "Asn", "Pro", "Gln", "Arg",
                      "Ser", "Thr", "Val", "Trp", "Tyr" };

これは、文字列へのポインタの配列を宣言し、その中身を“=”以下で初期化するという意味になります。配列の大きさは、20個の文字列で初期化しているので自動的に20になります。この場合も、こういう書き方があることを覚えておいて下さい。

最後に、アミノ酸の3文字表記に対応する1文字表記を覚えるためのプログラムを作ってみましょう。 プログラムでやることは、

  1. 20種類のアミノ酸の3文字表記および1文字表記が入った配列を用意する。
  2. 3文字表記でアミノ酸を出力する。
  3. キーボードで入力された1文字表記のアミノ酸を読み取る。
  4. 入力された文字列が正しい1文字表記かを調べ、正しければ「正解」、間違っていれば正しい1文字表記を出力する。
  5. 2.から4.をすべての種類のアミノ酸分繰り返す。

上記の方針に従って作成したプログラムを示します。入力して、three2one.cというファイル名で保存して下さい。

#include <stdio.h>
  
#define MAXCHR      8
  
int main()
{
    int    i;
    char   input[MAXCHR];
    char  *three[] = { "ALA", "CYS", "ASP", "GLU", "PHE",
                       "GLY", "HIS", "ILE", "LYS", "LEU",
                       "MET", "ASN", "PRO", "GLN", "ARG",
                       "SER", "THR", "VAL", "TRP", "TYR" };
    char  *one = "ACDEFGHIKLMNPQRSTVWY";
  
    for (i = 0; i < 20; i++) {
        printf("3-letter code : %s\n", three[i]);
        printf("1-letter code ? ");
        fgets(input, MAXCHR, stdin);
     
        if (input[0] == one[i]) {
            printf("right!\n");
        } else {
            printf("wrong! %c\n", one[i]);
        }
    }
    return 0;
}

このプログラムでは、キーボードから入力された文字を記録するために新しい命令が2つ増えています(3行目、18行目)。

まず3行目は、プログラム中に存在する“MAXCHR”という文字列をすべて数字の“8”に置き換えるという意味です。#define命令により、プログラム中に何度も出てくる数字などを一度に指定することができます。この命令をうまく使うことで、プログラムの変更に伴う数値の修正のし忘れなどが少なくなるはずです。#defineの詳しい使い方はCの文法書を参照して下さい。

次に18行目は、標準入力から入力された文字を“MAXCHR”だけ文字列“input[]”に格納するという意味です。“MAXCHR”は#define命令により“8”に置き換えられているので、実際は標準入力からの8文字を文字列に代入します。なお、標準入力とはキーボードによる入力、リダイレクションによる入力を指します。

その他の新しい点は、20行目にあるif文です。このif文はPythonの場合と同じ様な使い方をします。おさらいすると、if文は if ( 判定式 ){ 命令1 } else { 命令2 } という形で使い、判定式が成り立てば命令1、成り立たなければ命令2を実行します(else以降は省略可能)。もし判定式が2つ以上あれば、 if ( 判定式1 ){ 命令1 } else if ( 判定式2 ) { 命令2 } else { 命令3 } という形になります。複数の判定式がある場合に、Pythonでは“elif”でつなぎますが、C言語では“else if”でつなぎます。

また、判定式において2つの変数の中身が同じかどうかを知りたい場合は“==”(=が2つ重なる)、異なるかどうかを知りたい場合は“!=”が使われます。C言語では配列同士を比較することはできず、必ず変数同士の比較となります。

なお、このプログラムのポイントは、アミノ酸の3文字表記が代入されている順番と1文字表記が代入されている順番を同じにするところです。これにより出力した3文字表記と入力する1文字表記の対応を簡単に見ることができるようになっています。

練習問題

  1. プログラムthree2one.cを改変し、正しい1文字表記を入力できた数を出力するようにしなさい。
  2. 1.のプログラムを改変し、アミノ酸の1文字表記に対応する3文字表記を覚えるプログラムにしなさい。ただし、文字列の比較には、strncmp()関数を用いる(string.hをincludeする必要があり)。strncmp()関数は2つの文字列を比較し、等しければ0、等しくなければ0以外の値を返す。具体的な使い方は、
     if (strncmp(three[i], input, 3) == 0) {
         printf("right\n");
     } else {
         printf("wrong! %s\n", three[i]);
     }
    

    となる。

  3. コドン3塩基を入力すると、それに対応するアミノ酸を出力するプログラムを作成しなさい。