C++ Core Programming: Memory, References, Functions, and Object-Oriented Basics

Memory Partitioning in C++

When a C++ program executes, memory is divided into four main areas:

  • Code Area: Stores binary code of functions, managed by the operating system.
  • Global Area: Stores global variables, static variables, and constants.
  • Stack Area: Automatically allocated and released by the compiler; stores function parameters, local variables, etc.
  • Heap Area: Allocated and released by the programmer; if not released, the operating system reclaims it at program termination.

Significance of Memory Partitioning: Data in different areas has different lifetimes, providing greater flexibility in programming.

1.1 Before Program Execution

After compilation, an executable (e.g., .exe on Windows) is generated. Before execution, memory is divided into two areas:

  • Code Area:
    • Contains machine instructions executed by the CPU.
    • Shared: Only one copy of frequently executed code is kept in memory.
    • Read-only: Prevents accidental modification of instructions.
  • Global Area:
    • Stores global variables and static variables.
    • Also contains the constant area, which holds string constants and other constants.
    • Data in this area is released by the OS after program termination.

Example:

#include <iostream>
using namespace std;

// Global variables
int globalA = 10;
int globalB = 10;

// Global constants
const int constGlobalA = 10;
const int constGlobalB = 10;

int main() {
    // Local variables
    int localA = 10;
    int localB = 10;

    // Printing addresses
    cout << "Local variable localA address: " << (intptr_t)&localA << endl;
    cout << "Local variable localB address: " << (intptr_t)&localB << endl;
    cout << "Global variable globalA address: " << (intptr_t)&globalA << endl;
    cout << "Global variable globalB address: " << (intptr_t)&globalB << endl;

    // Static variables
    static int staticA = 10;
    static int staticB = 10;
    cout << "Static variable staticA address: " << (intptr_t)&staticA << endl;
    cout << "Static variable staticB address: " << (intptr_t)&staticB << endl;

    // String constants
    cout << "String constant address: " << (intptr_t)&"hello world" << endl;
    cout << "String constant address: " << (intptr_t)&"hello world1" << endl;

    cout << "Global constant constGlobalA address: " << (intptr_t)&constGlobalA << endl;
    cout << "Global constant constGlobalB address: " << (intptr_t)&constGlobalB << endl;

    const int constLocalA = 10;
    const int constLocalB = 10;
    cout << "Local constant constLocalA address: " << (intptr_t)&constLocalA << endl;
    cout << "Local constant constLocalB address: " << (intptr_t)&constLocalB << endl;

    system("pause");
    return 0;
}

Summary:

  • Before program execution, memory is divided into the global area and code area.
  • Code area: shared and read-only.
  • Global area stores global variables, static variables, and constants.
  • Constant area stores constants modified by const and string constants.

1.2 After Program Execution

Stack Area

  • Automatically allocated and released by the compiler; stores function parameters, local variables, etc.
  • Note: Do not return the address of a local variable. The compiler automatically releases stack data.

Example:

int* func() {
    int a = 10;
    return &a; // Dangerous: returning address of local variable
}

int main() {
    int* p = func();
    cout << *p << endl; // Undefined behavior
    cout << *p << endl;
    system("pause");
    return 0;
}

Heap Area

  • Allocated and released by the programmer; if not released, the OS reclaims it at program termination.
  • In C++, use new to allocate memory on the heap.

Example:

int* func() {
    int* a = new int(10);
    return a;
}

int main() {
    int* p = func();
    cout << *p << endl;
    cout << *p << endl;
    delete p; // Release memory
    system("pause");
    return 0;
}

Summary:

  • Heap data is managed by the programmer using new to allocate and delete to release.

1.3 The new Operator

  • Use new to allocate data on the heap.
  • Dat a allocated with new must be released manually using delete.
  • Syntax: new dataType
  • Returns a pointer to the allocated data.

Example 1: Basic Syntax

int* func() {
    int* a = new int(10);
    return a;
}

int main() {
    int* p = func();
    cout << *p << endl;
    cout << *p << endl;

    delete p; // Release memory
    // cout << *p << endl; // Error: accessing freed memory

    system("pause");
    return 0;
}

Example 2: Allocating Arrays

int main() {
    int* arr = new int[10];
    for (int i = 0; i < 10; ++i) {
        arr[i] = i + 100;
    }
    for (int i = 0; i < 10; ++i) {
        cout << arr[i] << endl;
    }
    // Release array with delete[]
    delete[] arr;
    system("pause");
    return 0;
}

References

2.1 Basic Usage of References

Purpose: Create an alias for a variable. Syntax: dataType &alias = originalName;

Example:

int main() {
    int a = 10;
    int &b = a;
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    b = 100;
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    return 0;
}

2.2 Notes on References

  • References must be initialized.
  • Once initialized, a reference cannot be changed to refer to another variable.

Example:

int main() {
    int a = 10;
    int b = 20;
    // int &c; // Error: must be initialized
    int &c = a; // c is now an alias for a
    c = b; // This assigns b's value to a, but c still refers to a
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
    return 0;
}

2.3 References as Function Parameters

Purpose: Modify actual parameters via references in function calls. Advantage: Simplifies pointer usage.

Example:

// Pass by value
void swapByValue(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

// Pass by address
void swapByAddress(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// Pass by reference
void swapByRef(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int a = 10, b = 20;

    swapByValue(a, b); // a and b unchanged
    cout << "a:" << a << " b:" << b << endl;

    swapByAddress(&a, &b); // a and b swapped
    cout << "a:" << a << " b:" << b << endl;

    swapByRef(a, b); // a and b swapped again
    cout << "a:" << a << " b:" << b << endl;

    return 0;
}

Summary: Reference parameters produce the same effect as address passsing, but with cleaner syntax.

2.4 References as Function Return Values

Purpose: Return a reference from a function. Note: Do not return a reference to a local variable. Usage: A function call that returns a reference can be used as an lvalue.

Example:

// Returning reference to local variable (unsafe)
int& test01() {
    int a = 10; // local
    return a;
}

// Returning reference to static variable
int& test02() {
    static int a = 20;
    return a;
}

int main() {
    // Cannot return reference to local variable
    int& ref = test01();
    cout << "ref = " << ref << endl; // Undefined behavior
    cout << "ref = " << ref << endl;

    int& ref2 = test02();
    cout << "ref2 = " << ref2 << endl;
    cout << "ref2 = " << ref2 << endl;

    test02() = 1000; // Function call as lvalue
    cout << "ref2 = " << ref2 << endl; // 1000
    cout << "ref2 = " << ref2 << endl;

    return 0;
}

2.5 The Essence of References

Essence: Internally, a reference is implemented as a constant pointer (Type* const).

Explanation:

// When the compiler sees a reference, it converts it to: int* const ref = &a;
void func(int& ref) {
    ref = 100; // The compiler does: *ref = 100;
}

int main() {
    int a = 10;
    int& ref = a; // Automatically: int* const ref = &a; (pointer constant, pointing cannot change)
    ref = 20; // Internally: *ref = 20;
    cout << "a:" << a << endl;
    cout << "ref:" << ref << endl;
    func(a);
    return 0;
}

Conclusion: C++ recommends references for syntactic convenience. The essence is a constant pointer, but the compiler handles all pointer operations.

2.6 Constant References

Purpose: Modify formal parameters to prevent unintended modifications. Add const to function parameters to protect actual arguments.

Example:

void showValue(const int& v) {
    // v += 10; // Error: v is const
    cout << v << endl;
}

int main() {
    // int& ref = 10; // Error: reference requires a modifiable lvalue
    const int& ref = 10; // OK: compiler creates temporary: int temp = 10; const int& ref = temp;
    // ref = 100; // Error: ref is const
    cout << ref << endl;

    int a = 10;
    showValue(a); // a is not modified
    return 0;
}

Function Enhancements

3.1 Default Parameters

Functions can have default values for parameters. Syntax: returnType functionName(parameter = defaultValue) {}

Example:

int add(int a, int b = 10, int c = 10) {
    return a + b + c;
}

// If a parameter has a default value, all subsequent parameters must have defaults.
// If a function declaration has defaults, the definition cannot have them.
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
    return a + b;
}

int main() {
    cout << "result = " << add(20, 20) << endl; // 50
    cout << "result = " << add(100) << endl; // 120
    return 0;
}

3.2 Placeholder Parameters

Parameters can be placeholders; they must be provided when calling. Syntax: returnType functionName(dataType) {}

Example:

void func(int a, int) { // Second parameter is placeholder
    cout << "this is func" << endl;
}

int main() {
    func(10, 10); // Must provide argument for placeholder
    return 0;
}

3.3 Function Overloading

3.3.1 Overview

Purpose: Functions with the same name but different parameters improve reusability. Conditions for overloading:

  • Same scope
  • Same function name
  • Different parameter types, number, or order

Note: Return type alone cannot differentiate overloaded functions.

Example:

void func() {
    cout << "func() called" << endl;
}
void func(int a) {
    cout << "func(int a) called" << endl;
}
void func(double a) {
    cout << "func(double a) called" << endl;
}
void func(int a, double b) {
    cout << "func(int a, double b) called" << endl;
}
void func(double a, int b) {
    cout << "func(double a, int b) called" << endl;
}

/*
int func(double a, int b); // Not allowed: only return type differs
*/

int main() {
    func();
    func(10);
    func(3.14);
    func(10, 3.14);
    func(3.14, 10);
    return 0;
}

3.3.2 Notes on Function Overloading

  • References as overloading condition: int& vs const int& are different.
  • Overloading with default parameters can cause ambiguity.

Example:

void func(int &a) {
    cout << "func(int &a) called" << endl;
}
void func(const int &a) {
    cout << "func(const int &a) called" << endl;
}

void func2(int a, int b = 10) {
    cout << "func2(int a, int b = 10) called" << endl;
}
void func2(int a) {
    cout << "func2(int a) called" << endl;
}

int main() {
    int a = 10;
    func(a);  // Calls non-const version
    func(10); // Calls const version (rvalue binds to const reference)

    // func2(10); // Ambiguous: matches both overloads (default parameter / single parameter)
    return 0;
}

Classes and Objects

C++ object-oriented programming has three main characteristics: encapsulation, inheritance, polymorphism. C++ views everything as an object, which has attributes and behaviors.

Example:

  • A person object: attributes like name, age, height; behaviors like walking, runnning, eating.
  • A car object: attributes like tires, steering wheel; behaviors like carrying people, playing music.

Classes abstract objects with the same properties.

4.1 Encapsulation

4.1.1 Meaning of Encapsulation

Encapsulation is one of the three OOP features in C++. Meaning:

  • Combine attributes and behaviors into a single unit to represent real-world entities.
  • Control access to attributes and behaviors through access specifiers.

Syntax: class ClassName { accessSpecifier: attributes / behaviors };

Example 1: Circle class to calculate circumference

const double PI = 3.14;

class Circle {
public:  // Public access
    int radius;  // Attribute: radius
    double getCircumference() {  // Behavior: calculate circumference
        return 2 * PI * radius;
    }
};

int main() {
    Circle c1;
    c1.radius = 10;
    cout << "Circumference: " << c1.getCircumference() << endl; // 62.8
    return 0;
}

Example 2: Student class

class Student {
public:
    void setName(string name) { m_name = name; }
    void setID(int id) { m_id = id; }
    void show() {
        cout << "Name: " << m_name << " ID: " << m_id << endl;
    }
public:
    string m_name;
    int m_id;
};

int main() {
    Student stu;
    stu.setName("Garen");
    stu.setID(250);
    stu.show();
    return 0;
}

Access Specifiers:

  • public: Acessible inside and outside the class.
  • protected: Accessible inside the class and in derived classes.
  • private: Accessible only inside the class.

Example:

class Person {
public:
    string m_Name; // public
protected:
    string m_Car;  // protected
private:
    int m_Password; // private

public:
    void func() {
        m_Name = "Zhang San";
        m_Car = "Tractor";
        m_Password = 123456;
    }
};

int main() {
    Person p;
    p.m_Name = "Li Si"; // OK
    // p.m_Car = "Benz"; // Error: protected
    // p.m_Password = 123; // Error: private
    return 0;
}

4.1.2 Difference Between struct and class

The only difference is default access level:

  • struct: default is public
  • class: default is private
class C1 {
    int m_A; // private by default
};

struct C2 {
    int m_A; // public by default
};

int main() {
    C1 c1;
    // c1.m_A = 10; // Error: private
    C2 c2;
    c2.m_A = 10; // OK
    return 0;
}

Tags: C++ Memory Management References Function Overloading Object-Oriented Programming

Posted on Sat, 16 May 2026 18:32:59 +0000 by mbh23