Microsoft Visual Studioで作っています
buffにchar str[200] = "あいうえお";のうだけを取り出して入れたいのですが実行すると
「うと表示されてしまいます。
「はどこからきたのでしょうか?教えてほしいです。また表示させない方法もお願いします。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<math.h>
int main(void) {
char str[200] = "あいうえお"; char buff[1000]; strncpy(buff, str+3,3 ); printf("%s\r\n", buff); return 0;
}
追記
C++では文字列から1文字だけを取り出すのは不可能なのでしょうか?
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答6件
0
そもそもchar
型はたったの8bitしかありません。人類史上何度となく文字を固定長で表そうとする試みが続けられてきました。しかしそのことごとくは失敗に終わっています。常に文字は可変長のメモリーを必要とすることをおさえてください。
しかもさらに驚くべきことに、「文字」の定義はなんと4通りも存在します。何byteか、何単位か、何コードポイントか、何書記素クラスタか、の4つです。
C++標準化委員会、ついに文字とは何かを理解する: char8_t#Unicodeにおける4つの文字の定義
自分が意図している文字の定義はいったいどれなのか把握する必要があります。
文字は直接は計算機で扱えません。そこで特定のbyte列に変換して取り扱います。この方法が文字エンコードです。近年日本語圏で使われているのはUTF-8(cp65001)、ついでShift-JIS(cp932)です。まずはどの文字エンコードでエンコードされたbyte列を取り扱おうとしているかを把握する必要があります。
とはいえ、Visual Studioに付属のC++コンパイラであるcl.exeに限定してもなお、コンパイラオプション次第で多様な文字エンコードを利用できます。
ここまで見てくればわかるように、コンピュータで文字を取り扱うのは全くかんたんではありません。たかが「あいうえお」が何文字かを考えるだけでも考慮しないといけないことが山のようにあるのです。
ここで文字エンコードがUTF-8であることを保証された文字(列)リテラルがC++11/C++17で追加されています。これを使ってみましょう。
cpp
1//今まで使ってきたもの 2"あいうえお";//長さは実装依存、const char[N]型 3//今回紹介するもの 4u8"あいうえお";//UTF-8でエンコードされている。C++17まではconst char[16]型、C++20からconst char8_t[16]型
さて、今回はひとまず一文字の定義を何codepointか、にすることとしましょう。書記素クラスタまで考えると難しすぎます。絵文字とか濁点がついたものがとりあえず出てこないのでこれで十分でしょう
UTF-8は先頭のbyteを検査することでその1文字が何byte使用しているかわかるようになっています。この性質を利用して質問にあったことを実現してみましょう。
cpp
1#include <iostream> 2#include <cstring> 3#if !defined(__cpp_char8_t) 4using char8_t = char; 5#endif 6std::size_t charlen(const char8_t* s) 7{ 8 if (*s == 0) { 9 } else if ((*s & 0x80) == 0) { 10 return 1u; 11 } else if ((*s & 0xE0) == 0xC0) { 12 return 2u; 13 } else if ((*s & 0xF0) == 0xE0) { 14 return 3u; 15 } else if ((*s & 0xF8) == 0xF0) { 16 return 4u; 17 } 18 return 0; 19} 20int main() 21{ 22 const auto str = u8"あいうえお"; 23 std::size_t front_i = 0, len = 0; 24 for (std::size_t i = 0; i < 2; ++i, front_i += len = charlen(&str[front_i])); 25 char8_t dest[5]; 26 std::memcpy(dest, &str[front_i], len); 27 dest[len] = u8'\0'; 28 //コンソールの文字エンコードがUTF-8だと仮定する 29 std::cout << reinterpret_cast<const char*>(dest) << std::endl; 30}
https://wandbox.org/permlink/BIkWAMmrI0t1KHa6
Windowsで動作させるときはWindows 10 1607よりあとのOSで、あらかじめchcp 65001
したコマンドプロンプトで実行してください。
なお余談ですが、C++がCから受け継いだ文字列操作系関数はいくつもありますが、このうち多くの関数が安全に使うのが難しいものです。実際MSVCでは_CRT_SECURE_NO_WARNINGS
なしではそれらの関数に警告を出すはずです。なのでもしC言語のような文字操作をするなら、memcpy
を利用すると良いです。strcpy
と違い、文字数より一つ多くバッファを確保しなければならないことを忘れる懸念は自分で明示的にNULL文字を代入しますから減りますし、実際MSVCは何も言ってきません。
しかしまあ、せっかくC++を使うのですからもう少しだけC++っぽく扱ってみましょう。std::basic_string
とC++17で追加されたstd::basic_string_view
を使います。std::basic_string_view
を使ってstd::basic_string
を構築するときに勝手にNULL文字終端する性質を利用しています。
cpp
1#include <iostream> 2#include <string> 3#include <string_view> 4#include <stdexcept> 5#if !defined(__cpp_char8_t) 6using char8_t = char; 7#endif 8std::size_t charlen(const char8_t* s) 9{ 10 if (*s == 0) { 11 } else if ((*s & 0x80) == 0) { 12 return 1u; 13 } else if ((*s & 0xE0) == 0xC0) { 14 return 2u; 15 } else if ((*s & 0xF0) == 0xE0) { 16 return 3u; 17 } else if ((*s & 0xF8) == 0xF0) { 18 return 4u; 19 } 20 return 0; 21} 22auto extract_nth_char(std::basic_string_view<char8_t> s, std::size_t n) 23{ 24 std::size_t front_i = 0, len = 0, limit = n ? n - 1 : throw std::invalid_argument("0 is invalid"); 25 for (std::size_t i = 0; i < limit; ++i, front_i += len = charlen(&s.data()[front_i])); 26 return s.substr(front_i, len); 27} 28int main() 29{ 30 using namespace std::literals; 31 const auto str = u8"あいうえお"sv;//string_viewになる 32 //std::basic_stringに変換することでNULL終端する 33 const std::basic_string<char8_t> dest{extract_nth_char(str, 3)}; 34 //コンソールの文字エンコードがUTF-8だと仮定する 35 std::cout << reinterpret_cast<const char*>(dest.c_str()) << std::endl; 36}
https://wandbox.org/permlink/sSSbwj1fHIyB06jH
なおより本格的に扱う場合はicuというライブラリを利用します。
参照
- C++標準化委員会、ついに文字とは何かを理解する: char8_t
- UTF-8エンコーディングされた文字の型として
char8_t
を追加 - cpprefjp C++日本語リファレンス - UTF-8文字リテラル - cpprefjp C++日本語リファレンス
- UTF-8文字列リテラル - cpprefjp C++日本語リファレンス
- svリテラル - cpprefjp C++日本語リファレンス
- basic_string_view::substr - cpprefjp C++日本語リファレンス
- basic_string::コンストラクタ - cpprefjp C++日本語リファレンス (14)のコンストラクタを呼び出している
投稿2020/05/30 12:56
編集2020/05/30 14:13総合スコア5852
0
wchar_t
1つで一文字を表せない文字を使用しないのであれば…
C
1#define _CRT_SECURE_NO_WARNINGS 2#include<stdio.h> 3#include <string.h> 4#include <locale.h> 5int main(void) { 6 setlocale(LC_ALL, "Japanese"); 7 wchar_t str[200] = L"あいうえお"; 8 wchar_t buff[1000] = {}; 9 wcsncpy(buff, str + 2, 1); 10 wprintf(L"%s\n", buff); 11 return 0; 12}
投稿2020/05/30 15:42
編集2020/05/30 16:02総合スコア4079
0
Cが出来た頃は、コンピュータで扱う文字は当然にアルファベット、数字と一部記号だけでした。
なので、Cでは一文字というのはつまりそういう文字のことを意味します。
そして、char型は、それらの文字を格納するのに十分なサイズをもった整数型の変数、というのがCにおける定義です。
その後、コンピュータの能力は向上し応用範囲は広がって、英語圏以外の文字も扱えるようになりました。当然ながら、アルファベットにしか対応していないchar型の一文字分では、例えば日本語の一文字を格納することは不可能です。
ということで、日本語の一文字を一文字として扱いたいならば、C(その発展形としてのC++)での一文字(=アルファベット等)という扱いに頼らずに自分で「一文字」を定義し、処理する必要が出てきます。
その意味では、C++の「基本機能では」文字列から一文字を取り出すことは出来ない、と言っていいでしょう。
さらに面倒なことに、コンピュータが各国言語に対応するときの諸般の事情によって、日本語の文字とコンピュータ上で扱うときのデータ表の対応に複数のものが存在する、というややこしい状況になってしまいました。
自分で「一文字」を処理するにも、自分が扱おうとしている「日本語」はどういう対応表(文字コード表)に依っているかを知らないといけない、という状況になっています。
もちろん、いろいろあるとは言え文字コード表として流通するものには限られた種類しかありませんから、先人たちがそのへんの処理はさんざんやっていて「ライブラリ」としてまとまっているものもあります。プログラムを作るのが目的であればそういうライブラリを使うのがよいかと思います。
ただ、今回の質問については雰囲気が「学習用」に見えます。であるならば、上記の事情を自分で理解するために、日本語の「あいうえお」がコンピュータ上でどう扱われているのかを調べたうえで「一文字をとりだす」処理を書けるようになるのがあなたのミッションではないかと思うのですがいかがでしょうか。
投稿2020/05/29 23:07
総合スコア7703
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
C
1#include <stdio.h> // printf, putchar 2#include <stdlib.h> // mblen, MB_CUR_MAX 3#include <locale.h> // setlocale, LC_CTYPE 4 5int main(void) 6{ 7 char str[32] = "あいうえお"; 8 9 setlocale(LC_CTYPE, ""); 10 for (int n, i = 0; str[i]; i += n) { 11 n = mblen(str + i, MB_CUR_MAX); 12 printf("[%.*s] ", n, str + i); 13 } 14 putchar('\n'); 15}
C
1#include <stdio.h> // printf, putchar 2#include <string.h> // strlen 3#include <stdlib.h> // mbstowcs 4#include <locale.h> // setlocale 5 6int main(void) 7{ 8 char str[32] = "あいうえお"; 9 wchar_t wstr[32]; 10 11 setlocale(LC_CTYPE, ""); 12 mbstowcs(wstr, str, strlen(str) + 1); 13 for (int i = 0; wstr[i]; i++) 14 printf("[%lc] ", wstr[i]); 15 putchar('\n'); 16}
理解できない場合は、どこが分からないのかを質問してください。
投稿2020/05/31 02:58
総合スコア8224
0
文字コードは、シフトJISのようですね。
この場合、str[200] は、
{ 0x80, 0xA0, 0x80, 0xA2, 0x80, 0xA4, 0x80, 0xA6, 0x80, 0xA8, 0x00 } となっています。
このデータに対して、strncpy()で、str[3]からstr[6]まで、つまり、
{ 0xA2, 0x80, 0xA4 } とbuffにコピーしています。
シフトJISで文字コード0xA2は、"「"(厳密には半角)になりますので、"「う"と表示されます。
修正は、以上の意味を確認できれば可能ですので、省略させていただきます。
投稿2020/05/29 14:27
総合スコア1750
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/05/29 14:54
2020/05/29 15:38
2020/05/29 17:19
2020/05/30 22:29
0
文字コードは何でしょうか?
- シフトJISの場合、
str+3
はい
の2バイト目に当たります。 - UTF-8の場合、
str+3
はい
の位置です。
どちらにしても、う
を取り出すという動作はしないかと思います。
投稿2020/05/29 13:28
総合スコア146018
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。