Loose-Info.com
Last Update 2023/12/15
TOP - 各種テスト - C++ - 宣言

概要

記憶域(storage)クラス指定子
static
extern

typedef
using宣言
usingディレクティブ
usingによるエイリアス宣言(typedefとの比較)

型(type)修飾子
const

型指定子
enum(列挙型)

名前空間
名前空間使用例
名前空間エイリアス
名前なし名前空間
inline キーワード

言語リンケージに関する挙動確認

属性構文
アライメント指定子 alignas
deprecated 属性
fallthrough 属性
maybe_unused 属性
nodiscard 属性
noreturn 属性

ポインタ
リファレンス(参照)
メンバへのポインタ
配列
初期化子
文字配列
リファレンス
リスト初期化


記憶域(storage)クラス指定子 static


sample.cpp
#include <iostream> int n1; // グローバル変数を宣言 static int n2; // static指定子によりグローバル変数を宣言 void sampfunc() { int n3 = 20; // sampfunc()内で変数を宣言 static int n4; // sampfunc()内でstatic指定子により変数を宣言 n1++; n2++; n3++; n4++; std::cout << "sampfunc() --->" << std::endl; std::cout << " n1 = " << n1 << std::endl; std::cout << " n2 = " << n2 << std::endl; std::cout << " n3 = " << n3 << std::endl; std::cout << " n4 = " << n4 << std::endl; std::cout << "<--- sampfunc()" << std::endl; } int main() { n1 = 10; n2 = 10; int n3 = 10; // main()内で変数を宣言 int n4 = 10; // main()内で変数を宣言 std::cout << "n1(1) = " << n1 << std::endl; std::cout << "n2(1) = " << n2 << std::endl; std::cout << "n3(1) = " << n3 << std::endl; std::cout << "n4(1) = " << n4 << std::endl; sampfunc(); std::cout << "n1(2) = " << n1 << std::endl; std::cout << "n2(2) = " << n2 << std::endl; std::cout << "n3(2) = " << n3 << std::endl; std::cout << "n4(2) = " << n4 << std::endl; sampfunc(); std::cout << "n1(3) = " << n1 << std::endl; std::cout << "n2(3) = " << n2 << std::endl; std::cout << "n3(3) = " << n3 << std::endl; std::cout << "n4(3) = " << n4 << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out n1(1) = 10 <--- static宣言無しのグローバル変数 n2(1) = 10 <--- static宣言されたのグローバル変数 n3(1) = 10 <--- 関数内で宣言されたローカル変数 n4(1) = 10 <--- 関数内でstatic指定子により変数を宣言 sampfunc() ---> n1 = 11 n2 = 11 n3 = 21 n4 = 1 <--- sampfunc() n1(2) = 11 n2(2) = 11 n3(2) = 10 n4(2) = 10 sampfunc() ---> n1 = 12 n2 = 12 n3 = 21 n4 = 2 <--- 宣言した関数内での静的変数 <--- sampfunc() n1(3) = 12 <--- static宣言無しのグローバル変数も静的変数として機能 n2(3) = 12 <--- static宣言されたのグローバル変数 n3(3) = 10 <--- main()のローカル変数(呼び出し関数の影響なし) n4(3) = 10 <--- main()のローカル変数(呼び出し関数の影響なし)

記憶域(storage)クラス指定子 extern


sample1.cpp
#include <iostream> // 関数外でのextern指定子による宣言 extern int extern_n1; void func() { // func関数内でのextern指定子による宣言 extern int extern_n2; extern_n1++; extern_n2 += 10; std::cout << "func() --- extern_n1 = " << extern_n1 << " : extern_n2 =" << extern_n2 << std::endl; } int main() { // main関数内でのextern指定子による宣言 extern int extern_n2; for (int i=0; i<3; i++) { func(); extern_n2++; std::cout << "extern_n2 = " << extern_n2 << std::endl; } }

sample2.cpp
// sample2.cpp - 変数初期化 int extern_n1 = 0; int extern_n2 = 10;

実行結果
extern指定子による宣言は、場所を問わずに静的記憶域期間を持つ
$ gcc -Wall sample1.cpp sample2.cpp -lstdc++ $ ./a.out func() --- extern_n1 = 1 : extern_n2 =20 extern_n2 = 21 func() --- extern_n1 = 2 : extern_n2 =31 extern_n2 = 32 func() --- extern_n1 = 3 : extern_n2 =42 extern_n2 = 43

typedef


sample.cpp
#include <iostream> // typedef名td_intをintの同義語として宣言 typedef int td_int; // typedef名td_cをクラスSamp_cの同義語として宣言 typedef struct Samp_c { int n; } td_class; void func0(int , const char *); void func1(int , const char *); /* typedef名td_funcを戻り値の無い 引数(int, const char*)を伴う関数へのポインタの同義語として宣言 */ typedef void (*td_func)(int, const char*); // 引数にtypedef名td_funcを使用する関数のプロトタイプ宣言 void func10(int , td_func); int main() { td_int sample1 = 1; td_class sample2; sample2.n = 2; std::cout << "sample1 = " << sample1 << std::endl; std::cout << "sample2.n = " << sample2.n << std::endl; func10(10, func0); func10(10, func1); } void func0(int i, const char *s) { std::cout << "func0(" << i * 10 << ", \"" << s << "\"))" << std::endl; } void func1(int i, const char *s) { std::cout << "func1(" << i * 100 << ", \"" << s << "\"))" << std::endl; } void func10(int i, td_func func) { std::cout << "func10(" << i << ", "; (*func)(i, "abc"); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out sample1 = 1 sample2.n = 2 func10(10, func0(100, "abc")) func10(10, func1(1000, "abc"))

using宣言


sample.cpp
#include <iostream> #include <iomanip> // 基底クラス class S0 { long double d; public: void get1(long double x) { std::cout << x << std::endl; } void get2(long double x) { std::cout << x << std::endl; } }; struct Smpl1 : public S0 { // 基底クラスのメンバをusing宣言 using S0::get1; void get1(int n) { std::cout << n << std::endl; get1(static_cast<long double>(n)); // 基底クラスの関数を呼び出し // using宣言が無い場合はSmpl内で再帰呼び出し } }; // 基底クラスのアクセス制御がprivate struct Smpl2 : private S0 { // 基底クラスのメンバをusing宣言 using S0::get2; }; namespace Smpl3 { void get3(long double x) { std::cout << x << std::endl; } } namespace Smpl4 { using Smpl3::get3; // Smpl3::get3()を参照 } int main() { Smpl1 s1; Smpl2 s2; std::cout << std::scientific << std::setprecision(17); s1.get1(123); s2.get2(1.23456789012345678L); Smpl4::get3(2.34567890123456789L); // Smpl3::get3()の呼び出し }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out 123 1.23000000000000000e+02 1.23456789012345678e+00 2.34567890123456789e+00

usingディレクティブ


sample.cpp
#include <iostream> int n = 1; int x = 5; namespace Nam1 { int n = 10; void f1() { std::cout << "Nam1::f1() : n = " << ++n << std::endl; } void f2() { std::cout << "Nam1::f2() : x = " << ++x << std::endl; } } namespace Nam2 { int n = 100; void f3() { std::cout << "Nam2::f3() : n = " << ++n << std::endl; } namespace Nam3 { int n = 1000; void f4() { std::cout << "Nam2::Nam3::f4() : n = " << ++n << std::endl; } } } int main() { std::cout << "変数初期値" << std::endl; std::cout << "n = " << n << std::endl; std::cout << "x = " << x << std::endl; std::cout << "Nam1::n = " << Nam1::n << std::endl; std::cout << "Nam2::n = " << Nam2::n << std::endl; std::cout << "Nam2::Nam3::n = " << Nam2::Nam3::n << std::endl; std::cout << std::endl; // usingディレクティブで指定された名前空間の名前が、 // 以降のスコープ内で使用可能となる using namespace Nam1; using namespace Nam2; using namespace Nam2::Nam3; f1(); f2(); f3(); f4(); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out 変数初期値 n = 1 x = 5 Nam1::n = 10 Nam2::n = 100 Nam2::Nam3::n = 1000 Nam1::f1() : n = 11 Nam1::f2() : x = 6 Nam2::f3() : n = 101 Nam2::Nam3::f4() : n = 1001

usingによるエイリアス宣言(typedefとの比較)


sample.cpp
#include <iostream> // typedefによる宣言 typedef int int_typedef; typedef void (*f_typedef)(int); // usingによるエイリアス宣言 using int_using = int; using f_using = void (*)(int); void f(int n) { std::cout << "f(n) = " << "f(" << n << ")" << std::endl; } int main() { int_typedef int1 = 100; int_using int2 = 101; f_typedef f1 = &f; f1(int1); f_using f2 = &f; f2(int2); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out f(n) = f(100) f(n) = f(101)

型(type)修飾子 const


sample1.cpp
型修飾子const無し
#include <iostream> int main() { int samp_int = 1; std::cout << "samp_int = " << samp_int << std::endl; samp_int = 2; std::cout << "samp_int = " << samp_int << std::endl; char samp_str1[] = "abc"; std::cout << "samp_str1 = " << samp_str1 << std::endl; samp_str1[1] = 'd'; std::cout << "samp_str1 = " << samp_str1 << std::endl; char samp_str2[] = "aaa"; char *samp_p1 = samp_str2; samp_p1++; *samp_p1 = 'e'; std::cout << "samp_p1 = " << samp_p1 << std::endl; char *samp_p2 = samp_str2; samp_p2++; *samp_p2 = 'e'; std::cout << "samp_p2 = " << samp_p2 << std::endl; }

実行結果
$ gcc -Wall sample1.cpp -lstdc++ $ ./a.out samp_int = 1 samp_int = 2 samp_str1 = abc samp_str1 = adc samp_p1 = ea samp_p2 = ea

sample2.cpp
#include <iostream> int main() { const int samp_int = 1; std::cout << "samp_int = " << samp_int << std::endl; samp_int = 2; std::cout << "samp_int = " << samp_int << std::endl; const char samp_str1[] = "abc"; std::cout << "samp_str1 = " << samp_str1 << std::endl; samp_str1[1] = 'd'; std::cout << "samp_str1 = " << samp_str1 << std::endl; char samp_str2[] = "aaa"; const char *samp_p1 = samp_str2; samp_p1++; *samp_p1 = 'e'; std::cout << "samp_p1 = " << samp_p1 << std::endl; char *const samp_p2 = samp_str2; samp_p2++; *samp_p2 = 'e'; std::cout << "samp_p2 = " << samp_p2 << std::endl; }

実行結果
$ gcc -Wall sample2.cpp -lstdc++ sample2.cpp: In function ‘int main()’: sample2.cpp:8:18: error: assignment of read-only variable ‘samp_int’ 8 | samp_int = 2; <--- 変数 samp_int はconstにより変更不可 | ~~~~~~~~~^~~ sample2.cpp:14:22: error: assignment of read-only location ‘samp_str1[1]’ 14 | samp_str1[1] = 'd'; <--- 変数 samp_str1 はconstにより変更不可 | ~~~~~~~~~~~~~^~~~~ sample2.cpp:21:18: error: assignment of read-only location ‘* samp_p1’ 21 | *samp_p1 = 'e'; <--- ポインタ samp_p1 が指すオブジェクトはconstにより変更不可 | ~~~~~~~~~^~~~~ sample2.cpp:25:9: error: increment of read-only variable ‘samp_p2’ 25 | samp_p2++; <--- ポインタ samp_p2 そのものはconstにより変更不可 | ^~~~~~~

型指定子


sample.cpp
#include <iostream> #include <typeinfo> int main() { std::cout << "各種型指定子および実装定義型名" << std::endl; std::cout << std::endl; std::cout << "char : " << typeid(char).name() << std::endl; std::cout << "unsigned_char : " << typeid(unsigned char).name() << std::endl; std::cout << "signed_char : " << typeid(signed char).name() << std::endl; std::cout << "char16_t : " << typeid(char16_t).name() << std::endl; std::cout << "char32_t : " << typeid(char32_t).name() << std::endl; std::cout << "bool : " << typeid(bool).name() << std::endl; std::cout << "unsigned : " << typeid(unsigned).name() << std::endl; std::cout << "unsigned int : " << typeid(unsigned int).name() << std::endl; std::cout << "signed : " << typeid(signed).name() << std::endl; std::cout << "signed int : " << typeid(signed int).name() << std::endl; std::cout << "int : " << typeid(int).name() << std::endl; std::cout << "unsigned short int : " << typeid(unsigned short int).name() << std::endl; std::cout << "unsigned short : " << typeid(unsigned short).name() << std::endl; std::cout << "unsigned long int : " << typeid(unsigned long int).name() << std::endl; std::cout << "unsigned long : " << typeid(unsigned long).name() << std::endl; std::cout << "unsigned long long int : " << typeid(unsigned long long int).name() << std::endl; std::cout << "unsigned long long : " << typeid(unsigned long long).name() << std::endl; std::cout << "signed long int : " << typeid(signed long int).name() << std::endl; std::cout << "signed long : " << typeid(signed long).name() << std::endl; std::cout << "signed long long int : " << typeid(signed long long int).name() << std::endl; std::cout << "signed long long : " << typeid(signed long long).name() << std::endl; std::cout << "long long int : " << typeid(long long int).name() << std::endl; std::cout << "long long : " << typeid(long long).name() << std::endl; std::cout << "long int : " << typeid(long int).name() << std::endl; std::cout << "long : " << typeid(long).name() << std::endl; std::cout << "signed short int : " << typeid(signed short int).name() << std::endl; std::cout << "signed short : " << typeid(signed short).name() << std::endl; std::cout << "short int : " << typeid(short int).name() << std::endl; std::cout << "short : " << typeid(short).name() << std::endl; std::cout << "wchar_t : " << typeid(wchar_t).name() << std::endl; std::cout << "float : " << typeid(float).name() << std::endl; std::cout << "double : " << typeid(double).name() << std::endl; std::cout << "long double : " << typeid(long double).name() << std::endl; std::cout << "void : " << typeid(void).name() << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out 各種型指定子および実装定義型名 char : c unsigned_char : h signed_char : a char16_t : Ds char32_t : Di bool : b unsigned : j unsigned int : j signed : i signed int : i int : i unsigned short int : t unsigned short : t unsigned long int : m unsigned long : m unsigned long long int : y unsigned long long : y signed long int : l signed long : l signed long long int : x signed long long : x long long int : x long long : x long int : l long : l signed short int : s signed short : s short int : s short : s wchar_t : w float : f double : d long double : e void : v

enum(列挙型)


sample.cpp
#include <iostream> // 識別子・初期化子無し enum { ABC, DEF, GHI }; // 識別子無し・初期化子有り・リスト終端コンマ enum { JKL = 10, MNO, }; // 識別子有り enum en1 { PQR = 20, STU }; // スコープ付き列挙(class) enum class E1 : int { ABC = 100, DEF, GHI }; // スコープ付き列挙(struct) enum struct E2 : int { ABC = 200, DEF, GHI }; struct S { // クラススコープで宣言された列挙 enum es {ABCD = 300, EFGI}; // クラススコープ列挙のクラスメンバ es x; S() : x(EFGI) {} }; void f() { // f()内スコープ enum { f_ABC = 40, f_DEF }; int samp_int1 = DEF; int samp_int2 = JKL; int samp_int3 = f_DEF; en1 samp_en1 = STU; std::cout << " --- samp_int1 = " << samp_int1 << std::endl; std::cout << " --- samp_int2 = " << samp_int2 << std::endl; std::cout << " --- samp_int3 = " << samp_int3 << std::endl; std::cout << " --- samp_en1 = " << samp_en1 << std::endl; } int main() { std::cout << "enum(列挙型)" << std::endl; std::cout << std::endl; // main()内スコープ enum { m_ABC = 30, m_DEF }; // 識別子・初期化子無し列挙を整数変数に代入 int samp_int1 = DEF; int samp_int2 = JKL; int samp_int3 = m_DEF; // 識別子の有る列挙型オブジェクト en1 samp_en1 = STU; // スコープ付き列挙型のオブジェクト E1 samp_e1 = E1::DEF; E2 samp_e2 = E2::DEF; // クラススコープの列挙型をもつクラスオブジェクト S s; // 列挙子を格納した整数の出力 std::cout << "samp_int1 = " << samp_int1 << std::endl; std::cout << "samp_int2 = " << samp_int2 << std::endl; std::cout << "samp_int3 = " << samp_int3 << std::endl; std::cout << "samp_en1 = " << samp_en1 << std::endl; // スコープ付き列挙型オブジェクトの出力 // スコープ付きは明示的型変換が必要 std::cout << "samp_e1(class) = " << static_cast<int>(samp_e1) << std::endl; std::cout << "samp_e2(struct) = " << static_cast<int>(samp_e2) << std::endl; // クラススコープ列挙のクラスメンバの出力 std::cout << "s.x = " << s.x << std::endl; // クラス内の列挙子の参照 std::cout << "s.ABCD = " << s.ABCD << std::endl; // クラスオブジェクトとドット演算子 std::cout << "S::EFGI = " << S::EFGI << std::endl; // クラス名とスコープ解決演算子 std::cout << std::endl; f(); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out enum(列挙型) samp_int1 = 1 samp_int2 = 10 samp_int3 = 31 samp_en1 = 21 samp_e1(class) = 101 samp_e2(struct) = 201 s.x = 301 s.ABCD = 300 S::EFGI = 301 --- samp_int1 = 1 --- samp_int2 = 10 --- samp_int3 = 41 --- samp_en1 = 21

名前空間使用例


sample.cpp
#include <iostream> int n = 1; int x = 5; namespace Nam1 { int n = 10; // Nam1内で宣言されたnを出力する関数 void f1() { std::cout << "Nam1::f1() : n = " << ++n << std::endl; } // グローバル変数として宣言されたxを出力する関数 void f2() { std::cout << "Nam1::f2() : x = " << ++x << std::endl; } } namespace Nam2 { int n = 100; // Nam2内で宣言されたnを出力する関数 void f1() { std::cout << "Nam2::f1() : n = " << ++n << std::endl; } namespace Nam3 { // Nam2内で宣言されたnを出力する関数 void f2() { std::cout << "Nam2::Nam3::f2() : n = " << ++n << std::endl; } int n = 1000; // Nam3内で宣言されたnを出力する関数 void f3() { std::cout << "Nam2::Nam3::f3() : n = " << ++n << std::endl; } // Nam3内で宣言されたnを出力する関数(宣言のみ) void f5(); } // Nam1内で宣言されたnを出力する関数 // スコープ解決演算子使用 void f4() { std::cout << "Nam2::f4() : Nam1::n = " << ++Nam1::n << std::endl; } // Nam3内で宣言されたf5の関数定義 void Nam3::f5() { std::cout << "Nam2::Nam3::f5() : n = " << ++n << std::endl; } } // グローバル変数として宣言されたnを出力する関数 void f1() { std::cout << "f1() : n = " << n << std::endl; } // Nam2内で宣言されたnを出力する関数 // スコープ解決演算子使用 void f2() { std::cout << "f2() : Nam2::n = " << ++Nam2::n << std::endl; } int main() { std::cout << "変数初期値" << std::endl; std::cout << "n = " << n << std::endl; std::cout << "x = " << x << std::endl; std::cout << "Nam1::n = " << Nam1::n << std::endl; std::cout << "Nam2::n = " << Nam2::n << std::endl; std::cout << "Nam2::Nam3::n = " << Nam2::Nam3::n << std::endl; std::cout << std::endl; f1(); f2(); Nam1::f1(); Nam1::f2(); Nam2::f1(); Nam2::Nam3::f2(); Nam2::Nam3::f3(); Nam2::f4(); Nam2::Nam3::f5(); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out 変数初期値 n = 1 x = 5 Nam1::n = 10 Nam2::n = 100 Nam2::Nam3::n = 1000 f1() : n = 1 f2() : Nam2::n = 101 Nam1::f1() : n = 11 Nam1::f2() : x = 6 Nam2::f1() : n = 102 Nam2::Nam3::f2() : n = 103 Nam2::Nam3::f3() : n = 1001 Nam2::f4() : Nam1::n = 12 Nam2::Nam3::f5() : n = 1002

名前空間エイリアス


sample.cpp
「名前空間使用例」のsample.cppをエイリアスを使用して書き換え
#include <iostream> int n = 1; int x = 5; namespace Nam1 { int n = 10; void f1(); void f2(); } namespace Nam2 { int n = 100; void f1(); namespace Nam3 { // 親の名前空間(Nam2)で宣言されたnを出力する関数 // 名前空間エイリアスを使用してスコープ外で定義した場合は、 // Nam3内のnが使用されるため、この位置で宣言・定義 void f2() { std::cout << "C::f2() : n = " << ++n << std::endl; } // f2()の宣言・定義後に宣言された変数n int n = 1000; void f3(); void f5(); } void f4(); } // グローバル変数として宣言されたnを出力する関数 void f1() { std::cout << "f1() : n = " << n << std::endl; } // 名前空間エイリアスの宣言 namespace A = Nam1; namespace B = Nam2; namespace C = Nam2::Nam3; // B内で宣言されたnを出力する関数 // スコープ解決演算子使用 void f2() { std::cout << "f2() : B::n = " << ++B::n << std::endl; } // A内で宣言されたnを出力する関数 void A::f1() { std::cout << "A::f1() : n = " << ++n << std::endl; } // グローバル変数として宣言されたxを出力する関数 void A::f2() { std::cout << "A::f2() : x = " << ++x << std::endl; } // B内で宣言されたnを出力する関数 void B::f1() { std::cout << "B::f1() : n = " << ++n << std::endl; } // C内で宣言されたnを出力する関数 void C::f3() { std::cout << "C::f3() : n = " << ++n << std::endl; } // C内で宣言されたf5の関数定義 void C::f5() { std::cout << "C::f5() : n = " << ++n << std::endl; } // Nam1内で宣言されたnを出力する関数 void B::f4() { std::cout << "B::f4() : A::n = " << ++A::n << std::endl; } int main() { std::cout << "変数初期値" << std::endl; std::cout << "n = " << n << std::endl; std::cout << "x = " << x << std::endl; std::cout << "A::n = " << A::n << std::endl; std::cout << "B::n = " << B::n << std::endl; std::cout << "C::n = " << C::n << std::endl; std::cout << std::endl; f1(); f2(); A::f1(); A::f2(); B::f1(); C::f2(); C::f3(); B::f4(); C::f5(); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out 変数初期値 n = 1 x = 5 A::n = 10 B::n = 100 C::n = 1000 f1() : n = 1 f2() : B::n = 101 A::f1() : n = 11 A::f2() : x = 6 B::f1() : n = 102 C::f2() : n = 103 C::f3() : n = 1001 B::f4() : A::n = 12 C::f5() : n = 1002

名前なし名前空間


sample1.cpp
#include <iostream> namespace { // 名前なし名前空間 int n = 100; int m = 10; void f1() { std::cout << "sample1.cpp --- f1() --- n = " << n << std::endl; } } extern void f2(); int main() { std::cout << "sample1.cpp --- m = " << m << std::endl; std::cout << "sample1.cpp --- n = " << n << std::endl; f1(); std::cout << std::endl; f2(); }

sample2.cpp
#include <iostream> namespace { // 名前なし名前空間 int n = 200; int m = 20; void f1() { std::cout << "sample2.cpp --- f1() --- n = " << n << std::endl; } } void f2() { std::cout << "sample2.cpp --- f2() --- m = " << m << std::endl; std::cout << "sample2.cpp --- f2() --- n = " << n << std::endl; f1(); }

実行結果
$ gcc -Wall sample1.cpp sample2.cpp -lstdc++ $ ./a.out sample1.cpp --- m = 10 sample1.cpp --- n = 100 sample1.cpp --- f1() --- n = 100 sample2.cpp --- f2() --- m = 20 sample2.cpp --- f2() --- n = 200 sample2.cpp --- f1() --- n = 200


名前なし名前空間を使用せずに sample1.cpp と sample2.cpp をコンパイル

sample1.cpp(変更後)
#include <iostream> int n = 100; int m = 10; void f1() { std::cout << "sample1.cpp --- f1() --- n = " << n << std::endl; } extern void f2(); int main() { std::cout << "sample1.cpp --- m = " << m << std::endl; std::cout << "sample1.cpp --- n = " << n << std::endl; f1(); std::cout << std::endl; f2(); }

sample2.cpp(変更後)
#include <iostream> int n = 200; int m = 20; void f1() { std::cout << "sample2.cpp --- f1() --- n = " << n << std::endl; } void f2() { std::cout << "sample2.cpp --- f2() --- m = " << m << std::endl; std::cout << "sample2.cpp --- f2() --- n = " << n << std::endl; f1(); }

実行結果
$ gcc -Wall sample1.cpp sample2.cpp -lstdc++ /usr/bin/ld: /tmp/ccIo7WUU.o:(.data+0x0): multiple definition of `n'; /tmp/ccyTh54v.o:(.data+0x0): first defined here /usr/bin/ld: /tmp/ccIo7WUU.o:(.data+0x4): multiple definition of `m'; /tmp/ccyTh54v.o:(.data+0x4): first defined here /usr/bin/ld: /tmp/ccIo7WUU.o: in function `f1()': sample2.cpp:(.text+0x0): multiple definition of `f1()'; /tmp/ccyTh54v.o:sample1.cpp:(.text+0x0): first defined here collect2: error: ld returned 1 exit status 複数定義に関するエラー発生

inline キーワード


sample.cpp
#include <iostream> namespace M { int m1 = 0; int n1 = 0; int m2 = 0; int n2 = 0; } inline namespace N { // inline により N の外側へ int m1 = 10; int n1 = 100; namespace N1 { int n2 = 200; inline namespace N12 { // inline により N12 の外側へ int m2 = 20; } } inline namespace N2 { // inline により N2 の外側へ ---> N の外側へ int m2 = 40; namespace N21 { int n2 = 400; } inline namespace N22 { // inline により N22 の外側へ ---> N2 の外側へ ---> N の外側へ int n2 = 500; } } } int main() { std::cout << "m1 = " << m1 << std::endl; std::cout << "n1 = " << n1 << std::endl; std::cout << "m2 = " << m2 << std::endl; std::cout << "n2 = " << n2 << std::endl; std::cout << std::endl; std::cout << "M::m1 = " << M::m1 << std::endl; std::cout << "M::n1 = " << M::n1 << std::endl; std::cout << "M::m2 = " << M::m2 << std::endl; std::cout << "M::n2 = " << M::n2 << std::endl; std::cout << std::endl; std::cout << "N::m1 = " << N::m1 << " --- inline" << std::endl; std::cout << "N::n1 = " << N::n1 << " --- inline" << std::endl; std::cout << "N::m2 = " << N::m2 << std::endl; std::cout << "N::n2 = " << N::n2 << std::endl; std::cout << std::endl; std::cout << "N1::m2 = " << N1::m2 << std::endl; std::cout << "N1::n2 = " << N1::n2 << std::endl; std::cout << "N1::N12::m2 = " << N1::N12::m2 << std::endl; std::cout << std::endl; std::cout << "N2::m2 = " << N2::m2 << " --- inline" << std::endl; std::cout << "N2::n2 = " << N2::n2 << std::endl; std::cout << "N21::n2 = " << N21::n2 << std::endl; std::cout << "N22::n2 = " << N22::n2 << std::endl; std::cout << std::endl; std::cout << "N2::N21::n2 = " << N2::N21::n2 << std::endl; std::cout << "N2::N22::n2 = " << N2::N22::n2 << " --- inline" << std::endl; std::cout << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out m1 = 10 n1 = 100 m2 = 40 n2 = 500 M::m1 = 0 M::n1 = 0 M::m2 = 0 M::n2 = 0 N::m1 = 10 --- inline N::n1 = 100 --- inline N::m2 = 40 N::n2 = 500 N1::m2 = 20 <--- inline の効果は N1 内まで N1::n2 = 200 N1::N12::m2 = 20 N2::m2 = 40 --- inline N2::n2 = 500 N21::n2 = 400 N22::n2 = 500 N2::N21::n2 = 400 N2::N22::n2 = 500 --- inline

言語リンケージに関する挙動確認


sample.cpp
#include <iostream> int main() { // C言語で記述された関数 puts("sample --- puts()"); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out sample --- puts() $ gcc -Wall sample.cpp -lstdc++ -E > sample_e1.txt

上記の実行結果で生成された sample_e1.txt
1 # 0 "sample.cpp" 2 # 0 "<built-in>" 3 # 0 "<command-line>" 22439 # 1 "/usr/include/bits/libc-header-start.h" 1 3 4 22440 # 28 "/usr/include/stdio.h" 2 3 4 22441 22442 extern "C" { 22443 22444 22445 22446 # 1 "/usr/lib/gcc/x86_64-pc-linux-gnu/12.2.0/include/stddef.h" 1 3 4 22447 # 34 "/usr/include/stdio.h" 2 3 4 23095 extern int __uflow (FILE *); 23096 extern int __overflow (FILE *, int); 23097 # 909 "/usr/include/stdio.h" 3 4 23098 } 23099 # 43 "/usr/include/c++/12.2.0/cstdio" 2 3 33735 # 3 "sample.cpp" 33736 int main() 33737 { 33738 33739 puts("sample --- puts()"); 33740 }

生成結果のファイルをコピー
$ cp sample_e1.txt sample_e2.txt

sample_e2.txt の以下の言語リンケージを削除
22442 extern "C" { ↓ 22442 // extern "C" { 23098 } ↓ 23098 // }

sample_e1.txt と sample_e2.txt をそれぞれコンパイル
$ gcc -Wall -x c++ sample_e1.txt -lstdc++ <--- エラー無しでコンパイル $ ./a.out sample --- puts() $ gcc -Wall -x c++ sample_e2.txt -lstdc++ /usr/bin/ld: /tmp/cc2AuHFW.o: in function `main': sample_e2.txt:(.text+0xa): undefined reference to `puts(char const*)' collect2: error: ld returned 1 exit status <--- extern "C"を削除した場合(デフォルト)は関数への未定義参照エラーが発生

sample_e2.txt の以下の箇所に言語リンケージを追加
22942 extern int puts (const char *__s); ↓ 22942 extern "C" int puts (const char *__s);

sample_e2.txt をコンパイル
$ gcc -Wall -x c++ sample_e2.txt -lstdc++ <--- エラー無しでコンパイル $ ./a.out sample --- puts()

sample_e2.txt の以下の言語リンケージを "C++" に変更
22942 extern "C" int puts (const char *__s); ↓ 22942 extern "C++" int puts (const char *__s);

sample_e2.txt をコンパイル
$ gcc -Wall -x c++ sample_e2.txt -lstdc++ /usr/bin/ld: /tmp/ccfhyNPI.o: in function `main': sample_e2.txt:(.text+0xa): undefined reference to `puts(char const*)' collect2: error: ld returned 1 exit status <--- "C++"とした場合はデフォルトと同じく関数への未定義参照エラーが発生

アライメント指定子 alignas


sample.cpp
#include <iostream> int main() { std::cout << "1バイト境界でアラインメント" << std::endl; alignas(1) unsigned char buf11[] = { 1, 2, 3 }; alignas(1) unsigned char buf12[] = { 1, 2 }; alignas(1) unsigned char buf13[] = { 1, 2, 3 }; alignas(1) unsigned char buf14[] = { 1 }; std::cout << &buf11 << std::endl; std::cout << &buf12 << std::endl; std::cout << &buf13 << std::endl; std::cout << &buf14 << std::endl; std::cout << std::endl; std::cout << "4バイト境界でアラインメント" << std::endl; alignas(4) unsigned char buf21[] = { 1, 2, 3 }; alignas(4) unsigned char buf22[] = { 1, 2 }; alignas(4) unsigned char buf23[] = { 1, 2, 3 }; alignas(4) unsigned char buf24[] = { 1 }; std::cout << &buf21 << std::endl; std::cout << &buf22 << std::endl; std::cout << &buf23 << std::endl; std::cout << &buf24 << std::endl; std::cout << std::endl; std::cout << "long int型のアラインメントを適用" << std::endl; alignas(long int) unsigned char buf31[] = { 1, 2, 3 }; alignas(long int) unsigned char buf32[] = { 1, 2 }; alignas(long int) unsigned char buf33[] = { 1, 2, 3 }; alignas(long int) unsigned char buf34[] = { 1 }; std::cout << &buf31 << std::endl; std::cout << &buf32 << std::endl; std::cout << &buf33 << std::endl; std::cout << &buf34 << std::endl; std::cout << std::endl; std::cout << "16バイト境界でアラインメント" << std::endl; alignas(16) unsigned char buf41[] = { 1, 2, 3 }; alignas(16) unsigned char buf42[] = { 1, 2 }; alignas(16) unsigned char buf43[] = { 1, 2, 3 }; alignas(16) unsigned char buf44[] = { 1 }; std::cout << &buf41 << std::endl; std::cout << &buf42 << std::endl; std::cout << &buf43 << std::endl; std::cout << &buf44 << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out 1バイト境界でアラインメント 0x7fffcd04f8dd 0x7fffcd04f8db 0x7fffcd04f8d8 0x7fffcd04f8d7 4バイト境界でアラインメント 0x7fffcd04f8d4 0x7fffcd04f8d0 0x7fffcd04f8cc 0x7fffcd04f8c8 long int型のアラインメントを適用 0x7fffcd04f8c0 0x7fffcd04f8b8 0x7fffcd04f8b0 0x7fffcd04f8a8 16バイト境界でアラインメント 0x7fffcd04f8a0 0x7fffcd04f890 0x7fffcd04f880 0x7fffcd04f870

deprecated 属性


sample.cpp
#include <iostream> [[deprecated("非推奨の根拠を説明する文字列リテラル")]] void viewstr(char *s) { printf("<cstdio>(<iostream>) - printf() ---> %s\n", s); } int main() { char s[] = "sample"; viewstr(s); std::cout << "<iostream> - std::cout ---> " << s << std::endl; std::cout << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ <--- コンパイル時に「非推奨」の警告が発生 sample.cpp: In function ‘int main()’: sample.cpp:12:16: warning: ‘void viewstr(char*)’ is deprecated: 非推奨の根拠を説明する文字列リテラル [-Wdeprecated-declarations] 12 | viewstr(s); | ~~~~~~~^~~ sample.cpp:3:61: note: declared here 3 | [[deprecated("非推奨の根拠を説明する文字列リテラル")]] void viewstr(char *s) | ^~~~~~~ $ ./a.out <--- エラーの発生は無く実行ファイルは生成される <cstdio>(<iostream>) - printf() ---> sample <iostream> - std::cout ---> sample

fallthrough 属性


sample.cpp
break文によりフォールスルーの発生は無し
#include <iostream> #include <string> int main(int argc, char *argv[]) { if (argc > 1) { std::string sarg = argv[1]; char& c = sarg[0]; if ((c >= '0') && (c <='4')) { switch (c - '0') { case 0: std::cout << "a --- "; break; case 1: std::cout << "b --- "; break; case 2: std::cout << "c --- "; break; case 3: std::cout << "d --- "; break; case 4: std::cout << "e --- "; break; default: std::cout << c; } } std::cout << std::endl; } else { std::cout << "引数無し" << std::endl; } }

実行結果
$ gcc -Wall sample.cpp -lstdc++ <--- フォールスルーに関するオプション無しでコンパイル $ ./a.out 0 a --- $ gcc -Wall -Wimplicit-fallthrough=5 sample.cpp -lstdc++ <--- フォールスルーに関するオプション(属性のみが警告を抑制)でコンパイル $ ./a.out 0 a ---

sample.cpp の全てのbreak文をコメントアウト
#include <iostream> #include <string> int main(int argc, char *argv[]) { if (argc > 1) { std::string sarg = argv[1]; char& c = sarg[0]; if ((c >= '0') && (c <='4')) { switch (c - '0') { case 0: std::cout << "a --- "; // break; case 1: std::cout << "b --- "; // break; case 2: std::cout << "c --- "; // break; case 3: std::cout << "d --- "; // break; case 4: std::cout << "e --- "; // break; default: std::cout << c; } } std::cout << std::endl; } else { std::cout << "引数無し" << std::endl; } }

実行結果
$ gcc -Wall sample.cpp -lstdc++ <--- フォールスルーに関するオプション無しでコンパイル $ ./a.out 0 a --- b --- c --- d --- e --- 0 $ gcc -Wall -Wimplicit-fallthrough=5 sample.cpp -lstdc++ <--- フォールスルーに関するオプション(属性のみが警告を抑制)でコンパイル sample.cpp: In function ‘int main(int, char**)’: sample.cpp:16:46: warning: this statement may fall through [-Wimplicit-fallthrough=] <--- 以下、フォールスルー発生に対する警告 16 | std::cout << "a --- "; | ^~~~~~~~ sample.cpp:19:25: note: here 19 | case 1: | ^~~~ sample.cpp:20:46: warning: this statement may fall through [-Wimplicit-fallthrough=] 20 | std::cout << "b --- "; | ^~~~~~~~ sample.cpp:23:25: note: here 23 | case 2: | ^~~~ sample.cpp:24:46: warning: this statement may fall through [-Wimplicit-fallthrough=] 24 | std::cout << "c --- "; | ^~~~~~~~ sample.cpp:27:25: note: here 27 | case 3: | ^~~~ sample.cpp:28:46: warning: this statement may fall through [-Wimplicit-fallthrough=] 28 | std::cout << "d --- "; | ^~~~~~~~ sample.cpp:31:25: note: here 31 | case 4: | ^~~~ sample.cpp:32:46: warning: this statement may fall through [-Wimplicit-fallthrough=] 32 | std::cout << "e --- "; | ^~~~~~~~ sample.cpp:35:25: note: here 35 | default: | ^~~~~~~ $ ./a.out 0 a --- b --- c --- d --- e --- 0

コメントアウトしたbreakの位置にfallthrough 属性を追加
#include <iostream> #include <string> int main(int argc, char *argv[]) { if (argc > 1) { std::string sarg = argv[1]; char& c = sarg[0]; if ((c >= '0') && (c <='4')) { switch (c - '0') { case 0: std::cout << "a --- "; [[fallthrough]]; // break; case 1: std::cout << "b --- "; [[fallthrough]]; // break; case 2: std::cout << "c --- "; [[fallthrough]]; // break; case 3: std::cout << "d --- "; [[fallthrough]]; // break; case 4: std::cout << "e --- "; [[fallthrough]]; // break; default: std::cout << c; } } std::cout << std::endl; } else { std::cout << "引数無し" << std::endl; } }

実行結果
$ gcc -Wall -Wimplicit-fallthrough=5 sample.cpp -lstdc++ <--- 警告無しでコンパイル終了 $ ./a.out 0 a --- b --- c --- d --- e --- 0

maybe_unused 属性


sample.cpp
#include <iostream> int main() { // 使用される変数 int i0 = 1; // 未使用変数 int i1 = 2; // maybe_unused 属性のある未使用変数 [[maybe_unused]] int i2 = 3; std::cout << "i0 = " << i0 << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ sample.cpp: In function ‘int main()’: sample.cpp:9:13: warning: unused variable ‘i1’ [-Wunused-variable] <--- 未使用・[[maybe_unused]]無しの場合、警告発生 9 | int i1 = 2; | ^~ $ ./a.out i0 = 1

nodiscard 属性


sample.cpp
#include <iostream> // nodiscard 属性の指定無し int f0() { return 0; } // nodiscard 属性の指定有り [[nodiscard]] int f1() { return 0; } int main() { // 関数の戻り値を破棄 f0(); f1(); // 関数の戻り値を使用 std::cout << "f0() = " << f0() << std::endl; std::cout << "f1() = " << f1() << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ sample.cpp: In function ‘int main()’: ↓↓↓ 属性指定・戻り値破棄の場合、警告発生 sample.cpp:19:11: warning: ignoring return value of ‘int f1()’, declared with attribute ‘nodiscard’ [-Wunused-result] 19 | f1(); | ~~^~ sample.cpp:10:19: note: declared here 10 | [[nodiscard]] int f1() | ^~

noreturn 属性


sample.cpp
#include <iostream> // ********** 返らない関数 ********** // noreturn 属性の指定無し void e0() { std::exit(0); } // noreturn 属性の指定有り [[noreturn]] void e1() { std::exit(0); } // ************ 空の関数 ************ // noreturn 属性の指定無し void e2() { } // noreturn 属性の指定有り [[noreturn]] void e3() { } // ******** 戻り値の有る関数 ******** // noreturn 属性の指定無し int e4() { return 0; } // noreturn 属性の指定有り [[noreturn]] int e5() { return 0; } int main() { e0(); e1(); e2(); e3(); e4(); e5(); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ sample.cpp: In function ‘int e5()’: ↓↓↓ noreturn属性を指定した関数にreturn文がある事に対する警告 sample.cpp:40:16: warning: function declared ‘noreturn’ has a ‘return’ statement 40 | return 0; | ^ sample.cpp: In function ‘void e3()’: sample.cpp:27:1: warning: ‘noreturn’ function does return <--- noreturn属性を指定した関数が「戻る」事に対する警告 27 | } | ^ sample.cpp: In function ‘int e5()’: sample.cpp:40:16: warning: ‘noreturn’ function does return <--- noreturn属性を指定した関数が「戻る」事に対する警告 40 | return 0; | ^

ポインタ


sample.cpp
#include <iostream> int main() { int i0 = 0; // int型 int i1 = 1; // int型 int a0[5] = { 5, 6, 7 }; // int型配列 const int i2 = 8; // int型(const) // int型へのポインタ int *pi0 = &i0; // int型(配列)へのポインタ int *pa0 = &a0[0]; int *pa1 = &a0[2]; // int型(const)へのポインタ // ポインタが指すオブジェクトの値(*pi2)の変更は不可 const int *pi2 = &i2; // int型へのポインタ定数 // ポインタが指すオブジェクト(pi3)の変更は不可 int *const pi3 = &i0; // int型へのポインタのポインタ int **pp0 = &pa0; // int型へのポインタ定数へのポインタ // 「ポインタのポインタ」が指す「ポインタのオブジェクト(*pp1)」の変更は不可 int i3 = 9, *const pi4 = &i3, *const *pp1 = &pi4; std::cout << "i0 = " << i0 << " (" << &i0 << ")" << std::endl; std::cout << "i1 = " << i1 << " (" << &i1 << ")" << std::endl; std::cout << "a0[0] = " << a0[0] << " (" << &a0[0] << ")" << std::endl; std::cout << "a0[1] = " << a0[1] << " (" << &a0[1] << ")" << std::endl; std::cout << "a0[2] = " << a0[2] << " (" << &a0[2] << ")" << std::endl; std::cout << "i3 = " << i3 << " (" << &i3 << ")" << std::endl; std::cout << std::endl; std::cout << "*pi0 = " << *pi0 << " (pi0 : " << pi0 << ")" << std::endl; std::cout << "*pa0 = " << *pa0 << " (pa0 : " << pa0 << ")" << std::endl; std::cout << "*pa1 = " << *pa1 << " (pa1 : " << pa1 << ")" << std::endl; std::cout << "*pi2 = " << *pi2 << " (pi2 : " << pi2 << ")" << std::endl; std::cout << "**pp0 = " << **pp0 << " (*pp0 : " << *pp0 << ")" << std::endl; std::cout << "*pp0 = " << *pp0 << " (pp0 : " << pp0 << ")" << std::endl; std::cout << "*pi4 = " << *pi4 << " (pi4 : " << pi4 << ")" << std::endl; std::cout << "**pp1 = " << **pp1 << " (*pp1 : " << *pp1 << ")" << std::endl; std::cout << "*pp1 = " << *pp1 << " (pp1 : " << pp1 << ")" << std::endl; std::cout << std::endl; // ポインタが指すオブジェクトを変更 pi0 = &i1; std::cout << "pi0 = &i1 ---> *pi0 = " << *pi0 << " (pp0 : " << pi0 << ")" << std::endl; // ポインタの加算・減算 pa0++; pa1--; std::cout << "pa0++ ---> *pa0 = " << *pa0 << " (pa0 : " << pa0 << ")" << std::endl; std::cout << "pa1-- ---> *pa1 = " << *pa1 << " (pa1 : " << pa1 << ")" << std::endl; // int型(const)へポインタが指すオブジェクトを変更 pi2 = &i1; std::cout << "pi2 = &i1 ---> *pi2 = " << *pi2 << " (pi2 : " << pi2 << ")" << std::endl; // ポインタ定数が指すオブジェクトの値を変更 (*pi3)++; std::cout << "(*pi3)++ ---> *pi3 = " << *pi3 << " (pi3 : " << pi3 << ")" << std::endl; // 「ポインタのポインタ」が指すポインタを変更 pp0 = &pa1; std::cout << "pp0 = &pa1 ---> **pp0 = " << **pp0 << " (*pp0 : " << *pp0 << ")" << std::endl; std::cout << "pp0 = &pa1 ---> *pp0 = " << *pp0 << " (pp0 : " << pp0 << ")" << std::endl; // 「ポインタ定数へのポインタ」が指すポインタを変更 pp1 = &pa1; std::cout << "pp1 = &pa1 ---> **pp1 = " << **pp1 << " (*pp1 : " << *pp1 << ")" << std::endl; std::cout << "pp1 = &pa1 ---> *pp1 = " << *pp1 << " (pp1 : " << pp1 << ")" << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out i0 = 0 (0x7ffd3c82da04) i1 = 1 (0x7ffd3c82da00) a0[0] = 5 (0x7ffd3c82d9e0) a0[1] = 6 (0x7ffd3c82d9e4) a0[2] = 7 (0x7ffd3c82d9e8) i3 = 9 (0x7ffd3c82d9c4) *pi0 = 0 (pi0 : 0x7ffd3c82da04) *pa0 = 5 (pa0 : 0x7ffd3c82d9e0) *pa1 = 7 (pa1 : 0x7ffd3c82d9e8) *pi2 = 8 (pi2 : 0x7ffd3c82d9dc) **pp0 = 5 (*pp0 : 0x7ffd3c82d9e0) *pp0 = 0x7ffd3c82d9e0 (pp0 : 0x7ffd3c82d9d0) *pi4 = 9 (pi4 : 0x7ffd3c82d9c4) **pp1 = 9 (*pp1 : 0x7ffd3c82d9c4) *pp1 = 0x7ffd3c82d9c4 (pp1 : 0x7ffd3c82d9b8) pi0 = &i1 ---> *pi0 = 1 (pp0 : 0x7ffd3c82da00) pa0++ ---> *pa0 = 6 (pa0 : 0x7ffd3c82d9e4) pa1-- ---> *pa1 = 6 (pa1 : 0x7ffd3c82d9e4) pi2 = &i1 ---> *pi2 = 1 (pi2 : 0x7ffd3c82da00) (*pi3)++ ---> *pi3 = 1 (pi3 : 0x7ffd3c82da04) pp0 = &pa1 ---> **pp0 = 6 (*pp0 : 0x7ffd3c82d9e4) pp0 = &pa1 ---> *pp0 = 0x7ffd3c82d9e4 (pp0 : 0x7ffd3c82d9c8) pp1 = &pa1 ---> **pp1 = 6 (*pp1 : 0x7ffd3c82d9e4) pp1 = &pa1 ---> *pp1 = 0x7ffd3c82d9e4 (pp1 : 0x7ffd3c82d9c8)

リファレンス(参照)


sample.cpp
#include <iostream> // 参照渡しと値渡しとして引数を受け取る関数 void f0(int& i, int j) { ++i; ++j; } // 引数を左辺値参照として受け取る関数 void f1(int& i) { std::cout << "lvalue reference ---> i = " << i << std::endl; } // 引数を右辺値参照として受け取る関数 void f1(int&& i) { std::cout << "rvalue reference ---> i = " << i << std::endl; } int main() { int i0 = 0; int i1 = 1; int i2 = 1; std::cout << "各変数の初期値とアドレス" << std::endl; std::cout << "i0 = " << i0 << " (&i0 : " << &i0 << ")" << std::endl; std::cout << "i1 = " << i1 << " (&i1 : " << &i1 << ")" << std::endl; std::cout << "i2 = " << i2 << " (&i2 : " << &i2 << ")" << std::endl; std::cout << std::endl; // i0 のリファレンス(参照)を作成(初期設定必須) int& r0 = i0; std::cout << "r0 = " << r0 << " (&r0 : " << &r0 << ")" << std::endl; std::cout << std::endl; // r0 を使用した演算(i0 に関する演算) r0++; std::cout << "r0++ を実行" << std::endl; std::cout << " i0 = " << i0 << " (&i0 : " << &i0 << ")" << std::endl; std::cout << " r0 = " << r0 << " (&r0 : " << &r0 << ")" << std::endl; std::cout << std::endl; // j0 を r0 を初期値として生成 int j0 = r0; std::cout << "j0 = " << j0 << " (&j0 : " << &j0 << ")" << std::endl; std::cout << std::endl; // j0 を使用した演算(初期値としての r0 には影響無し) j0++; std::cout << "j0++ を実行" << std::endl; std::cout << " i0 = " << i0 << " (&i0 : " << &i0 << ")" << std::endl; std::cout << " r0 = " << r0 << " (&r0 : " << &r0 << ")" << std::endl; std::cout << " j0 = " << j0 << " (&j0 : " << &j0 << ")" << std::endl; std::cout << std::endl; // 参照渡しと値渡しとして引数を受け取る関数の実行 f0(i1, i2); std::cout << "f0(i1, i2) を実行" << std::endl; std::cout << " void f0(int& i, int j)" << std::endl; std::cout << " ++i;" << std::endl; std::cout << " ++j;" << std::endl; std::cout << " i1 = " << i1 << " (&i1 : " << &i1 << ")" << std::endl; std::cout << " i2 = " << i2 << " (&i2 : " << &i2 << ")" << std::endl; std::cout << std::endl; // 左辺値を引数として関数 f1 を実行 std::cout << "f1(i1) を実行" << std::endl; f1(i1); // 右辺値を引数として関数 f1 を実行 std::cout << "f1(1) を実行" << std::endl; f1(1); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out 各変数の初期値とアドレス i0 = 0 (&i0 : 0x7ffca99f5be0) i1 = 1 (&i1 : 0x7ffca99f5bdc) i2 = 1 (&i2 : 0x7ffca99f5bd8) r0 = 0 (&r0 : 0x7ffca99f5be0) r0++ を実行 i0 = 1 (&i0 : 0x7ffca99f5be0) r0 = 1 (&r0 : 0x7ffca99f5be0) j0 = 1 (&j0 : 0x7ffca99f5bd4) j0++ を実行 i0 = 1 (&i0 : 0x7ffca99f5be0) r0 = 1 (&r0 : 0x7ffca99f5be0) j0 = 2 (&j0 : 0x7ffca99f5bd4) f0(i1, i2) を実行 void f0(int& i, int j) ++i; ++j; i1 = 2 (&i1 : 0x7ffca99f5bdc) i2 = 1 (&i2 : 0x7ffca99f5bd8) f1(i1) を実行 lvalue reference ---> i = 2 f1(1) を実行 rvalue reference ---> i = 1

メンバへのポインタ


sample.cpp
#include <iostream> struct Samp { int n0; int n1; Samp(int i1, int i2) : n0(i1), n1(i2) { } int g0() { return n0;} int g1() { return n1;} }; int main() { // クラスメンバへのポインタを宣言 int Samp::* p = &Samp::n0; int (Samp::* pf)() = &Samp::g0; // Sampクラスのオブジェクトを生成 Samp s(5, 10); // Sampクラスへのポインタを生成 Samp *ps = &s; // int型メンバへのポインタで、メンバ n0、n1 へアクセス std::cout << "int Samp::* p = &Samp::n0" << std::endl; std::cout << " s.*p = " << s.*p << std::endl; std::cout << " ps->*p = " << ps->*p << std::endl; p = &Samp::n1; std::cout << "p = &Samp::n1" << std::endl; std::cout << " s.*p = " << s.*p << std::endl; std::cout << " ps->*p = " << ps->*p << std::endl; std::cout << std::endl; // メンバ関数へのポインタで、メンバ関数 g0, g1 を呼び出し std::cout << "int (Samp::* pf)() = &Samp::g0" << std::endl; std::cout << " (s.*pf)() = " << (s.*pf)() << std::endl; std::cout << " (ps->*pf)() = " << (ps->*pf)() << std::endl; pf = &Samp::g1; std::cout << "pf = &Samp::g1" << std::endl; std::cout << " (s.*pf)() = " << (s.*pf)() << std::endl; std::cout << " (ps->*pf)() = " << (ps->*pf)() << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out int Samp::* p = &Samp::n0 s.*p = 5 ps->*p = 5 p = &Samp::n1 s.*p = 10 ps->*p = 10 int (Samp::* pf)() = &Samp::g0 (s.*pf)() = 5 (ps->*pf)() = 5 pf = &Samp::g1 (s.*pf)() = 10 (ps->*pf)() = 10

配列


sample.cpp
#include <iostream> #include <iomanip> // バイト列表示用関数 void byteseq(const unsigned char *cp, std::size_t nlen, const char *tname) { std::cout << std::uppercase << std::hex; std::cout << tname << " = "; for (std::size_t i=0; i<nlen; i++) { std::cout << std::setw(2) << std::setfill('0') << static_cast<int>(cp[i]) << " "; } std::cout << std::endl; } struct Samp { int n0; int n1; Samp() : n0(0x1234), n1(0xABCD) {} int g0() { return n0;} int g1() { return n1;} }; int main(int argc, char *argv[]) { constexpr std::size_t n0 = 5; unsigned char n1 = 3; // 要素数「5」で配列を宣言 char a0[3]; a0[0] = 1; // 要素「0」に1を代入 a0[2] = 3; // 要素「2」に3を代入 byteseq(reinterpret_cast<unsigned char *>(a0), sizeof(a0), "a0[]"); std::cout << std::endl; // constexpr 指定したstd::size_t型変数を要素数として宣言 // {} で初期化 char a1[n0] = { 'a', 'b', 'c' }; std::cout << "n0 = " << n0 << std::endl; byteseq(reinterpret_cast<unsigned char *>(a1), sizeof(a1), "a1[]"); std::cout << std::endl; // unsigned char型変数を要素数として宣言 char a2[n1] = { 'a', 'b', 'c' }; std::cout << "n1 = " << static_cast<int>(n1) << std::endl; byteseq(reinterpret_cast<unsigned char *>(a2), sizeof(a2), "a2[]"); std::cout << std::endl; // main()関数の引数 argc を要素数としてchar へのポインタの配列を宣言 char *a3[argc]; for (int i=0; i<argc; i++) { a3[i] = argv[i]; std::cout << "a3[" << i << "] = " << a3[i] << std::endl; } std::cout << std::endl; // 多次元配列を宣言 int a4[2][2] = {{1, 2}, {3, 4}}; byteseq(reinterpret_cast<unsigned char *>(a4), sizeof(a4), "a4[][]"); std::cout << std::endl; // クラスを要素とする配列を宣言 Samp C0[2]; byteseq(reinterpret_cast<unsigned char *>(C0), sizeof(C0), "C0[]"); std::cout << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out aaa bbb a0[] = 01 00 03 n0 = 5 a1[] = 61 62 63 00 00 n1 = 3 a2[] = 61 62 63 a3[0] = ./a.out a3[1] = aaa a3[2] = bbb a4[][] = 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 C0[] = 34 12 00 00 CD AB 00 00 34 12 00 00 CD AB 00 00

初期化子


sample.cpp
#include <iostream> #include <iomanip> // バイト列表示用関数 void byteseq(const unsigned char *cp, std::size_t nlen, const char *tname) { std::cout << std::uppercase << std::hex; std::cout << tname << " = "; for (std::size_t i=0; i<nlen; i++) { std::cout << std::setw(2) << std::setfill('0') << static_cast<int>(cp[i]) << " "; } std::cout << std::endl; } struct Samp { int n0; int n1; }; // ゼロ初期化 char c0; double d0; unsigned int u0; int main() { byteseq(reinterpret_cast<unsigned char *>(&c0), sizeof(c0), "c0"); byteseq(reinterpret_cast<unsigned char *>(&d0), sizeof(d0), "d0"); byteseq(reinterpret_cast<unsigned char *>(&u0), sizeof(u0), "u0"); // a0[3]~ ゼロ初期化 char a0[10] = { 'a', 'b', 'c' }; byteseq(reinterpret_cast<unsigned char *>(&a0), sizeof(a0), "a0"); // ゼロ初期化 int i0{}; char *p0{}; byteseq(reinterpret_cast<unsigned char *>(&i0), sizeof(i0), "i0"); byteseq(reinterpret_cast<unsigned char *>(&p0), sizeof(p0), "p0"); // ポインタをnullptrで初期化(比較用) char *pn = nullptr; byteseq(reinterpret_cast<unsigned char *>(&pn), sizeof(pn), "pn"); // デフォルト初期化 Samp s0; byteseq(reinterpret_cast<unsigned char *>(&s0), sizeof(s0), "s0"); // コピー初期化 int i1 = 2; byteseq(reinterpret_cast<unsigned char *>(&i1), sizeof(i1), "i1"); // クラスを初期化子リストで初期化 Samp s1{1, 2}; byteseq(reinterpret_cast<unsigned char *>(&s1), sizeof(s1), "s1"); Samp s2 = {3, 4}; byteseq(reinterpret_cast<unsigned char *>(&s2), sizeof(s2), "s2"); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out c0 = 00 d0 = 00 00 00 00 00 00 00 00 u0 = 00 00 00 00 a0 = 61 62 63 00 00 00 00 00 00 00 i0 = 00 00 00 00 p0 = 00 00 00 00 00 00 00 00 pn = 00 00 00 00 00 00 00 00 s0 = 00 00 00 00 00 00 00 00 i1 = 02 00 00 00 s1 = 01 00 00 00 02 00 00 00 s2 = 03 00 00 00 04 00 00 00

初期化子 - 文字配列


sample.cpp
#include <iostream> #include <iomanip> // バイト列表示用関数 void byteseq(const unsigned char *cp, std::size_t nlen, const char *tname) { std::cout << std::uppercase << std::hex; std::cout << tname << " = "; for (std::size_t i=0; i<nlen; i++) { std::cout << std::setw(2) << std::setfill('0') << static_cast<int>(cp[i]) << " "; } std::cout << std::endl; } int main() { // ナロー文字型配列の初期化 char s_chr[] = "abcde"; // char16_t型配列の初期化 char16_t s_c16[] = u"あいうえお"; // char32_t型配列の初期化 char32_t s_c32[] = U"あいうえお"; // wchar_t型配列の初期化 wchar_t s_wct[] = L"あいうえお"; byteseq(reinterpret_cast<unsigned char *>(&s_chr), sizeof(s_chr), "s_chr"); byteseq(reinterpret_cast<unsigned char *>(&s_c16), sizeof(s_c16), "s_c16"); byteseq(reinterpret_cast<unsigned char *>(&s_c32), sizeof(s_c32), "s_c32"); byteseq(reinterpret_cast<unsigned char *>(&s_wct), sizeof(s_wct), "s_wct"); std::cout << std::endl; // ナロー文字・ワイド文字における「\0」のバイト列 char sn0 = '\0'; wchar_t sw0 = '\0'; byteseq(reinterpret_cast<unsigned char *>(&sn0), sizeof(char), "sn0"); byteseq(reinterpret_cast<unsigned char *>(&sw0), sizeof(wchar_t), "sw0"); std::cout << std::endl; // 配列要素に満たない文字列による初期化 char s1[10] = "abcde"; char32_t s2[5] = U"あいう"; byteseq(reinterpret_cast<unsigned char *>(&s1), sizeof(s1), "s1"); byteseq(reinterpret_cast<unsigned char *>(&s2), sizeof(s2), "s2"); }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out s_chr = 61 62 63 64 65 00 s_c16 = 42 30 44 30 46 30 48 30 4A 30 00 00 s_c32 = 42 30 00 00 44 30 00 00 46 30 00 00 48 30 00 00 4A 30 00 00 00 00 00 00 s_wct = 42 30 00 00 44 30 00 00 46 30 00 00 48 30 00 00 4A 30 00 00 00 00 00 00 sn0 = 00 <--- charにおける「\0」 sw0 = 00 00 00 00 <--- wchar_tにおける「\0」 s1 = 61 62 63 64 65 00 00 00 00 00 <--- 明示的に初期化されていな要素は「\0」で初期化 s2 = 42 30 00 00 44 30 00 00 46 30 00 00 00 00 00 00 00 00 00 00 <--- 〃

初期化子 - リファレンス


sample.cpp
#include <iostream> #include <iomanip> // バイト列表示用関数 void byteseq(const unsigned char *cp, std::size_t nlen, const char *tname) { std::cout << std::uppercase << std::hex; std::cout << tname << " = "; for (std::size_t i=0; i<nlen; i++) { std::cout << std::setw(2) << std::setfill('0') << static_cast<int>(cp[i]) << " "; } std::cout << std::endl; } void f(int& i, int j) { ++i; ++j; } int main() { int i = 1; int& ri = i; std::cout << "初期化実行後" << std::endl; std::cout << "i = " << i << std::endl; std::cout << "&i = " << &i << std::endl; std::cout << "ri = " << ri << std::endl; std::cout << "&ri = " << &ri << std::endl; byteseq(reinterpret_cast<unsigned char *>(&ri), sizeof(ri), "ri"); std::cout << std::endl; std::cout << "リファレンスへの代入" << std::endl; ri = 4; std::cout << "i = " << i << std::endl; std::cout << "ri = " << ri << std::endl; std::cout << std::endl; std::cout << "リファレンスへのポインタ" << std::endl; int *pi = &ri; std::cout << "*pi = " << *pi << std::endl; std::cout << "pi = " << pi << std::endl; std::cout << std::endl; std::cout << "配列へのリファレンス" << std::endl; unsigned char u[3] = {1, 2, 3}; unsigned char (&ru)[3] = u; byteseq(reinterpret_cast<unsigned char *>(&u), sizeof(u), "u"); byteseq(reinterpret_cast<unsigned char *>(&ru), sizeof(ru), "ru"); std::cout << "「ru[0] = 4」 を実行" << std::endl; ru[0] = 4; byteseq(reinterpret_cast<unsigned char *>(&u), sizeof(u), "u"); byteseq(reinterpret_cast<unsigned char *>(&ru), sizeof(ru), "ru"); std::cout << std::endl; std::cout << "関数へのリファレンス" << std::endl; void (&rf)(int&, int) = f; int j = 2; std::cout << "「rf(ri, j)」 を実行" << std::endl; rf(ri, j); std::cout << "i = " << i << std::endl; std::cout << "j = " << j << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out 初期化実行後 i = 1 &i = 0x7ffc7b9c8478 ri = 1 &ri = 0x7ffc7b9c8478 ri = 01 00 00 00 リファレンスへの代入 i = 4 ri = 4 リファレンスへのポインタ *pi = 4 pi = 0x7ffc7b9c8478 配列へのリファレンス u = 01 02 03 ru = 01 02 03 「ru[0] = 4」 を実行 u = 04 02 03 ru = 04 02 03 関数へのリファレンス 「rf(ri, j)」 を実行 i = 5 j = 2

初期化子 - リスト初期化


sample.cpp
#include <iostream> #include <iomanip> // バイト列表示用関数 void byteseq(const unsigned char *cp, std::size_t nlen, const char *tname) { std::cout << std::uppercase << std::hex; std::cout << tname << " = "; for (std::size_t i=0; i<nlen; i++) { std::cout << std::setw(2) << std::setfill('0') << static_cast<int>(cp[i]) << " "; } std::cout << std::endl; } struct Samp { Samp(int i, int j) : m(i), n(j) {} private: int m; int n; }; int main() { // リスト初期化 std::cout << "変数定義の初期化" << std::endl; int i0{1}; int i1 = {2}; std::cout << "i0 = " << i0 << std::endl; std::cout << "i1 = " << i1 << std::endl; std::cout << std::endl; std::cout << "new演算子の初期化" << std::endl; Samp *psamp = new Samp{-1, 1}; byteseq(reinterpret_cast<unsigned char *>(psamp), sizeof(Samp), "psamp"); std::cout << std::endl; std::cout << "for文における範囲ベースの初期化" << std::endl; for (int n : {1, 2, 3}) { std::cout << "for n = " << n << std::endl; } std::cout << std::endl; }

実行結果
$ gcc -Wall sample.cpp -lstdc++ $ ./a.out 変数定義の初期化 i0 = 1 i1 = 2 new演算子の初期化 psamp = FF FF FF FF 01 00 00 00 for文における範囲ベースの初期化 for n = 1 for n = 2 for n = 3

実行環境

GNU bash, version 5.1.16
GCC-12.2.0
GNU C Library 2.36
GNU Binutils 2.39


コード例・出力内容中の表記

・実行例中の太字表記部分は、コマンドなどの入力された文字列を示します。
・「」や「...」の着色省略表記は、 実際のソースコードや出力内容などを省略加工した部分を示します。