C++におけるクラス内配列

クラス内配列を添字演算子[](index operator, subscript operator)のオーバーロードを利用して記述します。


1.クラス内で配列を静的に確保する

C/C++では配列そのものを関数の引数や戻り値にすることはできませんが、以下のArrayクラスではそれが可能です。これを使えば複数の値の値渡しや値戻しができます。関数の引数や戻り値がベクトルである場合に使うと便利です。もちろん、配列長が大きい場合は参照渡しや参照戻しを使うこともできます。

array1.cpp

#include <cassert>
#include <iostream>

template <int LENGTH> class Array {
  double array[LENGTH];
public:
  const int length = LENGTH;
  Array() {
    if (length > 0) {
      for (int i = 0; i < length; i++) {
        array[i] = 0;
      }
    }
  }
  Array& operator=(const Array& object) {
    if (length > 0) {
      for (int i = 0; i < length; i++) {
        array[i] = object.array[i];
      }
    }
    return *this;
  }
  double& operator[](int index) {
    assert(0 <= index && index < length);
    return array[index];
  }
  const double& operator[](int index) const {
    assert(0 <= index && index < length);
    return array[index];
  }
};

Array<10> echo1(Array<10> array) {
  return array;
}

const Array<10>& echo2(const Array<10>& array) {
  return array;
}

int main() {
  const int length = 10;
  //Array<length> array;
  auto array = Array<length>();
  for (int i = 0; i < array.length; i++) {
    array[i] = i;
  }
  auto array1 = Array<length>();
  array1 = array;
  for (int i = 0; i < array1.length; i++) {
    std::cout << array1[i] << std::endl;
  }
  for (int i = 0; i < echo1(array).length; i++) {
    std::cout << echo1(array)[i] << std::endl;
  }
  for (int i = 0; i < echo2(array).length; i++) {
    std::cout << echo2(array)[i] << std::endl;
  }
}

array2.cpp

#include <cassert>
#include <iostream>

template <int LENGTH> class Array {
  double array[LENGTH];
public:
  const int length = LENGTH;
  Array();
  Array& operator=(const Array&);
  double& operator[](int);
  const double& operator[](int) const;
};

template <int LENGTH> Array<LENGTH>::Array() {
  if (length > 0) {
    for (int i = 0; i < length; i++) {
      array[i] = 0;
    }
  }
}

template <int LENGTH> Array<LENGTH>& Array<LENGTH>::operator=(const Array& object) {
  if (length > 0) {
    for (int i = 0; i < length; i++) {
      array[i] = object.array[i];
    }
  }
  return *this;
}

template <int LENGTH> double& Array<LENGTH>::operator[](int index) {
  assert(0 <= index && index < length);
  return array[index];
}

template <int LENGTH> const double& Array<LENGTH>::operator[](int index) const {
  assert(0 <= index && index < length);
  return array[index];
}

Array<10> echo1(Array<10> array) {
  return array;
}

const Array<10>& echo2(const Array<10>& array) {
  return array;
}

int main() {
  const int length = 10;
  //Array<length> array;
  auto array = Array<length>();
  for (int i = 0; i < array.length; i++) {
    array[i] = i;
  }
  auto array1 = Array<length>();
  array1 = array;
  for (int i = 0; i < array1.length; i++) {
    std::cout << array1[i] << std::endl;
  }
  for (int i = 0; i < echo1(array).length; i++) {
    std::cout << echo1(array)[i] << std::endl;
  }
  for (int i = 0; i < echo2(array).length; i++) {
    std::cout << echo2(array)[i] << std::endl;
  }
}


2.クラス内で配列を動的に確保する

スタックに格納できないサイズの配列を扱う際に使うと便利です。また、ディストラクタが自動で動的に確保した配列の後始末をしてくれます。

注)オブジェクトの値渡しでは、値渡しの際にコピーコンストラクタが呼び出され、関数を抜ける時にディストラクタが呼び出されます。

注)メンバ変数にポインタがあるオブジェクトの値渡しでは、関数を抜ける時に呼び出されるディストラクタがコピー元の実体を解放するので、値渡しの際に実体がコピーされるようにコピーコンストラクタを定義しておきます。

array3.cpp

#include <cassert>
#include <iostream>

class Array {
  double* array;
public:
  int length;
  Array(int length) : array(NULL), length(length) {
    if (length > 0) {
      array = new double[length];
      for (int i = 0; i < length; i++) {
        array[i] = 0;
      }
    }
  }
  Array(const Array& object) : array(NULL), length(object.length) {
    if (length > 0) {
      array = new double[length];
      for (int i = 0; i < length; i++) {
        array[i] = object.array[i];
      }
    }
  }
  Array& operator=(const Array& object) {
    array = NULL;
    length = object.length;
    if (length > 0) {
      array = new double[length];
      for (int i = 0; i < length; i++) {
        array[i] = object.array[i];
      }
    }
    return *this;
  }
  ~Array() {
    if (array != NULL) {
      delete[] array;
      array = NULL;
    }
  }
  double& operator[](int index) {
    assert(0 <= index && index < length);
    return array[index];
  }
  const double& operator[](int index) const {
    assert(0 <= index && index < length);
    return array[index];
  }
};

Array echo1(Array array) {
  return array;
}

const Array& echo2(const Array& array) {
  return array;
}

int main() {
  int length = 10;
  //Array array(length);
  auto array = Array(length);
  for (int i = 0; i < array.length; i++) {
    array[i] = i;
  }
  auto array1 = Array(length);
  array1 = array;
  for (int i = 0; i < array1.length; i++) {
    std::cout << array1[i] << std::endl;
  }
  for (int i = 0; i < echo1(array).length; i++) {
    std::cout << echo1(array)[i] << std::endl;
  }
  for (int i = 0; i < echo2(array).length; i++) {
    std::cout << echo2(array)[i] << std::endl;
  }
}

array4.cpp

#include <cassert>
#include <iostream>

class Array {
  double* array;
public:
  int length;
  Array(int);
  Array(const Array&);
  Array& operator=(const Array&);
  ~Array();
  double& operator[](int);
  const double& operator[](int) const;
};

Array::Array(int length) : array(NULL), length(length) {
  if (length > 0) {
    array = new double[length];
    for (int i = 0; i < length; i++) {
      array[i] = 0;
    }
  }
}

Array::Array(const Array& object) : array(NULL), length(object.length) {
  if (length > 0) {
    array = new double[length];
    for (int i = 0; i < length; i++) {
      array[i] = object.array[i];
    }
  }
}

Array& Array::operator=(const Array& object) {
  array = NULL;
  length = object.length;
  if (length > 0) {
    array = new double[length];
    for (int i = 0; i < length; i++) {
      array[i] = object.array[i];
    }
  }
  return *this;
}

Array::~Array() {
  if (array != NULL) {
    delete[] array;
    array = NULL;
  }
}

double& Array::operator[](int index) {
  assert(0 <= index && index < length);
  return array[index];
}

const double& Array::operator[](int index) const {
  assert(0 <= index && index < length);
  return array[index];
}

Array echo1(Array array) {
  return array;
}

const Array& echo2(const Array& array) {
  return array;
}

int main() {
  int length = 10;
  //Array array(length);
  auto array = Array(length);
  for (int i = 0; i < array.length; i++) {
    array[i] = i;
  }
  auto array1 = Array(length);
  array1 = array;
  for (int i = 0; i < array1.length; i++) {
    std::cout << array1[i] << std::endl;
  }
  for (int i = 0; i < echo1(array).length; i++) {
    std::cout << echo1(array)[i] << std::endl;
  }
  for (int i = 0; i < echo2(array).length; i++) {
    std::cout << echo2(array)[i] << std::endl;
  }
}

注)テンプレートクラスのメンバー関数の実装はヘッダーファイルに記述してください。


参考サイト

1.演算子のオーバーロード
2.subscript operator overloading
3.Overloading the C++ indexing subscript operator []
4.Overloading the Subscript Operator [] the Right Way
5.クラス内での配列の動的確保
6.関数とオブジェクト
7.オブジェクト利用時の注意点
8.迷信:new に失敗すると NULL が返る?
9.コピーコンストラクタ、代入演算子、デストラクタ(The Law of The Big Three)
10.コピー操作と参照
11.ロベールのC++教室 – クラステンプレート
12.テンプレートの実装をヘッダに書かなければならない理由

広告

C++におけるオブジェクトの値渡し・値戻し

1.オブジェクトの値渡し

関数の引数にオブジェクトを値渡しすると、オブジェクトのコピーの際にコピーコンストラクタが呼び出され、関数を抜ける時にディストラクタが呼び出されます。

passA.cpp

#include <iostream>

class A {
public:
  A() {
    std::cout << "Constructor!" << std::endl;
  }
  A(const A& object) {
    std::cout << "Copy Constructor!" << std::endl;
  }
  ~A() {
    std::cout << "Destructor!" << std::endl;
  }
};

void passA(A a) {
  std::cout << "Pass A!" << std::endl;
}

int main() {
  auto a = A();
  passA(a);
}

〈実行結果〉

Constructor!
Copy Constructor!
Pass A!
Destructor!
Destructor!


2.オブジェクトの値戻し

オブジェクトが値戻しされる時も、最後にディストラクタが呼び出されます。

returnA.cpp

#include <iostream>

class A {
public:
  A() {
    std::cout << "Constructor!" << std::endl;
  }
  A(const A& object) {
    std::cout << "Copy Constructor!" << std::endl;
  }
  ~A() {
    std::cout << "Destructor!" << std::endl;
  }
};

A returnA() {
  std::cout << "Return A!" << std::endl;
  return A();
}

int main() {
  returnA();
}

〈実行結果〉

Return A!
Constructor!
Destructor!


参考サイト

1.C++ クラス設計に関するノート
2.関数とオブジェクト
3.オブジェクト利用時の注意点
4.参照の復習とコピーコンストラクタ
5.コピーコンストラクタ、代入演算子、デストラクタ(The Law of the Big Three)
6.コピー

C++によるテンプレートメタプログラミング

テンプレートメタプログラミングのサンプルです。

template.cpp

#include <iostream>

template <typename T = int> T add(T x, T y) {
  return x + y;
}

int main() {
  std::cout << add<>(1, 1) << std::endl;
  std::cout << add<int>(1, 1) << std::endl;
}


array.h

#pragma once

template <typename T = int, int LENGTH = 10> class Array {
  T array[LENGTH];
public:
  const int length = LENGTH;
  T& operator[](int);
  T sum();
};

template <typename T, int LENGTH> T& Array<T, LENGTH>::operator[](int index) {
  return array[index];
}

template <typename T, int LENGTH> T Array<T, LENGTH>::sum() {
  T s = 0;
  for (int i = 0; i < LENGTH; i++) {
    s += array[i];
  }
  return s;
}

main.cpp

#include "array.h"
#include <iostream>

int main() {
  //Array<> array;
  const int length = 10;
  //Array<int, length> array;
  auto array = Array<int, length>();
  for (int i = 0; i < array.length; i++) {
    array[i] = i;
    std::cout << array[i] << std::endl;
  }
  std::cout << array.sum() << std::endl;
}

注)テンプレートクラスのメンバー関数の実装はヘッダーファイルに記述してください。


参考サイト

1.Google:C++ template
2.Google:C++ template meta programming
3.C++テンプレートメタプログラミング超入門
4.テンプレートの実装をヘッダに書かなければならない理由

C++によるプロセス制御

1.fork関数によるマルチプロセス

fork関数を呼び出すと現在のプロセス(親プロセス)が複製され、子プロセスが生成されます。
親プロセスは pid > 0、子プロセスは pid = 0 です。getpid() は自プロセスのプロセスIDを表します。

fork1.cpp (Cygwin)

#include <iostream>
#include <sys/wait.h>
#include <unistd.h>

auto main() -> int {
  std::cout << "Hello!" << std::endl;
  pid_t pid = fork();
/*
  int status;
  wait(&status);
*/
  for (int i = 0; i < 10; i++) {
    std::cout << pid << ":" << getpid() << ":" << "Multi-Process!" << std::endl;
  }
}

上記のプログラムを実行するとfork関数以降が親プロセスと子プロセスのマルチプロセスで実行されます。尚、wait関数を使うと親プロセスは子プロセスが終了するまで待機します。

〈実行結果〉

$ ./exec
Hello!
2735:1557:Multi-Process!
2735:1557:Multi-Process!
0:2735:Multi-Process!
0:2735:Multi-Process!
2735:1557:Multi-Process!
2735:1557:Multi-Process!
2735:1557:Multi-Process!
2735:1557:Multi-Process!
2735:1557:Multi-Process!
0:2735:Multi-Process!
0:2735:Multi-Process!
0:2735:Multi-Process!
2735:1557:Multi-Process!
2735:1557:Multi-Process!
2735:1557:Multi-Process!
0:2735:Multi-Process!
0:2735:Multi-Process!
0:2735:Multi-Process!
0:2735:Multi-Process!
0:2735:Multi-Process!


親プロセスと子プロセスで異なる処理をさせる場合は以下のようにします。

fork2.cpp (Cygwin)

#include <iostream>
#include <unistd.h>

auto main() -> int {
  pid_t pid = fork();
  if (pid > 0) {
    std::cout << "Parent Process!" << std::endl;
    return 0;
  }
  if (pid == 0) {
    std::cout << "Child Process!" << std::endl;
    return 0;
  }
  if (pid < 0) {
    std::cerr << "Error!" << std::endl;
    return -1;
  }
}


2.pipe関数によるプロセス間通信(interprocess communication)

pipe関数を使えば親プロセスと子プロセスの間で通信を行えます。下記のプログラムは子プロセスから親プロセスにデータを送信するプログラムです。

pipe.cpp (Cygwin)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <unistd.h>

auto main() -> int {
  char buffer[1024];
  int pipefd[2];
  pipe(pipefd);
  pid_t pid = fork();
  if (pid > 0) {
    close(pipefd[1]);
    read(pipefd[0], buffer, sizeof(buffer));
    std::cout << buffer;
    close(pipefd[0]);
    return 0;
  }
  if (pid == 0) {
    close(pipefd[0]);
    fgets(buffer, sizeof(buffer), stdin);
    write(pipefd[1], buffer, strlen(buffer) + 1);
    close(pipefd[1]);
    return 0;
  }
  if (pid < 0) {
    std::cerr << "Error!" << std::endl;
    return -1;
  }
}


3.exec関数によるプロセスの置き換え(replacement of current process)

exec関数で別プロセスを起動すると、自プロセスが別プロセスに置き換えられます。以下の例では、Hello!は標準出力されません。

exec.cpp

#include <iostream>
#include <unistd.h>

auto main() -> int {
  execl("main.exe", "a0", "a1", "a2", NULL);
  std::cout << "Hello!" << std::endl;
}

main.cpp

#include <iostream>

auto main(int argc, char* argv[]) -> int {
  std::cout << argc << std::endl;
  for (int i = 0; i < argc; i++) {
    std::cout << i << ":" << argv[i] << std::endl;
  }
}


4.popen関数による外部プロセスの標準出力取得

popen関数を使うと外部プロセスの標準出力を取得することができます。

popen.cpp

#include <cstdio>
#include <cstdlib>
#include <iostream>

int main() {
  const char* command = "main.exe a0 a1 a2";
  FILE* fp = popen(command, "r");
  if (fp == NULL) return -1;
  char buffer[1024];
  while (fgets(buffer, sizeof(buffer), fp) != NULL) {
    std::cout << buffer;
  }
  pclose(fp);
  return 0;
}

main.cpp

#include <iostream>

auto main(int argc, char* argv[]) -> int {
  std::cout << argc << std::endl;
  for (int i = 0; i < argc; i++) {
    std::cout << i << ":" << argv[i] << std::endl;
  }
}

popen.bat

set PATH=D:\sdk\MinGW\bin
g++ popen.cpp -o popen.exe
g++ main.cpp -o main.exe
popen.exe


参考サイト

1.fork関数
2.プロセスの作成 fork
3.マルチプロセス
4.メモリとプロセスとスレッド編
5.C言語 popen()でコマンドを実行して出力を読み込む
6.system関数とpopen関数を適当に選んではいけない
7.外部プログラムの戻り値を取得するには?
8.Google:C言語 pipe関数
9.クライアント・サーバーとは?(パイプによるプロセス間通信)
10.Man page of PIPE
11.pipe関数

C++による文字列の操作

string.cpp

#include <iostream>

auto main() -> int {
  std::string string("0123456789");
  std::cout << string.size() << std::endl; // -> 10
  std::cout << string[0] << std::endl; // -> 0
  std::cout << string.substr(1) << std::endl; // -> 123456789
  std::cout << string.substr(1, 5) << std::endl; // -> 12345
  std::cout << string.insert(1, "abcde") << std::endl; // -> 0abcde123456789
  std::cout << string.erase(10) << std::endl; // -> 0abcde123456
  std::string s1 = "11111";
  std::string s2 = "22222";
  auto s = s1 + s2;
  //string s = "11111" + "22222"; // -> error
  std::cout << s << std::endl; // -> 1111122222
  s += "33333";
  std::cout << s << std::endl; // -> 111112222233333
}


split.cpp

#include <iostream>
#include <sstream>
#include <vector>

auto split(const std::string& input, char delimiter = ',') -> std::vector<std::string> {
  std::stringstream stringstream(input);
  std::string token;
  std::vector<std::string> output;
  while (std::getline(stringstream, token, delimiter)) {
    output.push_back(token);
  }
  return output;
}

auto main() -> int {
  std::string string = "a1,a2,a3,a4,a5";
  for (std::string& s : split(string, ',')) {
    std::cout << s << std::endl;
  }
}


readCSV.cpp

#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>

auto readCSV(const std::string& file, std::vector<std::vector<std::string>>& table, char delimiter = ',') -> void {
  std::fstream fstream(file);
  while (!fstream.eof()) {
    std::string buffer;
    fstream >> buffer;
    std::stringstream stringstream(buffer);
    std::string token;
    std::vector<std::string> vector;
    while (getline(stringstream, token, delimiter)) {
      vector.push_back(token);
    }
    table.push_back(vector);
  }
}

auto main() -> int {
  std::string file = "sample.csv";
  std::vector<std::vector<std::string>> table;
  readCSV(file, table);
  for (int i = 0; i < table.size(); i++) {
    std::vector<std::string> record(table[i]);
    for (int j = 0; j < record.size(); j++) {
      std::cout << record[j] << ",";
    }
    std::cout << std::endl;
  }
}


参考サイト

1.C++での文字列の使い方まとめ
2.C++で文字列のsplit
3.getlineはsplitに使える
4.[C++]CSVファイルの読み込み