【C++】string型をcharに変換/コピーする方法【値 配列 ポインタ string to char】

C++の文字列クラスをchar型やchar配列、C言語形式の文字列へ変換またはコピーする方法を紹介します。

目次

std::string → const char*

std::string型のメンバ関数c_strで、C言語スタイルの文字列をポインタとして取得することができます。

std::string str("abc");
const char* cstr = str.c_str();
取得した文字列データを勝手に変更しないよう注意してください。
ちなみにC++11環境であればc_str関数と同一の働きをするdata関数を利用することもできます。ただしC++11よりも古い規格ではdata関数はNULL終端されていない文字列参照を返すケースがあるため注意が必要です。

ダングリングポインタに注意する

std::string型オブジェクトの寿命が尽きた場合、c_str関数やdata関数で取得していたポインタは不定なものになります。

const char *cstr;
{
  std::string str = "a";
  const char *cstr = str.c_str();
  std::cout << cstr; // OK
}
std::cout << cstr;   // NG(解放済みのポインタを参照する危険性あり)

このように無効なメモリ領域を指すようになったポインタはダングリングポインタ(dangling pointer)と呼ばれ、誤って利用すると様々な問題を引き起こすことに繋がるため注意してください。

std::string → char*

上記のダングリングポインタを回避するためには、手動によるメモリ確保と値コピーが必要です。

char_traits::copy

C言語スタイルの文字列をコピーする場合にはstd::char_traits<char>::copy関数を用います。c_strの終端ナル文字('\0')をコピーするために、str.size() + 1という形で余分な長さを指定する必要があります。

std::string str = "abc";
char* cstr = new char[str.size() + 1]; // メモリ確保

std::char_traits<char>::copy(cstr, str.c_str(), str.size() + 1);

delete[] cstr; // メモリ解放

C言語の標準ライブラリ関数を用いる方法

std::string型のc_strメンバ関数はC言語スタイルの文字列型(const char *)の値を返すため、これらの値をC言語の標準関数を用いてコピーすることもできます。C言語の文字列関数は#include <cstring>という形でインクルードします。

// #include <cstring> // std::strcpy
std::string str = "abc";
char* cstr = new char[str.size() + 1]; // メモリ確保

std::strcpy(cstr, str.c_str());        // コピー

delete[] cstr; // メモリ解放

std::strcpy関数は単純に文字列をコピーするための関数です。書き込み可能な長さを指定することができないため、利用用途によってはバッファオーバーランに注意して利用する必要があります。std::strncpy関数を用いた方法も知られていますが、扱いが難しい関数であるため注意して利用する必要があります。

#include <cstring> // std::strcpy, std::strncpy

// 文字列`from`を配列`to`にコピーする。
char *std::strcpy(char *to, const char *from);
// 文字列`from`の長さが指定された長さ`len`よりも小さい場合には、残りの分の領域にナル文字が書き込まれる。
// ただし`from`の長さが`len`以上の場合にはナル文字が追加されない点に注意が必要。
char *std::strncpy(char *to, const char *from, size_t len);
参考:【C言語】文字列をコピーする方法 #strcpy/strncpy関数による文字列コピー
参考:【C言語】文字列をコピーする方法 #strdup関数による便利な文字列複製

std::string → char[]

固定長配列へのコピーを行う場合には、バッファオーバーランの危険性を考慮し、より安全なbasic_string::copyメンバ関数や先程紹介した# char_traits::copy関数を使うと良いでしょう。

basic_string::copy

文字列クラスのメンバ関数copyを用いて固定長の文字列をコピーすることができます。第一引数に書き込み先の配列、第二引数に書き込みたい長さを指定します。

std::string str = "abcd";
char cary[4] = {'1', '2', '3', '4'};

str.copy(cary, 3);
//     cary == {'a', 'b', 'c', '4'}
str.copy(cary, 4);
//     cary == {'a', 'b', 'c', 'd'}

cary[3] = '\0'; // 必要に応じて終端ナル文字を書き込む
//     cary == {'a', 'b', 'c', '\0'}

char bary[4] = {}; // {'\0', '\0', '\0', '\0'}(ゼロ初期化)
str.copy(bary, 3);
//     bary == {'a', 'b', 'c', '\0'}

ナル文字による終端処理はされないため注意してください。なお第二引数に文字列長を超えた値が指定された場合には、文字列長が優先的に使用されます。つまりメンバ関数copyでは終端ナル文字('\0')を意図的に書き込むことができません。

char_traits::copyによる固定長配列への安全な書き込み

コピー元の文字列長が不定な場合はバッファオーバーランに注意し、書き込み先の配列のサイズを超えない範囲で安全にコピーする必要があります。

std::string s = "abc";
char a[4];     // a ≒ {'%', '&', '#', '$'}(未初期化)

if (s.size() < 4) {
  // 配列のサイズを超えない場合にのみ書き込む
  std::char_traits<char>::copy(a, s.c_str(), s.size() + 1);
               // a ≒ {'a', 'b', 'c', '\0'}
} else {
  // 必要に応じてエラー処理する
  a[0] = '\0'; // a ≒ {'\0', '&', '#', '$'}
  return -1;
}

C言語の標準ライブラリ関数を用いる方法(std::memcpy)

C言語の標準ライブラリ関数を用いる場合には、std::memcpy関数を利用します。

#include <cstring> // std::memcpy

// `from`のメモリブロックを`size`の長さだけ配列`to`へコピーする
void *std::memcpy(void *to, const void *from, size_t size);
参考:【C言語】文字列をコピーする方法 #固定長の文字列をコピー
参考:【C言語】文字列をコピーする方法 #可変長配列へのコピー

std::string → char[] (部分文字列をコピーする場合)

char*に固定長の部分文字列をコピーする場合には# std::string → char*# std::string → char[]の時と同等の方法を用いることができます。ただし終端ナル文字は必要に応じて手動で書き込む必要があります。

std::string str = "abc";
char* cstr = new char[3];
str.copy(cstr, 2); // cstr == {'a', 'b', '_'} (三文字目は不定な値)
cstr[2] = '\0';    // 必要に応じて終端ナル文字を書き込む

// 第三引数にコピーを開始する位置を指定することも可能
str.copy(cstr, 2, 1); // cstr == {'b', 'c', '\0'}

// copy関数の場合は開始位置をオフセット指定する(`str.c_str() + 2`)
std::char_traits<char>::copy(cstr, str.c_str() + 2, 2);
// cstr == {'c', '\0', '\0'}

C言語の標準ライブラリ関数を用いる場合には、memcpy関数を利用します。終端ナル文字が自動で書き込まれるstrlcpy関数を用いることもできます。

参考:【C言語】文字列をコピーする方法 #部分文字列のコピー

std::string → char

添字演算子[]の利用が一番手軽ですが、空文字チェックや範囲チェックは必ず行うようにしましょう。*str.c_str()でポインタ先頭の値を取得するテクニックもあります。

std::string str = "ab";
 
if (!str.empty()) {    // 空文字チェック
  char c = str[0];     // 'a'
}
 
if (str.size() > 1) {  // 境界チェック
  char c = str[1];     // 'b'
}
 
char c = *str.c_str(); // 'a'
 
std::string emp = "";
char e = *emp.c_str(); // '\0'

他にもat関数やfront関数を用いる方法がありますが、空文字時の挙動には注意が必要です(例外や未定義動作など)。参考までに、string型の変数が空文字だった場合のそれぞれの挙動をまとめておきます。

std::string s = "";

// 例外発生: libc++abi.dylib: terminating with uncaught exception of type std::out_of_range: basic_string
char a = s.at(0);

// C++11以降は常に末尾文字相当の'\0'を返す
char b = s[0];       // '\0'

// 未定義動作: 非const修飾の場合(C++98)
char b = s[0];       // '\\'

// 未定義動作: 例外は発生しないが、アサーションエラーが発生する可能性がある
char c = s.front();  // '\0'

// 未定義動作: 例外は発生しないが、`s[-1]`相当の不定なアクセスが発生する可能性がある
char d = s.back();   // '\\'

// 安全: もっとも確実
char e = *s.c_str(); // '\0'
参考: operator[], at, front, back メンバ関数

std::u16string → char16_t

char_traits<char16_t>::copy

char16_tやchar32_tの場合はchar_traits<T>::copyテンプレート関数で適切なコピーが行えます。

std::u16string str = u"abc";

char16_t* cstr = new char16_t[str.size() + 1]; // メモリ確保
std::char_traits<char16_t>::copy(cstr, str.c_str(), str.size() + 1); // コピー

delete[] cstr; // 解放

マルチバイト文字列・ワイド文字列のコピー

std::u16stringのchar16_t型やstd::wstringのwchar_t型の場合は、要素型のサイズが1バイト以上になるため、strcpy関数による方法は利用できません。先程のchar_traits<char16_t>::copywmemcpy関数、バイト単位でのコピーに対応したmemcpy関数を使います。なおC言語との連携を意識する場合にはmalloc関数によるメモリ確保を行うことになりますが、その場合はN * sizeof(char16_t)による倍数指定でのサイズ確保が必要になります。memcpy関数利用時もこの倍数指定が必要となります。

// #include <cstring> // std::memcpy
std::u16string str = u"abc";
size_t size = (str.size() + 1) * sizeof(char16_t); // size == 8

char16_t* cstr = (char16_t*)malloc(size); // メモリ確保
std::memcpy(cstr, str.c_str(), size);     // コピー

free(cstr); // メモリ解放
広告