Read SICP and learn Scheme, and the practical idea of abstract data types. Then coding in C is easy (since with SICP, a bit of C, and a bit of PHP, Ruby, etc... your thinking would be widen enough, and you would understand that object oriented programming might not be the best style in all cases, but only for some kind of programs). Be careful about C dynamic memory allocation, which is probably the hardest part. The C99 or C11 programming language standard and its C standard library is actually quite poor (it does not know about TCP or directories!), and you'll often need some external libraries or interfaces (e.g. POSIX, libcurl for HTTP client library, libonion for HTTP server library, GMPlib for bignums, some library like libunistring for UTF-8, etc...).
Your "objects" are often in C some related
struct-s, and you define the set of functions operating on them. For short or very simple functions, consider defining them, with the relevant
static inline in some header file
foo.h to be
Notice that object oriented programming is not the only programming paradigm. In some occasions, other paradigms are worthwhile (functional programming à la Ocaml or Haskell or even Scheme or Commmon Lisp, logic programming à la Prolog, etc etc... Read also J.Pitrat's blog about declarative artificial intelligence). See Scott's book: Programming Language Pragmatics
Actually, a programmer in C, or in Ocaml, usually does not want to code in an object oriented programming style. There is no reason to force yourself to think of objects when that is not useful.
You'll define some
struct and the functions operating on them (often thru pointers). You could need some tagged unions (often, a
struct with a tag member, often some
enum, and some
union inside), and you might find useful to have a flexible array member at the end of some of your
Look inside the source code of some existing free software in C (see github & sourceforge
to find some). Probably, installing and using a Linux distribution would be useful: it is made almost only of free software, it has great free software C compilers (GCC, Clang/LLVM) and development tools. See also Advanced Linux Programming if you want to develop for Linux.
Don't forget to compile with all warnings and debug info, e.g.
gcc -Wall -Wextra -g -notably during the development & debugging phases- and learn to use some tools, e.g. valgrind to hunt memory leaks, the
gdb debugger, etc. Take care to understand well what is undefined behavior and strongly avoid it (remember that a program could have some UB and sometimes seem to "work").
When you genuinely need object oriented constructs (in particular inheritance) you can use pointers to related structures and to functions. You could have your own vtable machinery, have each "object" starting with a pointer to a
struct containing function pointers. You take advantage of the ability to cast a pointer type to another pointer type (and of the fact that you can cast from a
struct super_st containing the same field types as those starting a
struct sub_st to emulate inheritance). Notice that C is enough to implement quite sophisticated object systems -in particular by following some conventions-, as GObject (from GTK/Gnome) demonstrates.
When you genuinely need closures, you'll often emulate them with callbacks, with the convention that every function using a callback is passed both a function pointer and some client data (consumed by the function pointer when it calls that). You could also have (conventionally) your own closure-like
struct-s (containing some function pointer and the closed values).
Since C is a very low level language, it is important to define and document your own conventions (inspired by the practice in other C programs), in particular about memory management, and probably some naming conventions also. It is useful to have some idea about instruction set architecture. Don't forget that a C compiler may do lots of optimizations on your code (if you ask it to), so don't care too much about doing micro-optimizations by hand, leave that to your compiler (
gcc -Wall -O2 for optimized compilation of released software). If you care about benchmarking and raw performance, you should enable optimizations (once your program has been debugged).
Don't forget that sometimes metaprogramming is useful. Quite often, large software written in C contain some scripts or ad-hoc programs to generate some C code used elsewhere (and you might also play some dirty C preprocessor tricks, e.g. X-macros). There exists some useful C program generators (e.g. yacc or gnu bison to generate parsers, gperf to generate perfect hash functions, etc...). On some systems (notably Linux & POSIX) you could even generate some C code at runtime in
generated-001.c file, compile it to a shared object by running some command (like
gcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so) at runtime, dynamically load that shared object using dlopen & get a function pointer from a name using dlsym. I'm doing such tricks in MELT (a Lisp-like domain specific language which might be useful to you, since it enables customization of the GCC compiler).
Be aware of garbage collection concepts and techniques (reference counting is often a technique to manage memory in C, and it is IMHO a poor form of garbage collection which does not deal well with circular references; you could have weak pointers to help about that, but it might be tricky). On some occasions, you might consider using Boehm's conservative garbage collector.