トラック
/
C++
C++
/
演習
/
スピーディワゴン財団
スピーディワゴン財団

スピーディワゴン財団

学習演習

はじめに

他の多くの言語と同じように、C++にもポインターがあります。 参照とポインターが似ていることはもう知っているはずです。ただ、ポインターはコンピューターの内部動作にもう一歩近づいたものだと考えてください。 ポインターは、オブジェクトのアドレスを保持する変数です。 ポインターを使うとオブジェクトを直接操作でき、C++における動的なメモリ確保や効率的なデータ操作が可能になります。

ポインターに初めて触れると、少し不思議に感じるかもしれません。でも、慣れてしまえばとても素直な仕組みです。

C++を使ううえで欠かせない要素なので、じっくり時間をかけて理解しましょう。 このコンセプトで扱う最小限の形は、ダムポインターや生ポインターとも呼ばれます。 現代のC++にはスマートポインターもありますが、基本の型は決してスマートではなく、作業はすべて手動で行う必要があります。

詳細に入る前に、ポインターの用途を理解しておく価値があります。 ポインターは、オブジェクトのアドレスをプログラムの他の部分と共有する手段であり、次の2つの大きな理由から役立ちます:

  1. 参照と同様に、ポインターはコピーを避け、プログラムのリソース消費を抑えるのに役立ちます。
  2. 参照とは異なり、ポインターは別のオブジェクトに付け替えることができます。
  3. ポインターはnull値を指すこともでき、その場合は現在どのオブジェクトも指していないことを示します。

一般的な構文

C++でポインターを宣言するときは、ポインターが指すデータ型を指定し、その後にアスタリスク(*)とポインター名を書きます。 ポインターは、宣言しただけでは自動的に初期化されません。 明示的に代入しない場合、ポインターはたいてい不定の値、いわゆる「ゴミアドレス」を保持します。 コンパイラーによってはポインターをnullptrに初期化することもありますが、その動作がすべてのコンパイラーで保証されているわけではないので、当てにしてはいけません。 問題を避けるために、生ポインターは明示的に初期化し、使う前にnullでないことを確認するのが良い習慣です。

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

変数のアドレスをポインターに代入するには、アドレス演算子(&)を使います。 ポインターの参照外しは、間接演算子(*)を使って行います。

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

注意:参照外しは明示的に行う必要がありますが、参照はエイリアスのようにそのまま使えました。

ポインター演算

ポインター演算を使うと、ポインターに対して算術演算を行えます。これは配列を扱うときに特に便利です。 ポインターに整数を足すと、別の要素を指すようになります。

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

C++のポインター演算は、注意深く扱わないと簡単に__未定義動作__を引き起こします。 未定義動作は、予期しないプログラムの動作、クラッシュ、さらにはセキュリティ脆弱性として現れることがあります。 未定義動作が引き起こした結果として悪名高い例に、1996年のアリアン5ロケットの爆発があります。そこでは、64ビット浮動小数点数を16ビット符号付き整数に変換したことで生じたソフトウェアの例外が、壊滅的な失敗につながりました。

メンバー変数へのアクセス

C++では、->演算子を使って、オブジェクトへのポインターを通じてそのオブジェクトのメンバーにアクセスします。 これは、ポインターが指すオブジェクトのメンバーにアクセスするための簡略記法です。 たとえば、ptrがメンバー変数xを持つオブジェクトへのポインターである場合、(*ptr).xと書く代わりに、ptr->xと直接書けます。 この演算子を使うと、オブジェクトへのポインターを扱うときにコードが読みやすくなり、記述も短くなります。

簡単な例を見てみましょう。メンバー変数superpowerを持つstructのSuperheroを考えます。 main関数は、Superheroオブジェクト(ワンダーウーマンを表します)へのポインターdianaPrinceを作成します。 ->演算子を使ってメンバー変数superpowerにアクセスし、ワンダーウーマンの象徴である「真実の投げ縄」を表現しています。

struct Superhero {
    std::string superpower;
};
Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";
// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

ポインターと参照の違い

ポインターと参照はどちらもオブジェクトへの間接的なアクセスを可能にしますが、できることと安全性の面で違いがあります。 ポインターは、指す対象を自由に変更でき、nullを代入することもできます。 しかし、この柔軟さには、nullポインターの参照外しやダングリングポインターの生成といったリスクが伴います。 一方、参照はnullになれず、作成時に有効なオブジェクトに束縛されるため、こうしたリスクを避けられます。 このように安全性が高いことから、ポインターが提供する追加の機能が必要でない限り、ポインターよりも参照を選ぶべきです。

説明

ようこそ、エンジニア! スピーディワゴン財団に残る、最後のベテランの一人です。この財団は、何十年もの間、柱の男のような古の脅威と戦ってきた秘密組織です。 その活動の中で、最先端の技術と古びたライブラリを組み合わせて作られた財団の技術システムの保守に、何年も費やしてきました。

しかし、最近では、柱の男の活動を追跡するセンサーが誤作動を起こしています。 財団のシステムは古く、そのコードは更新できないレガシーなC++ライブラリと連携しています。 ここでの任務は、古風なポインタベースのライブラリを使って、柱の男センサーの活動を監視する4つの中核となる関数を実装することです。

現代のC++エンジニアなら、スマートポインタを使いたいところでしょう。しかし残念ながら、レガシーコードは古い流儀に敬意を払うことを求めてきます。 人類の命運はこのポインタにかかっているかもしれません。慎重に進めてください。波紋エネルギーが導いてくれますように。

Note

センサーの測定値は膨大になることがあるため、実際のライブラリで使われるモックアップの_構造体_を用意しています。 コードはすでにヘッダーファイルに実装済みです。

struct pillar_men_sensor {
    int activity{};
    std::string location{};
    std::vector<int> data{};
};

1. センサーの接続を確認する(connection_check)

最初のタスクは、柱の男センサーが正しく接続されていることを確認することです。 接続されていないセンサーによる誤報は避けなければなりません。 connection_check関数を書きます。この関数は、nullptrかどうかを調べることで、センサーのポインタが有効かどうかを判定します。

  • pillar_men_sensor_構造体_へのポインタを入力として受け取る関数を定義します。
  • この関数は、センサーのポインタがnullでなければtrueを、そうでなければfalseを返します。
pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false

2. センサーの活動を数える(activity_counter)

柱の男は影に潜んでいます。センサーが何らかの活動を検知したかどうかを知る必要があります。 activity_counter関数を書きます。この関数は、センサーの配列と、配列内のセンサーの数を示す容量を受け取ります。

  • 配列の先頭要素へのポインタと、その配列の容量を入力として受け取る関数を定義します。
  • ポインタ演算を使ってセンサー配列をループし、活動の測定値を合計します。
  • 合計した活動を返します。
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123

3. アラームの制御(alarm_control)

すべてのセンサーがアラームを鳴らせばよいわけではありません。本当の危険があるときだけ鳴らすべきです。 alarm_control関数は、活動レベルが0より大きい場合にだけ、センサーがアラームを鳴らすようにします。 また、この関数は、システムのクラッシュを防ぐために、nullのセンサーもチェックする必要があります。

  • pillar_men_sensorへのポインタを入力として受け取る関数を定義します。
  • この関数は、まずセンサーがnullptrかどうかをチェックします。センサーがnullptrならfalseを返します。
  • センサーが有効で、その活動が0より大きければtrueを返し、そうでなければfalseを返します。
pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true

4. uv_alarm関数でデータの異常を調べる

このタスクでは、紫外線の照射レベルとセンサーの活動に基づいて、アラームを鳴らすべきかどうかを判断するuv_alarm関数を実装します。 uv_alarm関数は、用意されているuv_light_heuristic関数を使います。この関数はデータのベクトルを処理し、特定のしきい値に基づいた値を返します。 これは、本番環境で動く複雑なコードのモックアップ版です。インターフェースは変更しないでください。

speedywagon名前空間にuv_alarm関数を定義します。動作は次のとおりです。

  • pillar_men_sensor_構造体_へのポインタを入力として受け取ります。
  • センサーのポインタがnullならfalseを返します。
  • センサーのdata配列のアドレスを渡して、uv_light_heuristic関数を呼び出します。
  • uv_light_heuristicが返した値がsensor->activityのレベルより大きければtrueを返し、そうでなければfalseを返します。
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
C++ Exercism

スピーディワゴン財団を始める準備はできましたか?

Exercismに登録すれば、19個のコンセプト100個の演習、そして本物の人間によるメンタリングとともに、C++を学んでマスターできます。すべて無料です。