11. 文字処理と関数とポインタ−と
 ここでは,文字列処理の関数化をとおして,C言語を使う上での必須の知識である関数とポインターに慣れることを目的とします.
 11.1 文字列を関数に渡す
 前にも述べたように,C言語のプログラムは,全体の処理を複数の小さな処理単位に分割し,この分割された処理単位を関数化します.当然,文字列についても様々な処理が考えられるわけで,データとしての文字列を関数へ引き渡す方法を知っておく必要があります.ここでは,関数へ文字列を引き渡す方法を紹介します.

 文字列データは,配列変数に代入された状態で扱われます.したがって,数値データの場合と同様,配列の先頭アドレス,言い換えれば,ポインター変数を引数としてやりとりされます.
プログラム例11.1.1に,文字型の1次元配列に代入された文字列を関数に渡す方法を示します.プログラム例では,配列の先頭アドレスを関数へ渡す時の表現を3種の「書き方」で表現してみました.各表現法については,数値型配列の場合と同じなので,あらためて説明する必要はないでしょう.


 プログラム例 11.1.1  実行結果
#include <stdio.h>
void print_string(char *sp);

void main(void){
    char a[21]={"Learning Programming"};
    char *ap = a;

    /* 書き方 1 */
    print_string(&a[0]);

    /* 書き方 2 */
    print_string(a);

    /* 書き方 3 */
    print_string(ap);
}

void print_string(char *sp){
    printf("%s\n", sp);
}
Learning Programming
Learning Programming
Learning Programming


 プログラム例11.1.2も,1次元の文字型配列を関数へ渡す方法の1つです.次のように,ダブルクォーテション(””)で囲んだ文字列を引数として渡しています.
   print_string("Learning Programming");
このように書いても,文字列の先頭アドレスを渡すことができるのです.よく使われる表現方法ですので,ちゃんと,覚えておきましょう.


 プログラム例 11.1.2  実行結果
#include <stdio.h>
void print_string(char *sp);

void main(void)
{
    print_string("Learning Programming");
}

void print_string(char *sp){
    printf("%s\n", sp);
}
Learning Programming



 11.2 2次元配列とポインターの配列

 こんどは,次のように複数の文字列が代入された2次元配列を関数に渡す方法について考えてみましょう.
   char a[3][11]={"Apple","Orange","Peach"};
もちろん,これを書き換えれば,次のようになります.
   char a[0][11]={"Apple"};
   char a[1][11]={"Orange"};
   char a[2][11]={"Peach"};


 まず,プログラム例11.2.1を見てください.
このプログラムでは,&a[i][0]の形で,3つの文字列の先頭アドレスを3回に分けてprint_string()関数に渡しています.最も基本的な配列(今の場合,文字配列)のアドレス)の渡し方ですね.


 プログラム例 11.2.1  実行結果
#include <stdio.h>
void print_string(char *ptr);

void main(void){
    int nfruits = 3;
    char a[3][11]={"Apple","Orange","Peach"};
    int i;

    for(i = 0; i < nfruits; i++){
       print_string(&a[i][0]);
    }
}

void print_string(char *ptr)
{
    printf("%s\n", ptr);
}
Apple
Orange
Peach


 整数型変数,実数型変数,文字型変数などの変数と同様,ポインター型変数の場合も配列を宣言することができます.これをポインタ型配列と呼びます.
ここでは,プログラム例11.2.1をポインター型の配列を使って書き換えてみます.

 ポインター型配列は,次のように宣言します.
   char *ap[3];

もちろん,アドレスは,次のように代入します.
   ap[i]=&a[i][11];

ap[i]の内容は,アドレスですから,関数は次のような形で呼び出すことになります.
   print_string(ap[i]);


 プログラム例 11.2.2  実行結果
#include <stdio.h>
void print_string(char *ptr);

void main(void){
    int nfruits = 3;
    char a[3][11]={"Apple","Orange","Peach"};
    char *ap[3];
    int i;
    
    for(i = 0; i < nfruits; i++){
       ap[i] = &a[i][0];
    }

    for(i = 0; i < nfruits; i++){
       print_string(ap[i]);
    }
}

void print_string(char *ptr)
{
    printf("%s\n", ptr);
}
Apple
Orange
Peach



 11.3 ポインターのポインター

 プログラム例11.2.2では,ポインターの配列ap[i]を宣言して使いました.
それでは,次の文は何を意味するでしょうか?
   &ap[0];
そうです.これは,アドレスを記憶したポインター型配列の先頭要素のアドレスです.
では,これを記憶する変数は定義できるのでしょうか?
答えは,Yes!です.次のように宣言します.
   char **ptr;
変数ptrは,ポインター型配列のアドレスを記憶するポインタ変数ですから,ポインタのポインタとよばれます.

 ポインタのポインタを使って,プログラム例11.2.2を書き換えると,プログラム例11.3.1のように書くことができます.
関数側の *ptrは,文字列の先頭のアドレスになることに注意しましょう.


 プログラム例 11.3.1  実行結果
#include 
void print_string(int n, char **ptr);

void main(void){
    int nfruits = 3;
    char a[3][11]={"Apple","Orange","Peach"};
    char *ap[3];
    int i;
    
    for(i = 0; i < nfruits; i++){
       ap[i] = &a[i][0];
    }

    /* 書き方 1 */
    print_string(nfruits, &ap[0]);
    printf("\n");

    /* 書き方 2 */
    print_string(nfruits, ap);
}

void print_string(int n, char **ptr)
{
    int i;

    for(i = 0; i < n; i++){ 
        printf("%s\n", *ptr);
        ptr++;
    }
}
Apple
Orange
Peach

Apple
Orange
Peach



プログラム例11.3.2は,例11.3.1の変数宣言部を書き換えたものです.
   char *ap[3] = {"Apple","Orange","Peach"};
このように書くと,ポインタ型配列の要素 ap[0], ap[1],ap[2]のそれぞれに"Apple","Orange","Peach"の先頭アドレスが自動的に代入されることになります.
これもよく使われる書き方ですので,しっかり記憶しておいてください.


 プログラム例 11.3.2  実行結果
#include <stdio.h>
void print_string(int n, char **ptr);

void main(void){
    int nfruits = 3;
    char *ap[3] = {"Apple","Orange","Peach"};

    /* 書き方 1 */
    print_string(nfruits, &ap[0]);
    printf("\n");

    /* 書き方 2 */
    print_string(nfruits, ap);
}

void print_string(int n, char **ptr)
{
    int i;

    for(i = 0; i < n; i++){ 
        printf("%s\n", *ptr);
        ptr++;
    }
}
Apple
Orange
Peach

Apple
Orange
Peach






 11.4 呼び出し回数の少ない関数を作る

 前節で紹介したプログラム例11.2.2(下記の11.4.1)と11.3.2(下記の11.4.2)を比較してみましょう.
プログラム全体の処理内容は,いずれの例も同じですが,2つのプログラムには大きな違いがあります.
同じ量のデータを処理するのに,プログラム例11.4.1では,文字列の数だけ関数呼び出しを実行していますが,例11.4.2では,1回だけで済ませているところです.

 一般に,CPUは「関数を呼び出し」を実行するとき,関数の内容を実行する前に,その準備段階として時間のかかる作業を実行します.アセンブリ言語でプログラムを作成した経験のあるひとなら,理解できると思いますが,それは結構長い時間を要します.
同一処理を実行するための関数は,いろいろ考えられますが,なるべく呼び出し回数の少ない関数を作成する工夫をすることは大切なことです.


 プログラム例 11.4.1  実行結果
#include <stdio.h>
void print_string(char *ptr);

void main(void){
    int nfruits = 3;
    char a[3][11]={"Apple","Orange","Peach"};
    char *ap[3];
    int i;
    
    for(i = 0; i < nfruits; i++)
       ap[i] = &a[i][0];

    for(i = 0; i < nfruits; i++)
       print_string(ap[i]);
}

void print_string(char *ptr)
{
    printf("%s\n", ptr);
}
Apple
Orange
Peach




 プログラム例 11.4.2  実行結果
#include <stdio.h>
void print_string(int n, char **ptr);

void main(void){
    int nfruits = 3;
    char *ap[3] = {"Apple","Orange","Peach"};

    print_string(nfruits, ap);
}

void print_string(int n, char **ptr)
{
    int i;

    for(i = 0; i < n; i++){ 
        printf("%s\n", *ptr);
        ptr++;
    }
}
Apple
Orange
Peach