江添亮のC++入門アウトプット
https://ezoeryou.github.io/cpp-intro/
int main()
{
std::cout << "hello"s ;
}
セミコロンを必ず書く
文字列を足して主力
int main()
{
std::cout << "hello"s + "world"s ;
}
バックスラッシュを文字列で使いたい場合は\と書く
と、改行文字を表す[\n]
std::cout << "aaa\nbbb\nccc"s ;
出力結果
aaa
bbb
ccc
文字列の他にも、整数や浮動小数点数を出力できる
int main()
{
std::cout
<< 3 + 5 << " "s << 3 - 5 << " "s
<< 3 * 5 << " "s << 3 / 5 << " "s
<< 3 % 5 ;
}
演算子*/%は演算子+-よりも優先される。
数学の世界と同じになる。
ただし数値と文字列を足すことはできない
文字列の最後のsはあとで説明するので今は文字列には必ず末尾にsをつける。
変数
int main()
{
int i = 123 ;
double d = 1.23 ;
std::string s = "123"s ;
}
intとdoubleは整数を代入できるが、値が変わる
int main()
{
// 浮動小数点数型を整数型に変換
int a = 3.14 ;
// 3
std::cout << a << "\n"s ;
// 整数型を浮動小数点数型に変換
double d = 123 ;
// 123
std::cout << d ;
int i = 0.9999 ;
// 0
std::cout << i ;
}
少数を整数にすると、小数部が切り捨てられる
関数(function)
int main()
{
// 関数
auto print = [](auto x)
{
std::cout << x << "\n"s ;
} ;
// 関数呼び出し
print(123) ;
print(3.14) ;
print("hello") ;
}
autoは全てに当てはまる変数の型だと思えばいい
これは引数に与えられた値が出力される関数
引数を取らないラムダ式を書く場合は、単に()と書く
。
int main()
{
auto no_args = []()
{
std::cout << "Nothing.\n" ;
} ;
no_args() ;
}
int main()
{
auto plus = []( auto x, auto y )
{ return x + y ; } ;
std::cout
<< plus( 1, 2 ) << "\n"s
<< plus( 1.5, 0.5 ) << "\n"s
<< plus( "123"s, "456"s) ;
}
関数はreturn文を実行すると処理を関数の呼び出し元に返す。
int main()
{
auto f = []()
{
std::cout << "f is called.\n" ;
return 0 ; // ここで処理が戻る
std::cout << "f returned zero.\n" ;
} ;
auto result = f() ;
}
f is called.
return文以降の文が実行されていない
本当の関数
int plus( int x, int y )
{
return x + y ;
}
int main()
{
型をすべて明示的に書かなければならない。型を間違えるとエラー
そして戻り値の型を正しく返さなければならない。
int f()
{
// エラー、return文がない
}
何も値を返さない関数を書く場合は、どの値でもない
という特別な型、void型を関数の戻り値の型として書かなければならないという特別なルールまである。
return文の型が宣言時の型と合っていればよい
void f()
{
// OK
}
コンパイルエラー
原因は4つ
3.4はめったにならないから無視
1.文法エラー
2.意味エラー
3.コンパイラーのバグ
4.コンピューターの故障
・文法エラー
よくある文法エラーとしては、文末のセミコロンを打ち忘れ
auto x = 1 + 1
auto y = x + 1 ;
コンパイルエラー文を見る
expected ‘,’ or ‘;’ before ‘auto’
auto y = x + 1 ;
日本語に翻訳すると以下のようになる。
‘auto’の前に','か';'があるべき
auto y = x + 1 ;
// 戻り値の型
int
// main関数の残りの部分
main() { }
main関数が何も値を返さない場合、return 0したものとみなされる
条件分岐
・複合文
複数の文を{}で囲むことで、一つの文として扱うことができる。これを複合文という
int main()
{
// 複合文開始
{
std::cout << "hello\n"s ;
std::cout << "hello\n"s ;
} // 複合文終了
// 別の複合文
{ std::cout << "world\n"s ; }
// 空の複合文
{ }
}
複合文には’;’はいらない。
変数の見え方・変数は宣言された最も内側の複合文の中でしか使えない。
int main()
{
auto a = 0 ;
{
auto b = 0 ;
{
auto c = 0 ;
// cはここまで使える
}
// bはここまで使える
}
// aはここまで使える
}
これを専門用語では変数の寿命とかブロックスコープ
(block-scope)という。
int main()
{
auto x = 0 ;
{
auto x = 1 ;
{
auto x = 2 ;
// 2
std::cout << x ;
}
// 1
std::cout << x ;
x = 42 ;
// 42
std::cout << x ;
}
// 0
std::cout << x ;
}
条件分岐にif文
int main()
{
auto a = 12345 + 6789 ;
auto b = 8073 * 132 / 5 ;
if ( a < b )
{
// bが大きい
std::cout << b ;
}
else
{
// aが大きい
std::cout << a ;
}
}
・条件式
int main()
{
auto a = 1 == 1 ; // 正しい
auto b = 1 != 1 ; // 間違い
std::cout << a << "\n"s << b ;
}
1
0
条件が正しい場合“1”になり、条件が間違っている
場合“0”になる
trueが1、falseが0となる。
正しい場合にbool型の値trueを、間違っている場合に
bool型の値falseを返す。
!=
&&
||
など使い方がある
標準入力
int main()
{
// 入力を受け取るための変数
std::string x{} ;
// 変数に入力を受け取る
std::cin >> x ;
// 入力された値を出力
std::cout << x ;
}
無限ループ
コードを複数回実行する場合複数回書くのは面倒
int main()
{
std::cout << 1 ;
// ラベルskipまで飛ぶ
goto skip ;
std::cout << 2 ;
// ラベルskip
skip :
std::cout << 3 ;
}
実行結果は
13
2を出力すべき文の実行が飛ばされている
“hello”を無限に出力するプログラム
void hello()
{
std::cout << "hello\n"s ;
}
int main()
{
loop :
hello() ;
goto loop ;
}
プログラムを実行すると、
関数helloが呼ばれる
goto文でラベルloopまで飛ぶ
1に戻る
という処理を行う。
int input()
{
std::cout << ">"s ;
int x {} ;
std::cin >> x ;
return x ;
}
int main()
{
int sum = 0 ;
loop :
// 一度入力を変数に代入
int x = input() ;
// 変数xが0でない場合
if ( x != 0 )
{// 実行
sum = sum + x ;
std::cout << sum << "\n"s ;
goto loop ;
}
// x == 0の場合、ここに実行が移る
// main関数の最後なのでプログラムが終了
}
ユーザーが0を入力した場合に繰り返しを終了する、
関数の定義は一度しか書けない。
// 定義
void f() {}
// エラー、再定義
void f() {}
必ず名前は使う前に宣言しなければならない。
int main()
{
// エラー
// 名前fは宣言されていない
f() ;
}
// 定義
void f() { }
条件付ループ
void hello_n( int n )
{
int i = 0 ;
loop :
if ( i != n )
{
std::cout << "hello\n"s ;
++i ;
goto loop ;
}
}
負の数が入力されるとエラーになる、なのでif文で0より小さい場合は処理を終了と作れば完成。コードには書かない。
while文
goto文は極めて原始的で使いづらい機能
while (true) 文
int main()
{
auto hello = []()
{ std::cout << "hello\n"s ; } ;
while (true)
hello() ;
}
九九の表を出力するプログラムを書きなさい
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81
↓
1*1 1*2 1*3 1*4 1*5 1*6 1*7 1*8 1*9
2*1 2*2 2*3 2*4 2*5 2*6 2*7 2*8 2*9
3*1 3*2 3*3 3*4 3*5 3*6 3*7 3*8 3*9
4*1 4*2 4*3 4*4 4*5 4*6 4*7 4*8 4*9
5*1 5*2 5*3 5*4 5*5 5*6 5*7 5*8 5*9
6*1 6*2 6*3 6*4 6*5 6*6 6*7 6*8 6*9
7*1 7*2 7*3 7*4 7*5 7*6 7*7 7*8 7*9
8*1 8*2 8*3 8*4 8*5 8*6 8*7 8*8 8*9
9*1 9*2 9*3 9*4 9*5 9*6 9*7 9*8 9*9
int main()
{
// 1から9まで
int a = 1 ;
while ( a <= 9 )
{
// 1から9まで
int b = 1 ;
while ( b <= 9 )
{
// 計算結果を出力
std::cout << a * b << "\t"s ;
++b ;
}
// 段の終わりに改行
std::cout << "\n"s ;
++a ;
}
}
・for文
for ( 変数の宣言 ; 終了条件の確認 ; 各ループの最後に必ず行う処理 ) 文
int main()
{
for ( int a = 1 ; a <= 9 ; ++a )
{
for ( int b = 1 ; b <= 9 ; ++b )
{ std::cout << a*b << "\t"s ; }
std::cout << "\n"s ;
}
}
while文と比べると変数の宣言がないので短くなる
・do文
do文はwhile文に似ている。
do 文 while ( 条件 ) ;
do文はまず文を実行する。しかる後に条件を確認し
trueの場合繰り返しを行う。
条件がなんであれ、最初に一度実行する
do文を使うと条件にかかわらず文を1回は実行する
int main()
{
do {
std::cout << "hello\n"s ;
} while ( false ) ;
}
・break文
ループの中から外に脱出したくなった場合に使用
break文はfor文、while文、do文の中でしか使えない
break文は最も内側の繰り返し文から脱出する
int main()
{
while ( true ) // 外側
{
while ( true ) // 内側
{
break ;
}
// ここに脱出
}
}
・continue文
今のループを打ち切って次のループに進みたい場合
continue文はfor文、while文、do文の中でしか使えない。
int main()
{
while ( true ) // 外側
{
while ( true ) // 内側
{
continue ;
// continueはここに実行を移す
}
}
}
vector
std::vectorはT型の値をいくらでも保持できる。Tには保持する値の型を指定する
int main()
{
// 整数型intの値を保持するvector
std::vector<int> vi ;
// 浮動小数点数型doubleの値を保持するvector
std::vector<double> vd ;
// 文字列型std::stringの値を保持するvector
std::vector<std::string> vs ;
}
td::vectorはメモリの続く限りいくらでも値を保持できる
int main()
{
std::vector<int> v ;
for ( int i = 0 ; i != 1000 ; ++i )
{
v.push_back( i ) ;
}
}
0から999までの1000個の整数をstd::vectorに保持
保持する値のことを要素という
int main()
{
std::vector<int> v ;
// vは空
// 要素数1、中身は{1}
v.push_back(1) ;
// 要素数2、中身は{1,2}
v.push_back(2) ;
// 要素数3、中身は{1,2,3}
v.push_back(3) ;
}
std::vectorではメンバー関数at(i)を使うことで、i番目の要素を取り出すことができる。
int main()
{
std::vector<int> v ;
for ( int i = 0 ; i != 10 ; ++i )
{
v.push_back(i) ;
}
// vの中身は{0,1,2,3,4,5,6,7,8,9}
// 0, 0番目の最初の要素
std::cout << v.at(0) ;
// 4, 4番目の要素
std::cout << v.at(4) ;
// 9, 9番目の最後の要素
std::cout << v.at(9) ;
}
メンバー関数at(i)に与える引数iの型は整数型ではあるのだがint型ではない。std::size_t型という特殊な型になる。メンバー関数sizeも同様にstd::size_t型を返す。
std::size_t型に-1はない、負の数しか使えないので都合が良い。
整数
符号付き整数型としては、signed char, short int, int, long int, long long intが存在する
符号なし整数型としては、unsigned char, unsigned short int, unsigned int, unsigned long int, unsigned long long intが存在する
・int型
int x = 123 ;
unsigned int x = 123 ;
unsigned int型は符号のないint型だ。
※単にsignedと書いた場合、それはintになる。
unsignedと書いた場合は、unsigned intになる。
・long int型
long int型はint型以上の範囲の整数を扱える型
long int a = 123 ;
unsigned long int b = 123
// long int
long a = 1 ;
// unsigned long int
unsigned long b = 1 ;
単にlongと書いた場合、それはlong intになる。
unsigned longと書いた場合、unsigned long intになる。
末尾にl/Lと書いた場合、値にかかわらずlong型になる
// int
auto a = 123 ;
// long
auto b = 123l ;
// long
auto c = 123L ;
符号なし整数型を意味するu/U
// unsigned long
auto a = 123ul ;
auto b = 123lu ;
・long long int型
long long int型はlong int型以上の範囲の整数を扱える型だ。longと同じくlong longはlong long intと同じで、unsigned long long intもある。
// long long int
long long a = 1 ;
// unsigned long long int
unsigned long long b = 1 ;
整数リテラルの末尾にll/LLと書くとlong long int型になる。
// long long int
auto a = 123ll ;
// long long int
auto b = 123LL ;
// unsigned long long int
auto c = 123ull ;
・short int型
short int型はint型より小さい範囲の値を扱う整数型だ。long, long longと同様に、unsigned short int型もある。単にshortと書くと、short intと同じ意味になる。
・整数型のサイズ
変数のサイズは、sizeof演算子で確認することができる。単位はバイト
int main()
{
auto print = []( std::size_t s )
{ std::cout << s << "\n"s ; } ;
print( sizeof(char) ) ;
print( sizeof(short) ) ;
print( sizeof(int) ) ;
print( sizeof(long) ) ;
print( sizeof(long long ) ) ;
}
1
2
4
8
8
名前の条件
使ってはいけない予約語がある
数字で始まってはならない。
int 123abc = 0 ; // エラー
ダブルアンダースコア
int __ = 0 ;
int a__ = 0 ;
int __a = 0 ;
アンダースコアに大文字から始まる名前
アンダースコアに小文字から始まる名前
int _A = 0 ;
int _a = 0 ;
名前空間
コロンやアングルブラケットは名前に使える文字ではない。
// エラー
int :: = 0 ;
int <int> = 0 ;
名前空間名を指定しなければ使えない
共同開発している他人もfという名前の関数を書いた
// アリスの書いた関数f
int f() { return 0 ; }
// ボブの書いた関数f
int f() { return 1 ; }
名前空間を使えばいい
// アリスの名前空間
namespace alice {
// アリスの書いた関数f
int f() { return 0 ; }
}
// ボブの名前空間
namespace bob {
// ボブの書いた関数f
int f() { return 1 ; }
}
使い方は
namespace ns {
int f() { return 0 ; }
}
int main()
{
ns::f() ;
f() ; // エラー 名前空間を指定しなければ使
えなくなる
}
グローバル名前空間
グローバル名前空間は名前の指定のない単なる::で指定することもできる。
// グローバル名前空間
int f() { return 0 ; }
namespace ns {
int f() { return 1 ; }
}
int main()
{
f() ; // 0
ns::f() ; // 1
}
・名前空間のネスト
名前空間の中に名前空間を書くことができる。
namespace A { namespace B { namespace C {
int name {} ;
} } }
int main()
{
A::B::C::name = 0 ;
}
名前空間のネストは省略して書くこともできる
namespace A::B::C {
int name { } ;
}
int main()
{
A::B::C::name = 0 ;
}
名前空間エイリアス
namespace very_long_name {
int f() { return 0 ; }
}
int main()
{
very_long_name::f() ;
}
毎回very_long_name::fと書くのは面倒
namespace 別名 = 名前空間名 ;
namespace very_long_name {
int f() { return 0 ; }
}
int main()
{
// 名前空間エイリアス
namespace vln = very_long_name ;
// vlnはvery_long_nameのエイリアス
vln::f() ;
}
名前空間エイリアスを関数の中で宣言すると、その関数の中でだけ有効になる。
namespace A { int x { } ; }
int f()
{
// Bの宣言
namespace B = A ;
// OK、Bは宣言されている
return B::x ;
}
int g()
{
// エラー、Bは宣言されていない
return B::x ;
}
指定を省略するusingディレクティブ
using namespace 名前空間名 ;
int main()
{
using namespace std ;
// std名前空間のstring
string s ;
// std名前空間のvector<int>
vector<int> v ;
// std名前空間のcout
cout << 123 ;
}
namespace abc {
int f() { return 0 ; }
}
int f() { return 1 ; }
int main()
{
using namespace std ;
// OK、名前空間abcのf
abc::f() ;
// OK、グローバル名前空間のf
::f() ;
}
・inline名前空間はinline namespaceで定義する
inline namespace name { }
inline namespace A {
int a { } ;
}
namespace B {
int b { } ;
}
int main()
{
a = 0 ; // A::a
A::a = 0 ; // A::a
b = 0 ; // エラー、名前bは宣言されていな
い
B::b = 0 ; // B::b
}
型名
intやdoubleのように言語組み込みのキーワードを使
うこともあれば、独自に作った型名を使うこともある
別名を宣言するエイリアス宣言
using 別名 = 型名 ;
int main()
{
// エイリアス宣言
using Number = int ;
// Numberはintの別名
Number x = 0 ;
}
昔のコードでよく出てくる宣言方法
typedef 型名 typedef名 ;
int main()
{
// typedef名による型名の宣言
typedef int Number ;
Number x = 0 ;
}
スコープ
namespace ns
{ // 名前空間スコープの始まり
} // 関数スコープの終わり
void f()
{ // 関数スコープの始まり
} // 関数スコープの終わり
これとは別にブロック文のスコープもある
void f()
{ // 関数スコープ
{ // 外側のブロックスコープ
{ // 内側のブロックスコープ
}
}
}
名前が有効な範囲は、宣言された最も内側のスコープ
名前空間も同じ
// グローバル名前空間スコープ
namespace ns {
int a {} ;
void f()
{
a = 0 ; // OK
}
} // 名前空間nsのスコープの終了
int main()
{
// エラー
a = 0 ;
// OK
ns::a ;
}
イテレーターの基礎
vectorの要素にアクセスする方法としてメンバー関数at(i)ただし最初の要素は0番目
int main()
{
std::vector<int> v = {1,2,3,4,5} ;
int x = v.at(2) ; // 3
v.at(2) = 0 ;
// vは{1,2,0,4,5}
}
イテレーターはstd::begin(v)で取得
int main()
{
std::vector<int> v = {1,2,3,4,5} ;
auto i = std::begin(v) ;
int x = *i ; // 1
*i = 0 ;
// vは{0,2,3,4,5}
}
*iを読み込むと指し示す要素の値を読むことができ、
*iに代入をすると指し示す要素の値を変えることができる。
std::begin(v)はvectorの変数vの最初の要素を指し示す
std::end(v)はvectorの変数vの最後の次の要素を指し示す
int main()
{
std::vector<int> v = {1,2,3,4,5} ;
for ( auto iter = std::begin(v), last =
std::end(v) ;
iter != last ; ++iter )
{
std::cout << *iter << "\n"s ;
}
}
関数を使って
auto output_all = []( auto first, auto last )
{
for ( auto iter = first ; iter != last ; +
+iter )
{
std::cout << *iter << "\n"s ;
}
} ;
int main()
{
std::vector<int> v = {1,2,3,4,5} ;
output_all( std::begin(v), std::end(v) ) ;
}