私はJavaバックグラウンド(CSクラスから)とC++の学期から来ました。純粋なCであるCo-OpのOpenCVプロジェクトを仕上げているところなので、この質問をするのに少し遅れました。
純粋なCの設計プロセスとコーディング標準は何ですか?
オブジェクト指向プログラミング、設計、およびベストプラクティスに精通しています。 Cのような非オブジェクト指向言語では少し途方に暮れています。すべての変数と関数はグローバルであるように見えます。それは私にとって本当の混乱のように感じさせます。
同様の質問 への回答をチェックすることに興味があるかもしれません。さらに、Cスタイルガイドに興味がある場合は、C(およびC++)スタイルガイドのリポジトリであるため、 このページ を参照することをお勧めします。 良い笑いを好むなら、 NASA Cスタイルガイド をご覧ください。特に、massiveコメントを見てください...私が話しているコメントがわかります。このようなコメントは書かないでください。
私は個人的に Indian Hill Cスタイルガイド をいくつか変更してお勧めします。さらに、Cで大規模なプログラムの設計に問題がある場合は、本 C Interfaces and Implementations を購入することをお勧めします。
正直なところ、StackOverflowに関する答えがいくつもあれば、適切に構造化されたCプログラムを設計および作成する方法がわかるとは思いません。あなたは良い本を読む必要があります、そして読むのに明白なものは Cプログラミング言語 カーニハン&リッチーによるものです。
私はCでの専門的な経験はありません(C++でのみ)。したがって、アドバイス、トリック、およびヒントは、「オブジェクトのような指向」であるため、あまり真剣に受け取らないでください。
基本的なオブジェクトのような機能のシミュレーションは簡単に実行できます。
ヘッダーで、型を宣言し、typedefで転送し、「メソッド」を宣言します。例えば:
/* MyString.h */
#include <string.h>
/* Forward declaration */
struct StructMyString ;
/* Typedef of forward-declaration (note: Not possible in C++) */
typedef struct StructMyString MyString ;
MyString * MyString_new() ;
MyString * MyString_create(const char * p_pString) ;
void MyString_delete(MyString * p_pThis) ;
size_t MyString_length(const MyString * p_pThis) ;
MyString * MyString_copy(MyString * p_pThis, const MyString * p_pSource) ;
MyString * MyString_concat(MyString * p_pThis, const MyString * p_pSource) ;
const char * MyString_get_c_string(const MyString * p_pThis) ;
MyString * MyString_copy_c_string(MyString * p_pThis, const char * p_pSource) ;
MyString * MyString_concat_c_string(MyString * p_pThis, const char * p_pSource) ;
各関数に接頭辞が付いているのがわかります。 「構造体」の名前を選択して、別のコードと衝突しないようにします。
また、OOのようなアイデアを維持するために「p_pThis」を使用したことがわかります。
ソースファイルで、タイプを定義し、関数を定義します。
/* MyString.c */
#include "MyString.h"
#include <string.h>
#include <stdlib.h>
struct StructMyString
{
char * m_pString ;
size_t m_iSize ;
} ;
MyString * MyString_new()
{
MyString * pMyString = malloc(sizeof(MyString)) ;
pMyString->m_iSize = 0 ;
pMyString->m_pString = malloc((pMyString->m_iSize + 1) * sizeof(char)) ;
pMyString->m_pString[0] = 0 ;
return pMyString ;
}
/* etc. */
「プライベート」関数(またはプライベートグローバル変数)が必要な場合は、Cソースでそれらを静的に宣言します。このように、それらは外から見えなくなります:
static void doSomethingPrivate()
{
/* etc. */
}
static int g_iMyPrivateCounter = 0 ;
継承が必要な場合は、ほとんど失敗しています。変数を含め、Cのすべてがグローバルであると考えている場合は、継承のシミュレーション方法を考える前に、Cの経験を積む必要があります。
たとえば、複数の返品は危険です。例えば:
void doSomething(int i)
{
void * p = malloc(25) ;
if(i > 0)
{
/* this will leak memory ! */
return ;
}
free(p) ;
}
これには、「静的」変数(静的関数ではない)が含まれます。
グローバル非const変数はほとんど常に悪い考えです(つまり、くだらない関数の例については、C API strtokを参照してください)。
関数と定義のために「名前空間」を選択します。これは:
#define GROOVY_LIB_x_MY_CONST_INT 42
void GroovyLib_dosomething() ;
定義はCでは避けられませんが、副作用がある可能性があります!
#define MAX(a, b) (a > b) ? (a) : (b)
void doSomething()
{
int i = 0, j = 1, k ;
k = MAX(i, j) ; /* now, k == 1, i == 0 and j == 1 */
k = MAX(i, j++) ; /* now, k == 2, i == 0 and j == 3, NOT 2, and NOT 1 !!! */
}
初期化せずに変数を宣言しないでください。
int i = 42 ; /* now i = 42 */
int j ; /* now j can have any value */
double k ; /* now f can have any value, including invalid ones ! */
初期化されていない変数は、苦痛なバグの原因です。
K&Rで説明されているC API関数リストは非常に小さいです。 20分でリスト全体を読むことができます。それらの機能を知っている必要があります。
C APIを書き換えます。たとえば、string.h関数の独自のバージョンを作成して、それがどのように行われるかを確認してください。
関数またはオブジェクトのスコープを「静的」に宣言することにより、ソースファイルに対してローカルにすることができます。それは少し役立ちます。
そうでなければ、名前空間の衝突を回避するために私が見る典型的なイディオムは、名前にある種のファシリティ識別子を付けることです。たとえば、foo.cのすべての名前はfoo_ *になります。
他の優れたCプログラマーと協力してください。彼らとコードレビューを行ってください。彼らにあなたのコードを見させるだけでなく、あなたは彼らのコードを見ます。
良いニュースは、Cでセミオブジェクト指向の方法でプログラミングできることです。データを保護したり、アクセス関数を公開したりできます。C++の空想のすべてを備えているわけではありませんが、他の人のC++コードについて私が見たものから、とにかく多くの人々は空想を使用しません。言い換えると、人々はクラス内でCコードを記述しますが、Cではクラスコンテナなしで同じコードを記述します。
まず、Cプログラミングとスタイルに関する本を読んでください。K&Rで結構です。次に、チェックアウト CERTプログラミング標準 をお勧めします。このサイトは主に「安全な」コーディング標準に焦点を当てていますが、ここにあるコンテンツの多くは、誰もが従うべき一般的なコード品質標準です。ここで説明したことを行うと、品質が向上し、厄介なバグがなくなり、副作用としてコードの安全性が高まります。
Linuxカーネルのソースをよく見たいと思うかもしれません。指向のプログラマーの場合、Cでのエラー処理は特に困難な作業になります。これが役立つかもしれません---> http://www.freetype.org/david/reliable-c.html
純粋なCでオブジェクト指向の設計を行うことができます。簡単なアプローチは、モジュールをclass
と見なし、パブリックメソッドをthis
パラメータを明示的な最初の引数として必要とする通常の関数と見なすことです。
class
名が関数名の接頭辞であり、すべてのプライベート関数とクラスデータがstatic
と宣言されている場合に役立ちます。メモリを取得するためにmalloc()
を使用してコンストラクタを作成し、データフィールドを明示的に初期化します。
完全にプライベートなデータメンバーを持つオブジェクトのコンストラクターは、不透明なポインター(void *
偶数、またはタイプセーフが必要な場合は不完全な型へのポインタとして)。パブリックデータメンバーのみが必要な場合は、パブリックに定義されたstruct
へのポインターが適切に機能します。
このパターンの後にいくつかのライブラリが続きます。初期化関数は、すべてのライブラリメソッドに渡される必要があるCookieを返し、1つのメソッドがデストラクタとして機能します。
もちろん、純粋なCで適切に設計する方法は他にもありますが、OOでうまくいく場合は、完全に放棄する必要はありません。
ファイルスコープの変数と関数の可視性をそれぞれのソースファイルに制限できます(ただし、これらのオブジェクトへのポインターを渡すことを妨げるものではありません)。
例えば:
/** foo.c */
static void foo_helper() {...} /* foo_helper cannot be called by name
outside of foo.c */
static int local_state; /* local state is visible at file scope,
but is not exported to the linker */
オープンソースアプリケーションのアーキテクチャ Cプログラムには、Git、Nginx、Open MPI、GPSD(C&Python)、GDB、FreeRTOS、Berkeley DB、Bash、Asterisk、
上記のプロジェクトはどれも読む価値があります。標準のコマンドラインツールも読むことができます。 Linuxカーネルにも多数の設計ドキュメントがあります。