C++でstd::string型からchar型へ変換する場合は、何を取得したいのかによって方法が異なります。
主に、次の3つのケースがあります。
std::stringから1文字だけ取得したい- C言語形式の文字列である
const char*として使いたい - 書き換え可能な
char*として使いたい
charとchar*は異なる型であるため、目的に応じて適切な方法を選ぶことが重要です。
std::stringから1文字をchar型で取得する方法
添字演算子を使用する
std::stringから特定の位置にある1文字を取得する場合は、添字演算子[]を使用します。
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
char ch = text[0];
std::cout << ch << std::endl;
}
実行結果は次のとおりです。
H
text[0]は先頭の文字、text[1]は2文字目を表します。
std::string text = "Hello";
char first = text[0]; // H
char second = text[1]; // e
std::stringは0から始まるインデックスで文字を管理しています。
空文字列を確認してから取得する
文字列の先頭に実際の文字が存在することを前提とする場合は、empty()で確認してから取得する方法が安全です。
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
if (!text.empty()) {
char ch = text[0];
std::cout << ch << std::endl;
}
}
現在のC++では、空のstd::stringに対してtext[0]を読み取ると、終端位置のヌル文字を参照します。
std::string text;
char ch = text[0];
この場合、chは通常、ヌル文字'\0'になります。
ただし、空文字列には実際の先頭文字が存在しないため、通常はempty()で確認する方が意図が明確です。
文字を書き換える
添字演算子を使うと、文字列内の特定の文字を書き換えることもできます。
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
text[0] = 'Y';
std::cout << text << std::endl;
}
実行結果は次のとおりです。
Yello
ただし、書き換える位置は次の範囲内でなければなりません。
0 <= index && index < text.size()
文字列のサイズを超える位置に書き込むと、不正なメモリアクセスにつながります。
at()を使用して安全に文字を取得する方法
at()の基本的な使い方
std::string::at()を使用すると、指定した位置が文字列の範囲内か確認しながら文字を取得できます。
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
char ch = text.at(0);
std::cout << ch << std::endl;
}
実行結果は次のとおりです。
H
[]とat()の違い
[]とat()の主な違いは、範囲外アクセスを検出するかどうかです。
std::string text = "Hello";
char a = text[10];
char b = text.at(10);
text.at(10)は範囲外アクセスを検出し、std::out_of_range例外を送出します。
一方、text[10]は範囲チェックを行わないため、安全ではありません。
例外処理を行う場合は、次のように記述できます。
#include <iostream>
#include <stdexcept>
#include <string>
int main() {
std::string text = "Hello";
try {
char ch = text.at(10);
std::cout << ch << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "指定した位置が文字列の範囲外です。" << std::endl;
}
}
operator[]とat()の終端位置の違い
std::stringでは、operator[]とat()で終端位置の扱いが異なります。
std::string text = "Hello";
この文字列のサイズは5です。
std::cout << text.size(); // 5
通常の文字が格納されている位置は、0から4までです。
H e l l o
0 1 2 3 4
text[5]は終端位置のヌル文字を参照します。
char end = text[5];
一方、text.at(5)は範囲外と判断され、std::out_of_range例外を送出します。
char end = text.at(5); // 例外
実際の文字を取得したい場合は、インデックスがtext.size()未満であることを確認してください。
if (index < text.size()) {
char ch = text[index];
}
std::stringをconst char*に変換する方法
c_str()を使用する
std::stringをC言語形式の文字列として使用したい場合は、c_str()を使用します。
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
const char* ptr = text.c_str();
std::cout << ptr << std::endl;
}
c_str()は、末尾にヌル文字'\0'が付いた文字列へのポインタを返します。
内部的には、次のような並びになっています。
'H' 'e' 'l' 'l' 'o' '\0'
C言語の関数に渡す
c_str()は、const char*を受け取るC言語形式の関数に文字列を渡す場合に使用できます。
#include <cstdio>
#include <string>
int main() {
std::string text = "Hello";
std::printf("%s\n", text.c_str());
}
また、独自の関数に渡すこともできます。
#include <iostream>
#include <string>
void printText(const char* text) {
std::cout << text << std::endl;
}
int main() {
std::string message = "Hello";
printText(message.c_str());
}
c_str()の戻り値は書き換えない
c_str()の戻り値はconst char*です。
そのため、取得したポインタから文字列の内容を書き換えることはできません。
std::string text = "Hello";
const char* ptr = text.c_str();
// ptr[0] = 'A'; // コンパイルエラー
次のようにconst_castで強制的にconstを外す方法もありますが、使用すべきではありません。
char* ptr = const_cast<char*>(text.c_str());
このポインタを使って内容を書き換えると、未定義動作につながる可能性があります。
書き換えが必要な場合は、data()や独立したバッファを使用してください。
std::stringをchar*として扱う方法
C++17以降ではdata()を使用できる
C++17以降では、非constのstd::stringに対してdata()を呼び出すと、書き換え可能なchar*を取得できます。
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
char* ptr = text.data();
ptr[0] = 'Y';
std::cout << text << std::endl;
}
実行結果は次のとおりです。
Yello
ただし、この方法はC++17以降を前提としています。
C++14以前では、data()の戻り値は基本的にconst char*です。
data()で変更できる範囲
data()で取得したポインタから変更できるのは、基本的に現在の文字列要素の範囲です。
std::string text = "Hello";
char* ptr = text.data();
ptr[0] = 'Y';
ptr[4] = '!';
実行後の文字列は次のようになります。
Yell!
安全に変更できる範囲は、次のとおりです。
0 <= index && index < text.size()
次の位置は、終端用のヌル文字が格納されている位置です。
ptr[text.size()]
この位置を通常の文字に変更してはいけません。
ptr[text.size()] = 'X'; // 不適切
文字列のサイズを超える位置への書き込みも行ってはいけません。
ptr[text.size() + 1] = 'X'; // 不正なアクセス
単純な変更なら[]を使用する
文字列の内容を1文字だけ変更する場合は、data()を使わずに[]で直接変更する方が分かりやすいです。
std::string text = "Hello";
text[0] = 'Y';
data()は、書き換え可能なchar*を要求する外部関数などに渡す場合に使用します。
C言語の関数にchar*として渡す場合
文字列の長さを変えない関数に渡す
C++17以降では、文字列の長さを変更しない関数であれば、data()を渡せます。
#include <iostream>
#include <string>
void changeFirstCharacter(char* text) {
if (text != nullptr && text[0] != '\0') {
text[0] = 'Y';
}
}
int main() {
std::string message = "Hello";
changeFirstCharacter(message.data());
std::cout << message << std::endl;
}
実行結果は次のとおりです。
Yello
文字列の長さを増やす関数には注意する
data()で取得したポインタに対して、文字列の長さを増やす処理を行うのは危険です。
例えば、次のような関数です。
#include <cstring>
void appendText(char* text) {
std::strcat(text, " World");
}
次の呼び出しは安全ではありません。
std::string message = "Hello";
appendText(message.data());
std::stringの保存領域に、追加する文字列を書き込むための十分な領域があるとは限らないためです。
また、外部関数から文字列を書き足しても、std::stringのsize()は自動的に更新されません。
文字列の長さを変更する関数には、独立した書き換え可能なバッファを渡す方が安全です。
独立したchar配列にコピーする方法
固定長のchar配列にコピーする
std::stringの内容を固定長のchar配列へコピーできます。
#include <cstring>
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
char buffer[100];
std::strcpy(buffer, text.c_str());
std::cout << buffer << std::endl;
}
ただし、strcpy()はコピー先の配列サイズを確認しません。
コピー元の文字列が配列より長い場合、バッファオーバーフローが発生します。
そのため、一般的にはコピーする文字数を制限する必要があります。
#include <cstring>
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
char buffer[100];
std::strncpy(buffer, text.c_str(), sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
std::cout << buffer << std::endl;
}
strncpy()は、コピー元が長い場合に自動でヌル終端しない可能性があります。
そのため、最後の要素に明示的に'\0'を代入しています。
memcpy()を使用してコピーする
コピーする文字数を明確に管理したい場合は、memcpy()を使用する方法もあります。
#include <algorithm>
#include <cstring>
#include <string>
int main() {
std::string text = "Hello";
char buffer[100]{};
std::size_t length =
std::min(text.size(), sizeof(buffer) - 1);
std::memcpy(buffer, text.data(), length);
buffer[length] = '\0';
}
この方法では、配列に収まる長さだけをコピーし、最後にヌル文字を追加しています。
動的なchar配列に変換する方法
newを使用して配列を作成する
文字列の長さに合わせて、動的にchar配列を確保することもできます。
#include <cstring>
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
char* buffer = new char[text.size() + 1];
std::strcpy(buffer, text.c_str());
std::cout << buffer << std::endl;
delete[] buffer;
}
text.size() + 1としているのは、末尾のヌル文字'\0'を格納する領域が必要だからです。
例えば、"Hello"は5文字ですが、C形式の文字列として保存するには6要素必要です。
'H' 'e' 'l' 'l' 'o' '\0'
ただし、手動でnew[]とdelete[]を使用すると、メモリ解放を忘れる可能性があります。
現代的なC++では、可能な限り自動的にメモリ管理される方法を使用する方が安全です。
vectorを使用する方法
書き換え可能なバッファを作成する
std::vector<char>を使うと、メモリ管理を自動化しながら、書き換え可能な文字列バッファを作成できます。
#include <iostream>
#include <string>
#include <vector>
int main() {
std::string text = "Hello";
std::vector<char> buffer(text.begin(), text.end());
buffer.push_back('\0');
std::cout << buffer.data() << std::endl;
}
buffer.push_back('\0')によって、末尾にヌル文字を追加しています。
char*を受け取る関数に渡す
std::vector<char>::data()は、書き換え可能なchar*として利用できます。
#include <iostream>
#include <string>
#include <vector>
void changeFirstCharacter(char* text) {
if (text != nullptr && text[0] != '\0') {
text[0] = 'Y';
}
}
int main() {
std::string message = "Hello";
std::vector<char> buffer(
message.begin(),
message.end()
);
buffer.push_back('\0');
changeFirstCharacter(buffer.data());
std::cout << buffer.data() << std::endl;
}
実行結果は次のとおりです。
Yello
std::vector<char>は元のstd::stringとは独立しているため、バッファを書き換えても元の文字列は変化しません。
c_str()やdata()のポインタを使う際の注意点
元のstd::stringが破棄されると無効になる
c_str()やdata()で取得したポインタは、元のstd::stringが破棄されると使用できなくなります。
const char* ptr;
{
std::string text = "Hello";
ptr = text.c_str();
std::cout << ptr << std::endl;
}
// textは破棄されている
// ptrを使用してはいけない
元の文字列が存在しなくなった後のポインタは、無効なポインタです。
一時オブジェクトから取得しない
次の書き方も危険です。
const char* ptr = std::string("Hello").c_str();
この一時的なstd::stringは、文の終了時点で破棄されます。
そのため、次の行でptrを使用することはできません。
std::cout << ptr; // 危険
安全に使用するには、std::stringオブジェクトを変数として保持します。
std::string text = "Hello";
const char* ptr = text.c_str();
std::cout << ptr;
文字列を変更した後は再取得する
c_str()やdata()で取得したポインタは、元の文字列を変更すると無効になる可能性があります。
std::string text = "Hello";
const char* ptr = text.c_str();
text += " World";
文字列の変更によって内部領域が再確保された場合、以前のptrは無効になります。
変更後は、改めてポインタを取得してください。
std::string text = "Hello";
text += " World";
const char* ptr = text.c_str();
std::cout << ptr << std::endl;
基本的には、ポインタを長期間保持せず、必要な場所でその都度取得する方法が安全です。
printText(text.c_str());
日本語文字列をchar型で扱う際の注意点
UTF-8では1文字が複数バイトになる
ASCII文字は通常、1文字を1つのcharで表現できます。
std::string text = "ABC";
char ch = text[0]; // A
一方、UTF-8の日本語文字や絵文字は、1文字が複数のcharで構成されます。
例えば、UTF-8で「あ」は一般的に3バイトです。
std::string text = "あ";
UTF-8環境では、次の結果が3になることがあります。
std::cout << text.size();
この場合、
char ch = text[0];
で取得できるのは「あ」という文字全体ではなく、UTF-8を構成する最初の1バイトだけです。
文字コードは環境によって異なる
通常の文字列リテラル、
"あ"
が必ずUTF-8として扱われるとは限りません。
実際のエンコーディングは、次の要素によって変わります。
- ソースファイルの文字コード
- コンパイラ
- コンパイラオプション
- 実行文字集合
- 使用する文字列リテラルの種類
そのため、std::string::size()が表示上の文字数と一致するとは限りません。
C++20以降のu8文字列
C++20以降では、UTF-8文字列を表すためにchar8_tが使用されます。
std::u8string text = u8"あ";
この場合、要素の型は通常のcharではなくchar8_tです。
日本語や絵文字を表示上の1文字単位で処理する場合は、UTF-8対応のライブラリや文字コード解析処理が必要です。
よくある間違い
std::stringをcharに直接代入する
次のコードはコンパイルできません。
std::string text = "Hello";
char ch = text;
std::stringは複数の文字を保持する型であり、charは1つの要素だけを保持する型だからです。
1文字だけ取得する場合は、位置を指定します。
char ch = text[0];
文字列が1文字か確認せずに変換する
文字列全体を1つのcharに変換したい場合は、文字列の長さを確認する必要があります。
if (text.size() == 1) {
char ch = text[0];
}
関数として実装する場合は、次のようにできます。
#include <stdexcept>
#include <string>
char stringToChar(const std::string& text) {
if (text.size() != 1) {
throw std::invalid_argument(
"文字列は1文字である必要があります。"
);
}
return text[0];
}
使用例は次のとおりです。
char ch = stringToChar("A");
"ABC"や空文字列を渡した場合は、例外が発生します。
c_str()をchar*に代入する
次のコードは通常、コンパイルエラーになります。
std::string text = "Hello";
char* ptr = text.c_str();
c_str()の戻り値はconst char*です。
正しくは次のように記述します。
const char* ptr = text.c_str();
C++17以降で書き換え可能なポインタが必要な場合は、data()を使用します。
char* ptr = text.data();
ヌル文字の領域を確保し忘れる
次のコードでは、文字列の内容を格納する領域しかありません。
std::string text = "Hello";
char* buffer = new char[text.size()];
C形式の文字列として使用するには、末尾のヌル文字を格納する1要素が必要です。
正しくは次のとおりです。
char* buffer = new char[text.size() + 1];
ただし、長さを別途渡すバイト列として使用する場合は、必ずしもヌル終端が必要とは限りません。
void processBytes(
const char* data,
std::size_t size
);
processBytes(text.data(), text.size());
目的別の使い分け
1文字だけ取得したい場合
char ch = text[0];
安全に取得する場合は、事前に文字列の長さを確認します。
if (!text.empty()) {
char ch = text[0];
}
指定位置を範囲チェック付きで取得したい場合
char ch = text.at(index);
範囲外の場合は、std::out_of_range例外が送出されます。
読み取り専用のC文字列として使いたい場合
const char* ptr = text.c_str();
主に、const char*を受け取るC言語形式の関数へ渡す場合に使用します。
書き換え可能なポインタが必要な場合
C++17以降では、次のように記述できます。
char* ptr = text.data();
ただし、文字列の現在の範囲を超えて書き込んではいけません。
独立した書き換え可能なバッファが必要な場合
std::vector<char> buffer(
text.begin(),
text.end()
);
buffer.push_back('\0');
取得する場合は、次のようにします。
char* ptr = buffer.data();
まとめ
C++でstd::string型からchar型へ変換する方法は、目的によって異なります。
1文字だけ取得する場合は、[]またはat()を使用します。
char ch = text[0];
読み取り専用のC形式文字列として使用する場合は、c_str()を使用します。
const char* ptr = text.c_str();
C++17以降で、現在の文字列の内容を書き換えるためのポインタが必要な場合は、data()を使用できます。
char* ptr = text.data();
元のstd::stringとは独立した書き換え可能なバッファが必要な場合は、std::vector<char>を使用する方法が安全です。
std::vector<char> buffer(
text.begin(),
text.end()
);
buffer.push_back('\0');
特別な理由がない限り、C++のプログラム内部ではstd::stringのまま扱い、C言語のAPIや外部ライブラリに渡す必要がある場所だけでc_str()やdata()を使用するのが基本です。
以上、C++でstring型からchar形に変換する方法についてでした。
最後までお読みいただき、ありがとうございました。
