配列の大きさは変数によって動的に変えられる。

「ISO/IEC 9899 : 1999 – Programming Language C」(略称C99)という新しい規格のC言語では、配列の宣言が、そのサイズを変数で指定できるようになった。いわゆる、可変長配列 (Variable-size array) が使える。

たとえば、以下の関数は巨大な10進数を2進数に変換するものだが、一時的に加算の中間結果を保存するため、配列 tmp[] が必要。しかし、関数が呼び出されるまでは配列のサイズが判らないので、いままでのCなら malloc関数等を利用してメモリを確保しないといけなかったが、可変長配列を使えば、すっきりした書き方ができる。

    char tmp[dec_len]

勿論、配列のサイズとなる、変数dec_lenの値は0(ゼロ)やマイナスにならないよう、きちんとしたチェックはお忘れなく。

int dec2bin(char *bin, int dec_len, char *dec)
{
    int  i;
    int  d, r;
    int  bin_len;
    int  non_zero;
    char tmp[dec_len];
for (i = 0; i < dec_len; i++) tmp[i] = dec[i]; bin_len = 0; do { r = 0; non_zero = 0; for (i = dec_len-1; i >= 0; i--) { d = tmp[i]; tmp[i] = d >> 1; if (r > 0) tmp[i] += 10/2; if (tmp[i] > 0) non_zero = 1; r = d & 1; } bin[bin_len++] = r; } while (non_zero); return bin_len; }

ただ、可変長配列はどこでも使えるわけではない。可変長配列が使えるのは、ブロックの中か関数引数/プロトタイプの中だけで、グローバル変数として宣言したり、struct や union の中のメンバとして宣言したりすることはできない。加えて、static や extern 付きの配列は、可変長配列にできない。

また、malloc関数はメモリのヒープ領域を使うのに対し、可変長配列はスタック領域を使うので、ご注意を。つまり、mallocのように、メモリほ確保しておいて、呼び出された親関数に使わせることは可変長配列では不可能!

あくまでも、自関数内か、子孫関数内で使うもの。

文字列のコピーに strcpy() 以外、より安全な関数 strncpy がC言語に用意されている。

#include <string.h>
char *strncpy(char *des, const char *src, size_t n);
 文字型配列 des に文字列 src を先頭から n バイトだけをコピー

注意点
① srcの長さがn以上のときにはn文字をコピーするが、このときに ‘\0’ の自動付加は行わないので注意が必要。
② srcの長さがnより少ない場合には、desの中の、後ろの部分に ‘\0’ で埋められる。
③ もし、src と des が重なっている場合には動作は未定義となる。

つまり、strncpy() は文字列のn文字コピーという名前だが、最後のナル文字’\0’が抜けたりするので、気をつけないといけない。

安全のため、こう使うべきだろう。

  char des[n+1];
  strncpy(des, src, n);
  des[n] = '\0';

あるいは、自分で関数をつくってしまう。 ↓お勧め。注意点の3点ともクリア。

char *strncpy(char *des, const char *src, size_t n)
{
    while (n-- > 0 && *src != '\0') *des++ = *src++;
    *des = '\0';
    return des;
}

‘\0’と0とは同一ものと認めるならば(論理的には両者は違うけど)、3行目はつぎのようにも書ける。

    while (n-- > 0 && *src) *des++ = *src++;

余談だが、Unixコマンドのコピーは
 cp source-file target-file
コピー元→コピー先という順番、strncpy() ではコピー先 ← コピー元の順。完全に逆。

何で統一性がないだろう。なぞのひとつ。

インクリメント演算子、デクリメント演算子は、アセンブラ(昔の記憶。この10年間触ったこともなかったけど)のINC, DECとかの命令に対応しているので、C言語にも取り入れられている。

基本的なところは、前置、後置という形で、それぞれ2種類の書き方がある。

  ++i  ← 前置インクリメント演算子
  i++  ← 後置インクリメント演算子
  –i  ← 前置デクリメント演算子
  i–  ← 後置デクリメント演算子

  前置 ⇒ 1 を加算してから、変数 i を評価
  後置 ⇒ 変数 i を評価してから 1を加算

この副作用によって、場合によっては混乱したりする。また事実、仕様では定義されていないケースもある。例えば、

  a[i] = ++i;
  a[i] = i--;
  a[++i] = ++i;
  a[++i] = i++;
  a[i++] = ++i;
  a[i++] = i++;

の違いや計算結果は予測できるだろうか。

自分の知識を誇示し、こういう難しいものを持ち出して解説したりするひとがいるようだが、いくつかのケースでは実は未定義となっていることはご存知だろうか。コンパイラによって全然結果が変わるし。

C言語のつくられた当初と違って、スピードもメモリも、インクリメント・デクリメント演算子を必要とする時代ではなくなっている。だから、混乱するときには正直に
 
 i = i + 1
 i = i – 1

でいこう。暇あれば、コンテスト問題でも解いてみたら。

ゼニになるプログラムには決して使わない関数だが、コンテストのような即席プログラムを書く場合では、もちろん喜んで使う。scanf という関数。

C言語をかじったことのあるひとなら、誰もがその初歩的な使い方が判ってるだろうが、よく理解して使わないと、その動きに戸惑いを感じるかもしれない。とくにスペースを入れるべきかどうかということ。

<入力データ>
1文字 整数 1文字 実数
3
s 1 E 1.2
c 21 D 2.56
a 4 D 3.4

例えば、上のような入力に対し、scanf() をどう使うのだろうか。なお、入力データの1行目にその後に続く行数が入る(上では3行)。

解答プログラムは形として、こんなものかな。

#include <stdio.h>
int main(void) { int n; int k, c1, c2; double d;
scanf("1673837824", &n); while (n-- > 0) { scanf(" -2082935264`0.000000", &c1, &k, &c2, &d); printf(" -2082935264 ` 0.000000\n", c1, k, c2, d); } return 0; }

あるいは、間にスペースを入れるべきだろうか。

    scanf("  -2082935264 ` 0.000000", &c1, &k, &c2, &d);

さらに改行文字「\n」 をいれるべきだろうか。

    scanf("1673837824\n", &n);
    scanf("  -2082935264 ` 0.000000\n", &c1, &k, &c2, &d);

改行文字の代わりにスペースを入れるとどうだろう。

    scanf("1673837824 ", &n);
    scanf("  -2082935264 ` 0.000000 ", &c1, &k, &c2, &d);

試してみると面白い。最後のように、スペースと改行(またはスペース)を入れると無難かもしれない。

なお、scanf 関数に関して、Linux付属のマニュアルにその英語説明が入っているので、紹介しておく。

<scanf 関数のホワイトスペースについて>

scanf(const char *format, …)

The format string may also contain other characters. White space (such as blanks, tabs, or newlines) in the format string match any amount of white space, including none, in the input. Everything else matches only itself. Scanning stops when an input character does not match such a format character. Scanning also stops when an input conversion cannot be made.

<訳>

format文字列は他の文字を含むこともありうる。ホワイトスペース(ブランク、タブ、改行など)がformat文字列にあれば、入力に含まれる何文字ものホワイトスペース(ホワイトスペースがなくてもOK)に一致する。その他の文字はその文字自身としか一致しない。入力にある文字がformatの文字と一致しないとscanf関数がストップする。また、入力変換ができない場合もscanf関数がストップする。