以前は、オブジェクト指向プログラミング言語(C++、Ruby、Python、PHP)のみを使用してきましたが、現在Cを学習しています。 「オブジェクト」。 CでOOPパラダイムを使用することは可能であることを理解していますが、Cの慣用的な方法を学びたいと思います。
プログラミングの問題を解決するとき、私が最初にすることは、問題を解決するオブジェクトを想像することです。非OOP命令型プログラミングパラダイムを使用する場合、これをどのステップに置き換えるか?
struct
を使用してデータをカプセル化できます。それでおしまい。
どのようにしてクラスを書きましたか?これが.Cファイルの記述方法です。もちろん、メソッドのポリモーフィズムや継承などは得られませんが、異なる関数名と composition を使用してそれらをシミュレートできます。
道を開くために、Functional Programming。を学習してください。クラスのオーバーヘッドwithoutを使用してできることは本当に驚くべきことです。
さらに読む
ANSI Cのオブジェクト指向
[〜#〜] sicp [〜#〜] を読んで、Schemeを学び、practical抽象的なデータタイプ。次に、Cでのコーディングは簡単です(SICP、Cのビット、PHP、Rubyなどのビットがあるため...)あなたの考えは十分に広がり、オブジェクト指向プログラミングは、すべてのケースですが、特定の種類のプログラムのみ)。 C動的メモリ割り当て に注意してください。これはおそらく最も難しい部分です。 C99 または C11 プログラミング言語標準とその C標準ライブラリ は実際にはかなり貧弱です(TCPまたはディレクトリ!)、および外部ライブラリまたはインターフェース(例 [〜#〜] posix [ 〜#〜] 、 libcurl HTTPクライアントライブラリの場合 libonion HTTPサーバーライブラリの場合 GMPlib bignumの場合- libunistring for UTF-8など...).
多くの場合、「オブジェクト」はCに関連するstruct
- sであり、それらを操作する一連の関数を定義します。短い関数または非常に単純な関数の場合は、関連するstruct
を使用して、一部のヘッダーファイルでstatic inline
として定義することを検討してくださいfoo.h
#include
-d他の場所。
オブジェクト指向プログラミングだけが プログラミングパラダイムではないことに注意してください。場合によっては、他のパラダイムが価値があります( 関数型プログラミング àla OcamlまたはHaskell、さらにはSchemeまたはCommmon LISP、 logic programming àla Prologなど)。 J.Pitratのブログ 宣言型人工知能について)。スコットの本を見てください: Programming Language Pragmatics
実際、CまたはOcamlのプログラマーは、通常、オブジェクト指向プログラミングスタイルでコーディングすることを望んでいません。それが役に立たないときに、自分でオブジェクトについて考えるように強制する理由はありません。
いくつかのstruct
とそれらを操作する関数(多くの場合、ポインターを介して)を定義します。 tagged unions (多くの場合、タグメンバーを含むstruct
、多くの場合はenum
、一部はunion
が内部にある)が必要になる可能性があります。一部のstruct
- sの最後に フレキシブル配列メンバー を置くと便利な場合があります。
既存のいくつかのソースコード内を調べます フリーソフトウェア[〜#〜] c [〜#〜](いくつかを見つけるには github & sourceforge を参照してください)。たぶんLinuxディストリビューションのインストールと使用が役に立つでしょう:これはほとんどフリーソフトウェアだけで作られ、すばらしいフリーソフトウェアCコンパイラ( [〜#〜] gcc [〜#〜 ] 、 Clang/LLVM )および開発ツール。 Linux向けに開発したい場合は Advanced Linux Programming も参照してください。
すべての警告とデバッグ情報を含めてコンパイルすることを忘れないでください。 gcc -Wall -Wextra -g
-特に開発段階とデバッグ段階-およびいくつかのツールの使用方法を学習します。 valgrind を狩る メモリリーク 、gdb
デバッガなど。注意してをよく理解してください 未定義の動作 およびそれを強く回避する(プログラムにUBがあり、「動作する」ように見える場合があることを忘れないでください)。
オブジェクト指向の構成要素(特に inheritance )が本当に必要な場合は、関連する構造体と関数へのポインタを使用できます。独自の vtable 機構を使用して、各「オブジェクト」を、struct
を含む関数ポインターへのポインターで開始することができます。ポインター型を別のポインター型にキャストする機能(およびstruct super_st
と同じフィールド型を含むstruct sub_st
からキャストして継承をエミュレートできるという事実)を利用します。 GObject (GTK/Gnomeから)が示すように、特にconventions-に従うことにより、Cは非常に高度なオブジェクトシステムを実装するのに十分であることに注意してください。
closures が本当に必要な場合、頻繁にcallbacks でエミュレートしますconventionですべての関数が使用しますコールバックには、関数ポインターと一部のクライアントデータの両方が渡されます(それを呼び出すときに関数ポインターによって消費されます)。また、(従来どおり)独自のクロージャーのようなstruct
- s(関数ポインターと閉じた値を含む)を持つこともできます。
Cは非常に低レベルの言語であるため、独自のconventionsを定義して文書化することが重要です(他のCプログラム)、特にメモリ管理、およびおそらくいくつかの命名規則についても。 命令セットアーキテクチャ についてのアイデアがあると便利です。 a [〜#〜] c [〜#〜]コンパイラーがコードに対して多くの 最適化 を実行する可能性があることを忘れないでください(必要に応じて)、マイクロ最適化を手動で行うことをあまり気にしないで、コンパイラーに任せます(リリースされたソフトウェアの最適化されたコンパイルの場合はgcc -Wall -O2
)。ベンチマークと生のパフォーマンスに関心がある場合は、最適化を有効にする必要があります(プログラムがデバッグされたら)。
時々 メタプログラミング が役立つことを忘れないでください。多くの場合、Cで記述された大規模なソフトウェアには、他の場所で使用されるCコードを生成するためのスクリプトまたはアドホックプログラムが含まれています(そして、 Cプリプロセッサ トリックなど、 X-macros )。いくつかの便利なCプログラムジェネレーター(たとえば、パーサーを生成するには yacc または gnu bison 、完全なハッシュ関数を生成するには gperf など)があります。) 。一部のシステム(特にLinuxおよびPOSIX)では、実行時にgenerated-001.c
ファイルでCコードを生成し、実行時にいくつかのコマンド(gcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so
など)を実行して共有オブジェクトにコンパイルし、動的にロードすることもできます。 dlopen を使用した共有オブジェクト& dlsym を使用して名前から関数ポインターを取得します。 [〜#〜] melt [〜#〜] でそのようなトリックを行っています(LISPのようなドメイン固有の言語です。これは [〜#〜] gcc [〜#〜] コンパイラ)。
ガベージコレクション の概念とテクニックに注意してください( 参照カウント はCでメモリを管理するためのテクニックである場合が多く、適切に処理されないガベージコレクションの貧弱な形式です。 循環参照 を使用すると、 弱いポインタ を使用してそれを支援できますが、注意が必要です)。場合によっては、 Boehmの保守的なガベージコレクタ の使用を検討することがあります。
プログラムの構築方法は、基本的に、問題を解決するために実行する必要があるアクション(関数)を定義することです(それが、手続き型言語と呼ばれる理由です)。各アクションは関数に対応します。次に、各関数が受け取る情報の種類と、それらが返す必要のある情報を定義する必要があります。
プログラムは通常、ファイル(モジュール)に分けられています。各ファイルには通常、関連する機能のグループがあります。各ファイルの冒頭で、そのファイル内のすべての関数によって使用される変数を(関数の外で)宣言します。 「静的」修飾子を使用する場合、これらの変数はそのファイル内でのみ表示されます(他のファイルからは表示されません)。関数の外で定義された変数に「静的」修飾子を使用しない場合、それらは他のファイルからもアクセス可能であり、これらの他のファイルは変数を「extern」として宣言する必要があります(ただし定義しない)ので、コンパイラーはそれらを検索します他のファイルで。
つまり、簡単に言えば、最初にプロシージャ(関数)について考え、次にすべての関数が必要な情報にアクセスできるようにします。
C APIは、多くの場合-多分通常は-正しく見れば、本質的にオブジェクト指向のインターフェースを持っています。
C++の場合:
_class foo {
public:
foo (int x);
void bar (int param);
private:
int x;
};
// Example use:
foo f(42);
f.bar(23);
_
C:
_typedef struct {
int x;
} foo;
void bar (foo*, int param);
// Example use:
foo f = { .x = 42 };
bar(&f, 23);
_
ご存じかもしれませんが、C++やその他のさまざまな正式なOO言語では、オブジェクトメソッドは、Cバージョンのbar()
。これがC++で表面に出てくる例の例として、_std::bind
_を使用してオブジェクトメソッドを関数のシグネチャに適合させる方法を検討してください。
_new function<void(int)> (
bind(&foo::bar, this, placeholders::_1)
// ^^^^ object pointer as first arg
);
_
他の人が指摘したように、本当の違いは、正式なOO言語は、ポリモーフィズム、アクセス制御、およびその他のさまざまな気の利いた機能を実装する可能性があるということです。しかし、オブジェクト指向プログラミングの本質、離散の作成と操作、複雑なデータ構造は、すでにCの基本的なプラクティスです。
人々がCを学ぶように勧められる大きな理由の1つは、それが高水準プログラミング言語の中で最も低い言語の1つであることです。 OOP言語により、データモデルとテンプレートコードおよびメッセージパッシングについて考えるのが容易になりますが、結局のところ、マイクロプロセッサはコードを段階的に実行し、ブロックに出入りしますプログラムのさまざまな部分がデータを共有できるように、コード(Cの関数)と変数(Cのポインター)への参照を移動します。Cを英語のアセンブリ言語と考えてください-コンピュータのマイクロプロセッサに段階的な指示を提供します-おまけとして、ほとんどのオペレーティングシステムインターフェイスは、OOPパラダイムではなくC関数呼び出しのように機能するため、Cを学ぶと、コード実行の動きについて多くのことがわかりますあるプログラム(OS)から別のプログラム(あなたのプログラム)へ、そして再び戻ってきます。