C言語の記録

C言語発展の記録です。

前田稔の超初心者のプログラム入門

  1. C言語(シーげんご)は、1972年に AT&T ベル研究所のデニス・リッチーが主体となって開発したプログラミング言語です。
    C言語は当初パソコンのプログラミング言語として広く使われるようになりました。
    その後パソコンの発展と共に大きく進化を遂げてきました。
    最初に実用化されたC言語は、真っ黒なコンソール画面に TEXT 文字を印字するものでした。
    2017/08 現在 Visual Studio 2017 でも Win32 コンソールアプリケーションから作成することが出来ます。
  2. 当初の Object Class の使い方です。
    例えば GcmLcm Object Class を使うときは GcmLcm *gcmlcm; でポインタを定義して、new GcmLcm(); でインスタンスを生成します。
    メンバ関数を呼び出すときは gcmlcm->gcm(x,y) のように「->」を付けて参照します。
    使い終わったインスタンスは delete で削除して下さい。
        GcmLcm  *gcmlcm;
        gcmlcm = new GcmLcm();
        printf("%d と %d の GCM は %d で、LCM は %d です", x,y,gcmlcm->gcm(x,y),gcmlcm->lcm(x,y));
        delete gcmlcm;
    
    次のような使い方をすると new で生成したり delete で開放する処理が不要になります。
    [gcmlcm;] で GcmLcm Class が構造体と同じように定義されます。
    メンバ関数を呼び出すときは gcmlcm.gcm(x,y) のように「.」を付けて参照します。
    class GcmLcm
    { private:
        int     w1,w2;
      public:
        int gcm(int v1, int v2)
        {   w1 = v1;
            w2 = v2;
            while(w1 != w2)
            {   if (w1 > w2)    w1-= w2;
                else            w2-= w1;
            }
            return w1;
        }
        int lcm(int v1, int v2)
        {   return v1*v2/gcm(v1,v2);
        }
    } gcmlcm;
    
    int main()
    {   int     x = 32;
        int     y = 24;
        printf("%d と %d の GCM は %d で、LCM は %d です", x,y,gcmlcm.gcm(x,y),gcmlcm.lcm(x,y));
        return 0;
    }
    
    文字列は char の配列として定義されていて、文字列処理に苦労したことが思い出されます。
        char moji = 'A';
        char str[10] = "ABCabc123"
    
    詳細は、私のページから[C言語(UnManeged Mode)の基本]を参照して下さい。

  3. 次にコンソール画面にカラー(16色)で簡単な図形を描画する機能が備わりました。
    真っ黒な画面からカラーになったのですから、白黒のテレビがカラーテレビになったような驚きでした。
    Visual C++ ver 1.5 までは動作しましたが、現在では動きません。
    if (!_setvideomode(_VRES16EXCOLOR)) return(-1); で Graphic Mode に切り替えます。
    _setpixel(x,y); は、x,y の座標にドット(点)を描画します。
    _moveto(x,y); は、現在座標を x,y に移動するコードです。
    _lineto(x,y); は、現在座標から x,y の座標に線を描きます。
    _setvideomode(_DEFAULTMODE); は、Graphic Mode を終了して通常のモードに切り替えるコードです。
    _setcolor(14); は、色を切り替えるコードです。
    #include <graph.h>
    int     main(void)
    {   int i;
        if (!_setvideomode(_VRES16EXCOLOR)) return(-1);
        //「X座標 100,Y座標 50」から 100 ドットを描画
        for(i=100; i<200; i++)      _setpixel(i,50);
        _setcolor(14);
        //「X座標 100,Y座標 100」から 100 ドットを描画
        for(i=100; i<200; i++)      _setpixel(i,100);
        _setcolor(12);
        //「X座標 100,Y座標 150」から「X座標 200,Y座標 150」にラインを描画
        _moveto(100,150);
        _lineto(200,150);
        _getch();
        _setvideomode(_DEFAULTMODE);
    }
    
    グラフィックモードを使って簡単な画像を描画することも出来るようになりました。
    詳細は、私のページから[グラフィックモード]を参照して下さい。

  4. 次に現れたのが C++ です。
    一見すると同じ言語だとは思えないほどで、C++ の説明だけで1冊の分厚い本が刊行されています。
    C++ の機能は現在も広く受け継がれていますが、見かけを左右する iostream 関係の機能は時々みかける程度です。
    ソースファイルの拡張子が[*.C]から[*.CPP]に変わりました。
    iostream を使った入出力です。
    コンソール(キーボード)からタイプ入力した数値を、コンソール(ディスプレイ)に印字しています。
    #include <iostream>
    using namespace std;
    int main(void)
    {   int     i;
        char    ans;
        for(ans='y',i=1; ans=='Y' || ans=='y'; i++)
        {   cout << '\n' << i;
            cout << "    処理を続けますか(Y/N)";
            cout.flush();       //入力する前にフラッシュ
            cin >> ans;
        }
        return(0);
    }
    
    Malloc(Alloc)でメモリ領域を割り当てていたのが、new で割り当てるようになりました。
    p_int は int 型のポインターで、p_char は char[] の先頭を指すポインターです。
    #include <stdio.h>
    #include <string.h>
    int     *p_int= NULL;
    char    *p_char= NULL;
    int  main()
    {
        p_int=  new int;
        p_char= new char[32];
        *p_int= 123;
        strcpy(p_char,"char[32] を確保");
        printf("New でメモリを割り当てる  %d  %s\n",*p_int,p_char);
        if (p_int)  delete p_int;
        if (p_char) delete [] p_char;
        return 0;
    }
    
    namespace(名前空間)と scope 演算子が使われるようになりました。
    #include <stdio.h>
    namespace name1
    {   int     val= 100;  }
    namespace name2
    {   int     val= 200;  }
    int main(void)
    {   using namespace name1;
        printf("val= %d\n", val);
        printf("name1::val= %d\n", name1::val);
        printf("name2::val= %d\n", name2::val);
        return 0;
    }
    
    template 関数を使うと関数の型を呼び出し側の型に合わせてくれます。
    template<class Type>
    Type  Max(Type n1, Type n2)
    {
        if (n1>n2)  return n1;
        return n2;
    }
    
    最初に int 型で、次に float 型で呼び出します。
        int     d1,d2;
        d1= 3;
        d2= 7;
        printf("d1=%d, d2=%d   MAX=%d\n",d1,d2,Max(d1,d2));
    
        float   f1,f2;
        f1= 12.3f;
        f2= 234.5f;
        printf("f1=%f, f2=%f   MAX=%f\n",f1,f2,Max(f1,f2));
    
    これ以外にも多くの仕様が定められています。
    詳細は、私のページから[C++言語]を参照するか、分厚い参考書をごらん下さい。

  5. 次に登場するのが STL(Standard Template Library) です。
    標準 C++ が規定するのは言語仕様だけなく、C++標準ライブラリも規格の中で明確に定められています。
    その C++ 標準ライブラリの一部として組み入れられたのが STL(Standard Template Library)です。
    STL で最も良く使われるのは string(wstring)でしょうか?
    #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {   string s;
    
        s= "12345";
        cout << "1番目の文字列: " << s << endl;
        s= "abcdefg";
        cout << "2番目の文字列: " << s << endl;
        s= "ABCDEFGHIJKLMN";
        cout << "3番目の文字列: " << s << endl;
        return 0;
    }
    
    vector コンテナ(配列に相当)を使ってみました。
    v.push_back(n); で vector に n の値を追加します。
    v[n] で vector の n 番目の値を参照します。
    #include <iostream>
    #include <vector>
    using namespace std;
    int main()
    {   vector<short> v;
        short   n,s,c;
        n= 8;
        s=c= 0;
        while(s!=8)
        {   v.push_back(n);
            s= n*2+c;
            n= s%10;
            c= s/10;
        }
        for(n=v.size()-1; n>=0; n--)    cout << v[n];
        cout << endl;
        return 0;
    }
    
    これ以外にも多くの仕様が定められています。
    詳細は、私のページから[STL(Standard Template Library)]を参照して下さい。

  6. MS-DOS の時代の文字コードは Shift-JIS(ANSI)と決まっていたのですが、Unicode が使われるようになってきました。
    Unicode にも 8 bit, 16 bit, 32 bit, etc が存在するのですが、ソースプログラムファイルには utf-8 が使われることが多いようです。
    C# や最新の C++ では内部コードとしては utf-16 が使われているようです。
    Unicode には BOM が格納されているものと、格納されていないものがあります。
    BOM(byte order mark)とはテキストファイルの先頭に格納される文字コードを示すIDです。
    文字コード BOM 説明
    utf-16 FFFE 先頭2バイトが BOM です
    utf-8 EFBBBF先頭3バイトが BOM です
    utf-8(BOM無し) BOM は格納されていません
    MultiByte(Shift_JIS) 文字列と Unicode(16 bit) 文字列を定義してみました。
        char    str1[80];
        LPSTR   str2 = "LPSTR で宣言";
    
        wchar_t str1[80];
        WCHAR   str2[80];
        LPWSTR  str1 = L"LPWSTR で宣言";
    
    詳細は、私のページから[Program Guid]を参照して下さい。

  7. 次に登場するのが CLI(Common Language Infrastructure) です。
    新しい言語仕様である C++/CLI(Common Language Infrastructure) はヨーロッパの工業標準化団体である ECMA によって標準言語として制定されました。
    もちろん、標準を名乗る以上、C++/CLI が対象とする実行環境は Windows でも .NET Framework でもなく、国際規格である「ISO/IEC 23271:2005, Common Language Infrastructure(CLI)」です。
    標準仕様 CLI を Microsoft 環境(Windows環境)で実際に使うために、Microsoft系システム(ランタイム)を用意したものが CLR(Common Language Runtime)です。
    CLI で作成される実行プログラム(*.exe)のコードは native code では無く、Java のような中間コードです。
    このコードを実行するには、中間コードからネイティブコードに変換しながら実行する仕組みが必要です。
    CLI の良い所は、マシンに依存しないことと、作成されるファイルのサイズが非常に小さいことです。
    CLI で最も気になる所は、ref class と value class が追加されたことでしょうか?
    また new で割り当てたメモリの開放を忘れても、ガベージ・コレクタの機能が働いて自動的に開放してくれるようです。
    CLI の主な機能を抜粋してみました。
    1. C言語の初期のころから使われている Normal class のプログラム例です。
      func(&data); で Class(構造体)のポインターを渡し、func(DATA* pData) で受け取ります。
      using namespace System;
      class   DATA
      { public:
          int     v1;
          int     v2;
          int     sum;
      };
      void func(DATA* pData)
      {   pData->sum = pData->v1+pData->v2;  }
      int main()
      {   DATA    data;
          data.v1 = 10;
          data.v2 = 20;
          func(&data);
          Console::WriteLine("10 + 20 = {0} です",data.sum);
          System::Console::ReadLine();
          return 0;
      }
      
    2. 新しく追加された ref class のプログラム例です。
      func(%data); で Class(構造体)のポインターを渡し、func(DATA^ pData) で受け取ります。
      using namespace System;
      ref  class   DATA
      { public:
          int     v1;
          int     v2;
          int     sum;
      };
      void func(DATA^ pData)
      {   pData->sum = pData->v1+pData->v2;  }
      int main()
      {   DATA    data;
          data.v1 = 10;
          data.v2 = 20;
          func(%data);
          Console::WriteLine("10 + 20 = {0} です",data.sum);
          System::Console::ReadLine();
          return 0;
      }
      
    3. 新しく追加された value class のプログラム例です。
      func(%data); で Class(構造体)を渡し、func(DATA^ pData) で受け取ります。
      value class では func() 関数で sum を計算しても値が反映されないので念のため。
      using namespace System;
      value  class   DATA
      { public:
          int     v1;
          int     v2;
          int     sum;
      };
      void func(DATA^ pData)
      {   pData->sum = pData->v1+pData->v2;  }
      int main()
      {   DATA    data;
          data.v1 = 10;
          data.v2 = 20;
          func(%data);
          Console::WriteLine("10 + 20 = {0} です",data.sum);
          System::Console::ReadLine();
          return 0;
      }
      
    4. Maneged Mode で ref class をパラメータで渡します。
      gcnew GCM(); で class GCM を生成します。
      func(pGcm); で class GCM を渡し、func(GCM^ pGcm) で受け取ります。
      #define SAFE_DELETE(p)  { if (p) { delete (p);  (p) = nullptr; } }
      using namespace System;
      ref class GCM
      { private:
          int     w1,w2;
        public:
          GCM()
          {   Console::WriteLine("Constructor の実行");  }
          ~GCM()
          {   Console::WriteLine("Destructor の実行");   }
          int gcm(int v1, int v2)
          {   w1 = v1;
              w2 = v2;
              while(w1 != w2)
              {   if (w1 > w2)    w1-= w2;
                  else            w2-= w1;
              }
              return w1;
          }
      };
      void func(GCM^ pGcm)
      {   Console::WriteLine("24 と 32 の GCM は {0} です", pGcm->gcm(24,32));  }
      
      int main()
      {   GCM^    pGcm = nullptr;
          pGcm = gcnew GCM();
          func(pGcm);
          SAFE_DELETE(pGcm);
          return 0;
      }
      
    5. 配列は array Object Class を使って定義します。
      一次元配列(array1D), 二次元配列(array2D), 三次元配列(array3D) を定義してみました。
          array<int>^ array1D = gcnew array<int>(10);
          array<int, 2>^ array2D = gcnew array<int, 2>(5, 6);
          array<int, 3>^ array3D = gcnew array<int, 3>(2, 3, 4);
      
    6. String が随分使いやすくなりました。
      "/" と "," を区切り符号として、文字列を切り分けてみました。
      using namespace System;
      void Debug(String^ msg, int v)
      {   String^ message = msg + "  " + v;
          Console::WriteLine(message);
      }
      int main()
      {   String^ str = "2014/09/04"; 
          array<String^>^ ymd;
          array<String^>^ sep= {"/", ","};
          int     yy,mm,dd;
          ymd= str->Split(sep, StringSplitOptions::None);
          yy= int::Parse(ymd[0]);
          mm= int::Parse(ymd[1]);
          dd= int::Parse(ymd[2]);
          Debug("YY=", yy);
          Debug("MM=", mm);
          Debug("DD=", dd);
          Console::ReadLine();
          return 0;
      }
      
    7. Windows Program(Form) も Console Mode と同じように CLI でプログラムすることができます。
      #using <System.dll>
      #using <System.Windows.Forms.dll>
      using namespace System;
      using namespace System::Windows::Forms;
      
      //フォームを作成するクラス
      ref class FormClass
      { private:
          Form^ form;
        public:
          //Constructor
          FormClass()
          {   form = gcnew Form();  }
          //Form のキャプションを設定
          Form^ SetText()
          {   form->Text = "C++/CLI のフォームです♪";
              return form;
          }
      };
      
      int main()
      {   MessageBox::Show("C++/CLI で Form を表示します","caption");
          FormClass MyForm;
          Form^ form = MyForm.SetText();
          Application::Run(form);
          return 0;
      }
      
    8. Delegate のテストプログラムです。
      += で設定して、-= で設定を解除します。
      #using <System.dll>
      using namespace System;
      delegate void TestDelegate();
      void method1()
      {   Console::WriteLine("method No1 が呼ばれました");  }
      void method2()
      {   Console::WriteLine("method No2 が呼ばれました");  }
      void method3()
      {   Console::WriteLine("method No3 が呼ばれました");  }
      void main()
      {   TestDelegate ^testdelegate = gcnew TestDelegate(&method1);
          TestDelegate ^test2 = gcnew TestDelegate(&method2);
          testdelegate += test2;
          testdelegate += gcnew TestDelegate(&method3);
          testdelegate();
          testdelegate -= test2;
          Console::WriteLine("※method No2 を削除");
          testdelegate();
          Console::ReadLine();
      }
      
    9. まだまだ多くの機能がありますが、詳細は私のページから[Maneged Mode]を参照して下さい。
      Windows Program は、私のページから[Windows Program]を参照して下さい。

  8. C++ Windows8(Store App)からC言語の様子が変わっています。
    Console Mode も使えますが Windwos Mode が主体となって、携帯やスマホにも対応しているようです。
    言語仕様も今までとは異なり、1から学ばなければならないようです。
    幾つかのテンプレートが用意されていて、Window のレイアウトには XAML が使われます。
    ストアアプリの String は、使い勝手が悪くて役に立ちそうにありません。 (^_^;)
    Class の様子も違っています。
    GcmClass.h です。
    namespace App1
    {   ref class GcmClass
        { internal:
            GcmClass(int v1, int v2);
          private:
            int     val;
          public:
            int get_gcm();
        };
    }
    
    GcmClass.cpp です。
    #include "pch.h"
    #include "GcmClass.h"
    namespace App1
    {   GcmClass::GcmClass(int v1, int v2)
        {   while(v1 != v2)
            {   if (v1 > v2)    v1-= v2;
                else            v2-= v1;
            }
            val= v1;
        }
        int GcmClass::get_gcm()
        {   return val;  }
    }
    
    Button_Click でデータを取得して Gcm Class を使って計算します。
    ref new GcmClass(d1,d2); で Class を生成することに注目して下さい。
    void App1::MainPage::Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {   int  d1,d2;
        GcmClass^  gcmcls;
        d1= _wtoi(data1->Text->Data());
        d2= _wtoi(data2->Text->Data());
        gcmcls = ref new GcmClass(d1,d2);
        gcm->Text= gcmcls->get_gcm().ToString();
    }
    
    詳細は、私のページから[C++ Windows8(Store App)]を参照して下さい。

  9. 以上のように一概にC言語と言っても多種多様です。
    基礎からしっかり学ぶなら[UnManeged Mode]がお勧めです。
    最新のプログラムを学ぶなら 超初心者のプログラム入門(Store C++) がお勧めです。
    C言語は複雑になりすぎたようで、C++/CLI の後継として C# が開発されました。
    超初心者のプログラム入門(C#) は C++ に比べて使いやすく、機能も充実しています。
    Microsoft はこちらの方がお勧めのようですが Store Application の DirectX は C++ で開発するようです。

超初心者のプログラム入門(C/C++)