0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C++ の勉強 ③ char と stringは異質?

Last updated at Posted at 2024-09-06

 Cの時代にstringはなかったです。たぶん。charを使った文字列だけだったです。stringは作られたけど、C++にsplit関数はないです。

charで文字列 たかが初期化

 文字列には終端が必要なのだそうです。"\0"と記述したらエラーになりました。NULLもエラーになりました。'\0'と記述するのが正しいようです。

 char data[4];

は、文字を入れる場所を四個確保したということで、入れ物のトップはdata[0]になります。すでにここから違和感がある。。。ゼロ・オリジンという言葉を思い出した。
 初期化をすると、

char data[4] = "123" + '\0';

array must be initialized with a brace-enclosed initializer
と怒られました。
 どうもC言語では文字列同士の連結に+記号は使えず、C++のstringでは使えるようです。
ともあれ、エラーは、
 「ブレースで囲まれた初期化子で配列を初期化する必要があります」
です。意味がわかりません。

 検索すると、一番正しい初期化方法は、

char data[4] = "123";

で、終端が4個目に入るようです。

char data[3] = "123";

だと、入りようがないですね。

 char data[] = {"123"};

でも、終端を引っ付けて初期化が行われるようです。

char data[4] = {"1", "2", "3"};

では、終端はなしのようです。いや、だめでした。シングルクォテーションでないといけないようです。
 次の書き方はセーフでした。

char data1[4] = {'1', '2', '3'};
std::cout << data1 << "\n";
char data[4]="123";
data[3] = '\0';

でも、いいわけで。添え字が気持ち悪い...

 誰も読んでいないですね、こんなこと書いても。

文字列リテラルは、"123"で、終端記号が付加される。

単一引用符 ( ' ) を使うと、

 char data[] = {'123'};

 エラーになります。使い分けがまだわからん。

 今気が付いたのだけれど、単一引用符 ( ' ) は、名前が示すように、文字1個だけを囲むときに使うんだという使い分けをすればいいのかな。
 そして、大原則は、

文字列は二重引用符で囲む必要があります。

 だって、Pythonはどっちでもよかったからねー。

つぎ!配列はポインタのアクセスでもOKだけど 混迷を深める結果になりそう

 初期化ができたので、その要素にアクセスします。

ex102.cpp
#include <iostream>
#include <string>

int main(){

char inData[]={"123"};
char * dataPointer = &inData[0];

std::cout << "inData data size " << sizeof(inData) << "\n";

std::cout << "<1>inData is "
<< inData[0] << " " << inData[1] << " " << inData[2] << " " << inData[3]
 << &inData
 << "\n";

std::cout << "<2>inData is "
<< dataPointer << " " << *dataPointer << " " << *(dataPointer + 1) << " " <<  *(dataPointer + 2) <<
 " " << "\n";

std::cout << "<3>inData is "
<< dataPointer << " " << dataPointer[0] << " " << dataPointer[1] << " " <<  dataPointer[2] <<
 " " << "\n";

return 0;
}

実行します。

$ g++ ex102.cpp
$ ./a.out
inData data size 4
<1>inData is 1 2 3 0xffffde367bd0
<2>inData is 123 1 2 3
<3>inData is 123 1 2 3

<1>は、一番普通のアクセス方法ですね。一番最初の値は、[0]でアクセスします。
<2>は、ポインタでアクセスしています。
<3>は検索していたら出ていたのですが、ポインタの配列というのではなく、[]内はポインタのインクリメントする数値を書いているようなのです。

<2>では、まず、

ポインタ変数に配列の先頭アドレスを代入する
が重要です。
 で、配列要素inData[X]の値は、 ポインタ*dataPointer+Xで参照できます。X
Xは0から始まります。

 <1>の方法だけ知っていたら十分なような気がしますが、現実はどうなんでしょうか。

stringは文字列?

std::stringを使用する際、サイズの制限はありません

 charを使用する場合、長さを常に把握しておかないと、はみ出してコピーしたりする事故があるようです。
 で、根本的なことを! stringを使えば、文字列の処理はすべて処理できる、んだよ。
であれば、charは不要だ。
なのです。

 ところが、書籍には、

C形式からC++形式への変換は、通常は自動で行われます。

C++の文字列をCの文字列に変換するには、メンバ関数c_str()を使用します。

のようなことが書かれています。
 変換することが、あるのでしょうか?全部std::stringという文字列型で済ませられないのでしょうか?
 検索すると、なぜか、printf文を使いたいときという事例が見つかります。そもそも使わなければいいわけですから論外です。

 最近、検索のトップにAIなんちゃらのお言葉が表示されるようになった。
ほぼ普通の検索の上位1点もしくは2点の内容を丸写ししている。
で、C言語に関して、AIさんは、日本語のサイトからしか知識を集めていない。
なんと、なんと! かしこいのだろう
 こういうマイナな話題に対応するまで百年はかかりそうな気がする

i2cのアクセスの正常化

 前回、ex005.cppの実行をすると、リードのたびにアドレスを送出するという変なアクセスをしていました。読めるからいいものの、全てのデバイスでうまくいくとは限りません。

Cpura201.png

 改善しました。温度だけですが、リード時に2バイトを連続して読み出せました。送っているアドレスも正常に表示されています。

Cpura202.png

 改善したプログラムです。

ex104.cpp
#include <iostream>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <unistd.h>
#include <string>

#define lps22hbAdr 0x5d
float press;
float temp;
int init();
float read_pressData();
float read_tempData();
int fd;

int main(){
    std::cout << "\nstart LPS22HB data read \n";
    init();
//    std::cout << "press is "  << read_pressData() << "hPa\n";
    std::cout << "temp is  "  << read_tempData() << "`C\n";
    return 0;
}

int init(){
    fd = open("/dev/i2c-1", O_RDWR);

    struct i2c_msg msg[1];  // /usr/include/linux/i2c.h
    struct i2c_rdwr_ioctl_data packets;  // /usr/include/linux/i2c-dev.h
    unsigned char data[2];

    // wake up
    msg[0].addr = lps22hbAdr;  // address
    msg[0].flags = 0;  // read、writeやアドレス長の指定
    msg[0].len = 2;    // bufに指定するdataのサイズ
    msg[0].buf = data;

    data[0] = 0x10;  // レジスタアドレス
    data[1] = 0x10;  // 書き込み値;5Hz
    packets.msgs = msg;
    packets.nmsgs = 1;  //  msgのサイズ指定

    int ret = ioctl(fd, I2C_RDWR, &packets);
    std::cout << "\nsend: 0x" << std::hex << msg[0].addr << " - 0x"
              << std::hex << (int)data[0] << " - 0x"
              << std::hex << (int)data[1] << "\n";
    return 0;
}


float read_tempData(){
    // read temp data
    struct i2c_msg msgT[2];
    struct i2c_rdwr_ioctl_data packetsT;
    unsigned char data1[1], data2[2];

    msgT[0].addr = lps22hbAdr;
    msgT[0].flags = 0;        // 最初はアドレスを書き込み
    msgT[0].len = 1;
    msgT[0].buf = data1;

    data1[0] = 0xab;          // 0x2b | 0x80 MSB data
    msgT[1].addr =lps22hbAdr;
    msgT[1].flags = I2C_M_RD; // リード時に設定
    msgT[1].len = 2;          // データを2byte読み出し
    msgT[1].buf = &data2[0];     // data2に読み出しデータが入る

    packetsT.msgs = msgT;
    packetsT.nmsgs = 2;       // アドレス書き込みとデータ読み出しでmsgは2
    int retT = ioctl(fd, I2C_RDWR, &packetsT);

    std::cout << "\ndata2 is 0x" << (int)data2[0] << (int)data2[1]  << "\n";

    int tempNative = (data2[1] << 8) | data2[0];
    int temp2scomp = -(tempNative & 0b1000000000000000) | (tempNative & 0b0111111111111111);
    temp =  std::stof( std::to_string(temp2scomp)) / 100;

    return temp;
}

 実行します。

$ g++ ex104.cpp
$ ./a.out

start LPS22HB data read

send: 0x5d - 0x10 - 0x10
temp is
data2 is 0xbea
27.5`C

 このプログラム、最初の例題の<?>に相当する形で、読み出したデータにアクセスしています。
msgT[1].buf = &data2[0];
左辺の構造体の要素はchar *なんです。ポインタ・アクセスの基本形ですね。まだなじめない。。。
(※)後で気が付いたのだけれど、三つのどれにも該当していないような気がする。ということは、最初の三つの説明が間違っているのかも。もっと勉強しよう。

 気圧を含めて書き直しました。

ex105.cpp
#include <iostream>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <unistd.h>
#include <string>
#include <iomanip>

#define lps22hbAdr 0x5d
float press;
float temp;
int init();
float read_pressData();
float read_tempData();
int fd;

int main(){
    std::cout << "\nstart LPS22HB data read \n";
    init();
    std::cout << "press is "  << std::fixed << std::setprecision(0) << read_pressData() << "hPa\n";
    std::cout << "temp is  "  << std::fixed << std::setprecision(1) << read_tempData() << "`C\n";
    return 0;
}

int init(){
    fd = open("/dev/i2c-1", O_RDWR);

    struct i2c_msg msg[1];  // /usr/include/linux/i2c.h
    struct i2c_rdwr_ioctl_data packets;  // /usr/include/linux/i2c-dev.h
    unsigned char data[2];

    // wake up
    msg[0].addr = lps22hbAdr;  // address
    msg[0].flags = 0;  // read、writeやアドレス長の指定
    msg[0].len = 2;    // bufに指定するdataのサイズ
    msg[0].buf = data;

    data[0] = 0x10;  // レジスタアドレス
    data[1] = 0x10;  // 書き込み値;5Hz
    packets.msgs = msg;
    packets.nmsgs = 1;  //  msgのサイズ指定

    int ret = ioctl(fd, I2C_RDWR, &packets);
    std::cout << "\nsend: 0x" << std::hex << msg[0].addr << " - 0x"
              << std::hex << (int)data[0] << " - 0x"
              << std::hex << (int)data[1] << "\n";
    return 0;
}

float read_pressData(){
    // read press data
    struct i2c_msg msgP[4];
    struct i2c_rdwr_ioctl_data packetsP;
    unsigned char data1[1], data2[3];

    msgP[0].addr = lps22hbAdr;
    msgP[0].flags = 0;        // 最初はアドレスを書き込み
    msgP[0].len = 1;
    msgP[0].buf = data1;

    data1[0] = 0xa8;          // 0x28 | 0x80 MSB data
    msgP[1].addr = lps22hbAdr;
    msgP[1].flags = I2C_M_RD; // リード時に設定
    msgP[1].len = 3;          // データを3byte読み出し
    msgP[1].buf = &data2[0];  // data2に読み出しデータが入る

    packetsP.msgs = msgP;
    packetsP.nmsgs = 2;       // アドレス書き込みとデータ読み出しでmsgは2
    int retP = ioctl(fd, I2C_RDWR, &packetsP);

    press = std::stof( std::to_string(((int)data2[2] << 16) | ((int)data2[1] <<8) | ((int)data2[0]) )) / 4096;
    return press;
}

float read_tempData(){
    // read temp data
    struct i2c_msg msgT[2];
    struct i2c_rdwr_ioctl_data packetsT;
    unsigned char data1[1], data2[2];

    msgT[0].addr = lps22hbAdr;
    msgT[0].flags = 0;        // 最初はアドレスを書き込み
    msgT[0].len = 1;
    msgT[0].buf = data1;

    data1[0] = 0xab;          // 0x2b | 0x80 MSB data
    msgT[1].addr =lps22hbAdr;
    msgT[1].flags = I2C_M_RD; // リード時に設定
    msgT[1].len = 2;          // データを2byte読み出し
    msgT[1].buf = &data2[0];     // data2に読み出しデータが入る

    packetsT.msgs = msgT;
    packetsT.nmsgs = 2;       // アドレス書き込みとデータ読み出しでmsgは2
    int retT = ioctl(fd, I2C_RDWR, &packetsT);

//    std::cout << "\ndata2 is 0x" << (int)data2[0] << (int)data2[1]  << "\n";

    int tempNative = (data2[1] << 8) | data2[0];
    int temp2scomp = -(tempNative & 0b1000000000000000) | (tempNative & 0b0111111111111111);
    temp =  std::stof( std::to_string(temp2scomp)) / 100;
    return temp;
}

 実行しました。

$ g++ ex105.cpp
$ ./a.out

start LPS22HB data read

send: 0x5d - 0x10 - 0x10
press is 1010hPa
temp is  25.0`C

 波形です。

Cpura203.png


下のリンクがおかしくなっています。C++の勉強①なんですが。。。

0
0
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?