籾がらの自習室

籾がらが学んだことのアウトプットをしています。

C++プログラミングのまとめ

概要

大学の講義や普段している競プロの勉強の中で学んだC++の知識についてまとめます。自身の備忘録的な側面もありますが、できるだけ他の人が見ても分かってもらえるようにまとめていきたいです。

更新履歴

更新日 更新内容
2022/10/25 記事作成
2022/10/25 C++のクラス機能」を追加
2022/11/03 C++のクラス機能」を更新
2022/11/08 クラスの配列とメモリ演算子について追加
2022/11/15 ポインタのお話と参照について追加
2022/11/22 関数のオーバーロードについて追加
2022/11/29 演算子オーバーロードについて追加
2022/12/07 継承のアクセス性、多重継承について追加
2022/12/13 C++の入出力について追加
2022/12/20 マニピュレータの作成について追加

C++の基本文法

デフォルト引数

関数の呼び出し時に指定されない引数があるとき、対応する仮引数にデフォルトの値を設定できる。

void f(int a = 0, int b = 0){
  cout << "a:" << a << ", b:" << b << '\n';
}

int main(){
  f();        // -> a:0, b:0
  f(10);      // -> a:10, b:0
  f(10, 99);  // -> a:10, b:99
}

C++の入出力システム

プログラム実行開始後に開く4つのストリーム

名前 機能
cin 標準入力
cout 標準出力
cerr 標準エラー出力
clog バッファ付き出力

iosのメンバー関数による書式設定

書式セットに用いる関数:fmtflags setf(fmtflags flags); 書式リセットに用いる関数:void unsetf(fmtflags flags);

flagの種類

flag 書式
skipws ストリーム入力中に先頭の空白文字を読み飛ばす
left 左寄せ
right 右寄せ
internal 符号と数字の間にブランクを入れる
dec 10進数表記
oct 8進数表記
hex 16進数表記
showbase 基数表示
showpoint 浮動小数点の出力で小数点、右端の0、eを表示
uppercase 数字の前に符号を表示
scientific 実数値を浮動小数点表示
fixed 固定小数点表示
unitbuf 出力ごとにすべてのストリームにフラッシュ
Stdio 出力ごとにstdout, stderrをフラッシュ

flagの使用例。[stream]にはcinやcoutのような作用させたいストリーム名を入れる。

[stream].setf(ios::showpoint);
[stream].setf(ios::showpoint | ios.fixed);

入出力マニピュレータ

入出力文字の内部で使用できる特別な入出力書式関数。endlなどもこれに含まれる。使用例:

#include <iomanip>
cout << hex << 100 << endl;    // => 64
cout << oct << 10 << endl;     // => 12
cout << setfill('X') << setw(10);
cout << 100 << " hi " << endl; // => XXXXXXX144 hi

<<演算子と>>演算子オーバーロードする

class Coord {
	int x, y;
public:
	Coord() { x = 0; y = 0; }
	Coord(int i, int j) { x = i; y = j; }
	friend ostream& operator<<(ostream& stream, Coord ob);
	friend istream& operator>>(istream& stream, Coord &ob);
};

ostream& operator<<(ostream& stream, Coord ob) {
	stream << ob.x << ", " << ob.y << '\n';
	return stream;
}

istream& operator>>(istream& stream, Coord &ob) {
	cout << "座標を入力:";
	stream >> ob.x >> ob.y;

	return stream;
}

独自マニピュレータの作成

入力マニピュレータであればistream、出力マニピュレータであればostream

istream &マニピュレータ名(istream &stream){
  // 独自のプログラムコード
  return stream;
}
ostream& setup(ostream& stream) {
	stream.width(10);
	stream.precision(4);
	stream.fill('*');

	return stream;
}

ostream& note(ostream& stream) {
	stream << "注意:";
	return stream;
}

int main() {
	cout << setup << 123.123456 << endl;
	cout << note << "消灯" << endl;
}

ファイル入出力

fstreamヘッダをインクルードする。3つのストリーム(ifstream in; ofstream out; fstream io;)がある。

ofstream fout("test.txt");	// 出力ファイルを作成する
if (!fout) {
  cout << "出力ファイルが開けません" << endl;
  return 1;
}
fout << "Hello!" << endl;
fout << 100 << ' ' << hex << 100 << endl;
fout.close();

ifstream fin("test.txt");		// 入力ファイルを開く
if (!fin) {
  cout << "入力ファイルが開けません" << endl;
  return 1;
}
char str[80];
int i;
fin >> str >> i;

// while (!fin.eof()) {
//   fin >> ch;
//   if (ch == ' ') ch = '|';
//   if (!fin.eof()) fout << ch;
// }

cout << str << ' ' << i << endl;
fin.close();

その他の出力設定

以下の設定は、出力のたびに設定しなおす必要がある。

  • フィールド幅の調整:streamsize width(streamsize w);
  • 精度の調整:streamsize precision(streamsize p);
  • 充填文字の設定:char fill(cahr ch);

C/C++のポインタ

ポインタ、アドレスとは

プログラム上で用いられる値(数値や文字列、配列)は、コンピュータの中のメモリというところに保存されている。あるデータが、どこのメモリに保存されているか分かるように、メモリにはそれぞれに住所が振れられていて、これがアドレスである。ポインタとは、このアドレスを覚えておく変数にあたる。

例を用いて、次のような対応関係を考える。

例え コンピュータ上の用語
データ
メモリ
住所 アドレス
住所のメモ ポインタ変数
人に会いに行く データを読み込む

ある人Aさんが住所aaaに住んでいるに住んでいた。Aさんに会いに行くためには、この住所を覚えておく必要があるだろう。そこでAさんの住所をメモしておく。こうすれば、毎回住所を聞かなくても、このメモを見てAさんを訪ねることができる。

この例におけるメモが、プログラム上のポインタ変数に当たります。伝わりますでしょうか…。分かりづらいなどあれば、コメントまでお願いいたします。

ポインタ変数の基本

  • ポインタ変数の宣言は、型名(もしくは変数名の前)に「*」を付けて行う。
  • 変数のアドレスは、変数名の前に「&」を付けて参照できる。
  • アドレスの先に保存されている値を参照するには、ポインタ変数の前に「*」を付ける。
int main(){
  int num = 1;  // int型変数
  int *p_int;   // int型ポインタ変数
  p_int = &num  // 変数numのアドレスをポインタ変数に代入

  cout << p_int  << '\n'; // 変数numのアドレスを表示
  cout << *p_int << '\n'; // 変数numの値(=1)を表示
}

ポインタの性質

  • ポインタ変数には、int型のポインタ、char型のポインタのように型がある
  • アドレスを保存したいデータの型とポインタの型は、一致させる必要がある。
  • 配列はデータが連続したアドレスに保存される。
  • 配列の変数名を書くと、配列の先頭アドレスを参照できる。
  • (ポインタ) + 1のように書くと、配列の1つ隣の値のアドレスになる。
  • (応用)char型のポインタに+1をすると、隣の要素のアドレスは1増える。int型のポインタに+1すると、隣の要素のアドレスは4増える。これは、char型が1バイト(アドレス1番地分)、int型が4バイト(アドレス4番地分)で保存されているためである。
int main() {
  int i_arr[] = {0, 1, 2, 3};
  int *p_int;

  p_int = i_arr;  // 配列の先頭アドレスが格納される。
  // &i_arr[0] == i_arr となり、どちらも配列の先頭アドレスを返す。

  p_int = &i_arr[1] // 配列の2番目の要素のアドレスが格納される。
  // &i_arr[1] == (i_arr + 1) となり、ポインタ+1で配列の次の要素を参照できる。
}

ポインタの詳しい話

  • ポインタ変数aは、ポインタをデータとして保存しているから、そのポインタの変数aの保存先アドレスも別で存在する(ポインタのポインタ)
  • ポインタのポインタを使うとクラスの配列の宣言が楽に行える。

参照(関数に変数を渡す)

  • 何もせずに関数の引数に変数を渡すと、その変数のコピーが関数の中で扱われる
  • 引数で渡した元の変数をいじりたい場合は、その変数のアドレス(ポインタ)を渡していじれるようにする(ポインタ渡し)か、引数を参照で受け取るように指定する(参照渡し)必要がある。
  • ポインタ渡しは、仮引数の変数はあくまでもポインタ変数であって、値にアクセスするには「*」が必要。
  • 参照渡しは、仮引数の変数を呼び出しで渡したものと同じものとして扱う。
void f1(int n){ n = 1; }    // main関数のnは変更されない。
void f2(int* n){ *n = 1; }  // ポインタ渡し。ポインタの指し示す先の値(「*」を付けて)を変更する。
void f3(int& n){ n = 1; }   // 参照渡し。

C++のクラス機能

クラスという概念についての説明は、ここで始めるとキリが無くなってしまうので、この記事では省きます。今後、クラスを始めとするオブジェクト指向についての説明も時間があるときに書きたいと思います。

クラスの定義

class (クラス名) {
  private:
    // プライベートメンバの宣言
    int a;
    void calc(int x, int y);
  
  public:
    // 公開メンバの宣言
    double num;
    void setA(int a);
};

メンバのアクセス性はコロン「:」で区切って宣言する。「private:」は省略できる。クラス宣言の最後にはセミコロンが必要

メンバ関数の定義

void (クラス名)::calc(int x, int y){ (関数の内容); }

クラス名にコロン2つ「::」を付けて関数の中身を定義する。「::」はスコープ解決演算子と呼ばれる。

関数の多重定義(ポリモーフィズム)
int abs(int value){
  return (value < ? -n : n);
}

double abs(double value){
  return (value < ? -n : n);
}

同じ関数名でも、引数の型によって複数定義することができる。

コンストラクタ関数 / デストラクタ関数


class MyClass {
public:
  MyClass();  // コンストラクタ
  ~MyClass(); // デストラクタ
}

MyClass::MyClass(){ ~~~ }   // コンストラクタの実装
MyClass::~MyClass(){ ~~~ }  // デストラクタの実装

インスタンスが生成されるときにコンストラクが呼び出され、インスタンスが破壊されるとき(スコープ外になる/プログラム終了時)にデストラクが呼び出される。
コンストラクタ/デストラクタの名前はクラス名と同じにし、戻り値は定義しない。デストラクタの場合はクラス名にチルダ「~」を付す。

コンストラクタの多重定義 / コピーコンストラク

コンストラクタ関数をオーバーロードする目的は、

  1. 柔軟性を得る
  2. 配列をサポートする
  3. コピーコンストラクタを作成する

コピーコンストラクタ。

class A {
  int* a;
public:
  A(int b){ a = new int(b); }
  A(const A& ob);
}

A::A(const A &ob){
  int value = *ob.a;
  a = new int;
  *a = value;
}

メンバへのアクセス


#include <iostream>
using namespace std;

class MyClass {
private:
	int a;

public:
	int num = 5;
	void setA(int n);
};

void MyClass::setA(int n) {
	a = n;
}

int main() {
	MyClass myC1;
  MyClass* p;
  
  p = &myC1;
	myC1.setA(10);
	cout << myC1.num << " == " << p->num;
}

メンバ変数、メンバ関数へのアクセスは、ドット演算子「.」で行える。
ポインタを用いたインスタンスのメンバーへのアクセスは、アロー演算子「->」を使って行える。

クラスの継承

あるクラスが他のクラスの機能を受け継ぐ仕組み。

図形クラス(基本クラス):高さと幅の変数
四角形クラス:面積を計算するメソッド
図形クラスを継承した四角形クラスでは、図形クラスの持つ高さと幅の変数を参照できる。

class A {
  int i;
public:
  void func();
};

class B : public A {
  int j;
};

継承したいクラスを、クラス名の後ろにコロン「:」を付けて追加する。継承したいクラス名にpublicを付けると、親クラスのpublicメンバーがpublicのまま引き継げる。

コンストラクタは親クラスが先に呼ばれ、デストラクタは子クラスが先に呼ばれる。

親クラスのコンストラクタに引数を渡す場合には、次のように記述する。

class B : public A{
  int j;
public:
  B(int n, int m) : A(m) { j = n; }
}
クラスの多重継承

一般形:

class クラス名 : [アクセス演算子] 基本クラス名1, [アクセス演算子] 基本クラス名2, ...{
  // クラスの定義
}

また、多重定義の場合のコンストラクタの呼び出しは次の形式で記述する。

クラス名(引数) : 基本クラス名1(仮引数), 基本クラス名2(仮引数), ...{
  // コンストラクタの定義
}

派生クラスが複数の基本クラスを継承する際、その基本クラスが同一のクラスが継承しているかもしれない。これにより、どちらの最上位クラスを参照するのか分からなくなってしまう。このときに仮想基本クラスを用いて、1つしか含まれないようにする。

class Base { ... };

class Derived1 : virtual public Base{ ... };
class Derived2 : virtual public Base{ ... };

// 基本クラスのコピーは1つしか持たない
class Derived3 : public Derived1, public Derived2{ ... };
クラスの継承のアクセス制御

一般形:

class クラス名:[アクセス指定子] 基本クラス名{
  // クラス定義
}
  • アクセス指定子をpublicにした場合、class A, B両方のpublicメンバにアクセスが可能。protectedのメンバはprivateの扱いになる。
  • アクセス指定子をprivateにした場合、class Bのpublicメンバにのみアクセスが可能
  • アクセス指定子をprotectedにした場合、クラスBの中であればクラスAのprotectedとpublicメンバにアクセス可能(≒private)
class B : public A

クラスのファイル分割

クラス用のヘッダーファイルとソースファイルを別に作成して管理する。

VisualStudioでは、「ヘッダーファイルフォルダ」と「ソースファイルフォルダ」にファイルを追加。

クラスの実装を書くソースファイルとmain関数のあるソースファイルでは、#include "(クラス名).h"として宣言部をインクルードする。

クラスの配列

インスタンスは変数なので、配列として扱うことができる。

コンストラクタがある場合は、次のようにして呼び出す。なお、コンストラクタの引数が複数ある場合は、呼び出しを省略できない。

class A{
public:
  A(int n){ ~~~ };
};

int main(){
  A sample[4] = {-1, -2, -3, -4};
  // これは {A(-1), A(-2), A(-3), A(-4)} が省略されている。
}

多次元配列の場合も同様に記述できる。

class A{
public:
  A(int n){ ~~~ };
};

int main(){
  A sample[2][2] = {
    -1, -2,
    -3, -4
  };
  // これは {A(-1), A(-2), A(-3), A(-4)} が省略されている。
}

ポインタのポインタを使うことにより、for文などで個々にコンストラクタを呼び出せる

int main() {
	MyClass** p;

	p = new MyClass* [10];

	// 個々にコンストラクタを呼び出す。
	for (int i = 0; i < 10; i++) {
		p[i] = new MyClass('A' + i);
	}

	return 0;
}

MyClassの実体はp[i] = new MyClass('A' + i);で生成される。生成したものそれぞれアドレスが、配列としてpの中に保存されている。

C++のメモリ周り

メモリの割り当てと解放

C言語C++で、次のような対応関係がある。

用途 C言語 C++
メモリの割り当て malloc() new演算子
メモリの解放 free() delete演算子

new演算子は、メモリの割り当てに失敗するとヌルを返す。

int main(){
  int *p;
  p = new int [5];  // 配列のメモリ割り当て
  
  if(!p){ //メモリ割り当てエラー }

  ~~~;

  delete [] p;  // メモリを解放
}

演算子オーバーロード

演算子の機能を、クラスによって各々に設定できる。

(戻り値) (クラス名)::operator(拡張したい演算子) (仮引数){
  // 実行する処理
}

2項演算子オーバーロード

class Vector2 {
	int x, y;
public:
	Vector2() { x = 0, y = 0; }
	Vector2(int i, int j) { x = i; y = j; }
	void GetXY(int& i, int& j) { i = x; j = y; }
	Vector2 operator+(Vector2 obj);
	int operator==(Vector2 obj);
};

int Vector2::operator==(Vector2 p) {
	return x == p.x && y == p.y;
}

Vector2 Vector2::operator+(Vector2 p) {
	Vector2 temp;
	temp.x = x + p.x;
	temp.y = y + p.y;
	return temp;
}

単項演算子オーバーロード

インクリメント演算子やデクリメント演算子では、前置と後置でそれぞれオーバーロードを作成する必要がある。

class Vector2 {
	int x, y;
public:
	Vector2() { x = 0, y = 0; }
	Vector2(int i, int j) { x = i; y = j; }
	void GetXY(int& i, int& j) { i = x; j = y; }
	Vector2 operator++();		// 前置用
	Vector2 operator++(int a);	// 後置用
};

Vector2 Vector2::operator++() {
	++x;
	++y;
	return *this;
}

Vector2 Vector2::operator++(int as) {
	Vector2 old = *this;
	++* this;
	return old;
}