コンテンツにスキップ

C++ の基礎 Day 2

1. 基本型

  • C++ のコア言語に用意されている基本型を示す。

整数型

  • ~ char 以外の整数型のサイズは規格で規定されていない。一般的なサイズを示す。
  • 負数の表現には 2 の補数が使われる。
型名 サイズ(バイト) 略名 範囲
signed char 1 -128 ~ 127
unsigned char 1 0 ~ 255
signed short int 2 short -32,768 ~ 32,767
unsigned short int 2 unsigned short 0 ~ 65,535
signed int 4 int -2,147,483,648 ~ 2,147,483,647
unsigned int 4 unsigned 0 ~ 4,294,967,295
signed long int LLP64: 4
LP64: 8
long -2,147,483,648 ~ 2,147,483,647 / -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
unsigned long int LLP64: 4
LP64: 8
unsigned long 0 ~ 4,294,967,295 / 0 ~ 18,446,744,073,709,551,615
signed long long int 8 long long -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
unsigned long long int 8 unsigned long long 0 ~ 18,446,744,073,709,551,615
  • Windows は LLP64 を、Linux, macOS は LP64 モデルを採用している。

浮動小数点型

型名 サイズ(バイト) 説明
float 4 単精度浮動小数点数
double 8 倍精度浮動小数点数
long double 8 または 16 倍精度浮動小数点数 / 拡張倍精度浮動小数点数

文字型

型名 サイズ(バイト) 説明
char 1
wchar_t 2 または 4 ワイド文字型
char8_t 1 UTF-8 文字型
char16_t 2 UTF-16 文字型
char32_t 4 UTF-32 文字型

真偽値型

型名 サイズ(バイト)
bool 1 true または false

#include <print>

int main()
{
	int a = 100;
	std::println("a = {}", a);

	unsigned long long b = 10000000000000000000ULL;
	std::println("b = {}", b);

	double c = 3.14;
	std::println("c = {}", c);

	char d = 'A';
	std::println("d = {}", d);

	bool e = true;
	std::println("e = {}", e);
}
出力
a = 100
b = 10000000000000000000
c = 3.14
d = A
e = true

2. サイズが規定された整数型の別名

  • 処理系による整数型のサイズの違いを吸収するため、ビット数が規定された整数型の別名が、標準ライブラリ <cstdint>(シーエスティーディーイント)で提供される。
  • さまざまな環境を想定したコードを書く場合、これらの型名を使うことが推奨される。
型名 サイズ(バイト) 範囲
std::int8_t 1 -128 ~ 127
std::uint8_t 1 0 ~ 255
std::int16_t 2 -32,768 ~ 32,767
std::uint16_t 2 0 ~ 65,535
std::int32_t 4 -2,147,483,648 ~ 2,147,483,647
std::uint32_t 4 0 ~ 4,294,967,295
std::int64_t 8 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
std::uint64_t 8 0 ~ 18,446,744,073,709,551,615
  • 具体的には、基本型の別名として、std::int_Ntstd::uint_Nt が宣言される。
例 1
typedef signed char        int8_t;
typedef long long          int64_t;
例 2
typedef signed char        int8_t;
typedef long               int64_t;

3. 変数

  • 基本型の変数は、明示的に値を初期化しない場合、不定値が入る。基本型の変数の値を読む前には、必ず初期化が必要である。

#include <print>

int main()
{
	int a = 42;
	std::println("a = {}", a);

	int b = 100, c = 200; // 1 行で複数の変数
	std::println("b = {}, c = {}", b, c);

	int d; // 初期化されていない
	std::println("d = {}", d);
}
出力
a = 42
b = 100, c = 200
d = (不定値)

4. スコープ

  • スコープは、変数が有効な範囲を示す。C++ では、{ } で囲まれたブロックがスコープを構成し、その中で宣言された変数はそのブロック内でのみ有効である。

#include <print>

int main()
{
	// ここではまだ a も b も使えない

	int a = 42;

	// ここでは a は使えるが、b はまだ使えない

	{
		// ここではまだ b は使えない

		int b = 100;

		// ここでは a も b も使える

		std::println("a = {}, b = {}", a, b);
	
	} // b はここでスコープ終了
	
	// ここでは a だけが使える

	std::println("a = {}", a);
}
出力
a = 42, b = 100
a = 42

  • 特定のスコープの変数名と、その外側のスコープの変数名が同じ場合、スコープ内の変数が優先される。これを Shadowing(シャドウイング)という。
  • 紛らわしさが生じるため、Shadowing はなるべく避けるべきである。
#include <print>

int main()
{
	int a = 42;
	{
		int a = 100; // 別の変数で Shadowing
		std::println("a = {}", a);
	}
	std::println("a = {}", a);
}
出力
a = 100
a = 42
  • 次のようなコードは Shadowing ではない。

#include <print>

int main()
{
	{
		int a = 42;
		std::println("a = {}", a);
	} // a はここでスコープ終了

	{
		int a = 100;
		std::println("a = {}", a);
	}
}
出力
a = 42
a = 100

  • C++ では、変数のスコープが狭いほど、その変数が使われる範囲が限定されるため、プログラムのメンテナンス性が向上する。

5. 標準入力

  • 標準入力からの入力は、<iostream>std::cin(シーイン)を使う。std::cin は、>>(入力)演算子を使って、変数に値を読み込む。

#include <iostream>
#include <print>

int main()
{
	int a; // 直後に cin を使うので、初期化しなくてもよい
	std::cin >> a;
	std::println("a = {}", a);

	double b;
	std::cin >> b;
	std::println("b = {}", b);
}
入力
42
3.14
出力
a = 42
b = 3.14

  • >> 演算子を連ねて、複数の変数に値を読み込むこともできる。
  • 空白や改行による区切りか、パースできない文字が出るまで入力文字列を消費し、値を読み込む。

#include <iostream>
#include <print>

int main()
{
	int a, b;
	std::cin >> a >> b;
	std::println("a = {}, b = {}", a, b);
}
入力
42 100
出力
a = 42, b = 100

#include <iostream>
#include <print>

int main()
{
	int a, b;
	char c;
	std::cin >> a >> c >> b;
	std::println("a = {}, b = {}", a, b);
	std::println("c = {}", c);
}
入力
12/31
出力
a = 12, b = 31
c = /

6. コメント

  • C++ のコメントは C 言語と同様に、//(スラッシュ・スラッシュ)で始まる行コメントと、/*(スラッシュ・アスタリスク)と */ で囲まれるブロックコメント。
  • ブロックコメントはネストできない。
int main()
{
	// 1 行コメント

	/*
		複数行コメント
	*/
}

コメントのテクニック 1

  • 先頭の / の有無を変更するだけで、ブロックコメントを有効・無効にできる。
#include <iostream>
#include <print>

int main()
{
	//*
	int x;
	std::cin >> x;
	std::println("x = {}", x);
	//*/
}
#include <iostream>
#include <print>

int main()
{
	/*
	int x;
	std::cin >> x;
	std::println("x = {}", x);
	//*/
}

コメントのテクニック 2

  • 先頭の / の有無を変更するだけで、ブロックコメントの範囲を変更できる。
#include <iostream>
#include <print>

int main()
{
	//*
	int x;
	std::cin >> x;
	std::println("x * x = {}", x * x);
	/*/
	int x, y;
	std::cin >> x >> y;
	std::println("x + y = {}", x + y);
	//*/
}
#include <iostream>
#include <print>

int main()
{
	/*
	int x;
	std::cin >> x;
	std::println("x * x = {}", x * x);
	/*/
	int x, y;
	std::cin >> x >> y;
	std::println("x + y = {}", x + y);
	//*/
}

7. 演算子

  • おもな演算子を紹介する。

7.1 算術演算子

演算子 名前 説明
+a 単項プラス a の値をそのまま返す
-a 単項マイナス a の符号を反転した値を返す
a + b 加算 a と b の和を返す
a - b 減算 a から b を引いた値を返す
a * b 乗算 a と b の積を返す
a / b 除算 a を b で割った商を返す。整数どうしの場合、小数以下は切り捨て。
a % b 剰余 a を b で割った余りを返す。浮動小数点数型には使えない。

#include <print>

int main()
{
	int a = 24, b = 10;
	std::println("a + b = {}", a + b);
	std::println("a - b = {}", a - b);
	std::println("a * b = {}", a * b);
	std::println("a / b = {}", a / b);
	std::println("a % b = {}", a % b);
}
出力
a + b = 34
a - b = 14
a * b = 240
a / b = 2
a % b = 4

7.2 ビット演算子

演算子 名前 説明
~a ビット否定 a のビットを反転した値を返す
a & b ビット積 a と b のビットごとの積を返す
a | b ビット和 a と b のビットごとの和を返す
a ^ b 排他的論理和 a と b のビットごとの排他的論理和を返す
a << b 左シフト a のビットを b ビット左にシフトする
a >> b 右シフト a のビットを b ビット右にシフトする

#include <print>

int main()
{
	unsigned a = 0b1010, b = 0b1100;
	std::println("~a = {:b}", ~a);
	std::println("a & b = {:04b}", a & b);
	std::println("a | b = {:04b}", a | b);
	std::println("a ^ b = {:04b}", a ^ b);
	std::println("a << 2 = {:b}", a << 2);
	std::println("a >> 2 = {:b}", a >> 2);
}
出力
~a = 11111111111111111111111111110101
a & b = 1000
a | b = 1110
a ^ b = 0110
a << 2 = 101000
a >> 2 = 10

  • 符号付き整数に対する << は論理シフトとなり、対応する符号無し整数の左シフトとビット表現が同じになる。
  • 符号付き整数に対する >> は算術シフトとなり、対応する符号無し整数の右シフトとはビット表現が異なる。

#include <print>

int main()
{
	int a = 1;
	int b = 1 << 31;
	std::println("{}", a);
	std::println("{}", b);
	std::println("{:b}", (unsigned)a);
	std::println("{:b}", (unsigned)b);

	std::println("--------------------");

	int c = -161;
	int d = -161 >> 1;
	std::println("{}", c);
	std::println("{}", d);
	std::println("{:b}", (unsigned)c);
	std::println("{:b}", (unsigned)d);
}
出力
1
-2147483648
1
10000000000000000000000000000000
--------------------
-161
-81
11111111111111111111111101011111
11111111111111111111111110101111

7.3 比較演算子

演算子 名前 説明
a == b 等しい a と b が等しい場合 true
a != b 等しくない a と b が等しくない場合 true
a < b より小さい a が b より小さい場合 true
a <= b 以下 a が b 以下の場合 true
a > b より大きい a が b より大きい場合 true
a >= b 以上 a が b 以上の場合 true
a <=> b 三方比較 比較する型と値の大小に応じた定数

#include <print>

int main()
{
	int a = 42, b = 100;
	std::println("a == b: {}", a == b);
	std::println("a != b: {}", a != b);
	std::println("a < b: {}", a < b);
	std::println("a <= b: {}", a <= b);
	std::println("a > b: {}", a > b);
	std::println("a >= b: {}", a >= b);
}
出力
a == b: false
a != b: true
a < b: true
a <= b: true
a > b: false
a >= b: false

7.4 論理演算子

演算子 名前 説明
!a 論理否定 a が false の場合 true
a && b 論理積 a と b がともに true の場合 true
a || b 論理和 a または b のどちらかが true の場合 true

#include <print>

int main()
{
	bool a = true, b = false;
	std::println("!a: {}", !a);
	std::println("a && b: {}", a && b);
	std::println("a || b: {}", a || b);
}
出力
!a: false
a && b: false
a || b: true

7.5 代入演算子

演算子 名前 説明
a = b 代入 a に b の値を代入する

#include <print>

int main()
{
	int a = 42;
	a = 100;
	std::println("a = {}", a);
}
出力
a = 100

7.6 複合代入演算子

演算子 名前 説明
a += b 加算代入 a + b の結果を a に代入する
a -= b 減算代入 a - b の結果を a に代入する
a *= b 乗算代入 a * b の結果を a に代入する
a /= b 除算代入 a / b の結果を a に代入する
a %= b 剰余代入 a % b の結果を a に代入する
a &= b ビット積代入 a & b の結果を a に代入する
a |= b ビット和代入 a | b の結果を a に代入する
a ^= b 排他的論理和代入 a ^ b の結果を a に代入する
a <<= b 左シフト代入 a << b の結果を a に代入する
a >>= b 右シフト代入 a >> b の結果を a に代入する

#include <print>

int main()
{
	int a = 42;
	a += 100;
	std::println("a = {}", a);

	unsigned b = 0b1010;
	b <<= 2;
	std::println("b = {:b}", b);
}
出力
a = 142
b = 101000

7.7 インクリメント・デクリメント演算子

演算子 名前 説明
++a 前置(ぜんち)インクリメント a の値を 1 増やしてから、その値を返す
a++ 後置(こうち)インクリメント a の値を返してから、その値を 1 増やす
--a 前置デクリメント a の値を 1 減らしてから、その値を返す
a-- 後置デクリメント a の値を返してから、その値を 1 減らす

#include <print>

int main()
{
	int a = 100;
	std::println("++a = {}", ++a);
	std::println("a = {}", a);

	int b = 100;
	std::println("b++ = {}", b++);
	std::println("b = {}", b);
}
出力
++a = 101
a = 101
b++ = 100
b = 101

7.8 添字(そえじ)演算子

演算子 名前 説明
a[b] 添字 a の b 番目の要素にアクセス

#include <print>

int main()
{
	int a[] = {1, 2, 3};
	std::println("a[0] = {}", a[0]);
	std::println("a[1] = {}", a[1]);
	std::println("a[2] = {}", a[2]);
}
出力
a[0] = 1
a[1] = 2
a[2] = 3

7.9 間接参照演算子

演算子 名前 説明
*a 間接参照 a が指す要素へアクセス

#include <print>

int main()
{
	int a = 42;
	int* p = &a;
	std::println("*p = {}", *p);
}
出力
*p = 42

7.10 アドレス演算子

演算子 名前 説明
&a アドレス a のアドレスを返す

#include <print>

int main()
{
	int a = 42;
	std::println("&a = {}", (void*)&a);
}
出力
&a = (アドレス)

7.11 関数呼び出し演算子

演算子 名前 説明
a(b, c, ...) 関数呼び出し b, c, ... を引数として関数 a を呼び出す

#include <print>

int Add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 42, b = 100;
	std::println("Add(a, b) = {}", Add(a, b));
}
出力
add(a, b) = 142

7.12 条件演算子

演算子 名前 説明
a ? b : c 条件演算子 a が true なら b, false なら c を返す

#include <print>

int main()
{
	int a = 42, b = 100;
	int c = (a < b) ? a : b;
	std::println("c = {}", c);
}
出力
c = 42

  • 選ばれなかったほうの式は評価されない。

7.13 演算子の優先順位

  • 表: C++ Operator Precedence
  • 1 つの式に複数の演算子が含まれる場合、表中の演算子の優先順位に従って評価される。
  • ( ) で囲むことで、演算子の優先順位を変更できる。
  • 優先順位が同じ場合、表中の結合法則(Associativity)の順に評価される。

7.14 演算子のオーバーロード

  • 7.1 ~ 7.12 の演算子は、ユーザー定義型に対しての挙動を定義できる。これを演算子のオーバーロードという。
  • のちの章で詳しく説明する。

8. 変数の名前の付け方

  • 使える文字は、アルファベット、数字、アンダースコア _
  • 大文字と小文字は区別される。
  • 数字で始めることはできない。
  • 予約語は使えない。
  • 連続するアンダースコアを含んだり(例えば a__b)、アンダースコアのあとに大文字から始まる名前(例えば _Cpp)は、コンパイラや標準ライブラリ側が特別な用途で使うため避ける。
変数名の例 有効な名前か?
price OK
Price OK, ただし変数名は小文字から始まるのが一般的
itemPrice OK
item_price OK
itemPrice2 OK
item price NG(間にスペースがあってはいけない)
2itemPrice NG(数字から始まってはいけない)
itemPrice! NG(! は使えない)
item__price NG(連続するアンダースコアは避ける)
_Price NG(アンダースコアと大文字から始まる名前は避ける)

力試し

Day 2 までの知識で解ける練習問題。最後に解答例あり。

  • 為替レートと、店舗が設定するスプレッド(手数料)が次の形式で標準入力から与えられる。
  • 入力に基づいたレート表を、客にわかりやすいと思う任意の形式で出力せよ。
  • レート表に続いて、1 万円を各通貨に両替する場合の金額も任意の形式で出力せよ。
入力の形式
eur
jpy
gbp
aud
cad
cny
inr
chf
spread
入力データ 意味
eur 1 ユーロが何 US ドルであるか
jpy 1 日本円が何 US ドルであるか
gbp 1 スターリング・ポンドが何 US ドルであるか
aud 1 オーストラリアドルが何 US ドルであるか
cad 1 カナダドルが何 US ドルであるか
cny 1 人民元が何 US ドルであるか
inr 1 インドルピーが何 US ドルであるか
chf 1 スイスフランが何 US ドルであるか
spread 店舗のスプレッド(手数料の割合)

為替レートが 1 US ドル 150 円で、スプレッドが 0.01 (1%) であるとき、US ドルの売値は 151.5 円、買値は 148.5 円となる。

入力例
1.05286
0.00668
1.21488
0.63917
0.7339
0.13678
0.01202
1.09163
0.015
出力例
=== Currency Exchange ===
     We sell    We buy
USD: 151.95     147.46
EUR: 159.98     155.25

...

客が 1 US ドルを買うには 151.95 円必要で、客が 1 US ドルを渡すと 147.46 円を受け取れること、1 ユーロを買うには 159.98 円必要で、1 ユーロを渡すと 155.25 円を受け取れることを伝える内容となっている。

解答例
#include <iostream>
#include <print>

int main()
{
	// 標準入力から 8 つの為替レートを受け取る
	double eur, jpy, gbp, aud, cad, cny, inr, chf;
	std::cin >> eur >> jpy >> gbp >> aud >> cad >> cny >> inr >> chf;

	// 標準入力からスプレッドを受け取る
	double spread;
	std::cin >> spread;

	// 各通貨の為替レート(JPY)を計算する
	double usd_jpy = (1.0 / jpy);
	double eur_jpy = (usd_jpy * eur); // (eur / jpy) でも同じ
	double gbp_jpy = (usd_jpy * gbp);
	double aud_jpy = (usd_jpy * aud);
	double cad_jpy = (usd_jpy * cad);
	double cny_jpy = (usd_jpy * cny);
	double inr_jpy = (usd_jpy * inr);
	double chf_jpy = (usd_jpy * chf);

	// スプレッド
	double sellSpread = (1.0 + spread);
	double buySpread = (1.0 - spread);

	std::println("=== Currency Exchange ===");
	std::println("     We sell\tWe buy");
	std::println("USD: {:.5}\t{:.5}", (usd_jpy * sellSpread), (usd_jpy * buySpread));
	std::println("EUR: {:.5}\t{:.5}", (eur_jpy * sellSpread), (eur_jpy * buySpread));
	std::println("GBP: {:.5}\t{:.5}", (gbp_jpy * sellSpread), (gbp_jpy * buySpread));
	std::println("AUD: {:.5}\t{:.5}", (aud_jpy * sellSpread), (aud_jpy * buySpread));
	std::println("CAD: {:.5}\t{:.5}", (cad_jpy * sellSpread), (cad_jpy * buySpread));
	std::println("CNY: {:.5}\t{:.5}", (cny_jpy * sellSpread), (cny_jpy * buySpread));
	std::println("INR: {:.5}\t{:.5}", (inr_jpy * sellSpread), (inr_jpy * buySpread));
	std::println("CHF: {:.5}\t{:.5}", (chf_jpy * sellSpread), (chf_jpy * buySpread));

	// 1 万円を各通貨に両替した場合の金額
	std::println("\n=== Exchange 10,000 JPY ===");
	std::println("USD: {:.2f}", (10000.0 / (usd_jpy * sellSpread)));
	std::println("EUR: {:.2f}", (10000.0 / (eur_jpy * sellSpread)));
	std::println("GBP: {:.2f}", (10000.0 / (gbp_jpy * sellSpread)));
	std::println("AUD: {:.2f}", (10000.0 / (aud_jpy * sellSpread)));
	std::println("CAD: {:.2f}", (10000.0 / (cad_jpy * sellSpread)));
	std::println("CNY: {:.2f}", (10000.0 / (cny_jpy * sellSpread)));
	std::println("INR: {:.2f}", (10000.0 / (inr_jpy * sellSpread)));
	std::println("CHF: {:.2f}", (10000.0 / (chf_jpy * sellSpread)));
}