Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

C++最速マスター その1

C++プログラミング入門

C++プログラミング入門

Index

業務でC++を使う事になったので少し勉強した内容をまとめておきます。内容をまとめるのに時間がかかりそうなので3回ぐらいの記事に分けてまとめたいと思います。今回の記事では以下を紹介します。

  • Compiler
    • man
    • options
    • usage
  • Hello World
  • Address / Pointer
    • Address / Pointer
    • 値渡し / 参照渡し
  • extern C
  • Class
    • Basic Class
    • Constructor / Destructor
    • Object Pointer
    • Inheritance
    • Singleton
    • virtual function
  • Link

Compiler

C言語コンパイラgccを利用しましたが、C++ではg++というコンパイラを利用します。基本的な使い方はgcc,g++ともに違いは無いようです。

man
$ man g++
G++(1)                             GNU Tools                            G++(1)

名称
       g++ - GNU プロジェクト C++ コンパイラ (v2.4)

書式
       g++ [option | filename ]...

解説
       C コンパイラおよび C++ コンパイラは統合されました。 g++ は gcc に C++ を解釈するようにするオプションをつ
       けてコールするスクリプトです。詳細は英語版のオンラインマニュアルおよび gcc(1) を参照して下さい。
Options

以下では代表的なオプションの表を記載しておきます。

オプション 解説
-c ソースファイルをコンパイルまたはアセンブルまではしますがリンクはしません。
-g デバック情報を生成します。
-llibrary 名前がlibraryであるライブラリをリンク時に使用します。
-Idir ディレクトリ dir をインクルードファイルの検索するディレクトリのリスト中に追加します。
-Ldir ディレクトリdir を ‘-l’ による検索が行なわれるディレクトリのリストに加えます。
-o file 出力先を file に指定します。
-O Level レベルに合わせた最適化を行います。Levelとして指定できるのは1〜3。
-O0 最適化を行いません。
-Os サイズ優先で最適化します。
-x language こ のオプションに続く入力ファイルの言語を language であると明示的に指定します。
-S コンパイルが終った所で処理を停止し、アセンブルは行いません。
-E プリプロセス処理が終了したところで停止します。コンパイルはしません。
-v (標準エラー出力に対して) コンパイルの各ステージで実行されるコマンドを表示します。
-W 特別な警告メッセージを表示します。
-Wall 全ての ‘-W’ オプションを結合したものです。
usage

g++というコマンドが設定されていることを確認し、hello.cppというファイルをhello実行ファイルとして出力する例になります。

$ which g++
/usr/bin/g++
$ g++ hello.cpp -o hello
$ ls -al
-rwxr-xr-x  1 yuta yuta 8548  4月 16 00:37 hello
-rw-r--r--  1 yuta yuta   95  4月 15 23:35 hello.cpp

Hello World

Hello Worldを標準出力する簡単なプログラムです。#include でiostreamというモジュールをincludeします。これは標準入出力を行うモジュールでstdを含みます。C言語で言う#include のようなものとして理解すると良いと思います。main関数はintで定義しないとエラーになるのでintとします。下のプログラムではcout(標準出力に)Hello Worldとendl(改行)を出力しなさいという命令になります。std::coutのようにstd::と付けているのは名前空間でcoutという関数を様々なモジュールで定義された時の衝突を防ぐために指定します。このSourceをhello.cppとして保存します。

#include <iostream>

int main()
{
    std::cout << "Hello World" << std::endl;
    return 0;
}
Execution result
$ g++ hello.cpp -o hello
$ ./hello               
Hello World

Adress / Pointer

C言語でとても厄介なのがPointerです。変数やClassなどのデータはメモリ上に格納されアドレスに対して記憶領域を持ちます。データを参照する際のメモリ上のアドレス番地をPointerと呼びます。下では各変数の値に対して&をつけてアドレスを参照するようにしています。またアドレスを変数格納するにはポインタ変数と呼ばれるアスタリスクを付けた変数を宣言します。

#include <iostream>
using namespace std;

int main()
{
    /* 変数 */
    int a = 10;
    
    /* &を付けるとアドレスを返す */
    cout << &a << endl;
    
    /* ポインタ変数 *を入れるとアドレスを格納できる */
    int *b = &a;
    cout << b << endl;

    /* 変数 */
    string c = "address"; 
    
    /* &を付けるとアドレスを返す */
    cout << &c << endl;
    
    /* ポインタ変数 *を入れるとアドレスを格納できる */
    string *d = &c; 
    cout << d << endl;

    return 0;
}
0x7fff1a9406e8
0x7fff1a9406e8
0x7fff1a9406e0
0x7fff1a9406e0
値渡し / 参照渡し

プログラミング言語の変数やインスタンスデータの渡し方には値をそのまま渡す(コピーを作成する)値渡しと、アドレス番地を渡して値コピーを防ぐ参照渡しがあります。それぞれにメリット/デメリットがありますが参照渡しを利用してコピー領域を少なくする手法が良く利用されると思います。まずは値渡しの例を見てみます。例えば関数に対して変数を渡す時の例です。mainで定義された変数のアドレスと関数に渡した時の変数アドレスが変わっているのが分かります。

#include <iostream>
using namespace std;

void showAddress( int a ) {
    cout << a << endl;
    cout << &a << endl;
    a = 20;
}

int main()
{
    /* 変数 */
    int a = 10;
    cout << a << endl;
    cout << &a << endl;
    showAddress( a );
    cout << a << endl;
    cout << &a << endl;
    return 0;
}
10
0x7fff2bb7b13c
10
0x7fff2bb7b11c
10
0x7fff2bb7b13c

上の関数定義の引数に対して&で値を入れるようにします。&はアドレスを渡す記号なので関数の中で変更した変数がmainの中でも有効になっているのが分かります。

#include <iostream>
using namespace std;

void showAddress( int&  a ) {
    cout << a << endl;
    cout << &a << endl;
    a = 20;
}

int main()
{
    /* 変数 */
    int a = 10;
    cout << a << endl;
    cout << &a << endl;
    showAddress( a );
    cout << a << endl;
    cout << &a << endl;
    return 0;
}
10
0x7fffbd0a096c
10
0x7fffbd0a096c
20
0x7fffbd0a096c

extern C

C言語C++のプログラムが混在している場合連携が通常はできません。そこでC言語からC++を呼び出す時、逆のC++からCを呼び出す時に利用するのがextern "C"という記述です。ここでは通常のC言語の記述を行い、そこからC++の関数を呼び出す例に付いて記述します。cppの型を定義するheaderファイルの読み込みC、C++の両方で行います。

  • test_extern.h
#ifdef __cplusplus
extern "C" {
#endif
    int CPlusMain();
#ifdef __cplusplus
};
#endif
  • c_extern.c
#include <stdio.h>
#include "test_extern.h"

int main() {
    CPlusMain();
    return 0;
}
  • cpp_extern.cpp
#include <string>
#include <iostream>
#include "test_extern.h"
using namespace std;

/* Classの定義 */
class Student{
private:
    string s_name;
public:
    void setName( string name );
    string getName();
};

/* メソッドの定義 */
void Student::setName( string name ) {
    s_name = name;
}

/* メソッドの定義 */
string Student::getName() {
    return s_name;
}

/* main関数の定義 */
extern "C" int CPlusMain()
{
    Student student;
    student.setName( "John" );
    cout << student.getName() << endl;
    return 0;
}
  • Compile / Exec
$ gcc -c c_extern.c
$ g++ -c cpp_extern.cpp
$ g++ -o extern  c_extern.o cpp_extern.o
$ ./extern
John

Class

Basic Class

C++C言語と異なりオブジェクト指向型のプログラミング言語です。JAVA等と同様にClassを定義する事ができます。Classを定義ではClassの型定義と詳細定義の記述を分けるのが一般的なようです。(定義と詳細を一緒に記述しているサンプルも沢山あります。)

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

/* Classの定義 */
class Student{
private:
    string s_name;
public:
    void setName( string name );
    string getName();
};

/* メソッドの定義 */
void Student::setName( string name ) { 
    s_name = name;
}

/* メソッドの定義 */
string Student::getName() {
    return s_name;
}

/* main関数の定義 */
int main()
{
   Student student;
   student.setName( "John" );
   cout <<student.getName() << endl;
}
Constructor / Destructor

C++でもClass初期化のConstructorと破棄のDestructorを定義する事が出来ます。記述の制約としてConstuctorはクラス名と同じメソッドとして定義、Destructorは更に接頭辞としてチルダ(~)を必要とします。Class 変数名を定義した時に自動的にConstructorが呼び出され、使わなくなったタイミングでDestructorが呼び出されます。上のBasicSourceに対してConstructorとDestructorを定義してみます。

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

/* Classの定義 */
class Student{
private:
    /*メンバ変数*/
    string s_name;
public:
    /*コンストラクタ*/
    Student();
    /*デストラクタ*/
    ~Student();
    void setName( string name );
    string getName();
};

/* コンストラクタ */
Student::Student() {
    cout << "Create Student" << endl;
}

/* デストラクタ */
Student::~Student() {
    cout << "Delete Student" << endl;
}

/* メソッドの定義 */
void Student::setName( string name ) {
    s_name = name;
}

/* メソッドの定義 */
string Student::getName() {
    return s_name;
}

/* main関数の定義 */
int main()
{
   Student student;
   student.setName( "John" ); 
   cout << student.getName() << endl;
}
Copy Constructor

インスタンスのコピーが発生した時に呼び出される特殊なConstructorです。コピーされた段階で元のConstructorは呼び出されずCopyConstructorだけ呼び出されます。

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

/* Classの定義 */
class Student{
private:
    /*メンバ変数*/
    string s_name;
public:
    /*コンストラクタ*/
    Student();
    /*コピーコンストラクタ*/
    Student( const Student& student );
    /*デストラクタ*/
    ~Student();
    void setName( string name );
    string getName();
};


/* コンストラクタ */
Student::Student() {
    cout << "Create Student" << endl;
}

/* コピーコンストラクタ */
Student::Student( const Student& student ) {
    cout << "Copy Student" << endl;
}

/* デストラクタ */
Student::~Student() {
    cout << "Delete Student" << endl;
}

/* メソッドの定義 */
void Student::setName( string name ) {
    s_name = name;
}

/* メソッドの定義 */
string Student::getName() {
    return s_name;
}

/* main関数の定義 */
int main()
{
   Student student1;
   Student student2 = student1;
   student2.setName( "John" ); 
   cout << student2.getName() << endl;
}
Create Student
Copy Student
John
Delete Student
Delete Student
ObjectPointer

C言語でのPointerとは変数や構造体のアドレスを格納する事でしたが、C++のクラス(Object)にのPointerも格納する事が出来ます。クラスをnewする事になるので使用が完了した時に必ずdeleteを忘れないようにします。またdeleteした変数にはnullを入れておくようにルールとして覚えます。newでConstructor、deleteでDestructorが呼び出されます。またクラスをPointer変数として格納した場合のメンバ関数の呼び出しはアロー演算子(->)で行います。

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

/* Classの定義 */
class Student{
private:
    string s_name;
public:
    void setName( string name );
    string getName();
};

/* メソッドの定義 */
void Student::setName( string name ) {
    s_name = name;
}

/* メソッドの定義 */
string Student::getName() {
    return s_name;
}

/* main関数の定義 */
int main()
{
   Student *student;
   /* pointer変数に対してnew結果を代入 */
   student = new Student();
   student->setName( "John" );
   cout << student->getName() << endl;
   /* newをしたら必ずdeleteする */
   delete student;
   /* deleteしたらNULLを入れておく */
   student = NULL;
   return 0;
}
Inheritance

C++ではクラスの継承も可能です。継承の目的は親クラスの定義を継承して子クラスで利用できるようにする事です。下のsourceでは親クラスのメソッドと子クラスで追加したメソッドの両方が利用できると思います。

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

/* Classの定義 */
class Student{
private:
    /*メンバ変数*/
    string s_name;
public:
    /*コンストラクタ*/
    Student();
    /*デストラクタ*/
    ~Student();
    void setName( string name );
    string getName();
};


/* コンストラクタ */
Student::Student() {
    cout << "Create Student" << endl;
}

/* デストラクタ */
Student::~Student() {
    cout << "Delete Student" << endl;
}

/* メソッドの定義 */
void Student::setName( string name ) {
    s_name = name;
}

/* メソッドの定義 */
string Student::getName() {
    return s_name;
}

/*子クラスの定義*/
class SubStudent : public Student {
private : 
    int s_age;
public :
    void setAge( int age );
    int getAge();
};

/* メソッドの定義 */
void SubStudent::setAge( int age ) {
    s_age = age;
}

/* メソッドの定義 */
int SubStudent::getAge() {
    return s_age;
}

/* main関数の定義 */
int main()
{
   SubStudent John;
   John.setName( "John" ); 
   John.setAge( 24 );
   cout << John.getName() << endl;
   cout << John.getAge() << endl;
}
Singleton

GoFデザインパターンのシングルトンについても良く使いそうなので記述しておきます。他言語と同様にConstructor/Destructorをprivateとして設定します。またCopyConsturctorや代入演算に付いてもprivateとして制御します。

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

/* Classの定義 */
class Singleton{
private:
    /* メンバ変数 */
    string comment;
    /*コンストラクタprivateに*/
    Singleton();
    ~Singleton();
    /*コピーコンストラクタ、代入演算も制御*/
    Singleton(const Singleton& r);
    Singleton& operator=(const Singleton& r);
public:
    string getComment();
    static Singleton* getInstance() {
       static Singleton instance;
       return &instance;
    };
};

/* コンストラクタ */
Singleton::Singleton() {
    comment = "Singleton Class";
}

/* デストラクタ */
Singleton::~Singleton() {
}

/* メソッドの定義 */
string Singleton::getComment() {
    return comment;
}

/* main関数の定義 */
int main()
{
    Singleton* inst = Singleton::getInstance();
    string comment = inst->getComment();
    cout << comment << endl;
    return 0;
}
virtual function

JAVAのabstructと同義で親クラスで抽象的なメソッドを定義し、子クラスでその詳細を再定義する関数です。デザインパターンのTemplate Methodパターン等に利用され例えば親クラスで処理の流れを定義し、子クラスでvirtual関数の詳細を定義するような時に利用します。

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

/* Classの定義 */
class Template {
protected:
    string p_data;
public:
    string MainProcess( string data );
    void setData( string data );
    /*仮想関数を定義 詳細は特に記述しない*/
    virtual string getData(){};
};

/* 基底処理の作成 */
string Template::MainProcess( string data ) {
    setData( data );
    return getData();
}

/* メソッドの定義 */
void Template::setData( string data ) {
    p_data = data;
}

/* 子クラスの定義 */
class SubClass : public Template {
public :
    string getData();
};

/* メソッドの定義  virtualの詳細を定義 */
string SubClass::getData() {
    return p_data;
}

/* main関数の定義 */
int main()
{
   SubClass temp;
   cout << temp.MainProcess( "Test Data" ) << endl;
   return 0;
}