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++ 参照渡しの勉強 ③ 参照渡し 復活中

Posted at

9/6現在、このコンテンツが消えてしまっていました。編集履歴から復活作業をしています。コメントをいただいていたと思いますが、たいへん申し訳ございません。

 値渡し、ポインタ渡し、そして最後の参照渡しです。なぜ、C言語になくて、C++で作られた仕組みなのか、調べると、ポインタ渡しがめんどう?というか、何か問題があったのでそれを解消したいという流れらしいです。

参照とは変数に別の名前をつけること

とある変数に別の名前をつけ、そうして、別名前の変数の値を変更した際にはその変更が呼び出し元の変数にも影響するので、値呼び出しとは違う仕組みです。

という説明を見つけました。
 影響するっていうのを、確かめてみましょう。

関数で使う

 CPUの温度を表示するプログラムです。値渡しで使いました。

woks2.cpp
#include <iostream>
#include <string>
#include <fstream>

auto readTempData(std::string cpuTemp){
   return stof(cpuTemp)/1000.0;
}
int  main(){
   std::ifstream ifs("/sys/class/thermal/thermal_zone0/temp");
   std::string cpuTemp;
   if (ifs.fail()) {
       std::cerr << "Failed to open file." << std::endl;
       return -1;
   }
   getline(ifs, cpuTemp);
   std::cout << "\n read CPU temp " << cpuTemp << std::endl;

   std::cout << " CPUの温度" <<  readTempData(cpuTemp) << "`C" << std::endl;
   return 0;
}

 実行します。

$ g++ works2.cpp -o auto
$ ./auto
read CPU temp 46300
CPUの温度46.3`C

 CPUの温度のデータを読み、読み取った値は千倍の整数のstring型なので、関数readTempData()で、実数に変換し、1000で割った値を返します。
 これを参照渡しに修正します。
 関数の呼び出しを、

readTempData(std::string cpuTemp)

から、

readTempData(std::string &cpuTemp)

付きにします。
 前回まで、&はアドレスの意味である「アドレス演算子」でしたが、今回は、参照を意味する「参照演算子」になったようです。
 なぜ、そういう混乱することを取り決めるのだ!
で、mainを書き換えます。

works2s.cpp
#include <iostream>
#include <string>
#include <fstream>
#include <cmath>
void readTempData(std::string &cpuTemp){
    cpuTemp = std::to_string(std::stof(cpuTemp)/1000.0);
}
int  main(){
    std::ifstream ifs("/sys/class/thermal/thermal_zone0/temp");
    std::string cpuTemp;
    if (ifs.fail()) {
        std::cerr << "Failed to open file." << std::endl;
        return -1;
    }
    getline(ifs, cpuTemp);
    std::cout << "\nread CPU temp " << cpuTemp << std::endl;
   readTempData(cpuTemp);
   std::cout << "CPUの温度" <<  cpuTemp << "`C" << std::endl;
    return 0;
}

 実行します。

$ g++ works2s.cpp -o auto
s$ ./auto
read CPU temp 46850
CPUの温度46.850000`C

 元の変数cpuTempの値が呼び出した関数によって書き換えられていることがわかります。

auto readTempData(std::string cpuTemp){
  return stof(cpuTemp)/1000.0;
}

 参照渡しで直に変数を書き換える関数:

void readTempData(std::string &cpuTemp){
    cpuTemp = std::to_string(std::stof(cpuTemp)/1000.0);
}

 型はstringを維持します。

ちょっとまて、最初の別名をつけるというのをやっていないじゃないか

  • 値呼び出しは、関数に対して一方的に情報を与えるだけのときに都合がよい方法
  • 参照は、関数内での変更を呼び出し元にも反映したい場合に使う
    という簡単明瞭な解説を見つけました。
     そう、値渡しでは、returnで計算結果を戻してもらうような関数が書きやすいです。C++の最新バージョンでは、複数結果を戻してもらうのも書きやすくなっています。
     参照渡しでは、return文がない?のが一般的に見えます。何も戻してもらわないので、関数の型はvoidでいいわけ? エラー・コードを戻すなら、次のように書けばいいのかな?試してないけど。
auto readTempData(std::string &cpuTemp){
    cpuTemp = std::to_string(std::stof(cpuTemp)/1000.0);
    return 0;
}

こうなると、アドレスとかという言葉は出てこないね。

 ここまでは関数の話で、値渡しとポインタのところで出てきた単なる変数の扱いでは、参照の話は出てこない?
と思っていたら、別名をつけるという事例が見つかりました。

works7.cpp
#include <iostream>
int main(){
    int cpuNiceData =  50000;
    int & new_cpuNiceData = cpuNiceData;
    std::cout << cpuNiceData << std::endl;
    new_cpuNiceData = 50001;
    std::cout << cpuNiceData << std::endl;
     return 0;
}

 実行します。

$ g++ works7.cpp -o auto
yoshi@yoshi:~/works$ ./auto
50000
50001

 参照!です。

参照変数ことを「エイリアス」とも呼びます
 そう解釈できるね。
 でも、これはどういうときに使うのか、わかりません。
今までも、将来も使わないかもしれないけど、ポリモーフィズム「多様性」「多態性」)な利用方法をするとき、オリジナルの引数名を厳守していると、使いようがないので、dataとか一般的な変数名にしておくというのが、ちらっと浮かんだけど、何の検証もしていない。。。あさってのほうこうでかんがえちがいをしているきもしないわけでない。

 関数の引数で参照するということは別名(dataに変更した)をつけて、最初のプログラムを修正しました。

works2se.cpp
#include <iostream>
#include <string>
#include <fstream>
void readTempData(std::string& data){
    data = std::to_string(std::stof(data)/1000.0);
}
int  main(){
    std::ifstream ifs("/sys/class/thermal/thermal_zone0/temp");
    std::string cpuTemp;
    if (ifs.fail()) {
        std::cerr << "Failed to open file." << std::endl;
        return -1;
    }
    getline(ifs, cpuTemp);
    std::cout << "\nread CPU temp " << cpuTemp << std::endl;
   readTempData(cpuTemp);
    std::cout << "CPUの温度" <<  cpuTemp << "`C" << std::endl;
    return 0;
}

 実行します。

$ g++ works2se.cpp -o auto
yoshi@yoshi:~/works$ ./auto
read CPU temp 45750
CPUの温度45.750000`C

 問題ないようですね。
別名をつけるメリットが見えてきません。もっと複雑、複数の関数から呼ぶとかの際に便利になるのかな。そういう事例が検索でみつかっていません。

検索を続けると、

配列は参照渡し
なんていう説明が見つかる。今回配列は話題にしていないので無視する
けど、構造体って、配列の従妹みたいだからなー。配列が巨大だったら、さすが値渡しは苦しいとか思ったりして。

構造体を参照渡しすると、破壊的操作が行えます

検索すると、参照渡しで、構造体のメンバを表示するだけという事例がたくさん見つかります。いやー、それだと参照にする意味があるのかという疑問がわいてきます。
 破壊なんぞしないぞ!って

構造体で使う

 今回の勉強の、最後の目的に到達しました。
 mainの中で、構造体のメンバのアクセスは、xxx.yyyで記述できますし、それは、関数内でも同様です。ポインタ渡しの時は、そうではありませんでしたから、書きやすく、見やすくなっているという解釈が成り立ちます。

works4s.cpp
#include <fstream>
struct cpuInfo {
    std::string User;
    std::string System;
    std::string Idle;
};
float getCpuInfoData(struct cpuInfo& data);
int  main(){
    cpuInfo info = {"123","234","345"};
    std::cout << "user:" << info.User << std::endl;
    std::cout << "system:" << info.System << std::endl;
    std::cout << "idle:" << info.Idle << std::endl;
    getCpuInfoData(info);
    std::cout << std::endl;
    std::cout << "user:" << info.User << std::endl;
    std::cout << "system:" << info.System << std::endl;
    std::cout << "idle:" << info.Idle << std::endl;
    float total = std::stof(info.User) + std::stof(info.System) + std::stof(info.Idle);
    std::cout << "\ntotal  " << total << std::endl;
    std::cout << "user %   " << (std::stof(info.User) / total) * 100 << std::endl;
    std::cout << "system % " << (std::stof(info.System) / total) * 100 << std::endl;
    std::cout << "idle %   " << (std::stof(info.Idle) / total) * 100 << std::endl;
    return 0;
}
float  getCpuInfoData(struct cpuInfo& data){
    std::cout << "
-in subroutine getCpuInfoData- " << std::endl;
    std::ifstream ifs("/proc/stat");
    std::string firstCpuData;
    if (ifs.fail()) {
        std::cerr << "Failed to open file." << std::endl;
        return -1;
   }
    getline(ifs, firstCpuData);
    ifs.close();
    std::cout << firstCpuData << std::endl;
    firstCpuData = firstCpuData.substr(5);
    std::string user = firstCpuData.substr(0,firstCpuData.find(" "));
    firstCpuData = firstCpuData.substr(user.size()+1);
    std::string nice = firstCpuData.substr(0,firstCpuData.find(" "));
    firstCpuData = firstCpuData.substr(nice.size()+1);
    std::string system = firstCpuData.substr(0,firstCpuData.find(" "));
    firstCpuData = firstCpuData.substr(system.size()+1);
    std::string idle = firstCpuData.substr(0,firstCpuData.find(" "));
    firstCpuData = firstCpuData.substr(idle.size()+1);
    data.User = user;
    data.System = system;
    data.Idle = idle;
    return 0;
}

 実行します。

$ g++ works4s.cpp -o auto
$ ./auto
user:123
system:234
idle:345
-in subroutine getCpuInfoData-
cpu  90101 3218 62043 123189932 12519 0 39 0 0 0
user:90101
system:62043
idle:123189932
total  1.23342e+08
user %   0.0730497
system % 0.0503016
idle %   99.8766

比較のため、最後の参照渡しをポインタ渡しに書き換えてみる

works4sp.cpp
#include <iostream>
#include <string>
#include <fstream>
struct cpuInfo {
    std::string User;
    std::string System;
    std::string Idle;
};
float getCpuInfoData(struct cpuInfo* info);
int  main(){
    cpuInfo info = {"123","234","345"};
    std::cout << "user:" << info.User << std::endl;
    std::cout << "system:" << info.System << std::endl;
    std::cout << "idle:" << info.Idle << std::endl;
    getCpuInfoData(&info);
    std::cout << std::endl;
    std::cout << "user:" << info.User << std::endl;
    std::cout << "system:" << info.System << std::endl;
    std::cout << "idle:" << info.Idle << std::endl;
    float total = std::stof(info.User) + std::stof(info.System) + std::stof(info.Idle);
    std::cout << "\ntotal  " << total << std::endl;
    std::cout << "user %   " << (std::stof(info.User) / total) * 100 << std::endl;
    std::cout << "system % " << (std::stof(info.System) / total) * 100 << std::endl;
    std::cout << "idle %   " << (std::stof(info.Idle) / total) * 100 << std::endl;
    return 0;
}
float  getCpuInfoData(struct cpuInfo* info){
    std::cout << "\n-in subroutine getCpuInfoData- " << std::endl;
    std::ifstream ifs("/proc/stat");
    std::string firstCpuData;
    if (ifs.fail()) {
        std::cerr << "Failed to open file." << std::endl;
        return -1;
    }
    getline(ifs, firstCpuData);
    ifs.close();
    std::cout << firstCpuData << std::endl;
    firstCpuData = firstCpuData.substr(5);
    std::string user = firstCpuData.substr(0,firstCpuData.find(" "));
    firstCpuData = firstCpuData.substr(user.size()+1);
    std::string nice = firstCpuData.substr(0,firstCpuData.find(" "));
    firstCpuData = firstCpuData.substr(nice.size()+1);
    std::string system = firstCpuData.substr(0,firstCpuData.find(" "));
    firstCpuData = firstCpuData.substr(system.size()+1);
    std::string idle = firstCpuData.substr(0,firstCpuData.find(" "));
    firstCpuData = firstCpuData.substr(idle.size()+1);
    info->User = user;
    info->System = system;
    info->Idle = idle;
    return 0;
}

 実行します。

$ g++ works4sp.cpp -o auto
yoshi@yoshi:~/works$ ./auto
user:123
system:234
idle:345
-in subroutine getCpuInfoData-
cpu  90727 3218 62402 123820251 12558 0 40 0 0 0

user:90727
system:62402
idle:123820251
total  1.23973e+08
user %   0.0731826
system % 0.050335
idle %   99.8765

 実はまだ続くんです。


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?