C++プログラミングの重要な注意点とベストプラクティス

1. イテレータとペアの値へのアクセス方法の違い

イテレータを介して値にアクセスする場合と、ループ内で直接ペアオブジェクトにアクセスする場合で、ドット演算子とアロー演算子の使い分けが必要です。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        std::unordered_map hash_map;
        for(int i = 0; i < nums.size(); i++) {
            // ハッシュマップ内でターゲット値を検索
            auto it = hash_map.find(target - nums[i]); 
            if(it != hash_map.end()) {
                // イテレータの指す要素の値にアクセス
                return {it->second, i};
            }
            // 見つからなければ、要素とそのインデックスをマップに追加
            hash_map[nums[i]] = i; 
        }
        return {};
    }
};

class Solution {
public:
    bool uniqueOccurrences(vector<int>& arr) {
        unordered_map hash_map;
        unordered_set<int> unique_counts;
        for(int i = 0; i < arr.size(); i++){
            hash_map[arr[i]]++;
        }
        for(const auto& kv_pair : hash_map){
            // ペアオブジェクトの値にアクセス
            unique_counts.insert(kv_pair.second);
        }
        return unique_counts.size() == hash_map.size();
    }
};

上記の最初のコードでは、`it`はハッシュマップのイテレータであり、その指す要素(キーと値のペア)にアクセスするため、アロー演算子(`->`)を使用します。

二つ目のコードでは、`kv_pair`は`std::pair`オブジェクトそのものであり、そのメンバ変数にアクセスするため、ドット演算子(`.`)を使用します。

2. マップへの挿入と要素の存在確認

マップへの挿入には、insertメソッドと直接代入の二つの方法があります。

// 方法1: insertメソッドを使用
my_map.insert(std::make_pair(key, value));

// 方法2: 直接代入(推奨)
my_map[key] = value;

countメソッドは、指定されたキーが存在するかどうかを確認し、存在すれば1、存在しなければ0を返します。

if (my_map.count(some_key)) {
    // キーが存在する
}

3. swap関数の用途

std::swap関数は、主にコンテナ内の二つの要素を交換するために使用されます。

std::vector<int> data_vec = {1, 2, 3, 4, 5};
std::swap(data_vec[1], data_vec[3]); // data_vecは{1, 4, 3, 2, 5}になる

4. sizeメソッドの使い方

std::stringstd::vectorstd::mapstd::setstd::queueなどのコンテナは、.size()メソッドで要素数を取得できます。

しかし、Cスタイルの配列(例: int arr[10])にはこのメソッドはありません。その場合、sizeof(arr)/sizeof(arr[0])を使用して要素数を計算します。

5. 文字列操作関数

  • substr(開始位置, 長さ): 文字列の部分文字列を取得します。
  • reverse(イテレータの開始, イテレータの終了): イテレータで指定された範囲の要素を反転させます。
std::string text_str = "hello";
std::string sub = text_str.substr(1, 3); // "ell"
std::reverse(text_str.begin(), text_str.end()); // "olleh"

6. find関数の使い分けと時間計算量

異なるコンテナに対して、異なるfind関数が提供されています。

  • 連想コンテナ(ハッシュテーブル、木): map.find(key)set.find(key)。平均的な時間計算量はO(1)(ハッシュテーブル)またはO(log n)(平衡二分木)です。
  • シーケンシャルコンテナ(ベクタ、リスト): std::find(vec.begin(), vec.end(), value)。最悪のケースの時間計算量はO(n)です。

7. 整数の平均値計算(データ型の制約あり)

32ビットの符号なし整数の配列の平均値を計算する際、64ビット整数や浮動小数点数を使用できない場合のテクニックです。

#include <iostream>
#include <vector>
using namespace std;

uint32_t calculateAverage(const vector& input_vec) {
    uint32_t quotient_sum = 0;
    uint32_t remainder_sum = 0;
    uint32_t n = input_vec.size();

    for (size_t i = 0; i < n; ++i) {
        quotient_sum += input_vec[i] / n;
        remainder_sum += input_vec[i] % n;
    }

    return quotient_sum + remainder_sum / n;
}

int main() {
    vector test_data = {41, 4, 7, 13};
    uint32_t avg = calculateAverage(test_data);
    cout << "平均値: " << avg << endl;
    return 0;
}

8. 時間計算量の基本

  • 二分探索: O(log n)
  • クイックソート: O(n log n)

9. スタックとキューの基本操作

  • スタック: stack.push(), stack.pop(), stack.top()。先入後出(LIFO)。
  • キュー: queue.push(), queue.pop(), queue.front(), queue.back()。先入先出(FIFO)。
  • スタックでキューを実装: 二つのスタック(入力用と出力用)を使用します。
  • キューでスタックを実装: 二つのキュー(一つはバックアップ用)を使用します。

10. 関数の戻り値の取り扱い

関数の戻り値を変数に格納する必要は必ずしもありません。直接利用することも可能です。

int max_val = getMax(a, b); // 変数に格納
cout << getMax(a, b); // 直接出力

11. 型変換

整数を文字列に変換するには、std::to_string()関数を使用します。

int number = 123;
std::string str_num = std::to_string(number);

12. 文字列配列の定義

固定長の文字列配列は、定数として定義するのが一般的です。

const std::string roman_numerals[] = {
    "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"
};

13. ビットシフト演算子の優先順位に注意

ビットシフト演算子(<<, >>)の優先順位は低いため、複雑な式では括弧で明確にする必要があります。

int a = 1, b = 2;
int result = (a << 2) + b; // 正しい
// int wrong_result = a << 2 + b; // 予期しない結果になる可能性あり

14. カスタム比較関数を用いたソート

std::sortに比較関数オブジェクトを渡すことで、独自のソートロジックを実装できます。

#include <algorithm>
#include <vector>
#include <cmath>

bool cmp_func(int a, int b) {
    return std::abs(a) > std::abs(b);
}

int main() {
    std::vector<int> my_vec = {-3, 1, -2, 4, -5};
    std::sort(my_vec.begin(), my_vec.end(), cmp_func);
    // my_vecは{-5, -3, -2, 1, 4}となる
}

15. reverse関数

コンテナの要素を反転させるには、std::reverseを使用します。

std::vector<int> numbers = {1, 2, 3, 4, 5};
std::reverse(numbers.begin(), numbers.end()); // numbersは{5, 4, 3, 2, 1}になる

16. 優先度付きキュー(ヒープ)

デフォルトでは最大ヒープですが、比較関数を指定することで最小ヒープにできます。

#include <queue>
#include <vector>

int main() {
    // 最小ヒープの宣言
    std::priority_queue min_heap;
    min_heap.push(10);
    min_heap.push(5);
    min_heap.push(20);
    // top()は常に最小値(5)を返す
}

タグ: C++ STL データ構造 アルゴリズム ハッシュテーブル

5月16日 09:32 投稿