C++ Notes
public, private, and protected each define an access level block for the members below them.class Cylinder { public: // data members double radius{}; double height{}; public: // default constructor (compiler generates it) Cylinder() = default; // parameterized constructor Cylinder(double r, double h) { radius = r; height = h; } // member function double volume() { return PI * radius * radius * height; } };
const variables and references cannot be left uninitialized, and also cannot be assigned inside the constructor body. They must be initialized before the constructor body runs.class Example { const int x; int& ref; public: Example(int v, int& r) : x(v), ref(r) // initializer list: runs BEFORE the body {} // body can be empty here };
| Syntax | What It Does |
|---|---|
Cylinder cylinder; | Stack object. Requires a default constructor. Primitive data members contain garbage values unless your constructor initializes them. |
Cylinder cylinder{}; | Stack object. Requires a default constructor. Primitive data members are zero-initialized if you don't define your own default constructor. If you do define one, behavior depends on how you initialize inside it. |
Cylinder cylinder(2, 3); | Stack object. Calls the parameterized constructor with arguments 2 and 3. |
Cylinder* c = new Cylinder(2, 3); | Heap object. Requires a parameterized constructor. Must be freed manually with delete. |
Cylinder cylinder(); | Disaster. This looks like object creation but is actually parsed as a function declaration. Never use this form. |
(*c).volume(), dereference the pointer first, then access the member, or c->volume(), the arrow operator, preferred, does exactly the same thing with cleaner syntax.delete c; c = nullptr; // prevent dangling pointer
new, call delete on them inside the destructor.~Cylinder() { delete p; // release heap memory owned by this object }
The compiler calls the destructor automatically when it's about to destroy the object. The important cases: when a local stack object goes out of scope (its block ends), when a heap object is released with delete, when an object is passed by value to a function (the local copy inside that function is destroyed when the function ends), and when a local object is returned from a function (the original copy may be destroyed after the return if RVO or NRVO optimization isn't applied).
Stack objects are destroyed in reverse order of creation, LIFO, last in, first out.
// creation order obj1 constructor called obj2 constructor called obj3 constructor called // destruction order (reversed) obj3 destructor called obj2 destructor called obj1 destructor called
If a class contains a raw pointer and you don't define a copy constructor, the compiler makes a shallow copy. Both objects end up pointing to the same heap memory. When one is destroyed, its destructor deletes that memory, the other object now holds a dangling pointer, and its destructor then tries to delete already-freed memory, a double-delete crash. This is exactly why the Rule of Three and Rule of Five exist.
this.this->name = name;), returning the current object from a method (used in method chaining), or printing it directly, std::cout << this; shows the address of the current object.By returning this (or *this) from a setter, you can call multiple setters in a single line. Two forms exist.
| Pointer Return (via this) | Reference Return (via *this) | |
|---|---|---|
| Return type | Cylinder* | Cylinder& |
| Returns | this | *this |
| Call syntax | Arrow: c->setHeight(10)->setRadius(5); | Dot: c.setHeight(10).setRadius(5); |
| Notes | Requires a pointer object to use cleanly | More common in C++. Works on both stack and heap objects |
// pointer return style Cylinder* setHeight(double h) { height = h; return this; } // reference return style (preferred) Cylinder& setHeight(double h) { height = h; return *this; }
struct, members are public by default.class, members are private by default.struct for simple data containers with no significant behavior, just grouping related data, and class for anything with real logic and encapsulation.sizeof(object) counts only data members. Member functions are not included in object size.sizeof counts the size of the pointer itself, 4 or 8 bytes, not the heap memory it points to.std::string is not a raw char pointer. sizeof(std::string) gives the size of the string object itself, typically 24 to 32 bytes, the actual characters are stored on the heap internally.sizeof(object) can be larger than the sum of its individual members.sizeof(object) is greater than or equal to the sum of its member sizes. The difference comes from padding for alignment, and from the fact that pointers are counted by their own size, not by what they point to.
#include <iostream> #include <string> #define PI 3.14159 // ============================================================ // CLASS WITH CONSTRUCTOR, DESTRUCTOR, METHOD CHAINING // ============================================================ class Cylinder { public: double radius{}; double height{}; double* volumePtr; // pointer member (heap memory) // default constructor Cylinder() { volumePtr = new double(0); std::cout << "Default constructor, this = " << this << "\n"; } // parameterized constructor Cylinder(double r, double h) { radius = r; height = h; volumePtr = new double(volume()); std::cout << "Parameterized constructor, this = " << this << "\n"; } // destructor: releases heap memory owned by this object ~Cylinder() { delete volumePtr; std::cout << "Destructor called, this = " << this << "\n"; } // const member function: does not modify the object double volume() const { return PI * radius * radius * height; } // method chaining via pointer return Cylinder* setHeight(double h) { height = h; return this; } Cylinder* setRadius(double r) { radius = r; return this; } // method chaining via reference return (more common in C++) Cylinder& setHeightRef(double h) { height = h; return *this; } Cylinder& setRadiusRef(double r) { radius = r; return *this; } }; // ============================================================ // MEMBER INITIALIZER LIST (for const and reference members) // ============================================================ class Example { const int x; int& ref; public: Example(int v, int& r) : x(v), ref(r) // must be initialized here, not in body {} void show() { std::cout << "x = " << x << ", ref = " << ref << "\n"; } }; // ============================================================ // STRUCT: public by default, used for simple data grouping // ============================================================ struct Point { int x; int y; void print() { std::cout << "(" << x << ", " << y << ")\n"; } }; // ============================================================ // MAIN // ============================================================ int main() { std::cout << "\n--- STACK OBJECTS ---\n"; Cylinder c1; // default constructor, garbage in radius/height Cylinder c2{}; // default constructor, radius/height zero-initialized Cylinder c3(2, 3); // parameterized constructor std::cout << "Volume of c3 = " << c3.volume() << "\n"; // PI*4*3 = ~37.7 std::cout << "\n--- HEAP OBJECT ---\n"; Cylinder* cHeap = new Cylinder(4, 5); std::cout << "Heap volume = " << cHeap->volume() << "\n"; (*cHeap).radius = 10; // dereference then access cHeap->height = 2; // arrow operator (preferred) std::cout << "Updated heap volume = " << cHeap->volume() << "\n"; delete cHeap; cHeap = nullptr; std::cout << "\n--- METHOD CHAINING ---\n"; c3.setHeight(10)->setRadius(5); // pointer style std::cout << "After pointer chaining: " << c3.volume() << "\n"; c3.setHeightRef(7).setRadiusRef(3); // reference style (preferred) std::cout << "After reference chaining: " << c3.volume() << "\n"; std::cout << "\n--- MEMBER INITIALIZER LIST ---\n"; int val = 100; Example ex(42, val); ex.show(); // prints: x = 42, ref = 100 val = 200; // change original variable ex.show(); // prints: x = 42, ref = 200 (ref sees the change) std::cout << "\n--- STRUCT ---\n"; Point p{1, 2}; p.print(); // prints: (1, 2) std::cout << "\n--- OBJECT SIZE ---\n"; std::cout << "sizeof(c1): " << sizeof(c1) << "\n"; // pointer + 2 doubles + padding std::cout << "sizeof(Point): " << sizeof(p) << "\n"; // 2 ints = 8 (no padding needed) // stack objects c1, c2, c3 destroyed here in reverse order (LIFO) return 0; }
Constructors, destructors, the hidden this pointer, and why a class object's size is rarely just the sum of its parts. Next up, inheritance, where one class starts building on another.