宮商定時制計算部

計算部日記180723

春以来の更新になります。
計算部では、前回の更新後、ほどなくしてC++の参考書の入門編を読み終えました。
C++はC言語に比べると情報量が多く、学習の労力も大きかったため、中級編に入る前にちょっと一休みして、これまで練習不足だった領域を練習しておこうか、ということになりました。
練習不足な部分があるのは、別にサボって飛ばしていたわけではなく、参考書に練習問題があまり載っていない章があるためです。
それは、C言語では「構造体」「リスト構造」、C++では「クラス」などです。
これらを練習する場合は、構造体やクラス内にメンバが一つだけということはないでしょうから、ある程度大きなデータが必要になります。
また、構造体やクラスは定義だけで何行も費やすことがあってプログラムが長くなりがちですし、目的によっては「リストやクラスなんか使わなくたって実現できるじゃん!」という場合もあるため、練習問題が作りにくいということが想像できます。
練習問題が手に入らないのなら、自分たちで何か課題を考えるしかありません。
今回考えたのは、C言語によるリスト構造を使った《データベースもどき》です。
まあこんなのは、ExcelなりAccessなりを使えばGUI操作でちょちょっとできてしまうので、それこそ「わざわざリスト構造なんか使わなくたって」な練習その1という気がしますが(笑)、構造体やリスト構造のプログラムを書くための練習にはなると思うので、やってみることにしました。

リスト構造――可変にできるものは全部可変にしてみよう!
それでは、構造体リストを作るプログラムを作ってみましょう。
計算部の活動でC言語のコードを書くのはかなり久しぶりになりますが、C++に慣れてしまうと、C言語のさまざまな制約がとても窮屈なものに感じられます。
特に、stringクラスが使えないのは、不自由なことこの上ないですよね。
Cの不自由さへの抵抗というか何と言うか、今回は「可変にできるものは、面倒でも全部可変にする」ことを方針として、少しでもC++に近づいたプログラムにしてみようと思います。
データは別ファイルから取り込むようにします。
実行のたびにキーボードからデータをいくつも入力する手間がかかったり、データも一緒に書き込んだせいでソースコードが異常に長くなったりするのは、美しくありません。
データのやりとりにはcsvファイルが使われることを想定します。
各データの型は、テキストやら数値やら日付やらさまざまでしょうが、ファイルに書かれているのは当然すべてテキストなので、とりあえず全部char型で読み取って、使うときに変換したり値が適切かどうかを検査したりすればよいでしょう。
★csvファイルは、半角カンマ「,」があるとそこで区切りになってしまうので、例えば「2,345」みたいな書き方の数字が含まれる場合は、扱いがすこぶる厄介なものになります。
半角カンマは、テキストデータについ入り込んでしまいがちな記号ですから、もしかするとタブ区切りのtsvファイルの方が楽に扱えるかもしれません。
しかし、tsvファイルにしたところで、区切り以外のタブがテキストデータに入り込まないという保証はないのです(半角カンマよりは可能性は低そうですけれどもね)。
ここでは、ひとまず「半角カンマは区切り以外には使われない」という前提で話を進めたいと思います。
今後、もし余力があれば、「"2,345"」のように、区切り誤認対策として項目がダブルコーテーションで囲まれたファイルでも対応できる処理も考えてみるつもりです。
……この場合は、区切り判定を「,」ではなく「","」にすればいけるでしょうか?
格納データがすべてchar型でよいということになると、メンバをchar型の配列にしてしまえば、メンバ数が可変の構造体が作れます。
例えば次のような。
struct ListNode {
    struct ListNode *next;
    char **member;
};
文字列データはmallocを使ってヒープ領域に格納していきます。
構造体の各ノードはそれらのポインタを格納するだけとします。
csvファイルの列数を読み取り、mallocで列数分のポインタ格納領域を確保するようにすれば、メンバ数を動的に設定することができます。
とはいえ、この構造体には大きな欠点があって、それはメンバ名がすべて「member[0]、member[1]、member[2]……」と、中身の分からないものになってしまうことです。
それを補うためには、どの添字にどんなデータが入っているのかを別に記録しておくしかありません。
反面、利点としては、上記のようにメンバ数を可変にできることのほか、メンバ名をずらずらと書き連ねた構造体の定義を書かなくても済むこと、データの格納にループが回せること、などを挙げることができるでしょう。
プログラムを考える前に、適当なダミーデータを用意しておきます。
csvファイルなら何でもいいのですが、とりあえず県のHPから取ってきた『月刊統計資料』というExcelファイルから、「都道府県別主要統計表」シートのデータに少し手を入れ、csvファイルとして保存したものを使うことにします。
内容は以下のようです。
北海道,2761826,5320082,2803,5804,101.7,400441,329263
青森県,589887,1278490,678,1626,101.4,362213,311470
岩手県,523065,1254847,617,1560,101.9,431402,429471
宮城県,980808,2323325,1279,2204,101,326569,325978
秋田県,426020,995649,460,1332,101.7,410268,315797
山形県,411919,1101699,594,1397,100.7,479610,372140
福島県,779244,1882300,1067,2260,100.8,463571,308588
茨城県,1221978,2892201,1712,2936,100.7,426857,393970
栃木県,817370,1956910,1108,1956,100.1,391163,293247
群馬県,831970,1959831,1112,2133,101.3,334600,288596
埼玉県,3212080,7309629,4299,5930,100.7,429424,357631
千葉県,2811702,6245613,3581,5244,100.7,418222,382795
東京都,6994147,13723799,8384,10399,100.5,477248,477826
神奈川県,4236072,9158670,5223,7080,100.3,409380,359919
新潟県,890293,2266519,1301,2662,101,440722,392942
富山県,414865,1055976,631,1170,101,549344,467693
石川県,478395,1147465,711,1122,101.8,469495,417813
福井県,289825,778595,511,871,101.3,480131,295419
山梨県,356363,823333,483,891,100.8,357577,309198
長野県,861074,2075807,1241,2272,101.4,405393,301000
岐阜県,809888,2008298,1194,2103,100.3,414144,394122
静岡県,1557733,3675356,2173,3654,101.1,440989,329644
愛知県,3214669,7524759,5077,6124,100.5,390537,334349
三重県,782840,1799620,1218,1985,100.6,407003,278753
滋賀県,566148,1412528,944,1204,101.4,423315,332868
京都府,1202380,2599167,1558,2410,101.2,468612,314073
大阪府,4223735,8823286,5427,8096,100.3,377438,318502
兵庫県,2507945,5503111,3329,5259,100.8,321697,407254
奈良県,587413,1347564,788,1263,101.2,471396,404677
和歌山県,440150,944889,545,1182,101.5,392646,234638
鳥取県,235502,565124,397,610,101.4,470200,358201
島根県,288790,684868,458,876,100.3,430339,381349
岡山県,835989,1907140,1140,1957,100.8,380806,311605
広島県,1300322,2828733,1907,2890,100.8,364745,335268
山口県,659804,1382901,736,1701,101.1,385700,314217
徳島県,334117,743323,414,940,101.2,351715,302757
香川県,436123,967445,593,1091,101.2,452962,543842
愛媛県,651763,1364071,820,1625,100.9,387400,369417
高知県,352694,713688,404,906,100.7,423702,398018
福岡県,2371459,5106669,3549,4831,101.2,379617,321464
佐賀県,328015,823773,563,925,101.4,484536,294817
長崎県,635020,1354038,906,1594,101.4,396377,305309
熊本県,770607,1765315,1293,2013,100.9,337517,269816
大分県,533406,1152257,709,1278,101.4,446014,391557
宮崎県,521627,1088780,747,1266,100.6,349327,368897
鹿児島県,807169,1625651,1158,2036,100.7,411156,328535
沖縄県,632826,1443116,1304,1150,101.2,305503,255108
//平成29年または平成30年の統計(年は項目によって異なる)。
//左から都道府県・世帯数・人口・出生数・死亡数・消費者物価指数・可処分所得・消費支出。
ここで扱うcsvファイルは、「区切り以外に半角カンマを使わない」「各行の右端=改行コードの手前にカンマを入れない」という条件が守られているものとします。
全体の方針として、main関数以外ではexitは使わず、エラーによる中断の場合も必ずmain関数に戻ってから終わらせるようにします。
また、それぞれの関数ではreturn文をできるだけ一つに限定するつもりですが、そのせいでネストが深くなって可読性が落ちることは避けたいので、これについては無理はしないこととします。
1. ファイルを開く
まずはファイルを開く関数から。
#include <stdio.h>
#include <stdlib.h>

//=============================================================================
/*プロトタイプ宣言。*/
FILE *FileOpen(const char * const file_name, const char * const mode);

//=============================================================================
/*ファイルを開く関数。*/
FILE *FileOpen(const char * const file_name, const char * const mode) {
    FILE *fp;
    
    fp = fopen(file_name, mode);
    if (fp == NULL) {
        printf("ファイル %s が開けませんでした。\n", file_name);
    } else {
        printf("ファイル %s を開きました。\n", file_name);
    }
    return fp;
}
 
//=============================================================================
int main(void) {
    FILE *fp;
    const char *file_name = "tokei.csv";
    
    setvbuf(stdout, NULL, _IONBF, 0);    //バッファなしモード。
    
    fp = FileOpen(file_name, "r");
    if (fp == NULL) {exit(1);}
    
    fclose(fp);
    
    return 0;
}
★ここでは、setvbuf関数で標準出力のバッファリングをオフにしています。
これを書き入れたのは、Windowsでmintty(MSYS2)を使った場合に、printfがリアルタイムで出力されないことがあったためです。
ファイル tokei.csv を開きました。
main関数の引数(int argc, char **argv)でファイルを指定して、実行と同時に指定ファイルを開くことももちろんできますが、ここではご覧のようにコード内にファイル名を書いておく方法で進めることにします。
2. 行数と列数を求める
ファイルが無事開けたら、データを読み取って記憶させる前に、まずcsvファイルの行数と列数をつきとめる処理を作っておきましょう。
そのためには、一度ファイル全体を走査する必要があります。
後ほど、データ格納時に再び走査することになりますが、先にファイルの行数と列数を求めておかないとこの後の処理が困難になりそうなので、走査を二回行う仕様にしました。
C言語はテキストが100万行くらいあってもわりと一瞬で走査してしまいますから、その性能に甘えさせてもらうことにしましょう。
処理の流れとしては、
  1. ファイルから文字を1文字ずつ読み取る。
  2. カンマ数をカウントする。
  3. 改行が出てきたら行数をカウントし、カンマ数カウンタを0に戻す。
  4. 1行目のカンマ数を基準数として記憶する。
  5. 2行目以降もカンマ数をカウントし、1行目のカンマ数と比較する。
  6. すべての行のカンマ数が一致したら、カンマ数 + 1 を列数として返却。
  7. カンマ数が合わない行があったら、メッセージを出して処理を中止する。
こんな感じでしょうか。
求めたい値が二つありますので、returnは使わず、関数にポインタを渡して呼び出し側の変数にじかに値を格納することにします。
では書いてみましょう。
//=============================================================================
//プロトタイプ宣言追加。
void RCCount(FILE *fp, const char * const file_name, 
             int *rows_num_p, int *columns_num_p);

//=============================================================================
void RCCount(FILE *fp, const char * const file_name, 
             int *rows_num_p, int *columns_num_p) {
    int c, first_comma_num = 0, comma_num = 0;
    int rows_num = 0, columns_num = 0;
    
    while ((c = fgetc(fp)) != EOF) { //ファイルから1文字ずつ読み取る。
        if (c == ',') {comma_num++;} //カンマ数をカウント。
        if (c == '\n') {             //改行が出てきたら……
            rows_num++;                //行数をカウント。
            if (rows_num == 1) {       //1行目なら……
                first_comma_num = comma_num;        //1行目のカンマ数を記憶。
                columns_num = first_comma_num + 1;  //列数 = カンマ数 + 1。
            } else {                   //2行目以降なら……
                if (comma_num != first_comma_num) { //1行目のカンマ数と比較。
                    printf("%s の %d 行目の列数が合いません。\n"
                        "該当行のカンマの数や、\n"
                        "空白行になっていないかどうかを確認してください。\n"
                        "処理を中止します。\n", file_name, rows_num);
                    columns_num = -1;  //カンマ数が合わなかった場合の列数。
                    break;
                }
            }
            comma_num = 0;             //カンマ数カウンタを初期化して次行へ。
        }
    }
    *rows_num_p = rows_num;
    *columns_num_p = columns_num;
}

//=============================================================================
int main(void) {
    FILE *fp;
    const char *file_name = "tokei.csv";
    int rows_num, columns_num;
     
    setvbuf(stdout, NULL, _IONBF, 0);
     
    fp = FileOpen(file_name, "r");
    if (fp == NULL) {exit(1);}
     
    RCCount(fp, file_name, &rows_num, &columns_num);
    if (columns_num == -1) {
        fclose(fp);
        exit(1);
    }
    printf("行数は %d です。\n", rows_num);
    printf("列数は %d です。\n", columns_num);
     
    fclose(fp);
     
    return 0;
}

ファイル tokei.csv を開きました。
行数は 46 です。
列数は 8 です。
ん? 列数は合っているけれど、行数がおかしいですね。
都道府県の数は「47」のはずですが……。
実はこれ、csvファイルの最後の行に改行が入っていないことが原因でした。
改行コードが出てきたときに行数をカウントしているため、改行がない最後の行の分がカウントされなかった、というわけです。
最終行に改行が入っていないcsvファイルはよくありますので、そのための処理を書き加えておく必要があるでしょう。
EOFに達したときに行数を加算する必要があるのは、「最後の改行からEOFまでの間に文字がある場合」です。
であるならば、行頭から行末までの文字数を毎行カウントしていき、EOFに達したときに文字数が0でなければ、そこには改行のない行が存在しているということになるので、その行をあらためてカウントし、同時にカンマ数比較も行う、という処理を加えればよさそうです。
そのように書き直したのが以下のコードです(ハイライト部)。
EOFだけをループ終了条件とせず、最後の改行からEOFまでの間に文字があったかどうかを確認し、あれば続行、なければ終了としました。
//=============================================================================
void RCCount(FILE *fp, const char * const file_name, 
             int *rows_num_p, int *columns_num_p) {
    int c, first_comma_num = 0, comma_num = 0, ch_num = 0, 
        rows_num = 0, columns_num = 0;
    
    while (1) {
        c = fgetc(fp);
        if (c == EOF) {              //EOFならば……
            if (ch_num == 0) {break;}  //文字数が0ならば終了。
            else {c = '\n';}           //文字数が0でなければcに「\n」を代入。
        }
        ch_num++;                    //各行の文字数をカウントする。
        if (c == ',') {comma_num++;}
        if (c == '\n') {             //改行が出てきたら……
            rows_num++;                //行数加算。
            if (rows_num == 1) {
                first_comma_num = comma_num;
                columns_num = first_comma_num + 1;
            } else {
                if (comma_num != first_comma_num) {
                    printf("%s の %d 行目の列数が合いません。\n"
                        "該当行のカンマの数や、\n"
                        "空白行になっていないかどうかを確認してください。\n"
                        "処理を中止します。\n", file_name, rows_num);
                    columns_num = -1;
                    break;
                }
            }
            comma_num = 0;             //カンマ数カウンタを初期化、さらに
            ch_num = 0;                //文字数カウンタを初期化して次行へ。
        }
    }
    *rows_num_p = rows_num;
    *columns_num_p = columns_num;
}
ファイル tokei.csv を開きました。
行数は 47 です。
列数は 8 です。
今度は正しい結果が返りました。
最終行に改行を入れて実行してみても、同じ結果になりました。
fgetcによる文字の読み取りは、一度EOFに達した後は何度ループしてもEOFが返され続けます。
ですから、最終行に改行が入っていないファイルを処理した場合は、「二度目の」EOFのときに10行目の条件が真になり、ループから抜けられるようになっています。
続いて、csvファイルに不備があった場合、ちゃんとエラーになるかどうかも確認しておきます。
試しに11行目の「埼玉県」の末尾に「,」を打ってみましょう。
「埼玉県,3212080……,357631,(\n)」
これで実行すると、
ファイル tokei.csv を開きました。
tokei.csv の 11 行目の列数が合いません。
該当行のカンマの数や、
空白行になっていないかどうかを確認してください。
処理を中止します。
ちゃんとエラーになりますね。
では、今度は最終行の「沖縄県」の、632826と1443116の間の「,」を削除してみます。
この行の終端には改行が入っていません。
「沖縄県,6328261443116,……255108(EOF)」
これで実行すると、
ファイル tokei.csv を開きました。
tokei.csv の 47 行目の列数が合いません。
該当行のカンマの数や、
空白行になっていないかどうかを確認してください。
処理を中止します。
こちらもちゃんとエラー判定されました。
実行結果は省略しますが、最終行に改行を入れても同じ結果になりました。
3. ファイルを読み込んで構造体リストを作る
では次に、ファイルから文字列を読み込んで、構造体ノードに格納し、リスト構造を作る処理に進みましょう。
読み込んだ文字列はすべてヒープ領域に格納し、構造体ノードにはそれらへのポインタを持たせます。
これを実現するために、次のような方法を考えてみました。
方法A:csvファイルの文字列をヒープ領域に書き込みながら、同時にリスト用構造体ノードを生成し、そこに文字列へのポインタを格納していく。
方法B:先にcsvファイルの全文字列をヒープ領域に書き込んでしまい、その文字列群全体の先頭ポインタを記憶しておいて、後からリスト用構造体ノードを生成し、そこに格納済み文字列へのポインタをコピーしていく。
単純なのはAの方ですが、こちらは関数が長くなりそうです。
Bの方は「読み込み+記憶」処理と「構造体への格納」処理とに関数を分けやすい反面、csvファイルを記憶する場合は三次元配列が必要になるので、見た目が複雑になるでしょう。
各項目が文字列で構成されたcsvファイルをExcelで開いてみると、二次元配列にしか見えません。
しかし、C言語では文字列も配列ですので、一次元増えて三次元配列として扱うことになります(Excelのセルの中にさらに配列が入っているような感じ)。
よって、csvファイル全体を記憶させる場合の先頭ポインタは、トリプル・ポインタ(***p)として宣言することになります。
下図は「方法B」を三次元配列で処理する場合のイメージです。
トリプルポインタ
とは言うものの、二次元配列までで処理する方法がないわけではありません。
それは、一行をカンマごとに分けないで、そのまま改行までを一箇所に格納してしまい、構造体ノードには「それぞれの項目の先頭アドレス」を記憶させるというものです。
もちろん、カンマと改行は、格納時に「\0」に書き換えておく必要があります。
これだと、各項目の先頭アドレスへのポインタを求めるために、各項目が行頭から何文字目になるかを求めて記憶しておかねばならないという欠点がある反面、freeがmember[0]だけで済むという利点があります。
下図は「方法B」を二次元配列で処理する場合のイメージです。
ダブルポインタ
「可変にできるものは可変にする」方針で作るとなると、mallocを多用するプログラムになるはずです。
それならfree処理はできるだけ簡単に済むようにしておきたいので、方法A・Bのどちらを取るにしても、ここでは「改行までを一箇所に格納する方法」で進めることにします。
あとは、「読み込み+記憶」と「リスト作成」を同時にやってしまうか(方法A)、それとも別の関数に分けるか(方法B)です。
ここではひとまず、処理自体はシンプルな「方法A」を採用しようと思います。
「方法A」は、「方法B」のように「ファイルを全部読み取って全体の先頭ポインタを得る」という中間処理をしませんので、関数は長くなるだろうけれども、性能的には「方法B」よりも上になると思われます。
「方法A」のイメージは、下図のようです。
リスト図
memberに格納する「文字列の各項目の先頭ポインタ」は、それぞれの項目の先頭が行頭から何文字目になるのかを配列に格納しておき、それを文字列へのポインタに加算して得ることにします(下図)。
インデックス図
「方法B」の場合は、このインデックスも二次元配列にして、別の関数に渡すために記憶しておかなければなりませんが、「方法A」なら、これを使って各メンバにポインタを割り振ってしまえばもう用済みとなり、次のノードでは上書きして使い回せばいいので、単次元のものを一つ作っておけば足りるわけです。
手順はこんな感じでしょう。
  1. 各列の文字列が行頭から何番目かを格納する配列を作る(インデックス)。
  2. ファイルから文字を1文字ずつ読み取る。
  3. 各行の文字数をカウントする。
  4. カンマでも改行でもない文字は、そのままバッファに格納する。
  5. カンマが出てきたら、「\0」に変えてバッファに格納する。
  6. (カンマが出てきたら~続き)次の文字が行頭から何番目の文字なのかをインデックスに記憶する。
  7. 改行が出てきたら、「\0」に変えてバッファに格納する。
  8. (改行が出てきたら~続き)そこまでの文字数分の領域を確保して、バッファの文字列をコピーする。
  9. (改行が出てきたら~続き)構造体ノードを生成する。
  10. (改行が出てきたら~続き)構造体メンバ領域を列数 + 1だけ確保する。
  11. (改行が出てきたら~続き)各メンバに各列先頭へのポインタを格納する。
  12. (改行が出てきたら~続き)ノードをリストに組み込む。
  13. (改行が出てきたら~続き)文字数カウンタとインデックスの添字を初期化。
  14. 2.から下を繰り返す。
10.でメンバ数をcsvファイルの列数(項目数)よりも一つ多く設定することにしたのは、その最後に番兵(NULL)を置くためです。
こうしておけば、もしリストを使うときにメンバ数が必要になったとしても、いちいちメンバ数の情報を引数でやりとりしなくても、ノード内を調べてその都度求めれば済むことになります。
では、書いてみましょう。
最終行に改行がない場合の条件分岐も入れておきます。
//リスト用構造体定義。
struct ListNode {
    struct ListNode *next;
    char **member;
};

//=============================================================================
struct ListNode *ListCreate(FILE *fp, const int columns_num) {
    char buf[128], *s;
    size_t buf_num = 0;
    int *ch_idx, ch_idx_num = 1, c, i;
    struct ListNode *p, *pre_p = NULL, *list_head_p = NULL;
                  //↓各列の先頭文字が行頭から何文字目かを格納する配列を作る。
    ch_idx = malloc(sizeof(int) * (size_t)columns_num);
    ch_idx[0] = 0;    //1列目は常に0文字目なので0を入れておく。
     
    rewind(fp);       //読み取り位置を先頭に戻す。
    while (1) {
        c = fgetc(fp);
        if (c == EOF) {
            if (buf_num == 0) {break;}
            else {c = '\n';}
        }                        //↓カンマ・改行以外なら……
        if ((c != ',') && (c != '\n')) {
            buf[buf_num++] = (char)c;  //そのままバッファに書き込む。
            continue;
        }
        if (c == ',') {          //カンマなら……
            buf[buf_num++] = '\0';     //「\0」に変えてバッファに書き込む。
            ch_idx[ch_idx_num++] = (int)buf_num;  //次の文字の文字番号を格納。
            continue;
        }
        if (c == '\n') {         //改行なら……
            buf[buf_num++] = '\0';     //「\0」に変えてバッファに書き込む。
            s = malloc(sizeof(char) * buf_num);  //行頭からの長さ分の領域確保。
            memcpy(s, buf, sizeof(char) * buf_num);  //そこに「\0」ごとコピー。
            p = malloc(sizeof(struct ListNode)); //構造体ノードを生成。
            p->member = malloc(sizeof(char *) * (size_t)(columns_num + 1));
            p->member[columns_num] = NULL; //↑メンバを列数 + 1だけ生成し、
            if (list_head_p == NULL) {     //その最後を番兵NULLとする。
                list_head_p = p;   //リスト構築。先頭へのポインタがNULLなら
            } else {               //当ノードを先頭とする。そうでなければ
                pre_p->next = p;   //当ノードは二つ目以降となるので、
            }                      //前ノードのnextに当ノードのポインタを格納。
            p->next = NULL;        //生成したノードのnextはNULLとしておく。
            for (i = 0; i < columns_num; i++) {
                p->member[i] = s + ch_idx[i];    //各メンバにポインタを格納。
            }
            pre_p = p;             //当ノードのポインタを記憶しておく。
            ch_idx_num = 1; buf_num = 0;  //カウンタ変数を初期化。
        }
    }
    free(ch_idx);          //先頭からの文字数格納配列を解放。
    fclose(fp);            //ファイルを閉じる。
    return list_head_p;    //構造体リストの先頭ポインタを返す。
}
リストができたかどうかを確認するため、全ノードのnext以外の全メンバを表示する関数も作ります。
リストの先頭ポインタを引数で渡しますが、番兵を置いたのでメンバ数を渡す必要はありません。
//=============================================================================
void ListDisplay(struct ListNode *p) {
    int i = 0;
    
    while (p != NULL) {
        while (p->member[i] != NULL) {
            if (i == 0) {printf("%s", p->member[i]);} 
            else {printf(" %s", p->member[i]);}
            i++;
        }
        printf("\n");
        i = 0;
        p = p->next;
    }
}
string.hをインクルードし、プロトタイプ宣言を書き加えた後、これらをmain関数から連続で呼び出すと……
#include <string.h>    //←追加。

//リスト用構造体定義。

//=============================================================================
//プロトタイプ宣言追加。
struct ListNode *ListCreate(FILE *fp, const int columns_num);
void ListDisplay(struct ListNode *p);

//=============================================================================
//FileOpen関数・RCCount関数・ListCreate関数・ListDisplay関数

//=============================================================================
int main(void) {
    FILE *fp;
    const char *file_name = "tokei.csv";
    int rows_num, columns_num;
    struct ListNode *p;
    
    setvbuf(stdout, NULL, _IONBF, 0);
      
    fp = FileOpen(file_name, "r");
    if (fp == NULL) {exit(1);}
      
    RCCount(fp, file_name, &rows_num, &columns_num);
    if (columns_num == -1) {
        fclose(fp);
        exit(1);
    }
    printf("行数は %d です。\n", rows_num);
    printf("列数は %d です。\n", columns_num);
    
    p = ListCreate(fp, columns_num);
    ListDisplay(p);
    
    fclose(fp);
      
    return 0;
}
ファイル tokei.csv を開きました。
行数は 47 です。
列数は 8 です。
北海道 2761826 5320082 2803 5804 101.7 400441 329263
青森県 589887 1278490 678 1626 101.4 362213 311470
岩手県 523065 1254847 617 1560 101.9 431402 429471

(中略)

宮崎県 521627 1088780 747 1266 100.6 349327 368897
鹿児島県 807169 1625651 1158 2036 100.7 411156 328535
沖縄県 632826 1443116 1304 1150 101.2 305503 255108
リストは無事作られたようです。
最終行に改行を入れても入れなくても同じ結果となりました。
しかし、これではまだ不十分な点があります。
上記のコードには、mallocが失敗したときの処理が入っていません。
すでに50行もある関数ですが、その処理も入れておかねばなりますまい。
mallocを使ったのは14、35、37、38行目の四箇所です。
それぞれにメモリ確保失敗時の処理を入れてみます(ハイライト部)。
//=============================================================================
struct ListNode *ListCreate(FILE *fp, const int columns_num) {
    char buf[128], *s;
    size_t buf_num = 0;
    int *ch_idx, ch_idx_num = 1, c, i;
    struct ListNode *p, *pre_p = NULL, *list_head_p = NULL, *q, *next_q;
     
    ch_idx = malloc(sizeof(int) * (size_t)columns_num);
    if (ch_idx == NULL) {
        puts("インデックスのメモリが確保できませんでした。");
        return list_head_p;    //失敗の場合はリストの先頭ポインタをNULLで返す。
    }
    ch_idx[0] = 0;
     
    rewind(fp);
    while (1) {
        c = fgetc(fp);
        if (c == EOF) {
            if (buf_num == 0) {break;}
            else {c = '\n';}
        }
        if ((c != ',') && (c != '\n')) {
            buf[buf_num++] = (char)c;
            continue;
        }
        if (c == ',') {
            buf[buf_num++] = '\0';
            ch_idx[ch_idx_num++] = (int)buf_num;
            continue;
        }
        if (c == '\n') {
            buf[buf_num++] = '\0';
            s = malloc(sizeof(char) * buf_num);
            if (s == NULL) {
                puts("文字列格納領域が確保できませんでした。");
                list_head_p = NULL;
                break;
            }
            memcpy(s, buf, sizeof(char) * buf_num);
            p = malloc(sizeof(struct ListNode));
            if (p == NULL) {
                puts("構造体が作れませんでした。");
                q = list_head_p;    //生成済みのリストを解放。ループが必要。
                while (q != NULL) {
                    next_q = q->next;
                    free(q->member[0]);
                    free(q->member);
                    free(q);
                    q = next_q;
                }
                list_head_p = NULL;
                break;
            }
            p->member = malloc(sizeof(char *) * (size_t)(columns_num + 1));
            if (p->member == NULL) {
                puts("構造体のメンバ領域が確保できませんでした。");
                free(p);            //当ノードはまだリスト外なので単独で解放。
                q = list_head_p;    //生成済みのリストを解放。上と同じ。
                while (q != NULL) {
                    next_q = q->next;
                    free(q->member[0]);
                    free(q->member);
                    free(q);
                    q = next_q;
                }
                list_head_p = NULL;
                break;
            }
            p->member[columns_num] = NULL;
            if (list_head_p == NULL) {
                list_head_p = p;
            } else {
                pre_p->next = p;
            }
            p->next = NULL;
            for (i = 0; i < columns_num; i++) {
                p->member[i] = s + ch_idx[i];
            }
            pre_p = p;
            ch_idx_num = 1; buf_num = 0;
        }
    }
    free(ch_idx);
    fclose(fp);
    return list_head_p;
}
メモリ確保失敗時の解放処理だけで36行も費やしてしまいました。
リストの方ではループを回さねばならないので、その分、上の二つの解放処理より長くなっています。
とは言え、リストの方の二箇所は57行目を除いて同じですので、ループ部分を関数化できるでしょう。
//=============================================================================
void ListFree(struct ListNode *p) {
    struct ListNode *next_p;
    
    while (p != NULL) {
        next_p = p->next;
        free(p->member[0]);    //←メンバすべての文字列が繋がっているので、
        free(p->member);       //member[0]を解放すれば全部解放される。
        free(p);               //free(*(p->member));と書いても同じ。
        p = next_p;
    }
}
これを組み込むと……
//=============================================================================
//プロトタイプ宣言追加。
void ListFree(struct ListNode *p);

//=============================================================================
struct ListNode *ListCreate(FILE *fp, const int columns_num) {
    char buf[128], *s;
    size_t buf_num = 0;
    int *ch_idx, ch_idx_num = 1, c, i;
    struct ListNode *p, *pre_p = NULL, *list_head_p = NULL;
     
    ch_idx = malloc(sizeof(int) * (size_t)columns_num);
    if (ch_idx == NULL) {
        puts("インデックスのメモリが確保できませんでした。");
        return list_head_p;
    }
    ch_idx[0] = 0;
     
    rewind(fp);
    while (1) {
        c = fgetc(fp);
        if (c == EOF) {
            if (buf_num == 0) {break;}
            else {c = '\n';}
        }
        if ((c != ',') && (c != '\n')) {
            buf[buf_num++] = (char)c;
            continue;
        }
        if (c == ',') {
            buf[buf_num++] = '\0';
            ch_idx[ch_idx_num++] = (int)buf_num;
            continue;
        }
        if (c == '\n') {
            buf[buf_num++] = '\0';
            s = malloc(sizeof(char) * buf_num);
            if (s == NULL) {
                puts("文字列格納領域が確保できませんでした。");
                list_head_p = NULL;
                break;
            }
            memcpy(s, buf, sizeof(char) * buf_num);
            p = malloc(sizeof(struct ListNode));
            if (p == NULL) {
                puts("構造体が作れませんでした。");
                ListFree(list_head_p);
                list_head_p = NULL;
                break;
            }
            p->member = malloc(sizeof(char *) * (size_t)(columns_num + 1));
            if (p->member == NULL) {
                puts("構造体のメンバ領域が確保できませんでした。");
                free(p);
                ListFree(list_head_p);
                list_head_p = NULL;
                break;
            }
            p->member[columns_num] = NULL;
            if (list_head_p == NULL) {
                list_head_p = p;
            } else {
                pre_p->next = p;
            }
            p->next = NULL;
            for (i = 0; i < columns_num; i++) {
                p->member[i] = s + ch_idx[i];
            }
            pre_p = p;
            ch_idx_num = 1; buf_num = 0;
        }
    }
    free(ch_idx);
    fclose(fp);
    return list_head_p;
}
ちょっとだけ短くなりましたが、まだ70行もありますね。
……さて、このコードには、一箇所だけ今回の方針に反する部分があります。
2行目の「char buf[128]」です。
「可変にできる箇所は可変にする」という方針なのに、このバッファ文字数だけ、格納されるであろう文字列の上限文字数を見積もっての、いわばギャンブル設定になってしまっています。
まあ、これは「いかにもC言語」な書き方ですので、誰も責めないとは思いますが……。
実を言うと、今回の処理では一文字ずつ読み取ってバッファに書き込んでいるわけですから、バッファを可変にするのはそれほど面倒な処理ではありません。
バッファをヒープ領域に設定して、そこに一文字ずつ文字を格納していき、文字がバッファ容量を超えそうになったら、reallocで領域を拡張すればいいのです。
これなら、メモリが許す限りどんなに長い文字列にも対応することができます。
では、その処理を関数にして組み込んでみましょう。
格納領域のバイト数を記憶するためにbuf_sizeという変数を新たに宣言します。
そして、一行の文字数(buf_num)が格納領域の大きさ(buf_size)に達したら、reallocで格納領域を広げてやります。
その関数(BufferExtension)を下方に加えます(プロトタイプ宣言を書かないのなら上方に書く必要あり)。
ハイライト部分は、先ほどのコードを書き換えたか、新たに書き加えたコードです。
#define INCREMENT_SIZE 512    //追加。

//=============================================================================
//プロトタイプ宣言追加。
char *BufferExtension(char *buf, const size_t buf_size);

//=============================================================================
struct ListNode *ListCreate(FILE *fp, const int columns_num) {
    char *buf = NULL, *s;
    size_t buf_num = 0, buf_size = 0;
    int *ch_idx, ch_idx_num = 1, c, i;
    struct ListNode *p, *pre_p = NULL, *list_head_p = NULL;
     
    ch_idx = malloc(sizeof(int) * (size_t)columns_num);
    if (ch_idx == NULL) {
        puts("インデックスのメモリが確保できませんでした。");
        return list_head_p;
    }
    ch_idx[0] = 0;
     
    rewind(fp);
    while (1) {
        c = fgetc(fp);
        if (c == EOF) {
            if (buf_num == 0) {break;}
            else {c = '\n';}
        }
        if (buf_size == buf_num) {  //バッファサイズに文字数が追いついたら……
            buf = BufferExtension(buf, buf_size);  //reallocでバッファを拡張。
            if (buf == NULL) {           //拡張に失敗したら……
                list_head_p = NULL;        //リスト先頭ポインタをNULLで返す。
                break;
            }
            buf_size += INCREMENT_SIZE;  //成功したらバッファサイズを更新。
        }
        if ((c != ',') && (c != '\n')) {
            buf[buf_num++] = (char)c;
            continue;
        }
        if (c == ',') {
            buf[buf_num++] = '\0';
            ch_idx[ch_idx_num++] = (int)buf_num;
            continue;
        }
        if (c == '\n') {
            buf[buf_num++] = '\0';
            s = malloc(sizeof(char) * buf_num);
            if (s == NULL) {
                puts("文字列格納領域が確保できませんでした。");
                list_head_p = NULL;
                break;
            }
            memcpy(s, buf, sizeof(char) * buf_num);
            p = malloc(sizeof(struct ListNode));
            if (p == NULL) {
                puts("構造体が作れませんでした。");
                ListFree(list_head_p);
                list_head_p = NULL;
                break;
            }
            p->member = malloc(sizeof(char *) * (size_t)(columns_num + 1));
            if (p->member == NULL) {
                puts("構造体のメンバ領域が確保できませんでした。");
                free(p);
                ListFree(list_head_p);
                list_head_p = NULL;
                break;
            }
            p->member[columns_num] = NULL;
            if (list_head_p == NULL) {
                list_head_p = p;
            } else {
                pre_p->next = p;
            }
            p->next = NULL;
            for (i = 0; i < columns_num; i++) {
                p->member[i] = s + ch_idx[i];
            }
            pre_p = p;
            ch_idx_num = 1; buf_num = 0;
        }
    }
    free(buf);                      //バッファを解放。
    free(ch_idx);
    fclose(fp);
    return list_head_p;
}

//=============================================================================
char *BufferExtension(char *buf, const size_t buf_size) {
    char *new_buf;    //受け取ったbufとは別の変数を用意。
      
    new_buf = realloc(buf, sizeof(char) * (buf_size + INCREMENT_SIZE));
    if(new_buf == NULL){
        puts("文字列バッファが確保できませんでした。");
        free(buf);    //拡張に失敗した場合、古い方の領域を解放。
    }
    return new_buf;
}
realloc関数は、処理系によっては拡張領域を元領域とは別の場所に作ることがあるそうです。
領域拡張が成功した場合、元領域は解放されますが、失敗すると元領域はそのままになります。
もし受け取ったbufに新領域へのポインタを上書きしたなら、領域拡張失敗と同時にbufにはNULLが入ってしまうので、アドレスが失われて元領域が解放できなくなります。
拡張領域へのポインタを収めるのにnew_bufという別変数を用意したのは、元領域へのポインタを残しておいて解放できるようにするためです。
では、BufferExtension関数がちゃんと動くかどうかをテストしてみましょう。
上のコードでは、あまり頻繁にreallocを呼ばなくても済むように(断片化対策)、拡張サイズをかなり大きい数字に設定しています。
512バイトあれば、ユニコードの全角文字でも170字程度は格納できますから、一行の長さが今使っているダミーデータ程度のファイルであるならば、バッファの大きさを一度も更新しないで済んでしまうでしょう。
ここでは、わざと拡張サイズをうんと小さくして、拡張するたびに領域の大きさを画面に表示させてみることにします。
マクロでINCREMENT_SIZEを「512」から「4」に変更し、上のコードの34行目の後に
printf("現在のバッファの大きさは %d です。\n", (int)buf_size);
というコードを書き加えて実行してみましょう。
現在のバッファの大きさは 4 です。
現在のバッファの大きさは 8 です。
現在のバッファの大きさは 12 です。
現在のバッファの大きさは 16 です。
現在のバッファの大きさは 20 です。
現在のバッファの大きさは 24 です。
現在のバッファの大きさは 28 です。
現在のバッファの大きさは 32 です。
現在のバッファの大きさは 36 です。
現在のバッファの大きさは 40 です。
現在のバッファの大きさは 44 です。
現在のバッファの大きさは 48 です。
現在のバッファの大きさは 52 です。
現在のバッファの大きさは 56 です。
現在のバッファの大きさは 60 です。
ちゃんと動いているようですね。
拡張が60で止まりましたので、このファイルは、バッファが60バイトあればどの行も格納可能であることが分かります。
4. プログラム構築
それでは、これらの関数をまとめて、例えば
ListTsukutchauyo("nanchara.csv");
と書けば、それだけでリストが生成されるようにしたいと思います。
一連の処理の主流となるのはFileOpen関数・RCCount関数・ListCreate関数の三つで、BufferExtension関数・ListFree関数・ListDisplay関数は支流です。
よって、主流の三つの関数を次々と呼び出す「まとめ関数」のようなものを作ります。
構造体リストを作る関数ですので、返却値はリストの先頭ポインタとなります。
//=============================================================================
//プロトタイプ宣言追加。
struct ListNode *smList(const char * const file_name);

//=============================================================================
struct ListNode *smList(const char * const file_name) {
    FILE *fp;
    struct ListNode *list_head_p = NULL;
    int rows_num, columns_num;
    
    fp = FileOpen(file_name, "r");
    if (fp == NULL) {return list_head_p;}
    
    RCCount(fp, file_name, &rows_num, &columns_num);
    if (columns_num == -1) {
        fclose(fp);
        return list_head_p;
    }
    
    list_head_p = ListCreate(fp, columns_num);
    fclose(fp);
    
    return list_head_p;
}

//=============================================================================
int main(void) {
    const char *file_name = "tokei.csv";
    struct ListNode *tokei_headp;
    
    setvbuf(stdout, NULL, _IONBF, 0);
    
    tokei_headp = smList(file_name);
    ListDisplay(tokei_headp);
    
    return 0;
}
関数名の頭の「sm」は、「サブ・メイン」というか「セミ・メイン」というか、とにかく「準メイン関数」を表す記号のつもりです。
また、fcloseは、 ListCreate関数からこちらに移動しました。
これでリストを作ってからListDisplay関数で表示してみますが、まとめ関数を作ったので、main関数が短くて読みやすくなりました。
北海道 2761826 5320082 2803 5804 101.7 400441 329263
青森県 589887 1278490 678 1626 101.4 362213 311470

(中略)

鹿児島県 807169 1625651 1158 2036 100.7 411156 328535
沖縄県 632826 1443116 1304 1150 101.2 305503 255108
大丈夫そうですね。
……と、ここまで来てようやく気づいたことがあります。
それは、RCCount関数で求めた「行数」の方が、ほかの関数に渡されていないということです。
RCCount関数内部では、1行目とそれ以降のカンマ数の比較や、「○行目のカンマ数が合いません」というエラーメッセージで「行数」を使っていますから、この値を求めること自体は無駄ではありません。
そのうえ、ループ終了条件がEOFだけだと改行なしの最終行が読み込まれないことも、「行数」を確認表示したおかげで早い段階で判明しましたから、結果オーライではありました。
しかし、「行数」をよそで使わないのなら、この関数は「列数」だけを返せばいいということになりますので、今はポインタを使って二つの値を呼び出し側の変数に直接書き込んでいるけれども、普通に一つの値だけをreturnする関数にしてしまった方が分かりやすくなりそうです。
というわけで、RCCount関数は書き換え、return値に合わせてColumnsCount関数と改名し、smList関数に組み込み直したいと思います。
なお、ついでに「項目数が一つだけで不完全なファイルだった場合」(下の25行目)と「空のファイルだった場合」(下の7、36、37、40~43行目)は処理を中止するコードも加えておきます。
後者の判定では行数を使いますので、やはり行数のカウントは有用でした。
さらに、文字数のカウンタ変数であるch_numと、今回新たに設定したtotal_ch_numは、カウントするだけでほかのint型変数との計算には使いませんので、size_t型にしておきます。
この関数では、columns_numが-1になる可能性があるのでint型にしておく必要があり、そうなるとその変数に代入されるfirst_comma_num、さらにそれに代入を行うcomma_numもint型となります。
また、rows_numは、行数をカウントするだけですからsize_t型でもよいのですが、行数・列数は型を合わせておいた方が見やすそうなので、負の数には決してならないけれどもint型にしておきたいと思います。
//=============================================================================
//プロトタイプ宣言書き換え(RCCount関数の代わり)。
int ColumnsCount(FILE *fp, const char * const file_name);
 
//=============================================================================
int ColumnsCount(FILE *fp, const char * const file_name) {
    int c, first_comma_num = 0, comma_num = 0, rows_num = 0, columns_num = 0;
    size_t ch_num = 0, total_ch_num = 0;
    
    while (1) {
        c = fgetc(fp);
        if (c == EOF) {
            if (ch_num == 0) {break;}
            else {c = '\n';}
        }
        ch_num++;
        if (c == ',') {comma_num++;}
        if (c == '\n') {
            rows_num++;
            if (rows_num == 1) {
                first_comma_num = comma_num;
                columns_num = first_comma_num + 1;
            } else {
                if ((comma_num != first_comma_num) ||
                    ((first_comma_num == 0) && (ch_num == 1) && (c == '\n'))) {
                    printf("%s の %d 行目が空白か、または列数が合いません。\n"
                        "該当行のカンマの数や、\n"
                        "空白行になっていないかどうかを確認してください。\n"
                        "処理を中止します。\n", file_name, rows_num);
                    columns_num = -1;
                    return columns_num;
                }
            }
            comma_num = 0;
            ch_num = 0;
        } else {
            total_ch_num++;    //改行以外の総文字数をカウントしておく。
        }
    }
    if ((rows_num == 0) || (total_ch_num == 0)) {
        printf("%s は空ファイルです。処理を中止します。\n", file_name);
        columns_num = -1;
    }
    return columns_num;
}

//=============================================================================
struct ListNode *smList(const char * const file_name) {
    FILE *fp;
    struct ListNode *list_head_p = NULL;
    int columns_num;
    
    fp = FileOpen(file_name, "r");
    if (fp == NULL) {return list_head_p;}
    
    columns_num = ColumnsCount(fp, file_name);
    if (columns_num == -1) {
        fclose(fp);
        return list_head_p;
    }
    
    list_head_p = ListCreate(fp, columns_num);
    fclose(fp);
    
    return list_head_p;
}
実行結果は省略しますが、これでもちゃんと動きます。
RCCount関数には引数が四つもあったけれど、ColumnsCount関数には二つしかありませんので、すっきりして使いやすくなりました。


ここで、ここまで書いてきたmain関数以外のコードを、まとめて掲げておきます(一部改、コメント参照)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define INCREMENT_SIZE 512

//リスト用構造体定義。
struct ListNode {
    struct ListNode *next;
    char **member;
};

//=============================================================================
/*プロトタイプ宣言。*/
FILE *FileOpen(const char * const file_name, const char * const mode);
int ColumnsCount(FILE *fp, const char * const file_name);
struct ListNode *ListCreate(FILE *fp, const int columns_num);

char *BufferExtension(char *buf, const size_t buf_size);
void ListFree(struct ListNode *p);
void ListDisplay(struct ListNode *p);

struct ListNode *smList(const char * const file_name);

//=============================================================================
/*ファイルを開く関数。*/
FILE *FileOpen(const char * const file_name, const char * const mode) {
    FILE *fp;
     
    fp = fopen(file_name, mode);    //↓エラーメッセージのみとした。
    if (fp == NULL) {printf("ファイル %s が開けませんでした。\n", file_name);}
    return fp;
}

//=============================================================================
/*csvファイルの列数(項目数)を求める関数。*/
int ColumnsCount(FILE *fp, const char * const file_name) {
    int c, first_comma_num = 0, comma_num = 0, rows_num = 0, columns_num = 0;
    size_t ch_num = 0, total_ch_num = 0;
    
    while (1) {
        c = fgetc(fp);
        if (c == EOF) {
            if (ch_num == 0) {break;}
            else {c = '\n';}
        }
        ch_num++;
        if (c == ',') {comma_num++;}
        if (c == '\n') {
            rows_num++;
            if (rows_num == 1) {
                first_comma_num = comma_num;
                columns_num = first_comma_num + 1;
            } else {
                if ((comma_num != first_comma_num) ||
                    ((first_comma_num == 0) && (ch_num == 1) && (c == '\n'))) {
                    printf("%s の %d 行目が空白か、または列数が合いません。\n"
                        "該当行のカンマの数や、\n"
                        "空白行になっていないかどうかを確認してください。\n"
                        "処理を中止します。\n", file_name, rows_num);
                    columns_num = -1;
                    return columns_num;
                }
            }
            comma_num = 0;
            ch_num = 0;
        } else {
            total_ch_num++;
        }
    }
    if ((rows_num == 0) || (total_ch_num == 0)) {
        printf("%s は空ファイルです。処理を中止します。\n", file_name);
        columns_num = -1;
    }
    return columns_num;
}

//=============================================================================
/*csvファイルを読み込んで構造体リストを作る関数。*/
struct ListNode *ListCreate(FILE *fp, const int columns_num) {
    char *buf = NULL, *s;
    size_t buf_num = 0, buf_size = 0;
    int *ch_idx, ch_idx_num = 1, c, i;
    struct ListNode *p, *pre_p = NULL, *list_head_p = NULL;
     
    ch_idx = malloc(sizeof(int) * (size_t)columns_num);
    if (ch_idx == NULL) {
        puts("インデックスのメモリが確保できませんでした。");
        return list_head_p;
    }
    ch_idx[0] = 0;
     
    rewind(fp);
    while (1) {
        c = fgetc(fp);
        if (c == EOF) {
            if (buf_num == 0) {break;}
            else {c = '\n';}
        }
        if (buf_size == buf_num) {
            buf = BufferExtension(buf, buf_size);
            if (buf == NULL) {
                list_head_p = NULL;
                break;
            }
            buf_size += INCREMENT_SIZE;
        }
        if ((c != ',') && (c != '\n')) {
            buf[buf_num++] = (char)c;
            continue;
        }
        if (c == ',') {
            buf[buf_num++] = '\0';
            ch_idx[ch_idx_num++] = (int)buf_num;
            continue;
        }
        if (c == '\n') {
            buf[buf_num++] = '\0';
            s = malloc(sizeof(char) * buf_num);
            if (s == NULL) {
                puts("文字列格納領域が確保できませんでした。");
                list_head_p = NULL;
                break;
            }
            memcpy(s, buf, sizeof(char) * buf_num);
            p = malloc(sizeof(struct ListNode));
            if (p == NULL) {
                puts("構造体が作れませんでした。");
                ListFree(list_head_p);
                list_head_p = NULL;
                break;
            }
            p->member = malloc(sizeof(char *) * (size_t)(columns_num + 1));
            if (p->member == NULL) {
                puts("構造体のメンバ領域が確保できませんでした。");
                free(p);
                ListFree(list_head_p);
                list_head_p = NULL;
                break;
            }
            p->member[columns_num] = NULL;
            if (list_head_p == NULL) {
                list_head_p = p;
            } else {
                pre_p->next = p;
            }
            p->next = NULL;
            for (i = 0; i < columns_num; i++) {
                p->member[i] = (char *)(s + ch_idx[i]);    //念のためキャスト。
            }
            pre_p = p;
            ch_idx_num = 1; buf_num = 0;
        }
    }
    free(buf);
    free(ch_idx);
    return list_head_p;
}

//=============================================================================
/*文字列格納領域を拡張する関数。*/
char *BufferExtension(char *buf, const size_t buf_size) {
    char *new_buf;
     
    new_buf = realloc(buf, sizeof(char) * (buf_size + INCREMENT_SIZE));
    if(new_buf == NULL){
        puts("文字列バッファが確保できませんでした。");
        free(buf);
    }
    return new_buf;
}

//=============================================================================
/*構造体リストを解放する関数。*/
void ListFree(struct ListNode *p) {
    struct ListNode *next_p;
    
    while (p != NULL) {
        next_p = p->next;
        free(p->member[0]);
        free(p->member);
        free(p);
        p = next_p;
    }
}

//=============================================================================
/*構造体リストの中身を表示する関数。*/
void ListDisplay(struct ListNode *p) {
    int i = 0;
    
    while (p != NULL) {
        while (p->member[i] != NULL) {
            if (i == 0) {printf("%s", p->member[i]);}
            else {printf(" %s", p->member[i]);}
            i++;
        }
        printf("\n");
        i = 0;
        p = p->next;
    }
}

//=============================================================================
/*ファイルを開いてから構造体リストを作るまでの関数をまとめた関数。*/
//csvファイルから構造体リストを作るときは、この関数を呼ぶだけでよい。
struct ListNode *smList(const char * const file_name) {
    FILE *fp;
    struct ListNode *list_head_p = NULL;
    int columns_num;
    
    fp = FileOpen(file_name, "r");
    if (fp == NULL) {return list_head_p;}
    
    columns_num = ColumnsCount(fp, file_name);
    if (columns_num == -1) {
        fclose(fp);
        return list_head_p;
    }
    
    list_head_p = ListCreate(fp, columns_num);
    fclose(fp);
    
    return list_head_p;
}
main関数は、この下に、目的に合わせて書くようにします。
5. いろいろ実験
これで構造体リストの作成が一行で書けるようになったので、いろいろ実験してみましょう。


複数のcsvファイルを次々に構造体リストにして表示する
まずは、csvファイルをいくつか用意して、それらを次々と読み込んでリストを作り、中身をすべて表示する処理を書いてみたいと思います。
メンバ数が可変ですので、いくつファイルがあってもいくつ列数(項目数)があっても、ループで書くことができます。
あまり巨大なファイルだと確認が大変ですから、データ数が数個程度のcsvファイルで実験してみましょう。
次のようなcsvファイルを用意しました。
//kyoka.csv。
A,豆バラ,卒業・入学おめでとう/白、ピンク、赤,77,5.5×13
……

//kokugakusha.csv。
契沖,けいちゅう,1640-1701,万葉代匠記,厚顔抄……
……

//katsudonisshi.csv。左から日付、曜日、活動内容。
20180721,土,活動なし
20180722,日,活動なし
20180723,月,C++中級編の自宅学習範囲確認、……
……

//bachetc.csv。左端が作曲家名、その右には作品が171ずつ並んでいる。
【バッハ】,輝く暁の明星のいと美わしきかな,ああ神よ……
【ベートーヴェン】,ピアノ三重奏曲第1番 変ホ長調,ピアノ三重奏曲第2番……
……
これを、以下のmain関数で実行します。
また、項目ごとに「★」マークが入るよう、ListDisplay関数に書き足しました(上のコードの186、187行目)。
//=============================================================================
int main(void) {
    const char *file_name[] = {      //ファイル名を配列に格納。
        "kyoka.csv", 
        "kokugakusha.csv", 
        "katsudonisshi.csv", 
        "bachetc.csv"
    };
    int file_num, i, j;
    struct ListNode **headps;  //リスト先頭ポインタ格納配列へのポインタを宣言。
    
    setvbuf(stdout, NULL, _IONBF, 0);
    
    file_num = sizeof(file_name) / sizeof(char *);    //ファイル数を求める。
    
    headps = malloc(sizeof(struct ListNode *) * (size_t)file_num);
    if (headps == NULL) {      //↑ファイル数分だけ上記配列の要素数を確保。
        puts("リスト先頭ポインタ格納配列の領域が確保できませんでした。");
        exit(1);
    }
    
    for (i = 0; i < file_num; i++) {  //全ファイルの構造体リストを作る。
        headps[i] = smList(file_name[i]);
        if (headps[i] == NULL) {      //リストが作れないものがあれば……
            for (j = 0; j < i; j++) {   //すでに生成したリストを解放する。
                ListFree(headps[j]);
            }
            free(headps);               //リスト先頭ポインタ格納配列も解放。
            exit(1);
        }
    }
    
    for (i = 0; i < file_num; i++) {    //それらを表示。
        printf("### %s ###\n", file_name[i]);  //ファイル名表示「### ~ ###」。
        ListDisplay(headps[i]);                //全データ表示関数。
        if (i != file_num - 1) {printf("\n");} //いちばん下以外は改行を入れる。
        ListFree(headps[i]);                   //表示し終えたらリストは解放。
    }
    free(headps);                       //リスト先頭ポインタ格納配列の解放。
    return 0;
}
### kyoka.csv ###
★A ★豆バラ ★卒業・入学おめでとう/白、ピンク、赤 ★77 ★5.5×13
★B ★布サテンバラ ★卒業・入学おめでとう/白、ピンク、赤 ★124 ★6×16
★C ★カトレヤ ★卒業・入学おめでとう/紫、ピンク、赤 ★162 ★8×17
★D ★リボンバラ ★赤・白・ピンク ★296 ★10×20
★E ★サテンバラ ★赤・白・ピンク ★334 ★9×20

### kokugakusha.csv ###
★契沖 ★けいちゅう ★1640-1701 ★万葉代匠記 ★厚顔抄 ★古今余材抄 ★勢語臆断 ★源註拾遺 ★百人一首改観抄 ★和字正濫鈔
★賀茂真淵 ★かものまぶち ★1697-1769 ★歌意考 ★万葉考 ★国意考 ★祝詞考 ★にひまなび ★冠辞考 ★源氏物語新釈
★本居宣長 ★もとおりのりなが ★1730-1801 ★紫文要領 ★石上私淑言 ★詞の玉緒 ★古今集遠鏡 ★古事記伝 ★うひ山ふみ ★源氏物語玉の小櫛
★平田篤胤 ★ひらたあつたね ★1776-1843 ★古史成文 ★古史徴 ★霊能真柱 ★古史伝 ★仙境異聞 ★勝五郎再生記聞 ★古今妖魅考

### katsudonisshi.csv ###
★20180721 ★土 ★活動なし
★20180722 ★日 ★活動なし
★20180723 ★月 ★C++中級編の自宅学習範囲確認、Qt5英文参考書読解、自由制作プログラムの内容検討
★20180724 ★火 ★C++中級編の自宅学習範囲確認、Qt5英文参考書読解、自由制作プログラムの内容検討、ホームページ更新記事作成→「春以来の更新になります。(改行)計算部では、前回の更新後、ほどなくしてC++の参考書の入門編を読み終えました。(改行)C++はC言語に比べると情報量が多く、学習の労力も大きかったため、中級編に入る前にちょっと一休みして、これまで練習不足だった領域を練習しておこうか、ということになりました。(改行)練習不足な部分があるのは、別にサボって飛ばしていたわけではなく、参考書に練習問題があまり載っていない章があるためです。(改行)それは、C言語では「構造体」「リスト構造」、C++では「クラス」などです。(改行)これらを練習する場合は、構造体やクラス内にメンバが一つだけということはないでしょうから、ある程度大きなデータが必要になります。(改行)また、構造体やクラスは定義だけで何行も費やすことがあってプログラムが長くなりがちですし、目的によっては「リストやクラスなんか使わなくたって実現できるじゃん!」という場合もあるため、練習問題が作りにくいということが想像できます。(改行)練習問題が手に入らないのなら、自分たちで何か課題を考えるしかありません。(改行)今回考えたのは、C言語によるリスト構造を使った《データベースもどき》です。(改行)まあこんなのは、ExcelなりAccessなりを使えばGUI操作でちょちょっとできてしまうので、それこそ「わざわざリスト構造なんか使わなくたって」な練習その1という気がしますが(笑)、構造体やリスト構造のプログラムを書くための練習にはなると思うので、やってみることにしました。 」

### bachetc.csv ###
★【バッハ】 ★輝く暁の明星のいと美わしきかな ★ああ神よ、天より見そなわし ★ああ神よ、いかに多き胸の悩み ★キリストは死の縄目につながれたり ★われはいずこにか逃がれゆくべき ★われらと共に留まりたまえ ★われらの主キリスト、ヨルダンの川に来たり ★いと尊き御神よ、いつわれは死なん ★われらに救いの来たれるは ★わがこころは主をあがめ ★神をそのもろもろの国にて頌めよ ★泣き、歎き、憂い、怯え ★わがため息、わが涙は ★神もしこの時われらと共にいまさずば ★主なる神よ、汝をわれらは讃えまつらん ★感謝の供えものを献ぐる者は、われを讃う ★天より雨くだり雪おちて ★かくて戦おこれり ★おお 永遠、そは雷のことば ★わがうちに憂いは満ちぬ ★イエス十二弟子を召寄せて ★汝まことの神にしてダビデの子よ ★まじりけなき心 ★汝の怒りによりてわが肉体には全きところなく ★ああいかにはかなき、ああいかにむなしき ★たれぞ知らん、わが終りの近づけるを ★神は頌むべきかな!いまや年は終り ★われら汝に感謝す、神よ、われら感謝す ★喜べ、贖われし群よ ★天は笑い、地は歓呼す ★いと尊きイエス、わが憧れよ ★ただ汝にのみ、主イエス・キリストよ ★おお永遠の火、おお愛の源よ ★霊と心は驚き惑う ★喜び勇みて羽ばたき昇れ ★信じてバプテスマを受くる者は ★深き悩みの淵より、われ汝に呼ばわる ★飢えたる者に汝のパンを分ち与えよ ★神の子の現れたまいしは ★イエスよ、いま讃美を受けたまえ ★この同じ安息日の夕べ ★神は喜び叫ぶ声と共に昇り ★ひとびと汝らを除名すべし ★人よ、汝はさきに告げられたり、善きことの何なるか ★考え見よ、われを襲いしこの痛みに ★おのれを高うする者は卑うせられ ★われ悩める人、われをこの死の体より ★われは行きて汝をこがれ求む ★いまや、われらの神の救いと力と ★全地よ、神にむかいて歓呼せよ ★偽りの世よ、われは汝に頼まじ ★罪に手むかうべし ★われ哀れなる人、われ罪の下僕 ★われは喜びて十字架を負わん ★試練に耐うる人は幸いなり ★ああ神よ、いかに多き胸の悩み ★人もしわれを愛せば、わが言を守らん ★おお 永遠、そは雷のことば ★いざ来ませ、異邦人の救い主 ★キリストの徒よ、この日を彫り刻め ★見よ、父のわれらに賜いし愛の ★人々シバよりみな来たりて ★喜べ、汝ら もろ人の心よ ★死人の中より甦りしイエス・キリストを覚えよ ★げに神はかくまで世を愛して ★わが魂よ、主を頌めまつれ ★目を覚まして祈れ!祈りて目を覚ましおれ! ★神はいにしえよりわが王なり ★すべてはただ神の御心のままに ★主よ、御心のままに、わが身の上になし給え ★人もしわれを愛せば、わが言を守らん ★乏しき者は食らいて ★もろもろの天は神の栄光を語り ★汝の主なる神を愛すべし ★イエスよ、汝はわが魂を ★主なる神は日なり、盾なり ★われらが神は堅き砦 ★すべて神によりて生まれし者は ★イエスは眠りたもう、わが望みはいずこにありや ★われは足れり ★喜び満ちし新しき契約の時 ★われはわが命運に満ち足れり ★われは善き牧者なり ★まことに、まことに汝らに告ぐ ★今までは汝らなにをもわが名によりて ★見よ、われは多くの漁る者を遣わし ★われ汝をいかになさんや、エフライムよ ★怖ろしき終わり汝らを引きさらう ★讃美を受けたまえ、汝イエス・キリストよ ★われは神の御胸の思いに ★尊き御神の統べしらすままにまつろい ★われいかで世のことを問わん ★キリストこそ わが生命 ★主キリスト、神の独り子 ★わがなす すべての業に ★神なしたもう御業こそ いと善けれ ★われらより取去りたまえ、主よ、汝 真実なる神よ ★主よ、汝の目は信仰を顧るにあらずや ★汝らは泣き叫び ★イスラエルの牧者よ、耳を傾けたまえ ★主よ、汝の下僕の審きにかかずらいたもうなかれ ★神の時こそ いと良き時 ★汝なんぞ悲しみうなだるるや ★わが去るは汝らの益なり ★われ信ず、尊き主よ、信仰なきわれを助けたまえ ★笑いはわれらの口に満ち ★わが神の御心のままに、常に成らせたまえ ★主はわが信実なる牧者 ★主イエス・キリスト、汝こよなき宝 ★ああ、愛しきキリストの徒よ、雄々しかれ ★備えて怠るな、わが霊よ ★汝 平和の君、主イエス・キリスト ★讃美と栄光 至高の善なる者にあれ ★おお、イエス・キリスト、わが生命の光 ★エルサレムよ、主を讃えよ ★神よ、讃美はシオンにて静けく汝に上がり ★主なる神、万物の支配者よ ★神よ、讃美はシオンにて静けく汝に上がり ★キリストを われらさやけく頌め讃うべし ★新たに生まれし嬰児 ★いと尊きインマヌエル、虔しき者らを率いたもう君侯 ★わがイエスをば われは放さず ★平安と歓喜もて われはいま ★主よ、われらを汝の御言のもとに保ち ★主イエス・キリスト、真の人にして神よ ★ただキリストの昇天にのみ ★主を頌めまつれ ★主なる神よ、われらこぞりて汝を頌め ★深き淵より われ汝に呼ばわる、主よ ★道を備え、大路を備えよ ★われは汝にありて喜び ★おのがイエス生きたもうと知る心は ★ああ主よ、哀れなる罪人なるわれを ★神よ、願わくばわれを探りて ★主を頌めまつれ、勢威強き栄光の主を ★汝なにゆえにうなだるるや、わが心よ ★幸いなるかな、おのが御神に ★目覚めよ、と われらに呼ばわる物見らの声 ★わが魂よ、主を頌め讃えよ ★おのがものを取りて、行け ★われは生く、わが心よ、汝を喜び楽しませんため ★われらは多くの患難を経て ★心と口と行いと生きざまもて ★その御名にふさわしき栄光を ★喜びと勝利の歌声は ★主よ、われ汝を仰ぎ望む ★甘き慰めなるかな、わがイエスは来ませり ★出で立て、信仰の道に ★見たまえ、御神、いかにわが敵ども ★いと尊きわがイエスは見失われぬ ★わが神よ、いつまで、ああいつまでか ★わが片足すでに墓穴に入りぬ ★汝われを祝せずば ★平安なんじにあれ ★見よ、われらエルサレムにのぼる ★来たれ、汝甘き死の時よ ★ああ、いまわれ婚筵に行かんとして ★各々に各々のものを ★汝ら、キリストの者と名のる徒 ★おお 聖なる霊と水の洗礼よ ★汝はいずこに行くや? ★もろびとよ、神の愛を讃えまつれ ★務めの報告をいだせ!と轟く雷のことば ★神にのみ わが心を捧げん ★満ち足れる安らい、嬉しき魂の悦びよ ★神よ、汝の誉れはその御名のごとく ★歌よ、響け ★高く挙げられし血肉よ ★われ いと高き者を心を尽して愛しまつる ★彼は己の羊の名を呼びて ★傲りかつ臆するは
★【ベートーヴェン】 ★ピアノ三重奏曲第1番 変ホ長調 ★ピアノ三重奏曲第2番 ト長調 ★ピアノ三重奏曲第3番 ハ短調 ★ピアノソナタ第1番 ヘ短調 ★ピアノソナタ第2番 イ長調 ★ピアノソナタ第3番 ハ長調 ★弦楽三重奏曲第1番 変ホ長調 ★弦楽五重奏曲 変ホ長調 ★チェロソナタ第1番 ヘ長調 ★チェロソナタ第2番 ト短調 ★四手のためのソナタ ニ長調 ★ピアノソナタ第4番 変ホ長調 ★弦楽三重奏のためのセレナード ニ長調 ★弦楽三重奏曲第2番 ト長調 ★弦楽三重奏曲第3番 ニ長調 ★弦楽三重奏曲第4番 ハ短調 ★ピアノソナタ第5番 ハ短調 ★ピアノソナタ第6番 ヘ長調 ★ピアノソナタ第7番 ニ長調 ★ピアノ三重奏曲第4番 変ロ長調『街の歌』 ★ヴァイオリンソナタ第1番 ニ長調 ★ヴァイオリンソナタ第2番 イ長調 ★ヴァイオリンソナタ第3番 変ホ長調 ★ピアノソナタ第8番 ハ短調『悲愴』 ★ピアノソナタ第9番 ホ長調 ★ピアノソナタ第10番 ト長調 ★ピアノ協奏曲第1番 ハ長調 ★ピアノと管楽器のための五重奏曲 変ホ長調 ★ホルンソナタ ヘ長調 ★弦楽四重奏曲第1番 ヘ長調 ★弦楽四重奏曲第2番 ト長調 ★弦楽四重奏曲第3番 ニ長調 ★弦楽四重奏曲第4番 ハ短調 ★弦楽四重奏曲第5番 イ長調 ★弦楽四重奏曲第6番 変ロ長調 ★ピアノ協奏曲第2番変ロ長調 ★七重奏曲 変ホ長調 ★交響曲第1番 ハ長調 ★ピアノソナタ第11番 変ロ長調 ★ヴァイオリンソナタ第4番 イ短調 ★ヴァイオリンソナタ第5番 ヘ長調『春』 ★セレナード ニ長調 ★ピアノソナタ第12番 変イ長調 ★ピアノソナタ第13番 変ホ長調『幻想曲風ソナタ』 ★ピアノソナタ第14番 嬰ハ短調『月光』 ★ピアノソナタ15番 ニ長調『田園』 ★弦楽五重奏曲 ハ長調 ★ヴァイオリンソナタ第6番 イ長調 ★ヴァイオリンソナタ第7番 ハ短調 ★ヴァイオリンソナタ第8番 ト長調 ★ピアノソナタ第16番 ト長調 ★ピアノソナタ第17番 ニ短調『テンペスト』 ★ピアノソナタ第18番 変ホ長調 ★歌曲『希望に寄せて』 ★7つのバガテル ★創作主題による6つの変奏曲 ヘ長調 ★15の変奏曲とフーガ 変ホ長調 ★交響曲第2番 ニ長調 ★ピアノ協奏曲第3番 ハ短調 ★ピアノ三重奏曲第8番 変ホ長調 ★2つの前奏曲 ★ヴァイオリンと管弦楽のためのロマンス第1番 ト長調 ★セレナード ニ長調 ★ノットゥルノ ニ長調 ★バレエ音楽『プロメテウスの創造物』 ★ディッタースドルフの『赤頭巾』の主題による14の変奏曲 変ホ長調 ★四手のための3つの行進曲 ★歌曲『アデライーデ』 ★ヴァイオリンソナタ第9番 イ長調『クロイツェル』 ★ゲレルトの詩による6つの歌曲 ★ピアノソナタ第19番 ト短調(やさしいソナタ) ★ピアノソナタ第20番 ト長調(やさしいソナタ) ★ヴァイオリンと管弦楽のためのロマンス第2番 ヘ長調 ★ロンド ハ長調 ★ロンド ト長調 ★8つの歌曲 ★ピアノソナタ第21番 ハ長調『ワルトシュタイン』 ★ピアノソナタ第22番 ヘ長調 ★交響曲第3番 変ホ長調『英雄』 ★ピアノ、ヴァイオリンとチェロのための三重協奏曲 ハ長調 ★ピアノソナタ第23番 ヘ短調『熱情』 ★ピアノ協奏曲第4番 ト長調 ★弦楽四重奏曲第7番 ヘ長調『ラズモフスキー第1番』 ★弦楽四重奏曲第8番 ホ短調『ラズモフスキー第2番』 ★弦楽四重奏曲第9番 ハ長調『ラズモフスキー第3番』 ★交響曲第4番 変ロ長調 ★ヴァイオリン協奏曲 ニ長調 ★ピアノ協奏曲 ニ長調 ★『コリオラン』序曲 ★弦楽五重奏曲 ★チェロとピアノのための二重奏曲 ★シェーナとアリア『ああ、不実なる者よ』 ★モーツァルトの『魔笛』の主題による12の変奏曲 ト長調 ★交響曲第5番 ハ短調『運命』 ★交響曲第6番 ヘ長調『田園』 ★チェロソナタ第3番 イ長調 ★ピアノ三重奏曲第5番 ニ長調『幽霊』 ★ピアノ三重奏曲第6番 変ホ長調 ★管楽六重奏曲 変ホ長調 ★歌劇『フィデリオ』 ★レオノーレ序曲第2番 ★レオノーレ序曲第3番 ★ピアノ協奏曲第5番 変ホ長調『皇帝』 ★弦楽四重奏曲第10番 変ホ長調『ハープ』 ★6つの歌曲 ★創作主題による6つの変奏曲 ニ長調 ★幻想曲 ★ピアノソナタ第24番 嬰ヘ長調『テレーゼ』 ★ピアノソナタ第25番 ト長調『かっこう』 ★合唱幻想曲 ハ短調  ★ピアノソナタ第26番 変ホ長調『告別』 ★六重奏曲 変ホ長調 ★4つのアリエッタと1つの二重唱曲 ★ゲーテの詩による3つの歌曲 ★付随音楽『エグモント』 ★オラトリオ『オリーヴ山上のキリスト』 ★ミサ曲 ハ長調 ★三重奏曲 ハ長調 ★歌曲『人生の幸せ』 ★ポロネーズ ハ長調 ★ピアノソナタ第27番 ホ短調 ★『ウェリントンの勝利、またはヴィットリアの戦い』 ★交響曲第7番 イ長調 ★交響曲第8番 ヘ長調 ★歌曲『希望に寄せて』 ★弦楽四重奏曲第11番 ヘ短調『セリオーソ』 ★ヴァイオリンソナタ第10番 ト長調 ★ピアノ三重奏曲第7番 変ロ長調『大公』 ★連作歌曲『遥かな恋人に寄せて』 ★歌曲『約束を守る男』 ★歌曲『メルケンシュタイン』 ★ピアノソナタ第28番 イ長調 ★チェロソナタ第4番 ハ長調 ★チェロソナタ第5番 ニ長調 ★管楽八重奏曲 変ホ長調 ★弦楽五重奏曲 ★6つの民謡主題と変奏曲 ★ピアノソナタ第29番 変ロ長調『ハンマークラヴィア』 ★10の民謡主題と変奏曲 ★25のスコットランドの歌 ★ピアノソナタ第30番 ホ長調 ★ピアノソナタ第31番 変イ長調 ★ピアノソナタ第32番 ハ短調 ★カンタータ『静かな海と楽しい航海』 ★付随音楽『アテネの廃墟』 ★『アテネの廃墟』のための行進曲と合唱曲 ★『命名祝日』序曲 ★三重唱曲『不信心な者よ、おののけ』 ★付随音楽『シュテファン王、またはハンガリー最初の善政者』 ★四重唱曲『悲歌』 ★11の新しいバガテル ★ディアベリのワルツによる33の変奏曲 ハ長調 ★ピアノ三重奏曲第11番 ト長調 ★奉献歌 ★盟友の歌 ★ミサ・ソレムニス ニ長調 ★『献堂式』序曲 ★交響曲第9番 ニ短調『合唱』 ★ピアノのための6つのバガテル ★弦楽四重奏曲第12番 変ホ長調 ★アリエッタ『接吻』 ★ロンド・カプリッチョ ト長調『失われた小銭への怒り』 ★弦楽四重奏曲第13番 変ロ長調 ★弦楽四重奏曲第14番 嬰ハ短調 ★弦楽四重奏曲第15番 イ短調 ★大フーガ 変ロ長調 ★4手ピアノのための大フーガ 変ロ長調 ★弦楽四重奏曲第16番 ヘ長調 ★カンタータ『栄光の瞬間』 ★弦楽五重奏のためのフーガ ニ長調 ★レオノーレ序曲第1番
★【ミヨー】 ★フランシス・ジャムの詩による歌曲 ★レオ・ラティルの3つの詩 ★ヴァイオリンソナタ第1番 ★歌劇『迷える羊』 ★弦楽四重奏曲第1番 ★フランシス・ジャムの詩による歌曲 ★「東方の認識」からの7つの詩 ★ピアノ組曲 ★アリサ ★リュシール・ド・シャトーブリアンの3つの詩 ★『ロマン派詩人の3つの詩』第1集 ★交響組曲第1番 ★カマルグの雅歌による詩曲 ★付随音楽『アガメムノン』 ★2つのヴァイオリンとピアノのためのソナタ ★弦楽四重奏曲第2番 ★付随音楽『プロテー』 ★春 ★『ロマン派詩人の3つの詩』第2集 ★レオ・ラティルの4つの詩 ★城 ★ジタンジャリの詩 ★クリケの主題による変奏曲 ★付随音楽『コエフォール』 ★組曲『春』第1集 ★バリトンのための4つの詩 ★ユージェニー・ド・ゲランの未刊のノートより ★異国の木 ★サランスのマリア ★2つの恋歌 ★コヴェントリー・パットモアの2つの詩 ★弦楽四重奏曲第3番 ★ピアノソナタ第1番 ★ユダヤの詩 ★ガードナーの2つの詩 ★子供の詩 ★3つの詩 ★木の葉を着た教会 ★2つの詩 ★ヴァイオリンソナタ第2番 ★付随音楽『エウメニデス』 ★カンタータ『放蕩息子の帰郷』 ★室内交響曲第1番『春』 ★逍遥歌 ★A.ランボーの2つの詩 ★弦楽四重奏曲第4番 ★フルート、オーボエ、クラリネットとピアノのためのソナタ ★バレエ音楽『男とその欲望』 ★組曲『男とその欲望』 ★室内交響曲第2番『パストラール』 ★フランシス・ジャムの詩による歌曲 ★2つの小歌曲 ★2つの詩 ★詩篇129 ★フランシス・トンプソンの詩 ★ペトログラードの夜 ★農機具 ★交響組曲第2番 ★バレエ音楽『屋根の上の牡牛』 ★シネマ・ファンタジー ★フラテリーニのタンゴ ★屋根の上の牡牛 ★ジャン・コクトーの3つの詩 ★花のカタログ ★バラード ★セレナード ★5つの練習曲 ★弦楽四重奏曲第5番 ★体温表 ★組曲『春』第2集 ★ブラジルの郷愁 ★溶けたキャラメル ★カクテル ★バレエ音楽『エッフェル塔の花嫁花婿』 ★室内交響曲第3番『セレナード』 ★詩篇第126 ★レオ・ラティルの日記からの詩 ★室内交響曲第4番 ★室内交響曲第5番 ★フルートとピアノのためのソナチネ ★弦楽四重奏曲第6番 ★3つのラグ・カプリス ★室内交響曲第6番 ★カチュルの4つの詩 ★バレエ音楽『世界の創造』 ★『世界の創造』による演奏会用組曲 ★シャブリエのオペレッタ「受けそこなった教育」のためのレチタティーヴォ ★バレエ音楽『サラダ』 ★エクスの謝肉祭 ★バレエ音楽『青列車』 ★歌劇『オルフェの不幸』 ★ヘブライの2つの民謡 ★弦楽四重奏曲第7番 ★2つの讃歌 ★歌劇『カルパントラのエステル』 ★即興劇 ★即興曲 ★歌劇『哀れな水夫』 ★ヴァイオリン協奏曲第1番 ★短編オペラ『エウロペの略奪』 ★ポルカ ★ヴナスク伯爵領ユダヤ人の行う日々の祈り ★パガニーニの3つのカプリッチョ ★短編オペラ『見捨てられたアリアーヌ』 ★短編オペラ『解放されたテセウス』 ★クラリネットとピアノのためのソナチネ ★バレエ音楽『最愛の女』 ★歌劇『クリストフ・コロンブ』 ★主を讃えるカンタータ ★映画音楽『アクチュアリテ』 ★発声練習 ★4行詩 ★映画音楽『リリーちゃん』 ★ヴィオラ協奏曲第1番 ★打楽器と小管弦楽のための協奏曲 ★歌劇『マクシミリアン』 ★コラール ★オルガン・ソナタ ★サンドラールの2つの詩 ★ロマン派詩人の2つのエレジー ★組曲『秋』 ★暴君の死 ★付随音楽『マリアへのお告げ』 ★藪から棒 ★ちょっと音楽を ★付随音楽『教皇の館』 ★アダージョ ★オンド・マルトノとピアノのための組曲 ★弦楽四重奏曲第8番 ★裸の手の前で ★悲しい帰還 ★バレエ音楽『夢』 ★ヴナスク伯爵領人の典礼 ★映画音楽『ハロー・エヴリボディ』 ★ピアノ協奏曲第1番 ★映画音楽『ボヴァリー夫人』 ★ボヴァリー夫人のアルバム ★『ボヴァリー夫人』からの3つのワルツ ★2つの歌 ★4つの無言歌 ★カンタータ『パンとシリンクス』 ★付随音楽『同じ花が好き』 ★ロンサールの恋 ★少し練習を ★音声練習 ★春のコンチェルティーノ ★チェロ協奏曲第1番 ★映画音楽『海馬』 ★映画音楽『タラスコンのタルタラン』 ★付随音楽『創造の循環』 ★弦楽四重奏曲第9番 ★智慧 ★白鳥 ★4行詩 ★バティスト・アネの10番目のソナタ ニ長調 ★付随音楽『ほら吹き』 ★映画音楽『幼児の声』 ★パストラール ★黒人女性の3つの歌 ★付随音楽『天の狂女』 ★映画音楽『愛すべき放浪者』 ★付随音楽『私からは逃げられない』 ★付随音楽『ベルトラン・ド・ボルン』 ★バレエ音楽『花咲ける中世』 ★トルバドゥールの3つの歌 ★プロヴァンス組曲 ★付随音楽『セビーリャのいかさま師』 ★序奏と葬送行進曲 ★付随音楽『征服者』 ★劇的な断片 ★ローヌ河讃歌
うまく表示されました。
最後の、作曲家の作品csvファイル表示は、「見づらい! そもそも、こんなファイル使うか?」という感じですが(笑)、まあただの実験ですので……。
★ちなみに、作品数が「171」と半端な数になっているのは、三人の中でいちばん作品数が少ないベートーヴェンに合わせたためです。
171だと、ご覧のように、バッハは教会カンタータすら全部は書き切れません。
ミヨーも500曲くらいあるので、ここに出ているのは3分の1程度です。
なお、ミヨーの作品のうち、ネット上に翻訳が見当たらなかったタイトルは、適当に訳しておきました。


以上、csvファイルからメンバ数可変(メンバの文字列へのポインタが配列になっているだけですが)の構造体リストを作り、それを表示するところまでいきました。
だいぶ長くなってしまいましたので、ここで日を分けたいと思います。
「5. いろいろ実験」は次の日記に続きます。