AlgoMaster Logo

C++ vs C

Medium Priority20 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

C++ didn't appear out of nowhere. It grew directly out of C in the early 1980s, when Bjarne Stroustrup added classes to C and called the result "C with Classes". For most of the 80s, the two languages were nearly identical at the syntax level. They've drifted apart since, but the shared DNA is still everywhere: pointers, headers, the preprocessor, even printf when needed. This chapter walks through what C++ added, what it kept, and where the two languages disagree.

Shared Heritage

C++ started as a strict extension of C. The earliest goal was practical: let C programmers write object-oriented code without discarding anything they already knew. That goal still shapes the language today. A C function dropped into a .cpp file will, most of the time, compile and run identically.

The shared surface is large:

  • Same fundamental types: int, char, float, double, long, etc.
  • Same control flow: if, for, while, switch, goto.
  • Same operators: arithmetic, bitwise, comparison, assignment.
  • Same preprocessor: #include, #define, #ifdef.
  • Same pointer semantics: addresses, dereferencing, pointer arithmetic.
  • Same compilation model: source files compile to object files, linker stitches them together.
  • Same calling convention for plain functions, which is why C++ can call C libraries directly.

One way to think of the relationship: "C++ = C + a much bigger toolbox". The C parts are still there. They just aren't the part most code uses first anymore.

The diagram is intentionally rough. Each of those additions is a deep topic in its own right, and they all combine in non-trivial ways. None of them replaced C. They were layered on top.

The cleanest place to see the family resemblance is "hello, world":

Both compile, both run, both print the same string. The C version uses printf and a format string. The C++ version uses a stream object and the overloaded << operator. The C++ approach is type-safe (the compiler picks the right overload for the type being printed), while printf's format specifiers are checked only loosely at runtime. That single example shows two big C++ additions: overloading and the standard library wrapping I/O in objects.

What C++ Adds

The features below are the ones that make C++ feel like a different language even though the syntax is similar. Each gets its own dedicated section later in the course, so this overview stays high-level.

Classes and Objects

C has struct, but a struct is just a bundle of fields. Behavior lives in free functions that take the struct as an argument. C++ promotes structs into full classes, with methods, constructors, destructors, access control (public, private, protected), inheritance, and polymorphism. That single change rewires how code is organized.

The same "product" in both styles:

Output (both):

The C version separates data (the struct) from behavior (free functions that take a pointer to the struct). The C++ version bundles them together and uses a constructor to enforce that no Product can exist in a half-initialized state. The C++ version also hides the internal fields behind private, so callers can only interact with the object through its methods. Encapsulation isn't possible in plain C.

References

C passes everything by value, including pointers. To modify an argument from inside a function, the caller passes a pointer and the function dereferences it. C++ adds references, which act as "pointers that can't be null and don't need to be dereferenced".

The & in double& price makes price an alias for the caller's variable. No * to write, no risk of a null pointer, no chance of forgetting to dereference. References aren't a replacement for pointers, but they're the everyday tool for reading or modifying the caller's object.

Namespaces

C has exactly one global namespace. If two libraries both define a function called parse, the program hits a name collision and won't link. C++ adds namespaces so libraries can scope their names.

Both functions can coexist. They're called as checkout::computeTotal(...) or shipping::computeTotal(...). The standard library lives in the std namespace, which is why std::cout and std::string appear everywhere.

Templates and Generic Programming

C has no way to write a function that works for multiple types other than copy-pasting it for each type or doing unsafe tricks with void*. C++ adds templates, which let the compiler generate a separate version of the code for each type that uses it.

One source function, three concrete implementations the compiler builds. Templates power the entire Standard Template Library, covered below.

Function and Operator Overloading

In C, every function needs a unique name. print_int, print_double, print_string. In C++, they can all share a name, and the compiler picks the right one based on the argument types:

Operator overloading takes the same idea further: a class can define +, ==, <<, [], and so on.

new / delete vs malloc / free

C allocates raw memory with malloc and releases it with free. The returned bytes are uninitialized; the caller has to set them up by hand. C++ adds the new and delete operators, which allocate memory AND run a constructor (or destructor) on it.

The C version returns raw bytes. The C++ version returns a fully-constructed std::string, complete with its internal buffer set up. The code still calls delete to release it, but in modern C++ raw new/delete is rare. Smart pointers take over.

RAII: Destructors Run Automatically

This is arguably the biggest leap from C, and it doesn't have a flashy keyword. RAII stands for Resource Acquisition Is Initialization. The idea: when an object's constructor runs, it acquires whatever resources it needs (memory, a file handle, a lock). When the object goes out of scope, its destructor runs automatically and releases them.

In C the programmer has to remember to call fclose. In C++, the destructor does it, even if an exception is thrown midway through. This single pattern eliminates entire categories of bugs (leaked memory, unclosed files, forgotten locks). The Memory Management section is essentially "how to apply RAII to everything".

Stronger Type System

C is famously permissive about types. It accepts assigning an int to a char and back, treats 0 as a stand-in for false, and allows many pointer types to convert implicitly. C++ tightens several of these:

  • bool is a real type. C added _Bool later, but C++ has had bool from the start.
  • enum class (since C++11) is type-safe and scoped. Plain C enums leak their values into the surrounding scope and convert to int without diagnosis.
  • Implicit conversions between pointer types are stricter. Casting void* to int* requires an explicit cast in C++; C accepts it without one.
  • Function signatures are part of the function's identity, thanks to overloading. C's name mangling rules are different and looser.

The practical effect is that more bugs get caught at compile time.

Standard Template Library (STL)

C's standard library is small and low-level: I/O, strings (as char arrays), math, memory. C++'s standard library is much larger and centers on the STL: a set of container types, iterators, and algorithms built on templates.

In C the programmer writes the array, writes a sort (or calls qsort with a comparator function pointer), and manages memory by hand if the array needs to grow. The STL provides vector, map, unordered_map, set, string, sort, find, and dozens of other tools out of the box. Section 16 covers it in full.

Exception Handling

C reports errors by return codes, errno, or out-parameters. The caller is expected to check after every call. Many callers don't. C++ adds exceptions: a function that hits a problem can throw, and the call stack unwinds until something catches it.

Exceptions are controversial. Some C++ codebases ban them entirely (Google's style guide historically did).

nullptr Instead of NULL

In C, NULL is typically a macro defined as ((void*)0) or 0. That causes subtle problems when overload resolution gets involved (does 0 match the int overload or the int* overload?). C++11 added nullptr, a real keyword with its own type, std::nullptr_t.

Older code still uses NULL. New code should always use nullptr.

std::string vs C-Style char Arrays

A C string is a char array terminated by a null byte. Length, allocation, and resizing are managed by hand with strlen, strcpy, strcat, malloc. The pitfalls are famous: buffer overflows, off-by-one bugs, forgetting the null terminator.

std::string handles allocation, growth, and bounds automatically. It works with all the STL containers and algorithms. There are still cases where C-strings matter (interop with C libraries, embedded systems with tight memory), but for application code, std::string is the default choice.

What C++ Keeps

C++ didn't throw anything out for the sake of purity. Almost every C feature still works, even when there's a cleaner C++ alternative:

  • Pointers. Raw pointers and pointer arithmetic still exist. They appear when interfacing with C libraries, doing low-level work, or when references aren't enough.
  • Manual memory management. malloc and free are still callable from C++ via <cstdlib>. New code should avoid them.
  • C-style arrays. int prices[5] still works exactly as it did in C, including the way arrays decay to pointers when passed to functions.
  • The C standard library. Almost the entire C standard library is available in C++ under headers like <cstdio>, <cstring>, <cmath>. printf, scanf, memcpy, strlen all still work.
  • Direct interop with C. A function declared extern "C" can link to a library written in C with no wrapper layer. This is why C++ is everywhere in systems programming: every operating system's API is fundamentally a C API.

The cost of this compatibility is that C++ is a big language with multiple ways to do the same thing. Modern C++ guidelines (the C++ Core Guidelines, in particular) help pick one.

What Changed Between Them

For about a decade, C++ was a strict superset of C. That isn't quite true anymore. The two languages have evolved separately since C99, and some valid C programs no longer compile as C++. The differences are mostly small but worth knowing:

AreaCC++
Implicit void* castAllowed (int* p = malloc(...))Requires explicit cast
bool_Bool (C99+) or intBuilt-in bool type
Function f()Means "unknown number of args"Means "no args" (same as f(void))
char literalHas type intHas type char
Designated initializersStandard since C99Standard since C++20
Variable-length arraysStandard since C99Not supported (use std::vector)
Linkage of const globalsExternal by defaultInternal by default

In practice, around 99% of valid C code compiles as C++ once the void* casts are added. But "C with a C++ compiler" isn't quite the same language as "C with a C compiler". On mixed projects, this matters.

Feature-by-Feature Summary

FeatureCC++
OOP (classes, inheritance, polymorphism)NoYes
Templates and generic programmingNoYes
ReferencesNoYes
NamespacesNoYes
Function overloadingNoYes
Operator overloadingNoYes
Exception handlingNoYes
RAII / automatic destructorsNoYes
Memory allocationmalloc / freenew / delete plus smart pointers
Default string typechar[] + <string.h>std::string
Standard containersNone (write your own)STL: vector, map, set, etc.
Null pointerNULL (a macro)nullptr (a keyword, since C++11)
Type safetyLooseStricter, especially for enums and casts
PointersYesYes (still supported)
PreprocessorYesYes (still supported, but discouraged for constants)
Compile speedFastSlower (templates and headers cost time)
Runtime speedExcellentExcellent (same baseline)

The bottom line is that C and C++ start from the same performance floor. C++ adds abstractions that mostly cost nothing at runtime (templates compile down to specialized code; references compile to pointers; RAII is deterministic function calls). The cost is compile time and language complexity.

When to Choose C vs C++

If both are options, C++ is usually the better default. It includes everything C has, plus a much larger standard library and modern features. There are still reasons to stick with C:

  • Tiny embedded systems. A C++ compiler exists for almost every platform, but C++ binaries can be larger and require runtime support (for exceptions, RTTI, the STL). On microcontrollers with 32 KB of flash, C is often the right call.
  • Operating system kernels. Linux, FreeBSD, and most OS kernels are written in C. The reason is partly historical and partly that C's runtime model is minimal, which makes it easier to reason about in kernel context.
  • ABI stability. C has a stable, well-defined Application Binary Interface. C++ doesn't, in practice. A library meant to be called from any language exposes a C API on the outside, even when the implementation is C++.
  • Simpler toolchain. A C codebase compiles faster and has fewer language features to learn. Some teams choose C specifically to keep the surface area small.

The flip side: choose C++ for the OOP toolkit, the STL, generic programming, exceptions, or better type safety. Most application code, game engines, browsers, financial systems, and high-performance servers are C++ for those reasons.

Allocating an Array, Side by Side

One more concrete comparison, since dynamic arrays are where C and C++ feel most different in everyday code.

Output (both):

The C version manages the allocation by hand, checks for failure, fills the array in a loop, and frees the buffer at the end. The C++ version uses std::vector, which sizes itself, grows on demand, and releases its memory automatically when it goes out of scope. Both produce the same output. The C++ version is harder to leak from and easier to extend (push_back can add more items at any time).

Quiz

C++ vs C Quiz

10 quizzes