OOP言語に偏った後、Cプログラマーとして考えるには?[閉まっている]


38

以前は、オブジェクト指向プログラミング言語(C ++、Ruby、Python、PHP)しか使用していませんでしたが、Cを学習しています。言語の概念のない言語で物事を行う適切な方法を見つけるのは難しいと感じています。 「オブジェクト」。CでOOPパラダイムを使用することは可能ですが、Cイディオマティックな方法を学びたいと思います。

プログラミングの問題を解決するとき、私が最初にすることは、問題を解決するオブジェクトを想像することです。非OOP命令型プログラミングパラダイムを使用する場合、これをどのステップに置き換えますか?


15
自分の考え方にぴったり合った言語をまだ見つけていないので、使用しているどの言語についても自分の考えを「コンパイル」する必要があります。ラベル、サブルーチン、関数、オブジェクト、モジュール、フレームワークなど、「コードユニット」という概念が有用だと思います。それらはすべてカプセル化され、明確に定義されたインターフェイスを公開する必要があります。トップダウンのオブジェクトレベルのアプローチを使用している場合、Cでは、問題が解決されたかのように動作する一連の関数を作成することから始めることができます。多くの場合、適切に設計されたC APIはOOPに似ていますが、にqux = foo.bar(baz)なりqux = Foo_bar(foo, baz)ます。
アモン

amonをエコーするには、グラフのようなデータ構造、ポインター、アルゴリズム、コード(関数)の実行(制御フロー)、関数ポインターに焦点を合わせます。
-rwong

1
LibTiff(githubのソースコード)は、大規模なCプログラムを編成する方法の例です。
rwong

1
C#プログラマーとして、オブジェクトを見逃すよりもはるかにデリゲート(1つのバインドされたパラメーターを持つ関数ポインター)を見逃していました。
CodesInChaos

私は個人的に、プリプロセッサを除いて、Cのほとんどを簡単かつ簡単に見つけました。Cを再学習しなければならなかった場合、それは私が多くの努力を集中する1つの領域です。
-biziclop

回答:


53
  • ACプログラムは機能の集合です。
  • 関数はステートメントのコレクションです。
  • を使用してデータをカプセル化できますstruct

それでおしまい。

どうやってクラスを書きましたか?これは、ほぼ.Cファイルの記述方法です。確かに、メソッドのポリモーフィズムや継承などは取得できませんが、とにかく異なる関数名と構成でそれらをシミュレートできます。

道を開くには、関数型プログラミングを勉強してください。クラスなしで できることは本当に驚くべきことであり、クラスのオーバーヘッドなしで実際にうまくいくものもあります。

ANSI Cでのオブジェクト指向の詳細


9
またtypedefstructそれをクラスのようなものにすることもできます。およびtypedef-edタイプはstruct、それ自体がtypedef-ed できる他のsに含めることができます。Cでは得られないのは、演算子のオーバーロードと、C ++で得られるクラスとそのメンバーの表面的に単純な継承です。また、C ++で得られる奇妙で不自然な構文はあまりありません。私はOOPの概念が大好きですが、C ++はOOPのofい実現だと思います。C 小さい言語であり、関数に任せるのに最適な言語の構文を省略しているため、Cが好きです。
ロバートブリストージョンソン

22
第一言語がCである/ Cである誰かとして、私はそれを言うことを敢えてします。a lot of things actually work better without the overhead of classes
ハネフムバラク

1
拡大するために、OOPなしで多くのものが開発されました:オペレーティングシステム、プロトコルサーバー、ブートローダー、ブラウザーなど。コンピューターはオブジェクトの観点から考えることも、考える必要もありません。実際、彼らがそれを強制することはしばしばかなり遅いです。
edmz

カウンターポイント:a lot of things actually work better with addition of class-based OOP。出典:TypeScript、Dart、CoffeeScript、および業界が関数型/プロトタイプOOP言語から逃げようとしている他のすべての方法。
デン

拡大するために、OOPで多くのものが開発されました。それ以外のすべてです。人間はオブジェクトやプログラムの観点から自然に考えており、プログラムは他の人間が読んで理解できるように書かれています。
デン

18

SICPを読み、Scheme、および抽象データ型の実用的なアイデアを学びます。その後、Cでのコーディングは簡単です(SICP、Cの少なさ、PHP、Rubyなどの少なさから...あなたの思考は十分に広がり、オブジェクト指向プログラミングはすべての場合、ただしある種のプログラムのみ)。おそらく最も難しい部分であるCの動的メモリ割り当てに注意してください。C99C11は、言語標準をプログラミングし、そのC標準ライブラリは、実際には非常に貧弱である(それはTCPやディレクトリについて知らない!)、そしてあなたは、多くの場合、いくつかの外部ライブラリ必要がありますまたはインターフェイスを(たとえば、POSIXlibcurlの HTTPクライアントライブラリのため、libonion HTTPサーバライブラリーのための、GMPlib bignumsため、のようないくつかのライブラリlibunistring)...など、UTF-8のために。

あなたの「オブジェクト」はしばしばCに関連するstruct-sであり、それらで動作する関数のセットを定義します。短い関数または非常に単純な関数については、他の場所で-d にするヘッダーファイルのstructようstatic inlineに、関連するを使用して定義することを検討してください。foo.h#include

オブジェクト指向プログラミングが唯一のプログラミングパラダイムではないことに注意してください。場合によっては、他のパラダイムの価値があります(OcamlやHaskellなどの関数型プログラミング、SchemeやCommmon Lisp、Prologなどのロジックプログラミングなど。宣言型人工知能に関するJ.Pitratのブログも読んでください)。Scottの著書「Programming Language Pragmatics」を参照してください

実際、CまたはOcamlのプログラマーは、通常、オブジェクト指向プログラミングスタイルでコーディングすることを望みません。役に立たないときにオブジェクトを考えるように強制する理由はありません。

いくつかstructとそれらを操作する関数を定義します(多くの場合、ポインターを介して)。あなたは、いくつかの必要がある可能性がタグ付けされた組合(多くの場合、structタグ部材と、多くの場合、いくつかのenum、およびいくつかのunion内部を)、あなたがあると便利かもしれません柔軟な配列メンバーをあなたのいくつかの末尾にstruct-s。

いくつかの既存のソースコード内で見てフリーソフトウェアC(参照githubのsourceforgeの いくつかを見つけるために)。おそらく、Linuxディストリビューションをインストールして使用すると便利です。ほとんどフリーソフトウェアのみで構成され、優れたフリーソフトウェアCコンパイラ(GCCClang / LLVM)および開発ツールを備えています。Linux向けに開発する場合は、Advanced Linux Programmingも参照してください。

例えば、すべての警告およびデバッグ情報を使用してコンパイルすることを忘れてはいけないgcc -Wall -Wextra -g開発中&phases-をデバッグ-notablyし、いくつかのツールを使用することを学ぶ、例えばvalgrindのハントにメモリリークgdbなど、デバッガをするように注意してくださいされているものも理解未定義振る舞い、それを強く避けてください(プログラムがUBを持ち、時々「動作する」ように見えることを思い出してください)。

オブジェクト指向のコンストラクト(特に継承)が本当に必要な場合は、関連する構造体や関数へのポインターを使用できます。独自のvtable機構を使用し、各「オブジェクト」structを含む関数ポインターへのポインターで開始することができます。ポインター型を別のポインター型にキャストする機能を利用します(および、継承をエミュレートするためにstruct super_st開始するフィールドタイプと同じフィールドタイプを含むからキャストできるという事実を利用しますstruct sub_st)。Cは、いくつか以下で特定-inかなり洗練されたオブジェクトシステムを実装するのに十分であることに注意してください規則を通り、 - のGObject(GTK / Gnomeのから)を示しています。

本当にクロージャーが必要な場合、コールバックを使用してすべての関数に関数ポインターといくつかのクライアントデータの両方が渡されるという規則で、コールバックでエミュレートすることがよくあります(呼び出し時に関数ポインターによって消費されます)。また、(通常)独自のクロージャのような-s(関数ポインタと閉じた値を含む)を使用することもできます。struct

Cは非常に低レベルの言語であるため、独自の規則(他のCプログラムの実践に触発された)、特にメモリ管理、およびおそらくいくつかの命名規則を定義および文書化することが重要です。命令セットアーキテクチャについて何らかの考えを持つことは役に立ちます。ことを忘れてはいけないのCコンパイラはたくさんやることがあり最適化をあなたのコードに(あなたはそれを聞いている場合)、その(そのコンパイラに、手で休暇をマイクロ最適化を行うことについてはあまり気にしないリリースの最適化コンパイルソフトウェア)。ベンチマークと生のパフォーマンスに関心がある場合は、最適化を有効にする必要があります(プログラムがデバッグされたら)。gcc -Wall -O2

時にはメタプログラミングが役立つことを忘れないでください。多くの場合、Cで書かれた大きなソフトウェアには、他の場所で使用されるCコードを生成するためのスクリプトまたはアドホックプログラムが含まれています(また、X-マクロなどの汚いCプリプロセッサトリックを再生することもできます)。便利な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を使用して名前から関数ポインターを取得します。私はMELTGCCコンパイラのカスタマイズを可能にするので、役に立つかもしれないLispのようなドメイン固有の言語)でそのようなトリックをしています。

点に注意してガベージコレクション(概念や技術参照カウントは、多くの場合、Cでメモリを管理するための技術であり、それはとうまく対処していないガベージコレクションの貧弱な形私見で循環参照 ;あなたが持っている可能性が弱いポインタをそのことについて、ヘルプを、しかし、それは難しいかもしれません)。場合によっては、Boehmの保守的なガベージコレクターの使用を検討することもできます。


7
正直なところ、この質問とは無関係に、SICPを読むことは間違いなく良いアドバイスですが、OPにとっては、次の質問「SICPに偏った後のCプログラマーとしての考え方」にたぶんつながるでしょう。
Doc Brown

1
いいえ、SICPとPHP(またはRubyまたはPython)のSchemeは非常に異なるため、OPはより広い思考を得るでしょう。そしてSICPはCでコーディングのために、特に、どのような実際には抽象データ型であり、それは理解することは非常に便利です非常によく説明
バジーレStarynkevitch

1
SICPは奇妙な提案です。SchemeはC.とは非常に異なっている
ブライアン・ゴードン

しかし、SICPは多くの良い習慣を教えており、CでコーディングするときにSchemeが役立つことを知っています(クロージャ、抽象データ型などの概念のために)
Basile Starynkevitch

5

プログラムの構築方法は、基本的に問題を解決するために実行する必要があるアクション(関数)を定義することです(そのため、手続き型言語と呼ばれています)。各アクションは機能に対応します。次に、各関数が受け取る情報の種類と、返す必要がある情報を定義する必要があります。

通常、プログラムはファイル(モジュール)に分けられ、各ファイルには通常、関連する機能のグループがあります。各ファイルの先頭で、そのファイル内のすべての関数で使用される変数を(関数の外で)宣言します。「静的」修飾子を使用すると、これらの変数はそのファイル内でのみ表示されます(他のファイルからは表示されません)。関数の外部で定義された変数に「静的」修飾子を使用しない場合、他のファイルからもアクセスできます。これらの他のファイルは変数を「extern」として宣言する必要があります(ただし定義しない)他のファイルに。

つまり、最初に手順(関数)について考え、次にすべての関数が必要な情報にアクセスできることを確認します。


3

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言語では、内部的にオブジェクトメソッドは、bar()上記のCバージョンと同様に、オブジェクトへのポインターである最初の引数を取ります。これがC ++で表面化する例については、std::bindオブジェクトメソッドを関数シグネチャに適合させる方法を検討してください。

new function<void(int)> (
    bind(&foo::bar, this, placeholders::_1)
//                  ^^^^ object pointer as first arg
);

他の人が指摘したように、本当の違いは、正式なオブジェクト指向言語がポリモーフィズム、アクセス制御、その他のさまざまな気の利いた機能を実装できることです。しかし、オブジェクト指向プログラミングの本質、離散的で複雑なデータ構造の作成と操作は、すでにCの基本的なプラクティスです。


2

Cを学ぶことを人々に勧める大きな理由の1つは、それが高レベルのプログラミング言語の中で最も低い言語の1つであることです。OOP言語を使用すると、データモデルとテンプレートコードとメッセージの受け渡しについて簡単に考えることができますが、最終的には、マイクロプロセッサがコードをステップバイステップで実行し、コードブロック(Cの関数)に出入りして移動しますプログラムのさまざまな部分がデータを共有できるようにするための変数(Cのポインター)への参照。Cを英語のアセンブリ言語と考えてください-コンピュータのマイクロプロセッサにステップバイステップの指示を提供します-そして、あなたはそれほど間違っていません。おまけとして、ほとんどのオペレーティングシステムインターフェイスは、OOPパラダイムではなくC関数呼び出しのように機能します。


2
IMHO Cは低レベルの言語ですが、アセンブラやマシンコードよりもはるかに高い言語です。Cコンパイラは多くの低レベルの最適化を実行できるためです。
バジルStarynkevitch

Cコンパイラは、「最適化」の名の下に、マシン上のコードの自然な振る舞いがたとえ定義されていない振る舞いを引き起こす入力を与えられたときに、時間と因果律を無効にする抽象的なマシンモデルに向かって動いていますそれ以外の場合は要件を満たします。たとえば、関数は16ビットのuint16_t blah(uint16_t x) {return x*x;}マシンでunsigned intも33ビット以上のマシンでも同じように動作します。unsigned intただし、
17〜32

...メソッドに46340を超える値が与えられるようなイベントチェーンが発生しない可能性があることをコンパイラに推測する許可を与えるため。どのプラットフォームでも65533u * 65533uを乗算すると値が生成されますが、にキャストするuint16_tと9が生成されますが、標準ではuint16_t、17ビットプラットフォームから32ビットプラットフォームで型の値を乗算する場合、このような動作は強制されません。
supercat

-1

私はOOネイティブ(一般的にはC ++)でもあり、Cの世界で生き残ることが必要な場合があります。

C ++では、エラーが発生した場所から処理できるトップレベルにエラーを渡すためのスローがあり、メモリやその他のリソースを自動的に解放するデストラクタがあります。

多くのC APIには、実際には構造体へのポインタであるtypedef'd void *を提供するinit関数が含まれていることに気付くかもしれません。次に、これをすべてのAPI呼び出しの最初の引数として渡します。基本的に、それがC ++からの「this」ポインターになります。隠されているすべての内部データ構造に使用されます(非常にオブジェクト指向の概念)。これを使用してメモリを管理することもできます。たとえば、メモリをmallocし、Cバージョンのthisポインタにmallocを記録するmyapiMallocと呼ばれる関数を使用して、APIが戻ったときに確実に解放されるようにします。また、最近発見したように、これを使用してエラーコードを保存し、setjmpとlongjmpを使用して、スローキャッチと非常によく似た動作を行うことができます。両方の概念を組み合わせることにより、C ++プログラムの多くの機能が提供されます。

これで、CをC ++に強制することを学びたくないと言いました。それは実際には私が説明していることではありません(少なくとも故意ではありません)。これは、C機能を活用するための(うまくいけば)うまく設計された方法です。OOフレーバーがいくつかあることが判明しました。おそらく、OO言語が開発された理由です。これは、一部の人々がベストプラクティスであることがわかった概念を形式化/実施/促進する方法でした。

これがあなたにOOを感じていると思うなら、代替手段はほとんどすべての関数がエラーコードを返すようにすることです。エラーコードはすべての関数呼び出し後にチェックして呼び出しスタックを伝播することを宗教的に確実にする必要があります すべてのリソースが各関数の最後だけでなく、すべての戻りポイント(続行できないことを示すエラーを返す可能性のある関数呼び出しの後にある可能性がある)で解放されるようにする必要があります。それは非常に退屈になる可能性があり、おそらくその潜在的なメモリ割り当てエラー(またはファイル読み取りまたはポート接続...)に対処する必要はないと考えてしまう傾向があります、私はそれがうまくいくと仮定します今すぐ「興味深い」コードを書き、戻ってエラー処理に対処します-これは決して起こりません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.