Y's note

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

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

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

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

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

index

最速C++マスター その2です。今回内容としてまとめることは以下の通りです。

  • Const
  • Static
  • Template
    • Template関数
    • Templateクラス
  • Struct
  • SmartPointer
  • STL
    • Container List
    • Vecotr
    • Map
  • curl

Const

Constは変数を後から書き換えができないようにする制約修飾子です。Const変数に対して書き換えを行うプログラミングを書くとコンパイル時にread-onlyエラーがでます。Constを付けると誤ったプログラミングを防ぐだけでなく、コンパイラが最適化しやすくなるので出来る限りConstを付けても良い箇所は付けるようにすると良いと思います。Constは通常の変数だけでなく、ポインタ変数、関数の引数、メンバ関数にも適用可能です。

#include <string>
#include <iostream>

class Student {
private :
    std::string name;
    int number;
public :
    void setName( const std::string& name );
    void setNumber( const int& number );
    std::string getName() const;
    int getNumber() const;
};

//Constructor
Student::Student() { }
//Destructor
Student::~Student() { 
    delete this;
}

// メンバ関数 引数をconstで定義
void Student::setName( const std::string& name ) {
    this->name = name;
}

// メンバ関数 引数をconstで定義
void Student::setNumber( const int& number ) {
    this->number = number;
}

// メンバ関数 メンバ関数もconstを指定できる 
std::string Student::getName() const {
    return this->name;
}

// メンバ関数 メンバ関数もconstを指定できる 
int Student::getNumber() const {
    return this->number;
}

int main(){
    const int a = 1;
    // errorになる
    // a = 2;
    Student *student = new Student();
    student->setName( "John" );
    student->setNumber( 2 );
    std::cout << student->getName() << std::endl;
    std::cout << student->getNumber() << std::endl;
    delete student;
    student = NULL;
    return 0;
}

Static

Staticを付けた変数は静的変数と呼ばれ初期化が一度しか行われない、アドレスが固定な特殊変数になります。staticを付けた変数は関数を抜けても参照が可能です。Static修飾子をClassのメンバ変数や関数に付与するとオブジェクトに属さないものとして定義できます。

#include <string>
#include <iostream>

class Student {
private :
    static std::string name;
    static int number;
public :
    // staticメンバ関数として定義
    static void setName( const std::string& name ) {
        Student::name = name;
    }
    static void setNumber( const int& number ) {
        Student::number = number;
    }
    static std::string getName() {
        return Student::name;
    }
    static int getNumber() {
        return Student::number;
    }
};
    
std::string Student::name;
int Student::number;

int main(){
    Student::setName( "John" );
    Student::setNumber( 2 );
    std::cout << Student::getName() << std::endl;
    std::cout << Student::getNumber() << std::endl;
    return 0;
}

Template

Template Function

C言語の関数は一般的には厳密な型定義を必要とします。型定義が厳密である故に例えば同じような処理の関数なんだけど引数の型が決まってしまうと、それぞれの型にあった関数を作らなければならないという問題が発生します。これはTemplateを使って引数の汎用化を行う事で解消できます。関数定義の前にtemplate 関数名()という記述をすることで型の固定定義を回避できます。これにより利用する側で様々な形式の引数を設定できます。

#include <string>
#include <iostream>

template <typename X>
X sum( X var_x, X var_y, X var_z ) { 
    return var_x + var_y + var_z;
}

template <typename X, typename Y, typename Z>
X max( Y var_y, X var_z ) { 
    if( var_y > var_z ) { 
        return var_y;
    }   
    return var_z;
}

int main(){
    // sumの型定義は一つ。同じ型であれば適用可能
    int i_sum = sum( 1,2,3 );
    std::cout << i_sum << std::endl;
        
    // 上と同じく全てfloat型。引数の汎用化に成功
    float f_sum = sum( 1.0,2.0,3.0 );
    std::cout << f_sum << std::endl;
        
    // 下は型定義のエラーになる。引数の方に intとdoubleが混在
    //double dd_sum = sum( 1, 2, 3.0 );
   
    // 引数の型指定を呼び出し側で行えるようにする
    double d_max = max<double, double, int>( 2.1, 3 );
    std::cout << d_max << std::endl;
    return 0;
}
Template Class

TemplateClassもTemplateFunctionと同様で、Classとして扱うデータの型定義を汎用化します。以下では汎用的なStackクラスを定義し様々なデータ型を保持可能としています。

#include <string>
#include <iostream>

template <typename TYPE>
class Stack {
private:
    TYPE buffer[100];
    int index;
public:
    Stack();
    bool push( TYPE var );
    TYPE pop();
};

template <typename TYPE>
Stack <TYPE> ::Stack() {
    index = 0;
}

template <typename TYPE>
Stack <TYPE> ::~Stack() {
}

template <typename TYPE>
bool Stack <TYPE> ::push( TYPE var ) {
    buffer[index] = var;
    index++;
    return true;
}

template <typename TYPE>
TYPE Stack <TYPE> ::pop() {
    --index;
    return buffer[index];
}

// main
int main(){
    Stack <int> int_stack;
    int_stack.push( 1 );
    int_stack.push( 2 );
    std::cout << int_stack.pop() << std::endl;
    std::cout << int_stack.pop() << std::endl;

    Stack <char *> str_stack;
    str_stack.push( "test" );
    str_stack.push( "stack" );
    std::cout << str_stack.pop() << std::endl;
    std::cout << str_stack.pop() << std::endl;

    return 0;
}

上の例をポインタを使って書き換えてみます。classの定義は同じなのでint mainの中だけ書き換えます。

// main
int main(){
    Stack <int> *int_stack = new Stack <int>;
    int_stack->push( 1 );
    int_stack->push( 2 );
    std::cout << int_stack->pop() << std::endl;
    std::cout << int_stack->pop() << std::endl;
    delete int_stack;
    int_stack = NULL;

    Stack <char *> *str_stack = new Stack <char *>;
    str_stack->push( "test" );
    str_stack->push( "stack" );
    std::cout << str_stack->pop() << std::endl;
    std::cout << str_stack->pop() << std::endl;
    delete str_stack;
    str_stack = NULL;

    return 0;
}

SmartPointer

stdに備わっているauto_ptr/shared_ptr/unique_ptrはSmartPointerと呼ばれるpointerで、newにより動的確保したオブジェクトポインタを自動的にdeleteしてくれる便利な機能です。auto_ptr 変数( new Class名前() );とクラスの動的呼び出しを行い、使わなくなった時点で自動的にdelete処理が働きClassのDestructorが呼び出されます。auto_ptrは所有権限の移行が=演算子で行われ、基は破壊されることになります。このような挙動問題がありauto_ptrは非推奨となっています。C++0xでは代わりに=演算子で所有権限が移行できないunique_ptrの利用が勧められています

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

class Student {
private :
    std::string name;
    int number;
public :
    Student();
    ~Student();
    void setName( const std::string& name );
    void setNumber( const int& number );
    std::string getName() const;
    int getNumber() const;
};

//Constructor
Student::Student() {
    cout << " ---constructor--- " << endl;
}
//Destructor
Student::~Student() { 
    cout << " ---destructor--- " << endl;
}

// メンバ関数 引数をconstで定義
void Student::setName( const std::string& name ) {
    this->name = name;
}

// メンバ関数 引数をconstで定義
void Student::setNumber( const int& number ) {
    this->number = number;
}

// メンバ関数 メンバ関数もconstを指定できる 
std::string Student::getName() const {
    return this->name;
}

// メンバ関数 メンバ関数もconstを指定できる 
int Student::getNumber() const {
    return this->number;
}

int main(){
    //auto_ptrは非推奨となっているので利用には注意してください。
    auto_ptr<Student> student( new Student() );
    student->setName( "John" );
    student->setNumber( 2 );
    std::cout << student->getName() << std::endl;
    std::cout << student->getNumber() << std::endl;
}

STL

STLはStandard Template Libraryの略で古くからあるC++のラリブラリです。Standard Template Library - Wikipedia はてなブックマーク - Standard Template Library - Wikipedia上で紹介したTemplateを有効に利用し、ライブラリ利用者はデータ型に捕われない使い方が可能です。以下では代表的なContainerの内容を中心に書きます。

Container List

Containerはデータを格納するものです。Cでは標準で備わっていなかった連想配列が使えたりします。それぞれのContainerに特徴があり使い方に注意しないと効率が悪いデータの持ち方をする可能性があります。以下簡単ですが表にまとめます。

Container Feature include header
vector 動的配列/ 配列にランダムアクセス可能 / 末尾への挿入削除は高速 / 要素の数の変化が多い場合は注意 vector
queue キュー / FIFO / 要素の挿入と取り出しのみが可能 queue
deque 両端キュー/ランダムアクセス可能 / 先頭、末尾のデータ追加、取得に適している。 deque
list 双方向リスト / シーケンシャルアクセス可能(ランダムアクセスには不向き) / 任意の位置に挿入、削除が高速 list
stack スタック / FILO / 最後の要素追加と取得に適している stack
priority_queue 優先順位付きqueue / 優先度の高いものから取り出す queue
set セット / ソートをして要素を追加 / 値の重複が禁止 set
multiset マルチセット / データの重複が可能なset set
map 辞書 / 連想配列を作るのに適している。key value形式 / keyの重複は禁止 map
multimap マルチ辞書 / 連想配列 / keyの重複が可能 map
Vector

Vectorは動的な配列です。C++では配列表現はvectorを使う事で可能ですし、C++では配列を使うのはできるだけ避けるべきと言われています。以下にサンプルコードを記載します。push_back,back,pop_backなどの関数を覚えて基本的な使い方に慣れると良いと思います。

#include<iostream>
#include<vector>
#include <algorithm>
using namespace std;

template<typename T>
void print_vector( const vector<T> & vect ) {
    for( int j=0; j<vect.size(); j++ ) {
        cout << vect[j] << endl;
    }
}

int main() {
    
    cout << " --- int vector --- " << endl;

    // int型のvectorを定義
    vector<int> vect;
    
    //末尾に要素追加
    vect.push_back( 1 );
    vect.push_back( 100 );
    vect.push_back( 2);

    // ソート
    cout << " --- sort --- " << endl;
    sort( vect.begin(), vect.end() );
    print_vector( vect );

    // 降順でソート
    cout << " --- sort desc --- " << endl;
    sort( vect.rbegin(), vect.rend() );
    print_vector( vect ); 

    //要素を末尾から取り出す
    cout << " --- pop --- " << endl; 
    while( !vect.empty() ) {
        cout << vect.back() << endl;
        vect.pop_back();
    }
   
    cout << " \n\n ";
    cout << " --- string vector --- " << endl;

    // string型のvectorを定義
    vector<string> s_vect;
    
    //末尾に要素追加
    s_vect.push_back( "vector" );
    s_vect.push_back( "map" );
    s_vect.push_back( "stack" );

    // ソート
    cout << " --- sort --- " << endl;
    sort( s_vect.begin(), s_vect.end() );
    print_vector( s_vect );

    // 降順でソート
    cout << " --- sort desc --- " << endl;
    sort( s_vect.rbegin(), s_vect.rend() );
    print_vector( s_vect ); 

    //要素を末尾から取り出す
    cout << " --- pop --- " << endl; 
    while( !s_vect.empty() ) {
        cout << s_vect.back() << endl;
        s_vect.pop_back();
    }
    return 0;
}
--- int vector --- 
--- sort --- 
1
2
100
--- sort desc --- 
100
2
1
--- pop --- 
1
2
100
 
--- string vector --- 
--- sort --- 
map
stack
vector
--- sort desc --- 
vector
stack
map
--- pop --- 
map
stack
vector
map

mapはkeyとvalueを組み合わせた連想配列です。データはkeyでsortされて保持されます。insert,find,erase関数を覚えておくと便利に使えます。

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

template<typename X,typename Y>
void print_map( map<X,Y>& value ) {
    typename map<X,Y>::iterator it = value.begin();
    while( it != value.end() ) {
        cout << it->first +  ":" + it->second << endl;
        ++it;
    }
}

int main() {
    
    cout << " --- key int map --- " << endl;
    //mapを定義 keyがint valueがstring
    map<int,string> int_values;
    
    //要素追加
    int_values.insert( map<int,string>::value_type( 5, "c++" ) );
    int_values.insert( map<int,string>::value_type( 10, "python" ) );
    int_values.insert( map<int,string>::value_type( 2, "php" ) );
    int_values.insert( map<int,string>::value_type( 1, "java" ) );
    
    //defaultはkeyでsort
    print_map( int_values );

    cout << " --- key string map --- " << endl;
    //mapを定義 keyもvalueもstring
    map<string,string> string_values;
    
    //要素追加
    string_values.insert( map<string,string>::value_type( "love", "c++" ) );
    string_values.insert( map<string,string>::value_type( "dislike", "python" ) );
    string_values.insert( map<string,string>::value_type( "hate", "php" ) );
    string_values.insert( map<string,string>::value_type( "like", "java" ) );
    
    //defaultはkeyでsort
    print_map( string_values );
   
    cout << " --- find map --- " << endl;

    //検索
    map<string,string>::iterator it = string_values.find( "like" );
    if( it != string_values.end() ) {
        cout << it->first + ":" + it->second << endl;
    }
    return 0;
}
--- key int map --- 
1:java
2:php
5:c++
10:python
--- key string map --- 
dislike:python
hate:php
like:java
love:c++
--- find map --- 
like:java

curl

C++curlにてhttp通信でデータを取得する方法について記述します。centosの環境ではcurl-develを必要とするのでyum install curl-develを実行して入れておきます。libcurlがcurl_ease_init,curl_ease_setopt,curl_easy_perfome,curl_easy_cleanupという関数を用意しているのでそれを組み合わせて使います。

#include <iostream> 
#include <string>
#include <curl/curl.h>
using namespace std;

size_t writeFunction( char *pointer, const size_t size, const size_t nmemb, string* userp ) {
     int realsize = size * nmemb;
     userp->append(pointer, realsize);
     return realsize;
}

int main() {
    CURL *ch;
    CURLcode res;
    string write_data;
    ch = curl_easy_init();
    curl_easy_setopt( ch, CURLOPT_URL, "http://d.hatena.ne.jp/yutakikuchi" ); 
    curl_easy_setopt( ch, CURLOPT_WRITEFUNCTION, writeFunction );
    curl_easy_setopt( ch, CURLOPT_WRITEDATA, (string*)&write_data );
    res = curl_easy_perform( ch );
    curl_easy_cleanup(ch);
    if( res != 0 ) {
        return 1;
    }
    printf("%s", write_data.c_str() );
    return 0;
}