C言語に対して、C++で追加になった機能を中心に記載します。
1.動作環境(サンプルの実行環境)
・VirtualBoxのCentOs7マシン
・CentOsのバージョン
# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
・コンパイラのバージョン
【c++コンパイラ】
・パッケージ一覧
yum list | grep gcc-c++
・インストール
yum -y install gcc-c++
・バージョン確認
g++ –version
g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
・インストール済みのgcc-c++パッケージ
# rpm -q gcc-c++
gcc-c++-4.8.5-44.el7.x86_64
g++ オプション
-Wall: g++ に 詳細なWarning を出力
-o file: 出力先をfileに指定
-v: コンパイルの各ステージで実行されるコマンドを表示する
-c: コンパイルのみ行い、リンクは行なわない
-S: コンパイルが終ったところで処理を中断する
-std:C++の規格を指定 g++ -std=c++11 main.cpp
(3)言語規格
C++は、C言語にオブジェクト指向プログラミングをはじめとする様々なプログラミングをサポートするための改良が加えられたもので仕様
・C++11『プログラミング言語 C++ のISO標準 ISO/IEC 14882:2011』
・C++14『プログラミング言語 C++ のISO標準 ISO/IEC 14882:2014』
・C++17『プログラミング言語C++の国際規格ISO/IEC 14882:2017』
・MISRA C++
英国The Motor Industry Software Reliability Association(MISRA)によって定められた、C++言語のコーディングガイドライン。
2.C++プログラムのサンプル
2.1 サンプル1
main()関数で構造体のメンバに値を設定して、値を出力する関数を呼んで表示するプログラム。1つのソースファイルに実装する場合と、2つのファイルに分けて実装する場合の2パターンについて作成する。
(1)1つのファイル(main1.cppのみ)で実装する場合
CからC++で変更した点
①ネームスペース(using namespace)の追加
②C++では文字列リテラルは const char[] 型として扱われてchar* 変数には文字列リテラルは代入できないため、string型を使用
③printf関数はchar*型を引数に取り、そのままではstring型の変数使えないため、coutオブジェクトを使う
④使用する入出力オブジェクトに合わせてインクルードするヘッダファイルを変更。
C++ではのように”.hファイル”でない識別子を使ってヘッダファイルに対応づけする仕様があるが、iostream.hのヘッダファイルも用意されている。
main1.cpp
#include <iostream>
using namespace std;
typedef struct _Person{ //構造体宣言
string name;
int age;
}Person;
void send(Person mperson);//プロトタイプ宣言
int main(){
Person man1;
man1.name="satou";
man1.age=20;
send(man1);
return 0;
}
void send(Person mperson){
cout<<"name:"<<mperson.name<<endl;
cout<<"age:"<<mperson.age<<endl;
}
(2)2つのファイル(main2.cpp,member.cpp)で実装する場合
main2.cpp
#include "member.h"
using namespace std;
int main(){
Person man1;
man1.name="satou";
man1.age=20;
send(man1);
return 0;
}
member.h
#include <string>
using namespace std;
typedef struct _Person{ //構造体宣言
string name;
int age;
}Person;
void send(Person mperson);//プロトタイプ宣言
member.cpp
#include <iostream>
#include "member.h"
using namespace std;
void send(Person mperson){
cout << "name:" << mperson.name << endl;
cout << "age:" << mperson.age << endl;
//return 0;
}
(解説)
coutオブジェクトはこの関数内でのみ使用するため、標準ライブラリのインクルード文はヘッダファイルに入れない。
自分のヘッダファイルを読み込み、コンパイル時にチェックできるようにする。
(3)前記(2)の関数呼出しをクラスの関数に変更する
C++はCの構造体を発展させたもので、ヘッダファイルでクラスの型を宣言して、クラスのメンバ関数、コンストラクタはソースファイルに実装する。
Member3.h
#include <string>
using namespace std;
class Member{
private:
string name;
int age;
public:
void send();//プロトタイプ宣言
Member(string name,int age);//コンストラクタ
};
ヘッダのクラス宣言はブロック文ではないため、}の後に”;”を忘れずにつける。
Member3.cpp
#include <iostream>
#include "Member3.h"
using namespace std;
//関数の実装
void Member::send(){
cout << "name:" << name << endl;
cout << "age:" << age << endl;
}
//コンストラクタの実装
Member::Member(string name,int age){
this->name=name;
this->age=age;
}
main3.cpp
#include "Member3.h"
#include <string>
using namespace std;
int main(){
Member m_member("satou",20);
m_member.send();
return 0;
}
2.2 サンプル2
複数人の健康診断データを構造体の配列として扱い、BMIを計算する関数の引数について、値渡しと構造体変数のポインタ渡しについて作成する。
(1)値渡しで実装
bmi.h
#include <string>
using namespace std;
//健康診断データ構造体
struct HealthCheck{
string name;
double height;
double weight;
double bmi;
};
//BMI関数プロトタイプ宣言
double getBmi(double height,double weight);
bmi.cpp
#include "bmi.h"
double getBmi(double height,double weight){
double bmi;
double mHeight;
int i;
mHeight=height/100;
bmi=weight/mHeight/mHeight;
return bmi;
}
main4.cpp
#include <iostream>
#include "bmi.h"
using namespace std;
int main(){
const int DATA_NUM=3;
HealthCheck people[DATA_NUM]={
{"yamada",180,70,0},
{"suzuki",175,65,0},
{"satou",170,63,0}
};
int i;
for(i=0;i<DATA_NUM;i++){
people[i].bmi=getBmi(people[i].height,people[i].weight);
cout << people[i].name << "さんのBMIは" << people[i].bmi << endl;
}
return 0;
}
(2)ポインタ渡し
bmi1.h
#include <string>
using namespace std;
//健康診断データ構造体
struct HealthCheck{
string name;
double height;
double weight;
double bmi;
};
//BMI関数プロトタイプ宣言
double getBmi(const HealthCheck *phc);
bmi1.cpp
#include "bmi1.h"
double getBmi(const HealthCheck *phc){
double bmi;
double mHeight;
int i;
mHeight=phc->height/100;
return phc->weight/mHeight/mHeight;
}
main5.cpp
#include <iostream>
#include "bmi1.h"
using namespace std;
int main(){
const int DATA_NUM=3;
HealthCheck people[DATA_NUM]={
{"yamada",180,70,0},
{"suzuki",175,65,0},
{"satou",170,63,0}
};
int i;
for(i=0;i<DATA_NUM;i++){
people[i].bmi=getBmi(&people[i]);
cout << people[i].name << "さんのBMIは" << people[i].bmi << endl;
}
return 0;
}
3.文法
ここではCに対して、C++で追加された機能を主に記載します。
3.1 データ型
(1)データ型
bool型が追加された
(2)リテラル(直定数)
リテラルを書けるのはデータ型で定義されたもののみ。
整数定数 int=5
実数定数 double=3.14, double=3.0e-10, double=3.14F、
文字定数 char=’A’ ,char=’\n’(改行),char=’\t’(タブ)
文字列定数 const char=”Hello\n”,const char=”あいうえお”
C++ではconst char *とする。文字列はstring(クラス)を使う。
3.2 コンソール入出力
C++では、cin、coutのオブジェクトが追加されている。
キーボードから入力された値を変数に格納するときは、
std::cin >> 変数 >> std::endl
また、変数に格納された値を画面に出力するときは、
std::cout << 変数 ここで、>>、<<はデータの入出力を行う演算子です。
endlは改行を出力し、データの書式設定を行うもので、入出力マニュピレータという。
3.3 クラス
C++のクラスはCの構造体を拡張したもので、クラスの宣言は、
class クラス名 {
private:
メンバ変数の宣言;
:
public:
メンバ関数のプロトタイプ宣言;
:
コンストラクタのプロトタイプ宣言;
}(オブジェクトの変数);
オブジェクトの変数は省略できる。
外部からアクセスするにはpublicキーワードをつける。メンバ変数は通常publicにしないで、メンバ変数の値の書き込みと読み出しは、関数(セッター/ゲッター)を通して行う。
publicのメンバのアクセス方法は構造体と同様、クラス型変数名.メンバ名でアクセスできる。
クラス宣言はヘッダファイルで行い、メンバ関数の実装はソースファイルで行う。
関数を実装するとき、どのクラスの関数か明示するため、スコープ解決演算子( :: ) を用いてクラスと関数名を指定する。
3.4 コンストラクタ
クラス名と同じ名前の関数であり戻り値を持たない。オブジェクト生成時に呼び出され、オブジェクトの初期化を行う。
コンストラクタ関数は引数を受け取ることができ、オブジェクトのメンバ変数のの初期化を行うことができる。
また、コンストラクタは引数の数、型が異なる複数の関数をオーバーロードして宣言できる。
3.5 スコープ
ローカルスコープとグローバルスコープ
ローカル変数との重複名称のとき、メンバ変数であることを明示したいとき
メンバ関数では スコープ解決演算子を用いてクラスを指定します
クラス名::メンバ変数;
グローバル変数を関数内で明示するには グローバルスコープ解決演算 子 ( :: ) を使用。
::変数;
で表す。
3.6 オブジェクトの配列
クラスのオブジェクトの配列の生成は、
クラス名 配列名[]();
又は、
クラス名 配列名[]=new クラス名();
ここで、クラス名()は、クラスのメンバ関数でコンストラクタのこと。
で生成し、メンバのアクセスは、
配列名.メンバ名
で行う。
サンプル main6.cpp
クラスのオブジェクトの配列の生成はクラス名 配列名[]();
クラスのオブジェクト配列のメンバ変数の初期化を行います。ここではメンバ変数もpublicとして、main()関数から、配列名.メンバ変数でアクセスします。
#include <string>
#include <iostream>
# define MEMBER_MAX 10
using namespace std;
class Member{
public:
string name;
int age;
public:
void send();
//コンストラクタ
Member(string name,int age);
Member();
};
int main(){
//オブジェクト配列生成
Member m_member[MEMBER_MAX];
//初期化
for(int i=0;i<MEMBER_MAX;i++){
m_member[i].name="";
m_member[i].age=0;
}
//読み出し
for(int i=0;i<MEMBER_MAX;i++){
m_member[i].send();
}
return 0;
}
//クラスの実装
Member::Member(string name,int age){
this->name=name;
this->age=age;
}
Member::Member(){
}
void Member::send(){
cout << "name:" << name << endl;
cout << "age:" << age << endl;
}
サンプル main7.cpp
前記と同様、クラスのオブジェクト配列のメンバ変数の初期化を行います。ここではメンバ変数をprivateとして、データをセットするset()関数を作り、main()からは配列名.メンバ関数でアクセスします。
#include <string>
#include <iostream>
#define MEMBER_MAX 10
using namespace std;
class Member{
private:
string name;
int age;
public:
void set(string name,int age);
void send();
//コンストラクタ
Member(string name,int age);
Member();
};
int main(){
//オブジェクト配列生成
Member m_member[MEMBER_MAX];
//初期化
for(int i=0;i<MEMBER_MAX;i++){
m_member[i].set("",0);
}
//読み出し
for(int i=0;i<MEMBER_MAX;i++){
m_member[i].send();
}
return 0;
}
//クラスの実装
Member::Member(string name,int age){
this->name=name;
this->age=age;
}
Member::Member(){
}
void Member::send(){
cout << "name:" << name << endl;
cout << "age:" << age << endl;
}
void Member::set(string name,int age){
this->name=name;
this->age=age;
}
サンプル main8.cpp
クラスのオブジェクトをコンストラクを使って生成とメンバ変数の初期化を行う。
#include <string>
#include <iostream>
#define MEMBER_MAX 10
using namespace std;
class Member{
public:
string name;
int age;
public:
void send();
//コンストラクタ
Member(string name,int age);
Member();
};
int main(){
//オブジェクト生成と初期化
Member m_member("",0);
//読み出し
m_member.send();
return 0;
}
//クラスの実装
Member::Member(string name,int age){
this->name=name;
this->age=age;
}
Member::Member(){
}
void Member::send(){
cout << "name:" << name << endl;
cout << "age:" << age << endl;
}
サンプル main9.cpp
クラスのオブジェクト配列をコンストラクを使って生成とメンバ変数の初期化を行う。
#include <string>
#include <iostream>
#define MEMBER_MAX 5
using namespace std;
class Member{
public:
string name;
int age;
public:
void send();
//コンストラクタ
Member(string name,int age);
Member();
};
int main(){
//オブジェクト生成と初期化
Member m_member[MEMBER_MAX]={
Member ("yama",10),
Member ("suzu",20),
Member ("iwa",25),
Member ("ueno",30),
Member ("satou",50)
};
//読み出し
for(int i=0;i<MEMBER_MAX;i++){
m_member[i].send();
}
return 0;
}
//クラスの実装
Member::Member(string name,int age){
this->name=name;
this->age=age;
}
Member::Member(){
}
void Member::send(){
cout << "name:" << name << endl;
cout << "age:" << age << endl;
}
3.7 オブジェクトのライフサイクル
オブジェクトの生成と破棄について考えます。
クラスのオブジェクトは変数と同様、関数の外で宣言されたグローバルオブジェクトと関数の中で宣言されたローカルオブジェクトがある。
オブジェクトが生成させるときコンストラクタが呼び出され、オブジェクトが破棄されるときデストラクが呼び出されるため、コンストラクタとデストラクタの呼出しタイミングを調べて、オブジェクトの生成と破棄のタイミングを調べる。
#include <string>
#include <iostream>
using namespace std;
class Member{
public:
string name;
public:
//コンストラクタ
Member(string name);
Member();
~Member();
};
void sub();
Member m_member_globalobj("グローバルオブジェクト");
int main(){
cout << "main関数が呼び出された" << endl;
sub();//1回目の呼出し
sub();//2回目の呼出し
return 0;
}
//クラスの実装
Member::Member(string name){
cout << name << "のコンストラクタが呼び出された" << endl;
this->name=name;
}
Member::Member(){
cout << this->name << "のコンストラクタが呼び出された" << endl;
}
Member::~Member(){
cout << this->name << "のデストラクタが呼び出された" << endl;
}
void sub(){
cout << "sub()が呼び出された" << endl;
//ローカルオブジェクトを宣言
Member m_member_localobj("ローカルオブジェクト");
cout << "sub関数終了" << endl;
}