dポイントプレゼントキャンペーン実施中!

VC++2008でフォームアプリケーションを作成しています。
シリアルポートから受け取った文字列の一部を抜き出して処理をするため,VBではmid関数に相当するような機能として,std::stringを使用しようとしています。
しかし,
std::string str("ABC" ,1,2); とした場合は『BC』が問題なく返ってきましたが,
std::string str(recieveddata ,1,2); のように,文字列の部分を変数にしたら,ビルドエラーになってしまいます。(ポインタ?を理解する必要があるのでしょうか?)

どのようにすれば,VBのmid相当の機能を実現できるでしょうか?

A 回答 (5件)

どうも、お礼いただきました。



お勧めしない方法として書いたので間に合わせコードになっていますが
No4の方サイドから攻めるなら、受け取りたい最大文字数が分かっていれば問題ないですが

そうでない場合、正確に言うと受け取りに十分な長さを持つchar配列を「動的に確保し、解放」しないと、文字数不足となって意図した動作にならない可能性があります。

終端文字を含んだ、最大の格納文字数をlenとすると

Cでは

char* c = (char*)malloc( len ); //確保
free(c); //解放

C++では

char* c = new char[ len ]; //確保
delete c; //解放

といった具合になります。

この最小限のlenをその都度求める方法は…
String^は、.NETの文字列ということで
.NETではUnicodeを使っているので

さらに、char* はANSI…
ということで、これもまた一筋縄ではいかない気がしますが

今回必要かどうか分からないので、どうにも必要そうだったらいってください。


.NETはUnicode固定とすれば
char より wchar_t の文字列の方がすんなりいくかもしれません。
両対応したい場合はTCHARを採用するのが吉です。

Unicode文字列とANSI文字列の違いなどについてはこちら
http://victreal.com/Junk/_T/index.html

なお、関数内でのchar c[サイズ];の場合は、ローカル変数と言って、メモリ上ではなくスタック上に確保され、関数を抜けるときにスタックが巻き戻されるので、解放コードを書く必要はありません。(というより、書いてはいけない、が正しいです。)

ただ、いずれの場合でも、lenやサイズを超えたところに書き込もうとした場合は、とても危険で、やってはいけない操作なので
場合によりますが、良心的なシステムならアプリが不正終了などするかもしれません。


動的に確保したら解放を明示的にしないといけない(あるいはすべき)というのは、ネイティブコードの基本原理なのですが、大規模コードだとこれを正確に行い続けるのは比較的難しくなります。


.NETサイド、つまりマネージド(こういったメモリ関連が自動的に管理される)なコードの利点は、それを意識する必要がないというところにあります。

String^ s = gcnew String("text");

の場合に解放処理を書く必要がないのは、使われなくなったら自動的に片づけ(ガベージコレクト)してくれたりするからです。
(というよりネイティブと違い、そう言う事を自分で完全には管理出来ないようになっています)

それゆえ、内部的なチェックやメモリの配置換えなどが起こる可能性から、パフォーマンス的には、よく練られたネイティブコードが勝ることがほとんど、という感じなのです。



と、いうわけで


>さらに,取りだした文字列(例えば000F)を数値に変換することは可能でしょうか?

これは、つまり16進表記で000Fなどだった数字が
一端文字列
"000F"
になったのをchar*やchar配列の文字列として受け取って
その文字列から、また数値に戻す、といった事でしょうか?


その場合は、'0'から'9'までは'0'をマイナスしてやることで同じ数値になるのですが

'a' から 'f' は規格上の保証はないです。

あとは一文字(1桁)ずれるごとに重みが16倍になると考えれば

例えばかなりCっぽく書くと


unsigned int num = 0;
char c_i;

for ( int i=0; c[i]; ++i ){
num <<= 4; //や num *=16;

c_i = tolower( c[i] ); //tolower関数などで大文字小文字をそろえつつ
if ( '0' <= c_i && c_i <= '9' ){ num += ( c_i - '0'); continue; }

switch ( c_i ){
case 'a': num += 10; break;
case 'b': num += 11; break;
case 'c': num += 12; break;
case 'd': num += 13; break;
case 'e': num += 14; break;
case 'f': num += 15; break;
}

}

//最終的にnumが数値


といった形になります。


例えば"A0C"→"0C"→12としたい、など最初の1文字をなくしたい場合は
for 分の最初を
int i = 0; → int i = 1;
としたり、あるいはポインタ操作などが分かればそれこそいくらでも方法はあります。

"000F"→ 00 0Fとみなして 0, 15と直したい
といったことであれば、ループの繰り返し条件の個所を、c[i]ではなくi<2などと回数固定にしたうえで
全体を2回行う、などとしてみてください。



どんな言語を使っても、積極的に探究しようという姿勢があれば、すらすら使えるようになるのは時間の問題だと思います。
    • good
    • 0
この回答へのお礼

お礼が遅くなってしまい申し訳ありませんでした。
お教えいただきた方法で必要な機能を実現することが出来ました。
LongSecret様のお陰で,プログラムが完成に近づいてきました。
本当のありがとうございました。

お礼日時:2012/01/13 14:19

ecieveddata.assign( (char*)pin, barray->Length );



recieveddata.assign( (char*)pin, barray->Length );

でしたね。失礼しました。

どうやら、std::string以外では、charなどの配列の場合、こんなんでも動くみたいですね。

String^ s = "abc";

enum { SIZE = 255 };
char c[SIZE];
sprintf_s( c, SIZE, "%s", s ); //なんと!?…(笑)
c[3] = '\0';

textRecieved->Text = gcnew String( c+1 );


ただ、printf系統の%sでSystem::String^がちゃんと保証されてるか調べてませんので、お勧めはできません。

保証されてたとしても、どちらかというと、.NETサイドに、ネイティブとマネージドコード間のマーシャリング用関数が色々と用意されていることから
#3のようにちゃんとマーシャリング的な事をしてる方をオススメします。

マーシャリングについては、#3の方法以外にも色々とあるようですから、興味がおありでしたら調べてみてください。
    • good
    • 0
この回答へのお礼

No3の方法は私の知識では理解できませんでした。(ネイティブとマネージド?の違いなどが良く分かっておりませんので,また検索などして勉強致します。)

No4の回答の通りにしたところ文字列から任意の文字を取りだすことができました。ありがとうございます。
さらに,取りだした文字列(例えば000F)を数値に変換することは可能でしょうか?
ここで質問してもよいのか迷いましたが,なにとぞご教授お願い致します。

お礼日時:2011/12/21 11:09

こんばんは。



これはC++/CLIのコードですね。(個人的には懐かしいです)
C++/CLIはC++の拡張言語の一種で、C++を使う事も可能ですが、少なくとも

System::String^ は ガベージコレクションなどが働く C++/CLI(マネージド)サイドの型で
std::string は C++(ネイティブ)の型で

これらの交換は、一筋縄ではいかないと思います。
詳しいことは長くなるので、↑の中で分からない単語があったら、それを検索などで調べてみてください。

説明のためにチェックとか省いてなるべく短くすると、イメージとしてはこんな感じでしょうか

using System::Text::Encoding;

Encoding^ encoding = Encoding::GetEncoding("shift_jis");

std::string recieveddata;
array<Byte>^ barray = Encoding::Convert( Encoding::Unicode,
encoding, Encoding::Unicode->GetBytes(recvData) );
pin_ptr<Byte> pin = &barray[0];

//ここでようやくString^ → std::string
ecieveddata.assign( (char*)pin, barray->Length );

std::string str(recieveddata,1,2);

//std::string → String^
textRecieved->Text = gcnew System::String( str.data(), 0, str.size(), encoding );
    • good
    • 0

「ビルドエラーになってしまいます」とのことですが, 具体的にはどこでどのようなエラーが出ているのですか?

    • good
    • 0
この回答へのお礼

デリゲードの中に
std::string str(recieveddata,1,2);と書くと,

'System::String ^' から 'const std::basic_string<_Elem,_Traits,_Ax>' へは変換できません。

とのエラーが出てしまいます。

お礼日時:2011/12/21 10:34

C:\test>type t.cpp


#include<iostream>
#include<string>
int main()
{
std::string recieveddata("ABC");
std::string str(recieveddata,1,2);
std::cout << str << std::endl;
return 0;
}

C:\test>cl /EHsc t.cpp
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.

t.cpp
Microsoft (R) Incremental Linker Version 10.00.30319.01
Copyright (C) Microsoft Corporation. All rights reserved.

/out:t.exe
t.obj

C:\test>t
BC

C:\test>

バージョンがちょっと違うけど,エラーになんかならないよ。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。
私の質問の仕方が悪かったかもしれません。
シリアルポートから受け取ったデータを,下記のCOMRevieveBuffer に入れていき,改行コードが来たら,デリゲートに処理を渡し,その中で,必要なデータを抜き出す,というような処理を行いたいのです。


private: System::Void serialPort_DataReceived() {

SerialDataRecievedDelegate^ dlgte = gcnew SerialDataRecievedDelegate( this, &SerialCommSample::Form1::SerialDataRecieved );

String^ RecievedData = serialPort->ReadExisting();

//改行コードを検出する
if(RecievedData == "\r" ) {
RecievedData = RecievedData + "\n";

COMRevieveBuffer = COMRevieveBuffer + RecievedData;

this->Invoke(dlgte, COMRevieveBuffer);

COMRevieveBuffer = "";
}

}

//別スレッドで起動される関数。
delegate void SerialDataRecievedDelegate(String^ recvData);
private: void SerialDataRecieved(String^ recvData){

textRecieved->Text = recvData;

}

デリゲートの中で
std::string recieveddata(recvData);
std::string str(recieveddata,1,2);
textRecieved->Text =str
と書いてみましたが,ビルドエラーになってしまいます。

C++を始めたばかりで,言葉の使い方などおかしい箇所があるかもしれませんんが,なにとぞアドバイスよろしくお願い致します。

お礼日時:2011/12/20 18:02

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!