C++のstrtok関数を用いた5つのテクニックをプロが解説

C++におけるstrtok関数を使ったプログラミング例のイメージC++
この記事は約13分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事では、C++プログラミング言語における重要な関数の一つ、strtok関数について詳しく解説します。

プログラミング初心者から中級者まで、文字列を効率的に扱う方法を学びたい方々に向けて、基本から応用技術までを段階的に説明していきます。

strtok関数を使いこなせるようになると、プログラム内でのデータ処理が格段にスムーズになります。

●strtok関数とは

strtok関数は、C++(およびC言語)における文字列分割を行うための標準関数です。

この関数を使うことで、文字列を特定の区切り文字(デリミタ)に基づいて複数のトークンに分割することができます。

多くのプログラミング初心者にとって、この関数は文字列操作の基本となるため、その使い方をマスターすることは非常に重要です。

○基本的な概要と機能

strtok関数は、最初の引数に文字列を指定し、二番目の引数にデリミタ(トークンを区切るための一つまたは複数の文字)を指定します。

関数は指定された文字列からデリミタを探し、見つかった場所をNULL文字で置換して、トークンの最初の部分を指すポインタを返します。

文字列がデリミタで完全に分割されるまで、strtok関数を繰り返し呼び出すことにより、文字列を順にトークン化していきます。

具体的な使い方をサンプルコードで見てみましょう。

このコードは、文字列 “hello world, welcome to C++ programming” をスペースとカンマで分割する例です。

#include <iostream>
#include <cstring>

int main() {
    char str[] = "hello world, welcome to C++ programming";
    char *token = strtok(str, " ,");

    while (token != NULL) {
        std::cout << token << std::endl;
        token = strtok(NULL, " ,");
    }

    return 0;
}

このコードでは、strtok関数が最初に”hello”を返し、続いて”world”、”welcome”、”to”、”C++”、”programming”という各単語を返します。

それぞれの単語は、最初に指定したデリミタ” “(スペース)および”,”(カンマ)によって区切られています。

これにより、元の文字列を意味のある部分に分割して処理することが可能となります。

●strtok関数の基本的な使い方

strtok関数を使用する際の基本的なステップは、文字列とデリミタを指定することから始まります。

初めて関数を呼び出すときは、操作したい文字列を指定し、その後の呼び出しではNULLを指定して同じ文字列の続きをトークン化します。

これは、strtokが内部で前回の呼び出し位置を記憶しているためです。

こうして、デリミタによって区切られた文字列の「トークン」を一つずつ取り出すことができます。

使用する際の一般的な流れは、まず元の文字列に含まれるデリミタの位置を見つけ、そこをNULL文字で置き換えて文字列を終端させ、トークンの開始位置を指すポインタを返すというものです。

このプロセスを繰り返すことで、全てのトークンを抽出します。

ここで重要な点は、strtok関数が元の文字列を変更してしまうため、元の文字列を保持したい場合は事前にコピーを取っておく必要があります。

また、マルチスレッド環境でstrtokを使用する際には注意が必要です。

strtokはスレッドセーフではないため、複数のスレッドから同時にアクセスされると予期せぬ動作を引き起こす可能性があります。

○サンプルコード1:文字列をスペースで分割する

このサンプルコードは、単純にスペースで文字列を分割する基本的な使い方を表しています。

この例では、「This is a sample text.」という文字列をスペースで区切り、各単語を出力します。

#include <iostream>
#include <cstring>  // strtokを使用するために必要

int main() {
    char text[] = "This is a sample text.";
    char *token = strtok(text, " ");  // スペースをデリミタとして指定

    while (token != NULL) {
        std::cout << token << std::endl;  // 分割されたトークンを出力
        token = strtok(NULL, " ");  // 次のトークンを取得
    }

    return 0;
}

このコードは、strtok関数を使って文字列をトークンに分割し、それぞれのトークンを標準出力に表示します。

最初にstrtokを呼び出すときは対象の文字列を指定し、次回からはNULLを指定して内部で保持している位置から処理を続けます。

○サンプルコード2:複数のデリミタを使用して分割する

より複雑な例として、複数の異なるデリミタを使って文字列を分割する方法を見てみましょう。

このコードでは、スペース、カンマ、ピリオドをデリミタとして使用しています。

#include <iostream>
#include <cstring>

int main() {
    char text[] = "Hello, world. Welcome to C++ programming.";
    char *token = strtok(text, " ,.");  // スペース、カンマ、ピリオドをデリミタとする

    while (token != NULL) {
        std::cout << token << std::endl;  // 分割されたトークンを出力
        token = strtok(NULL, " ,.");  // 次のトークンを取得
    }

    return 0;
}

この例では、「Hello, world. Welcome to C++ programming.」という文字列が、「Hello」「world」「Welcome」「to」「C++」「programming」というトークンに分割されます。

ここで使用されるデリミタはスペース、カンマ、ピリオドの組み合わせであり、これによりさまざまな記号で区切られた文章も効率的にトークン化することが可能です。

●strtok関数を使ったエラー処理

strtok関数を使用する際に注意が必要なエラー処理について解説します。

strtok関数は内部的に前回のトークンの位置を記憶しているため、複数のスレッドが同じ文字列に対してstrtokを同時に呼び出すと問題が発生します。

これはstrtokがスレッドセーフではないことに起因します。

また、渡された文字列がNULLの場合、またはデリミタがNULLまたは空文字列の場合、strtokは動作を停止します。

このような状況を避けるためには、呼び出し前に必ず引数をチェックする必要があります。

さらに、strtok関数は元の文字列を変更することに注意が必要です。

関数がトークンを見つけると、その直後の位置にNULL文字を挿入し、文字列を分割します。

これにより元の文字列が永続的に変更されるため、元のデータを保持する必要がある場合は、処理前に文字列をコピーしておくことが重要です。

○よくあるエラーとその対処法

strtok関数の使用中によく見られるエラーに対しては、適切な入力の検証とエラーチェックが不可欠です。

特に関数への入力がプログラムの外部から来る場合には、これらのチェックが重要になります。

デリミタとしてNULLまたは空文字列を受け付けないため、適切なデリミタが指定されているか事前に確認すること、最初の呼び出しで入力文字列としてNULLが渡されるとstrtokは動作を停止するため、有効な文字列が入力されているかを確認することが必要です。

また、strtokはスレッドセーフではないため、マルチスレッド環境での使用は避け、代わりにstrtok_rのようなスレッドセーフな関数を使用することが推奨されます。

○サンプルコード3:NULLポインタを安全に扱う方法

このサンプルコードは、strtok関数を使用する際にNULLポインタが安全に扱われるようにする方法を示します。

このコードは、入力文字列とデリミタが適切かどうかをチェックすることで、strtokが安全に実行されることを保証します。

#include <iostream>
#include <cstring>  // strtokを使用するために必要

int main() {
    char text[] = "Example string, with several delimiters.";
    char *delimiters = " ,;.";  // 使用するデリミタ

    // 入力の検証
    if (text == NULL || delimiters == NULL) {
        std::cerr << "Invalid input: text or delimiters cannot be NULL." << std::endl;
        return 1;
    }

    char *token = strtok(text, delimiters);
    while (token != NULL) {
        std::cout << "Token: " << token << std::endl;
        token = strtok(NULL, delimiters);
    }

    return 0;
}

このコード例では、最初に文字列とデリミタがNULLでないことを確認しています。

これにより、strtok関数が安全にトークンを抽出できるようになります。

●strtok関数の応用例

strtok関数は単に文字列を分割するだけでなく、多様なデータ処理の場面で活用することができます。

例えば、ログファイルから特定のデータを抽出する作業や、CSVファイルの解析など、実務でよく遭遇する課題を効率的に解決するのに役立ちます。

これらの応用例を通じて、strtok関数の柔軟性と実用性をさらに深掘りしてみましょう。

○サンプルコード4:CSVファイルのパーサー

CSVファイルは「カンマ区切り値」を含むテキストファイルで、表形式のデータを簡単に保存できるため、データ交換のフォーマットとして広く使用されています。

このサンプルコードは、CSVファイルの各行を読み込み、カンマで区切られた各要素をトークンとして抽出しています。

これにより、データの各列に簡単にアクセスし、必要な情報を抽出できます。

#include <iostream>
#include <fstream>
#include <cstring>

int main() {
    std::ifstream file("data.csv");
    char line[256];

    while (file.getline(line, sizeof(line))) {
        char *token = strtok(line, ",");
        while (token != NULL) {
            std::cout << "Data: " << token << std::endl;
            token = strtok(NULL, ",");
        }
    }

    file.close();
    return 0;
}

このコードは、ファイルから一行ずつ読み込み、その行をカンマで区切っています。

各トークンはCSVの一列に相当し、この方法でデータの列を個別に処理することができます。

○サンプルコード5:ログファイルから特定のデータを抽出

ログファイルはシステムやアプリケーションが自動的に記録するテキストファイルで、エラー解析やシステムのモニタリングに不可欠です。

このコードは、ログファイルから特定の情報を抽出する一例を表しており、strtok関数を使用してログの各行を解析し、必要なデータをフィルタリングしています。

#include <iostream>
#include <fstream>
#include <cstring>

int main() {
    std::ifstream logFile("system.log");
    char line[1024];

    while (logFile.getline(line, sizeof(line))) {
        char *token = strtok(line, " ");  // 空白で区切る
        while (token != NULL) {
            if (strstr(token, "Error")) {  // "Error"を含むトークンを探す
                std::cout << "Error Found: " << line << std::endl;
                break;
            }
            token = strtok(NULL, " ");
        }
    }

    logFile.close();
    return 0;
}

このコードでは、ログファイルの各行を空白文字で区切り、”Error”という文字列を含む行を探しています。

このようにstrtok関数を用いることで、複雑な文字列操作を簡単に、かつ効率的に行うことが可能です。

●経験者向けの補足知識

C++のプログラミングにおいて、strtok関数は基本的な文字列操作のツールとして知られていますが、その使用には注意が必要です。特に、マルチスレッド環境下での使用は推奨されていません。

これは、strtok関数が内部的に前回のトークンの位置を静的な記憶域に保存するため、複数のスレッドが同じ文字列リソースにアクセスした場合、データ競合が発生するリスクがあるからです。

○strtok_r関数の利点と使い方

この問題を解決するためには、strtok_r関数が有効です。

strtok_rは、strtokと同様に文字列をデリミタに基づいてトークンに分割する関数ですが、トークンの位置を保存するためのポインタを引数として直接受け取るため、スレッドセーフな操作が可能です。

この特性により、複数のスレッドが独立して文字列操作を行う場合にも、互いに影響を与えることなく安全に処理を行うことができます。

このサンプルコードでは、スレッドセーフな方法で文字列を分割しています。

#include <iostream>
#include <cstring>

int main() {
    char str[] = "A sample string";
    char* saveptr;
    char* token = strtok_r(str, " ", &saveptr);

    while (token != null) {
        std::cout << token << std::endl;
        token = strtok_r(NULL, " ", &saveptr);
    }

    return 0;
}

このコードでは、strtok_r関数を利用して文字列 “A sample string” を空白で分割しています。

第三引数の saveptr は、strtok_rが次のトークンの検索を開始する位置を記憶するためのポインタです。

このようにして、各スレッドが独自の saveptr を管理することで、複数のスレッドが同時に異なる文字列を安全に処理することが可能となります。

まとめ

この記事では、C++のstrtok関数の使い方から応用までを詳しく解説しました。

特に、マルチスレッド環境での安全な使用を考慮して、strtok_r関数の使い方も紹介しました。

この関数を活用することで、効率的な文字列処理が可能となり、プログラムの柔軟性と安全性を高めることができます。