Cには、オブジェクトと見なすことができる(構造体など)独自の準オブジェクトがあるようです(通常、私たちは高レベルで考えます)。
また、Cファイル自体も基本的には別個の「モジュール」です。では、モジュールも「オブジェクト」のようなものではないでしょうか? C++に非常に似ているように見えるCが、低レベルの「手続き型」言語と見なされている理由について混乱しています。C++は高レベルの「オブジェクト指向」言語です。
*編集:(説明)なぜ、どこに線が描かれるのか、「オブジェクト」とは何か、そうでないのか?
Cには、オブジェクトと見なすことができる「構造体」などの独自の準オブジェクトがあるようです
あなたと私が一緒に オブジェクト指向プログラミングのWikipediaページ を読み、伝統的にオブジェクト指向スタイルと見なされているものに対応するCスタイルの構造体の機能を確認してみましょう。
(OOP)は、「オブジェクト」を使用したプログラミングパラダイムです。データフィールドとメソッド、およびそれらの相互作用で構成されるデータ構造
C構造体は、フィールドとメソッド、およびそれらの相互作用で構成されていますか?番号。
プログラミング手法には、データの抽象化、カプセル化、メッセージング、モジュール性、多態性、継承などの機能が含まれる場合があります。
C構造体はこれらのことを「ファーストクラス」の方法で実行しますか?いいえ。この言語は、あらゆる段階であなたに対して不利に働きます。
オブジェクト指向のアプローチにより、プログラマは、プログラムの他の部分から直接アクセスできない場所にデータを配置することが推奨されます
C構造体はこれを行いますか?番号。
オブジェクト指向プログラムには通常、さまざまなタイプのオブジェクトが含まれます。各タイプは、管理される特定の種類の複雑なデータ、または実際のオブジェクトや概念に対応します。
C構造体はこれを行いますか?はい。
オブジェクトは、データが適切に使用されることを保証するように設計された一連の関数内でデータをラップすると考えることができます
番号。
各オブジェクトは、メッセージの受信、データの処理、および他のオブジェクトへのメッセージの送信が可能です
構造体自体がメッセージを送受信できますか?いいえ、データを処理できますか?番号。
OOPデータ構造は「独自のオペレーターを持ち運ぶ」傾向がある
これはCで起こりますか?番号。
動的ディスパッチ...カプセル化...サブタイプのポリモーフィズム...オブジェクトの継承...オープンな再帰...オブジェクトのクラス...クラスのインスタンス...アタッチされたオブジェクトに作用するメソッド...メッセージの受け渡し.. 。抽象化
C構造体のこれらの機能はありますか?番号。
構造体のどの特性が「オブジェクト指向」だと思いますか?any構造体がtypesを定義するという事実以外。
もちろん、関数へのポインタであるフィールドを持つ構造体を作成することもできます。構造体には、仮想メソッドテーブルに対応する、関数ポインターの配列へのポインターであるフィールドを含めることができます。等々。もちろんC++でC++をエミュレートできます。しかし、これはCでプログラミングするための非常に非慣用的な方法です。 C++を使用する方がよいでしょう。
また、Cファイル自体は基本的に別の「モジュール」です。では、モジュールも「オブジェクト」のようなものではないでしょうか?
繰り返しますが、モジュールのどのような特性がオブジェクトのように機能することを考えていますか?モジュールは、抽象化、カプセル化、メッセージング、モジュール性、多態性、継承をサポートしていますか?
抽象化とカプセル化はかなり弱いです。明らかにモジュールはモジュール式です。それがモジュールと呼ばれる理由です。メッセージング?メソッド呼び出しはメッセージであり、モジュールはメソッドを含むことができるという意味でのみです。ポリモーフィズム?いいえ。継承?いいえ。モジュールは「オブジェクト」のかなり弱い候補です。
キーワードは「オブジェクト」ではなく「指向」です。オブジェクトを使用するがstructsのようにそれらを使用するC++コードでさえ、オブジェクトorientedではありません。
CとC++はどちらもOOP(Cにアクセス制御がないことを除いて)を実行できます)が、Cで実行するための構文は(控えめに言っても)不便ですが、C++の構文ではCは手続き型であり、C++はオブジェクト型ですが、コア機能はほぼ同じです。
オブジェクトを使用して(通常、ポリモーフィズムを利用することを意味する)オブジェクトでのみ実行できる設計を実装するコードは、オブジェクト指向のコードです。オブジェクト指向の言語で継承を使用していても、データのバッグにすぎないオブジェクトを使用するコードは、実際には、必要以上に複雑な手続き型コードです。実行時にデータの完全な構造体で変更される関数ポインターを使用するCのコードは、ポリモーフィズムを実行しており、手続き型言語であっても「オブジェクト指向」であると言えます。
最高レベルのプリンシパルに基づく:
オブジェクトは、データと動作を相互に関連付けてカプセル化したもので、全体として動作し、複数回インスタンス化して、外部インターフェイスを知っている場合はブラックボックスとして機能させることができます。
構造体にはデータが含まれていますが動作はないため、オブジェクトとは見なされません。
モジュールには動作とデータの両方が含まれていますが、2つが関連しているようにカプセル化されていないため、複数回インスタンス化することはできません。
そして、それは継承と多態性に入る前です...
「構造体」はデータのみです。 「オブジェクト指向」の通常の迅速でダーティなテストは、「コードとデータを単一のユニットとしてカプセル化できる構造はありますか?」です。 Cはこれに失敗し、手続き型です。 C++はそのテストに合格しています。
Cは、C++と同様に、 Data Abstraction を提供する機能を備えています。これは、その前に存在していたオブジェクト指向プログラミングパラダイムの1つのイディオムです。
C++のOOPは、データを抽象化する手段を拡張します。 有害だと言う人もいます ですが、正しく使用すると良いツールだと考える人もいます。
ただし、Cが適切な抽象化を完全に行うことができる方法、およびC++によって作成されるオーバーヘッドが実際の問題の解決を妨げる方法について、多くのC "ハッカー"が説得していることがわかります。
2年後に非効率的な抽象化プログラミングモデルでは、一部の抽象化があまり効率的ではないことに気づきましたが、すべてのコードはその周囲のすべてのNiceオブジェクトモデルに依存しており、アプリを書き直さなければ修正できません。 -ライナストーバルズ
他の人はよりバランスの取れた方法でそれを見る傾向があり、両方の利点と欠点を受け入れます。
Cを使用すると、簡単に自分の足を撃つことができます。 C++はそれを難しくしますが、それを行うと脚全体が吹き飛ばされます。 -Bjarne Stroustrup
コインの反対側を見る必要があります:C++。
OOPでは、抽象的なオブジェクトを考えます(それに応じてプログラムを設計します)。たとえば、停止、加速、左または右に曲がる車などです。機能のバンドルを持つ構造体は、単に概念に適合しません。
「実際の」オブジェクトでは、たとえばメンバーを非表示にする必要があります。または、実際の「is a」関係などを使用して継承することもできます。
以下のコメントを読んだ後:まあ、(ほとんど)すべてがCでできる(それは常にtrueです)が、一見私は思ったcをc ++から分離するのは、プログラムを設計するときの考え方です。
実際に違いをもたらす唯一のものは、コンパイラーによるポリシーの強制です。つまり、純粋な仮想関数などです。しかし、この回答は技術的な問題にのみ関係しますが、主な違い(前述のとおり)は、コーディング中に最初に考える方法だと思います。C++では、そのようなことを行うための組み込み構文がOOPはCではやや不格好な方法で。
自分で言ったようなものです。 Cにはオブジェクトのようなものがありますが、それでもオブジェクトではありません。そのため、CはOOP言語とは見なされません。
オブジェクト指向とは、アーキテクチャパターン(またはメタパターン)と、このパターンの実装または使用を支援する機能を持つ言語の両方を指します。
"OO"デザインを実装できます(Gnomeデスクトップは、おそらくOO純粋なCで行われた最良の例です)。これはCOBOLで行われたことさえあります。
ただし、OO設計を実装できると、言語はOOになりません。純粋主義者は、JavaおよびC++は本当にOO「int」や「char」などの基本的な「タイプ」をオーバーライドまたは継承できないため、Javaは多重継承をサポートしていません。ただし、最も広く使用されているためOO言語とほとんどのパラダイムをサポート機能するコードを生成するために給料を受け取っているほとんどの「実際の」プログラマーは、それらをOO言語と見なします。
一方、Cは構造のみをサポートします(COBOL、Pascal、その他の数十の手続き型言語と同様)。これは、データの任意の部分で任意の関数を使用できるという点で多重継承をサポートすると主張できますが、ほとんどの場合、これはバグと見なされます。機能より。
オブジェクト指向の概念shrugを実装するには、Cは完全に適切で適切だと思います。オブジェクト指向と見なされる言語の共通分母サブセット間の私が見たほとんどの違いは、私の一種の実用的な観点から、本質的にマイナーで構文的です。
たとえば、情報を隠すことから始めましょう。 Cでは、構造体の定義を非表示にし、不透明なポインターを介してそれを操作するだけで、それを実現できます。これにより、クラスで取得するデータフィールドのpublic
とprivate
の区別が効果的にモデル化されます。標準のCライブラリはこれに大きく依存しているため、情報の非表示を実現するのに十分簡単で、慣用句をほとんど使用しません。
もちろん、不透明な型を使用すると、メモリ内の構造が割り当てられている場所を簡単に正確に制御できなくなりますが、それは、たとえばCとC++の注目すべき違いにすぎません。 C++は、メモリレイアウトの制御を維持しながらオブジェクト指向の概念をCでプログラミングする能力を比較する場合、間違いなく優れたツールですが、必ずしもJavaまたはC#がCより優れているという意味ではありません)その点では、これら2つにより、オブジェクトがメモリ内で割り当てられる場所を制御する機能が完全に失われます。
そして、fopen(file, ...); fclose(file);
とは対照的に、file.open(...); file.close();
のような構文を使用する必要がありますが、大きなエラーです。誰が本当に気にしますか?たぶん、IDEのオートコンプリートに大きく依存している人だけかもしれません。これは実用的な観点からは非常に便利な機能であると認めますが、言語がOOPに適しているかどうかについての議論を必要とする機能ではないかもしれません。
protected
フィールドを効果的に実装する機能はありません。完全に提出します。しかし、「All OO言語には、サブクラスがまだアクセスされるべきではない基本クラスのメンバーにアクセスすることを許可する機能が必要です)という具体的なルールがあるとは思いません。通常のクライアントによる。 "それに加えて、メンテナンスのハードルになることを少なくとも少しでも疑わない、保護されたメンバーの使用例はめったにありません。
そしてもちろん、OO関数ポインタのテーブルとそれらへのポインタを使用して多相性をエミュレートする必要があります。これらの類似のvtables
とvptrs
を初期化するために、もう少しボイラープレートを使用して動的ディスパッチしますが、少しボイラープレートは私にそれほど悲しみを引き起こしたことはありません。
継承はほとんど同じです。合成によってそれを簡単にモデル化することができ、コンパイラーの内部動作では、同じものに要約されます。もちろんdowncastにしたい場合はタイプセーフを失いますが、downcastingになりたい場合はCを使用しないでください。 downcastingをエミュレートするために人々がCで行うことは、タイプセーフの観点からは恐ろしいことですが、私はむしろdowncastをしないようにします。型の安全性は、コンパイラがビットとバイトだけを解釈するための非常に大きな余裕を提供し、コンパイル時にエラーをキャッチする機能を犠牲にするため、Cで簡単に見逃しがちなものですが、オブジェクト指向と見なされる言語もあります静的に型付けされることすらありません。
なので、私はそれでいいと思います。もちろん、Cを使用してSOLIDの原則に準拠する大規模なコードベースを作成しようとはしませんが、それは必ずしもオブジェクト指向の面での欠点が原因ではありません。A Cをそのような目的で使用しようとすると見逃してしまう機能の多くは、OOPのような強い型の安全性、自動的に呼び出されるデストラクタなど)の前提条件とは直接見なされない言語機能に関連します。オブジェクトがスコープから外れたとき、演算子のオーバーロード、テンプレート/ジェネリック、例外処理、C++に到達するための補助的な機能が不足しているときです。
OOの定義を見てみましょう。
Cはこれら3つを提供しません。特に、最も重要なMessagingは提供されません。
オブジェクト指向にはいくつかの主要な要素がありますが、大きな要素は、オブジェクトの状態が管理対象ユニットであることをコードの大部分がオブジェクト内にあるもの(それらは実装ではなくサーフェスインターフェイスを参照)を認識していないことです(つまり、オブジェクトが停止すると、その状態も変化します)。また、一部のコードがオブジェクトの操作を呼び出すとき、その操作が何であるか、または何を伴うかを正確に知らなくても、そのようにします(実行するすべてのパターンは、壁を越えた「メッセージ」)。
Cはうまくカプセル化します。構造の定義を認識しないコードは、(合法的に)構造を覗くことができません。これを行うには、次のような定義をヘッダーファイルに追加するだけです。
struct FooImpl;
typedef struct FooImpl *Foo;
もちろん、Foo
s(つまり、ファクトリー)を構築し、割り当てられたオブジェクト自体に(つまり、「コンストラクター」メソッドを介して)作業の一部を委任する必要がある関数が必要です。また、(「デストラクタ」メソッドを使用してオブジェクトをクリーンアップさせながら)オブジェクトを再度破棄する方法もありますが、それは詳細です。
メソッドのディスパッチ(つまり、メッセージング)は、構造体の最初の要素が実際には関数ポインターでいっぱいの構造体へのポインターであり、これらの各関数ポインターはFoo
をその最初の引数。その後、Dispatchは関数を検索し、正しい引数rewriteでそれを呼び出すという問題になります。これは、 macro で行うのは難しくなく、少し狡猾です。 (この関数の表は、クラスがC++のような言語で実際に何をしているかの中核です。)
さらに、これによりバインディングも遅くなります。ディスパッチコードが知っているのは、オブジェクトが指すテーブルに特定のオフセットを呼び出していることだけです。これは、オブジェクトの割り当てと初期化中にのみ設定する必要があります。 (速度を犠牲にして)より多くのランタイムダイナミズムを購入するさらに複雑なディスパッチスキームを使用することは可能ですが、それらは基本的なメカニズムの上にチェリーです。
しかし、それはCがOO言語であることを意味するものではありません。重要なのは、Cが規則を作成し、メカニズムをディスパッチするすべてのトリッキーな作業を自分で行う(またはサードパーティを使用する)ことです。ライブラリ。これは多くの作業です。また、構文またはセマンティックのサポートを提供しないため、完全なクラスシステム(継承などのもの)の実装は不必要に苦痛です;詳細に説明されている複雑な問題に対処している場合OOモデルの場合、OO言語はソリューションの記述に非常に役立ちます。追加の複雑さは正当化できます。