M.Hiroi's Home Page

Linux Programming

お気楽C++プログラミング超入門

[ PrevPage | C++ | NextPage ]

C++の基礎知識 (1)

プログラミング言語を手続き型言語、関数型言語、論理型言語の 3 つに分類するならば、C++は手続き型のプログラミング言語になります。プログラムの実行を制御する「文」、データを格納する「変数」、決められた処理を行う「関数」があります。

また、C++はオブジェクト指向プログラミング (OOP) に対応しているので、クラス、インスタンス、メソッド (メンバ関数)、継承など OOP 的な機能があります。このほかに「例外処理」や「テンプレート」によるジェネリックプログラミングといった高度な機能もサポートされています。

C++のプログラムで使用される変数や関数などの名前には、英数字とアンダースコア _ が使えます。英大文字と英小文字は区別されるので、FOO と Foo と foo は異なる名前と判断されます。コメントはC言語のように /* と */ で囲むか、// から行末までがコメントになります。

プログラミング言語で扱うデータの種類のことを「データ型 (data type)」といいます。C++は「数」や「配列」といった基本的なデータ型のほかに、C言語と同じく「構造体」や OOP の「クラス」を使って、ユーザーが新しいデータ型を定義することができます。

このほかに、C++にはC言語と同じく悪名高い「ポインタ (pointer)」があります。プログラミング言語の学習には、どの言語にもいくつかの難関があります。C言語ではポインタが最大の難関と言われていますが、コンピュータの基本 (CPU やメモリの概念) を正しく理解していれば、けっして難しい話ではありません。ポインタは「参照 (リファレンス)」といっしょに詳しく説明する予定です。

●こんにちはC++

それでは皆さんお馴染みの hello, world を表示するプログラムを作ってみましょう。次のリストを見てください。

リスト : hello.cpp

#include <iostream>

int main()
{
  std::cout << "hello, world\n";
}

C言語の場合、ソースファイルの拡張子は .c ですが、C++では .cc .cpp .cxx などが用いられます。本ページでは .cpp を使うことにしましょう。ライブラリ関数を使用するときは「ヘッダファイル」を #include 命令で読み込みます。ヘッダファイル名はC言語と同様に <...> の中に記述しますが、C++で標準ライブラリを使用する場合はファイル名に拡張子 .h を付けません。iostream はC++の標準入出力ライブラリで定義されているデータ型や関数の仕様 (関数名や引数と返り値のデータ型) などが記述されています。

次の int main(){ ... } で main という名前の関数を定義しています。関数はあとで詳しく説明しますが、先頭の int は関数が出力する値のデータ型 (返り値の型)、main が関数の名前、後ろのカッコ ( ) には関数の引数を記述します。引数がない場合は ( ) だけを書きます [*1]。そして、{ ... } の中に実行する処理を記述します。C言語と同様に、C++でもプログラムの実行は main 関数から開始されます。main 関数が定義されていないとコンパイルエラーになります。

このプログラムでは std::cout << "hello, world\n"; で文字列 hello, world を画面に出力しています。cout は標準出力を表す「ストリーム (stream)」で、<< はデータをストリームに出力する演算子 (出力演算子) です。<< はC言語と同じくビットシフト演算子として使うこともできます。文の最後にはセミコロン ( ; ) を付けます。C++の場合、文の終わりにはセミコロンを付けることに注意してください。

C++の標準ライブラリ (cout を含む) は std という「名前空間 (namespace)」の中に定義されています。この場合、記号 :: を使って次に示す記法で、名前空間に定義されている変数や関数にアクセスすることができます。

名前空間::名前

cout だけではコンパイルエラーになります。もしくは、次のように using namespace std; を指定して、名前空間 std に定義されているすべての名前を取り込みます。

リスト : 名前空間 std を取り込む

#include <iostream>
using namespace std;

int main()
{
  cout << "hello, world\n";
}

この場合、std に定義されている名前は、同じファイルであればどこからでも参照することができます。関数の中で using namespace std; を定義することもできます。その場合、名前を参照できるのは関数内だけになります。名前の有効範囲を「スコープ (scope)」といいます。名前空間の指定で用いた記号 :: は「スコープ解決演算子」といいます。スコープについてはあとで詳しく説明します。

関数は計算した値を出力することができます。これを「返り値」といいます。関数の返り値は return で指定します。main 関数の返り値は、そのプログラムが OS に返す終了コードになります。Windows や Unix 系 OS の場合、終了コードは整数で、0 はそのコマンドが正常に終了したことを表し、0 以外の値は異常終了したことを表します。C++の場合、main 関数の返り値は省略することができます。その場合、終了コードは 0 になります。

プログラムのコンパイルは clang++ で行います。clang++ がインストールされていなければ g++ でもかまいません。端末で次のコマンドを入力すると hello という実行ファイルが作成されます。

$ clang++ -o hello hello.cpp
$ ls
hello  hello.cpp
$ ./hello
hello, world

-o は実行ファイル名を指定するオプションです。-o を省略すると実行ファイル名は a.out になります。

-- note --------
[*1] C言語のように ( ) の中に void と書いてもコンパイルできます。

●数と四則演算

まず最初に、C++で扱うことができる「数」から説明しましょう。C言語と同様にC++は標準で「整数 (integer)」と「浮動小数点数 (floating point number)」を使うことができます。下表に基本的な数を示します。

表 : 基本的な数 (32 ビット処理系の場合)
型名範囲
char-128 ~ 127 (1 バイト)
unsigned char0 ~ 255 (1 バイト)
short int-32768 ~ 32767 (2 バイト)
unsigned short int0 ~ 65535 (2 バイト)
int-2147483648 ~ 2147483647 (4 バイト)
unsigned int0 ~ 4294967295 (4 バイト)
long int-2147483648 ~ 2147483647 (4 バイト)
unsigned long int0 ~ 4294967295 (4 バイト)
long long int-9223372036854775808 ~ 9223372036854775807 (8 バイト)
unsigned long long int0 ~ 18446744073709551615 (8 バイト)
float±1.1754944E-38 ~ 3.4028235E+38 (32 bit 単精度)
double±2.22507E-308 ~ 1.79769E+308 (64 bit 倍精度)

char は文字 (character) を表すデータ型ですが、文字コードを整数として扱うことができるので、ここでは整数型に含めています。unsigned は無符号整数を表します。short int, long int, long long int の int は省略してもかまいません。C言語と同様に、C++では整数値に 0x を付けると 16 進数、0 から始まる整数値は 8 進数となります。

C/C++の場合、数値の範囲は処理系によってかわります。規格で定められているのは最低限の条件だけです。一般に、32 bit 処理系では int, long が 4 バイトで、64 bit 処理系では int が 4 バイト、long が 4 バイトか 8 バイトになるようです。

さっそく簡単な例を示しましょう。次のリストを見てください。

リスト : 数と四則演算 (sample10.cpp)

#include <iostream>
using namespace std;

int a = 10;
int b = 20;
double c = 1.234;
double d = 5.678;

int main()
{
  cout << a + b << endl;
  cout << a - b << endl;
  cout << c * d << endl;
  cout << c / d << endl;
}

出力演算子 << は文字列だけではなく、いろいろなデータを表示することができます。また、<< をつなげて複数のデータを出力することもできます。endl は改行を表すデータです。endl をストリームに書き込むことで改行することができます。それでは実行してみましょう。

$ clang++ sample10.cpp
$ ./a.out
30
-10
7.00665
0.21733

C++では、あらかじめ使用する変数とそのデータ型を宣言する必要があります。変数の定義は次のように行います。

データ型 変数名1, 変数名2, ..., 変数名N;
データ型 変数名1 = 初期値1, 変数名2 = 初期値2, ..., 変数名N = 初期値N;

まず最初にデータ型を指定して、その後ろに変数を表す名前を書きます。同じデータ型であれば、カンマ ( , ) で区切って複数の変数を定義することができます。このとき、同時に初期値をセットすることができます。これを変数の「初期化」といいます。また、変数に値をセットすることを「代入」といい、代入には記号 = を使います。これを「代入演算子」といいます。初期値は式を与えてもかまいません。式の評価結果が変数の初期値になります。

変数 a, b には整数を、変数 c, d には浮動小数点数を代入します。そして、演算結果を << で表示します。このとき、最初に式 (a + b や a - b など) が計算されることに注意してください。その結果を << で cout に出力します。

主な算術演算子を下表に示します。

表 : 算術演算子
演算子操作
-x x の符号を反転
x + y x と y の和
x - y x と y の差
x * y x と y の積
x / y x と y の商
x % y x と y の剰余

異なるデータ型が混在する算術式を「混在式 (mixed expression)」といいます。混在式を計算するとき、C/C++は情報が失われないようにデータ型を自動的に変換 (型変換) します。たとえば、整数と浮動小数点数を演算する場合、C/C++は整数を浮動小数点数に変換してから行います。整数同士でもデータ型が異なる場合、たとえば int と long long int の計算は int を long long int に変換してから行われます。

なお、C/C++の代入演算子は代入した値が演算結果になります。たとえば、a = 5; の結果は 5 になります。また、b = (a = 5); は a = 5 の結果である 5 が変数 b に代入されます。代入演算子は他の演算子と違って「右結合」なので、b = (a = 5); は b = a = 5; と書くことができます。

●局所変数と大域変数

変数は関数の中でも定義することができます。これを「ローカル変数 (local variable)」とか「局所変数」といいます。C/C++では「自動変数」と呼ばれることもあります。これに対し、関数の外で定義されている変数を「グローバル変数 (global variable)」とか「大域変数」といいます。局所変数は定義されている関数の中だけしかアクセスできませんが、大域変数は同じファイル内で定義されている関数であればどこからでもアクセスすることができます。詳細は関数の回で詳しく説明します。

変数 a, b, c, d を関数内で定義すると次のようになります。

リスト : 局所変数の定義 (sample11.cpp)

#include <iostream>
using namespace std;

int main()
{
  int a = 10;
  int b = 20;
  double c = 1.234;
  double d = 5.678;

  cout << a + b << endl;
  cout << a - b << endl;
  cout << c * d << endl;
  cout << c / d << endl;
}

実行結果は同じです。

大域変数の場合、変数の初期値を指定しないと、整数型は 0 に、float 型や double 型は 0.0 に初期化されます。局所変数の場合、初期値を指定しないと初期化は行われないので、変数の値は不定になります。

実際に試してみましょう。

リスト : 大域変数と局所変数の初期化 (sample12.cpp)

#include <iostream>
using namespace std;

int a;
double b;

int main()
{
  int c;
  double d;
  cout << a << endl;
  cout << b << endl;
  cout << c << endl;
  cout << d << endl;
}
$ clang++ sample12.cpp
$ ./a.out
0
0
32655
6.92942e-310

変数 a は 0 に、変数 b は 0.0 に初期化されますが、変数 c と d は適当な値になります。

●文字と文字列

文字 (char) は クォート ' で囲んで表します。たとえば、'A' 'a' '0' のように表します。ただし、扱うことができる文字コードは ASCII コードだけです。文字列 (character string) はダブルクォート " で囲んで表します。C言語の場合、文字列は char 型の「配列」です。これを「Cスタイル文字列」といいます。配列は次回に詳しく説明します。

C++の場合、文字列を表すデータ型 string が標準ライブラリに用意されています。Cスタイルの文字列も使用することはできますが、string のほうが便利です。次の例を見てください。

リスト : 文字列 (sample13.cpp)

#include <iostream>
using namespace std;

int main()
{
  string s1 = "hello";
  string s2 = "world";
  cout << s1 + "\n";
  cout << s2 + "\n";
  cout << s1 + ' ' + s2 + "\n";
}
$ clang++ sample13.cpp
$ ./a.out
hello
world
hello world

データ型 string はヘッダファイル <string> をインクルードしなくても使用することができます。string は演算子 + で string, 文字列, 文字などを連結することができます。

'...' や "..." の中では \ (バックスラッシュ) は特別な働きをします。\ とその直後の文字で 1 文字を表します。これを「エスケープ・シーケンス (escape sequence)」といいます。画面に表示しにくい文字やキーボードからの入力が難しい制御文字などを表すために使います。たとえば、改行を表す \n、タブを表す \t、引用符を表す \' や \"、バックスラッシュ自身を表す \\ などがあります。

●if 文

if 文は「条件分岐」を行います。「条件分岐」と書くとなにやら難しい言葉のようにみえますが、プログラムにとっては最も基本的な動作のひとつです。簡単にいうと「もしも~~ならば○○をせよ」という動作です。下図を見てください。


               図 : if 文の動作

図 (1) では、「もしも条件を満たすならば、処理 A を実行する」となります。この場合、条件が成立しない場合は何も処理を実行しませんが、図 (2) のように、条件が成立しない場合でも処理を実行させることができます。(2) の場合では、「もしも条件を満たすならば処理 A を実行し、そうでなければ処理 B を実行する」となります。つまり、条件によって処理 A か処理 B のどちらかが実行されることになります。

一般に、プログラミング言語では、条件が成立することを「真 (true)」といい、条件が不成立のことを「偽 (false)」といいます。実際のプログラムでは真偽を表すデータ型と値が必要になります。C++の場合、データ型 bool で真偽値を表します。値は true と false の 2 つがあります。

真偽の判定はC言語と同じく整数型の 0 であれば偽、それ以外の整数値を真と判定します。真偽を判定するとき、bool 型のデータは自動的に整数型のデータに変換されて、true は 1 に、false は 0 になります。逆に、整数型のデータも必要に応じて bool 型のデータに変換することができます。0 が false に、それ以外の整数値は true に変換されます。

下図に if 文の構文を示します。

if (test) {
  処理A;
  処理B;
  処理C;
} else {
  処理D;
  処理E;
  処理F;
}

図 : if の構文 (1)

条件部 test を実行し、その結果が真であれば、処理A から処理C を実行します。{ } で囲まれた部分を「ブロック」と呼び、ここに複数の処理を書くことができます。then ブロックのことを then 節と呼ぶことがあります。test の結果が偽であれば、else から始まるブロックで書かれている処理D から処理F を実行します。 else ブロック (else 節) は省略することができます。また、ブロック内の処理が一つしかない場合は { } を省略することができます。

もう少し複雑な使い方を紹介しましょう。

if (test_a) {
  処理A;
} else {
  if (test_b) {
    処理B;
  } else {
    処理C;
  }
}

図  : if 文の入れ子

           図 : if 文の入れ子の動作

test_a が偽の場合は else 節を実行します。else 節は if 文なので、条件 test_b を実行します。この結果が真であれば処理 B を実行します。そうでなければ、else 節の処理 C を実行します。この処理は下図のように書き換えることができます。

if (test_a) {
  処理A;
} else if (test_b) {
  処理B;
} else {
  処理C;
}

図 : if の構文 (2)

if 文は eles if を使って複数の if 文を連結することができます。test_a が偽の場合は、次の else if の条件 test_b を実行します。この結果が真であれば処理 B を実行します。そうでなければ、else 節の処理 C を実行します。なお、else if はいくつでも繋げることができます。

●比較演算子と論理演算子

C/C++には下表に示す比較演算子と論理演算子が用意されています。

表 : 比較演算子
演算子意味
== 等しい
!= 等しくない
< より小さい
> より大きい
<= より小さいか等しい
>= より大きいか等しい

表 : 論理演算子
操作意味
!x x の否定(真偽の反転)
x && y x が真かつ y が真ならば真
x || y x が真まはた y が真ならば真

&& は左項が偽ならば右項を評価せずに偽を返します。|| は左項が真ならば右項を評価せずに左項の値を返します。このため、&& と || は「短絡演算子」と呼ばれることもあります。

●三項演算子

C/C++は三項演算子 ? : を使うことができます。三項演算子の構文を示します。

条件式 ? 真の場合の式 : 偽の場合の式

簡単な例を示しましょう。

// if 文
if (条件式) {
  a = 式1;
} else {
  a = 式2;
}

// 三項演算子
a = 条件式 ? 式1 : 式2;

このように三項演算子を使うとプログラムを簡潔に記述できる場合があります。

●while 文による繰り返し

繰り返しは同じ処理を何度も実行することです。まずは簡単な繰り返しから紹介しましょう。while 文は条件が真のあいだ、ブロックに書かれている処理を繰り返し実行します。

while (test) {
  処理A;
  処理B;
  処理C;
}

図 : while 文の構文

       図 : while 文の動作

上図を見ればおわかりのように、while 文はいたって単純です。なお、処理が一つしかない場合は { } を省略することができます。

簡単な例を示しましょう。Hello, World! を 10 回表示するプログラムを次のリストに示します。

リスト : hello. wolrd の表示 (sample14.cpp)

#include <iostream>
using namespace std;

int main()
{
  int i = 0;
  while (i < 10) {
    cout << "hello, world\n";
    i += 1;
  }
}
$ clang++ sample14.cpp
$ ./a.out
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world

変数 i を 0 に初期化し、i の値が 10 よりも小さいあいだ処理を繰り返します。i += 1 は i = i + 1 と同じ意味です。このほかに、-=, *=, /=, %= などがあります。i の値はブロックを実行するたびに +1 されていくので、i が 10 になった時点で繰り返しを終了します。

while 文には do { ... } while (test); という形式もあります。これはブロックの処理を実行してから条件部をチェックします。したがって、ブロックの処理は最低でも 1 回は必ず実行されます。do - while 文でプログラムを書き直すと、次のようになります。

リスト : hello, world の表示 (sample15.cpp)

#include <iostream>
using namespace std;

int main(void)
{
  int i = 0;
  do {
    cout << "hello, world\n";
    i += 1;
  } while (i < 10);
}

これでも hello, world を 10 回表示することができます。

●インクリメント演算子とデクリメント演算子

C/C++には値を 1 増やすインクリメント演算子 ++ と値を 1 減らすデクリメント演算子 -- があります。演算子を前に置くと、値を更新してから式を評価します。後ろに置いた場合は式を評価してから値を更新します。次の例を見てください。

リスト : インクリメント演算子 (sample16.cpp)

#include <iostream>
using namespace std;

int main()
{
  int i = 1, j = 10;
  int x = ++i;
  int y = j++;
  cout << i << endl;
  cout << j << endl;
  cout << x << endl;
  cout << y << endl;
}
$ clang++ sample16.cpp
$ ./a.out
2
11
2
10

変数 x, y に値をセットするとき、変数 i, j の値を +1 しています。このため、i, j の値は 2 と 11 になります。x の値は演算子 ++ が i の前についているので、i の値を +1 してから、i の値を x に代入します。このため、x の値は 2 になります。逆に、y の値は演算子 ++ が j の後ろに付いているので、j の値を y に代入してから j の値を +1 します。したがって、y の値は 10 になるのです。

簡単な例として、1 から 1000 までの総和を求めてみましょう。

リスト : 1 から 1000 までの総和 (sample17.cpp)

#include <iostream>
using namespace std;

int main()
{
  int i = 1, sum = 0;
  while(i <= 1000) sum += i++;
  cout << sum << endl;
}
$ clang++ sample17.cpp
$ ./a.out
500500

変数 i を 1 に、総和を表す変数 sum を 0 に初期化します。while 文で i の値を sum に加えてから、i の値を +1 します。この処理は sum += i++; と表すことができます。++ を i の後ろにつけることで、i の値を sum に加えてから、i の値を +1 することができます。++i とすると正しい値にはなりません。このように、++ と -- はとても便利な演算子ですが、使うときには十分に注意してください。

●FizzBuzz 問題

もう一つ簡単な例題として FizzBuzz 問題をC++で解いてみましょう。FizzBuzz 問題は 1 から 100 までの値を表示するとき、3 の倍数のときは Fizz を、5 の倍数ときは Buzz を表示するというものです。FizzBuzz 問題の詳細については Fizz Buzz - Wikipedia をお読みください。

プログラムは次のようになります。

リスト : FizzBuzz 問題 (fizzbuzz.cpp)

#include <iostream>
using namespace std;

int main()
{
  int i = 1;
  while (i <= 100) {
    if (i % 3 == 0 && i % 5 == 0) {
      // 15 の倍数
      cout << "FizzBuzz";
    } else if (i % 3 == 0) {
      // 3 の倍数
      cout << "Fizz";
    } else if(i % 5 == 0){
      // 5 の倍数
      cout << "Buzz";
    } else {
      // 数字をそのまま表示
      cout << i;
    }
    if(i % 20 == 0){
      cout << std::endl;
    } else {
      cout << " ";
    }
    i++;
  }
}
$ clang++ fizzbuzz.cpp
$ ./a.out
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz
Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz
41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz
61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz
Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz

変数 i を 1 に初期化し、while 文の中で 1 ずつ増やしていきます。最初の if 文で、i が 3 の倍数でかつ 5 の倍数かチェックします。この処理は 15 の倍数をチェックすることと同じなので、条件文を i % 15 == 0 としてもかまいません。そうでなければ、次の else if で i が 3 の倍数かチェックし、次の else if で i が 5 の倍数かチェックします。どの条件も該当しない場合は最後の else 節で i をそのまま出力します。

●数値積分

最後に数値積分で円周率 \(\pi\) を求めてみましょう。区間 [a, b] の定積分 \(\int_a^b f(x)\,dx\) を数値的に求めるには、区間を細分して小区間の面積を求めて足し上げます。小区間の面積を求める一番簡単な方法は長方形で近似することです。この場合、3 つの方法が考えられます。

  1. (b - a) * f(a)
  2. (b - a) * f(b)
  3. (b - a) * f((a + b) / 2)

1 は左端の値 f(a) を、2 は右端の値 f(b) を、3 は中間点の値 f((a + b) / 2) を使って長方形の面積を計算します。この中で 3 番目の方法が一番精度が高く、これを「中点則」といいます。このほかに、台形で近似する「台形則」や、2 次近似で精度を上げる「シンプソン則」という方法があります。

それでは実際に、中点則で \(\pi\) の値を求めてみましょう。\(\pi\) は次の式で求めることができます。

\( \pi = \displaystyle \int_0^1 \dfrac{4}{1 + x^2} \, dx \)

プログラムは次のようになります。

リスト : 数値積分で円周率を求める (midpoint.cpp)

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
  int n = 10000;
  double w = 1.0 / n;
  double s = 0.0;
  int i = 1;
  while (i <= n) {
    double x = (i - 0.5) * w;
    s += 4.0 / (1.0 + x * x);
    i++;
  }
  cout << setprecision(16) << s * w << endl;
}

変数 n が分割数です。最初に小区間の幅を求めて変数 w にセットします。面積は変数 s にセットします。次の while ループで区間 [0, 1] を n 個に分割して面積を求めます。

最初に x 座標を計算します。中間点を求めるため、変数 i を 1 から始めて、x 座標を次の式で求めます。

x = (i - 0.5) * w

たとえば、変数 i が 1 の場合は 0.5 になるので、x は区間 [0 * w, 1 * w] の中間点になります。あとは、4 / (1 + x * x) を計算して s に加算します。最後に s に w を掛け算して全体の面積を求めます。

setprecision() はヘッダ iomanip に定義されているマニピュレータで、小数点以下の桁数を指定するときに使います。デフォルトは 5 に設定されています。

実行結果を示します。

$ clang++ midpoint.cpp
$ ./a.out
3.141592654423134

今回はここまでです。次回は配列を中心にC言語の基本的な機能について説明します。


初版 2015 年 8 月 2 日
改訂 2023 年 4 月 9 日

Copyright (C) 2015-2023 Makoto Hiroi
All rights reserved.

[ PrevPage | C++ | NextPage ]