Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

C++最速マスター その3

C++プログラミング入門

C++プログラミング入門

index

  • Char Pointer
  • String Function
    • wide character / wstring
    • atof/atoi/atol
    • join/split
    • character encoding conversion
    • zen2han / han2zen
    • kata2hira / hira2kata / latin2kata / latin2kata / kata2latin/ latin2hira / hira2latin
  • Regex
    • regcomp / regexec / regfree / regerror

Char Pointer

C++の場合string型を使ってしまいがちですが、標準関数がcharにしか対応していない(stringがNG)ことも多くcharの性質を理解しておきます。charは1文字を表す型で配列にすると文字列が表現できます。ポインタにはデータのアドレスが格納、ポインタ変数には配列の先頭アドレスが格納されるのでインクリメントしてアドレスの中身であるデータを取得します。char配列はint配列と性質が異なります。例えばint *pp = {1,2,3,4,5}; はコンパイルエラーになりますが、char *stp = "abcedf";は格納できます。char型だけ特別と考えると良いと思います。
※RiSKさんからの指摘でint配列の繰り返し文が参照してはいけない添字にアクセスしているという事が分かり、while文を修正しました。(2012/5/1)

#include <iostream>
using namespace std;

int main() {
    int p[] = {1,2,3,4,5};
    int *pp = &p[0];
    // 下はerrorになる
    // int *pp = {1,2,3,4,5}; 
    int psize = sizeof( p ) / sizeof( p[0] ); 

       int n = 0;
    // int配列の中身を一つずつ出力
    cout << "int array" << endl;
    while( n < psize ) {
        cout << p[n] << endl;
        n++;
    }

    /*
    while( p[n] != '\0' ) {
        cout << p[n] << endl;
        ++n;
    }
    */

    putchar( '\n' );
    
    // int配列の中身をポインタで出力
    cout << "int pointer" << endl;
    
    n = 0;
    while( n < psize ) {
        cout << pp << endl;
        cout << *pp << endl;
        ++pp;
        ++n;
    }

    /*
    while( *pp != '\0' ) {
        cout <<  pp << endl;
        cout <<  *pp << endl;
        ++pp;
    }
    */

    putchar( '\n' );
    
    // 文字配列をポインタで参照し一つずつ出力
    cout << "char pointer" << endl;
    while( *stp != '\0' ) {
        cout <<  &stp << endl;
        cout <<  *stp << endl;
        ++stp;
    }
}

実行結果

int array
1
2
3
4
5

int pointer
0x7fffb08963e0
1
0x7fffb08963e4
2
0x7fffb08963e8
3
0x7fffb08963ec
4
0x7fffb08963f0
5

char array
a
b
c
e
d
f

char pointer
0x7fffb08963c8
a
0x7fffb08963c8
b
0x7fffb08963c8
c
0x7fffb08963c8
e
0x7fffb08963c8
d
0x7fffb08963c8
f

String Function

wide character / wstring

wstringとはUNICODE用の文字列格納型です。文字を宣言する時にLのリテラルを指定することと、単純なcoutでは文字化けするのでlocale設定wcoutを使うようにします。

#include <iostream>
#include <string>
#include <locale>
using namespace std;

int main() {
    locale::global(locale("ja_JP.UTF-8"));
    wstring wstr = L"あいうえお";
    wcout << wstr << endl;
    return 0;
}

実行結果

あいうえお
atof/atoi/atol

char型文字列を変換する関数です。atof:double型へ、atoi:int型へ、atol:long型へ変換します。

#include <iostream>
using namespace std;

int main() {

    char str_i[] = "12345";
    char str_d[] = "123.45a";

    double d = atof( str_d );
    int i = atoi( str_i );
    long l = atol( str_d );

    cout << i << endl;
    cout << d << endl;
    cout << l << endl;

    return 0;
}

実行結果

12345
123.45
123
join/split

C++の文字列/配列処理でjoin/splitが残念ながら用意されていないので自前で作成します。vectorとstringのappendを使ってjoin、stringstreamとgetlineを利用してvectorにpush_backさせるsplitを簡単に書いてみました。

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;

string join( vector<string> &vect, const string &delimiter ) {
        vector<string>::iterator it = vect.begin();
        string result;
        for( ; it != vect.end(); it++ ) {
                result.append( *it );
                if( it + 1 != vect.end() ) {
                        result.append( delimiter );
                }
        }
        return result;
}

vector<string> split( const string &str, const char delimiter ) {
        vector<string> result;
        stringstream ss( str );
        string item;
        while( getline( ss, item, delimiter ) ) {
                result.push_back( item );
        }
        return result;
}

template <typename T>
void show_vector( vector<T> &vect ) {
        vector<T>::iterator it = vect.begin();
        cout << "vector values" << endl;
        for( ; it != vect.end(); it++ ) {
                cout << *it << endl;
        }
}

int main() {
        vector<string> v;
        v.push_back( "a" );
        v.push_back( "b" );
        v.push_back( "c" );
        string r = join( v, "," );
        cout << "join result = " + r << endl;
        vector<string> vr = split( r, ',' );
        show_vector( vr );
        return 0;
}

実行結果

join result = a,b,c
vector values
a
b
c
character encoding conversion

C++文字コード変換をするにはライブラリを用います。libiconvまたはicuを用いるのが一般的なようです。icuのUnicodeStringオブジェクトを介して文字コードの変換を行います。以下の例ではutf-8の文字列をeuc-jpに変換してファイル保存します。まずはicuのライブラリが必要になりますが、以下ではcentosでのパッケージを載せています。またcompile時にicuを使うオプションを指定しないとエラーが出るので注意が必要です。

$ sudo yum install icu libicu libicu-devel -y
$ g++ `icu-config --cppflags --ldflags` encoding.cpp
#include <string>
#include <fstream>
#include <unicode/unistr.h>
using namespace std;

int main() {
    
    // utf-8文字列をファイルから読み込み
    ifstream ifs( "utf8.txt" );
    string str;
    ifs >> str;
    
    // Unicodeオブジェクト設定
    UnicodeString src( str.c_str(), "utf8" );
    char *res = new char[100];

    //変換
    src.extract(0, src.length(), res, "euc-jp" );
    
    // euc-jpとして保存
    ofstream ofs;
    ofs.open( "euc-jp.txt" );
    ofs << res << endl;
    ofs.close();

    return 0;
}
$ nkf -g utf8.txt                                   
UTF-8
$ nkf -g euc-jp.txt 
EUC-JP
zen2han / han2zen

ICU 49.1.1: Transliterator Class Reference はてなブックマーク - ICU 49.1.1: Transliterator Class Reference
icuライブラリを使って全角<=>半角の変換処理を行います。#include "unicode/translit.h"設定しTransliteratorクラスを使います。Transliteratorクラスに対して変換方法とUnicodeString文字列を渡すだけで変換してくれます。

#include "unicode/translit.h" 
#include <iostream>
using namespace std;

// 変換内部処理
string ConvertString( UnicodeString str, const UnicodeString convert_type ) {
    UErrorCode status = U_ZERO_ERROR;
    Transliterator *myTrans = myTrans->createInstance( convert_type, UTRANS_FORWARD, status );
    myTrans->transliterate(str);
    char* result = new char[ str.length() + 1 ];
    str.extract( 0, str.length(), result, "utf8" );
    return result; 
}

// 全角 -> 半角IF
string zen2han( const UnicodeString str ) {
    return ConvertString( str, "Fullwidth-Halfwidth" );
}

// 半角-> 全角IF
string han2zen( const UnicodeString str ) {
    return ConvertString( str, "Halfwidth-Fullwidth" );
}

int main() {
    cout << zen2han( "アイウエオ" ) << endl;
    cout << han2zen( "アイウエオ" ) << endl;
    return 0;
}

実行結果

アイウエオ
アイウエオ
kata2hira / hira2kata / latin2kata / latin2kata / kata2latin/ latin2hira / hira2latin

icuライブラリでzen2han,han2zenと同じ方法でカタカナ<=>ひらがな、ローマ字<=>カタカナ、ローマ字 <=> ひらがなの変換もできます。

#include "unicode/translit.h" 
#include <iostream>
using namespace std;

// 変換内部処理
string ConvertString( UnicodeString str, const UnicodeString convert_type ) {
    UErrorCode status = U_ZERO_ERROR;
    Transliterator *myTrans = myTrans->createInstance( convert_type, UTRANS_FORWARD, status );
    myTrans->transliterate(str);
    char* result = new char[ str.length() + 1 ];
    str.extract( 0, str.length(), result, "utf8" );
    return result; 
}

// 全角 -> 半角IF
string zen2han( const UnicodeString str ) {
    return ConvertString( str, "Fullwidth-Halfwidth" );
}

// 半角-> 全角IF
string han2zen( const UnicodeString str ) {
    return ConvertString( str, "Halfwidth-Fullwidth" );
}

// カタカナ->ひらがな
string kata2hira( const UnicodeString str ) {
    return ConvertString( str, "Katakana-Hiragana" );
}

// ひらがな->カタカナ
string hira2kata( const UnicodeString str ) {
    return ConvertString( str, "Hiragana-Katakana" );
}

// ローマ字->カタカナ 
string latin2kata( const UnicodeString str ) {
    return ConvertString( str, "Latin-Katakana" );
}

// カタカナ->ローマ字 
string kata2latin( const UnicodeString str ) {
    return ConvertString( str, "Katakana-Latin" );
}

// ローマ字->ひらがな
string latin2hira( const UnicodeString str ) {
    return ConvertString( str, "Latin-Hiragana" );
}

// ローマ字->ひらがな
string hira2latin( const UnicodeString str ) {
    return ConvertString( str, "Hiragana-Latin" );
}

int main() {
    cout << zen2han( "アイウエオ" ) << endl;
    cout << han2zen( "アイウエオ" ) << endl;
    cout << kata2hira( "アイウエオ" ) << endl;
    cout << hira2kata( "あいうえお" ) << endl;
    cout << latin2kata( "aiueo" ) << endl;
    cout << kata2latin( "アイウエオ" ) << endl;
    cout << latin2hira( "aiueo" ) << endl;
    cout << hira2latin( "あいうえお" ) << endl;
    return 0;
}

実行結果

アイウエオ
アイウエオ
あいうえお
アイウエオ
アイウエオ
aiueo
あいうえお
aiueo

Regex

regcomp / regexec / regfree / regerror

On-line Manual of "regcomp" はてなブックマーク - On-line Manual of
boostライブラリを使うとregexが簡単に実装できますが、regexを入れるのは面倒なのでできるかぎり標準関数で使いたいです。C++というよりはCの記述ですが以下のように実装可能です。regcomp : 正規表現コンパイル、regexec : 正規表現検索、regfree:正規表現の記憶域をfreeにする、regerror:エラーコードのメッセージを人が読めるように変形。

#include <regex.h>
#include <iostream>
using namespace std;

void print_match( char *str, int start, int end ) {
    char tmp[10];
    str += start;
    end -= start;
    strncpy( tmp, str, end );
    tmp[end] = '\0';
    cout << tmp << endl;
}

int main() {
    //正規表現 
    char *regex = "(reg)no(test)";
    //検索文字列
    char *str = "koreha regnotest desu";
    
    //コンパイルする正規表現文字列
    regex_t regext;
    //マッチ件数
    regmatch_t match[3];

    //コンパイル
    if( regcomp( &regext, regex, REG_EXTENDED | REG_NEWLINE ) ) {
        cout << "regex compile failed" << endl;
        return 1;
    }

    //実行
    if( regexec( &regext, str, 3, match, 0 ) ) {
        cout << "Not Found" << endl;
        return 1;
    }

    //出力
    print_match( str, match[0].rm_so, match[0].rm_eo );
    print_match( str, match[1].rm_so, match[1].rm_eo );
    print_match( str, match[2].rm_so, match[2].rm_eo );

    //解放
    regfree( &regext );
    return 0;
}

実行結果

regnotest
reg
test