C++ from Scratch: Constructors and Object Initialization

Starting from Objects: Why Do We Need Constructors?

Imagine we define a “Student” class that describes a student’s basic information, such as name and age. When we want to create a student object, directly assigning these values would be cumbersome and error-prone. This is where constructors act as an “automatic initializer”—they automatically complete the initialization of member variables when the object is created, ensuring the object starts with a reasonable state from the moment it is “born.”

What Is a Constructor?

A constructor is a special member function responsible for initializing member variables when an object is created. It has several key characteristics:
- The name must be exactly the same as the class name (case-sensitive);
- No return type (including void), and no return statement can be written;
- Automatically called when the object is created; it does not need to be called manually.

Example: Defining a Class with a Constructor

Suppose we want to create a Student class with member variables name (string) and age (integer), and use a constructor to initialize them:

#include <iostream>
#include <string>
using namespace std;

// Define the Student class
class Student {
public:
    // Member variables: student's name and age
    string name;
    int age;

    // Constructor: takes name and age as parameters to initialize member variables
    Student(string n, int a) {
        name = n;  // Assignment inside the constructor
        age = a;
    }
};

int main() {
    // When creating a Student object, the constructor is automatically called
    Student s("小明", 18);
    cout << "Student Name: " << s.name << ", Age: " << s.age << endl;
    return 0;
}

Output:

Student Name: 小明, Age: 18

Key Point: When Student s("小明", 18); is executed, the constructor is automatically called, assigning “小明” to name and 18 to age.

Default Constructor: Allowing Objects to “Be Born Without Parameters”

If no constructor is defined, the compiler automatically generates a default constructor (empty body, no parameters). However, if you define a constructor with parameters, the compiler will no longer generate a default constructor automatically.

Two Cases of Default Constructors:

  1. Parameterless Constructor:
   class Student {
   public:
       string name;
       int age;

       // Parameterless default constructor
       Student() {
           name = "Unknown";  // Assign default values to member variables
           age = 0;
       }
   };

   int main() {
       Student s;  // Create an object without parameters, calling the default constructor
       cout << "Default Name: " << s.name << ", Default Age: " << s.age << endl;
       return 0;
   }

Output:

   Default Name: Unknown, Default Age: 0
  1. Constructor with Default Parameters:
    If all parameters of a constructor have default values, the compiler also treats it as a default constructor. In this case, objects can be created without passing parameters:
   class Student {
   public:
       string name;
       int age;

       // Constructor with default parameters (equivalent to a default constructor)
       Student(string n = "Unknown", int a = 0) {
           name = n;
           age = a;
       }
   };

   int main() {
       Student s1;       // Calls Student("Unknown", 0)
       Student s2("小红");// Calls Student("小红", 0)
       Student s3("小刚", 20); // Calls Student("小刚", 20)
       return 0;
   }

Initializer List: A More Efficient Initialization Method

In addition to assigning values inside the constructor body, you can use an initializer list to directly initialize member variables. This is often more efficient, especially for custom types (such as strings or objects), avoiding the process of “default construction followed by assignment.”

Comparison: Assignment Inside Constructor Body vs. Initializer List

// 1. Assignment inside the constructor body
class Person {
public:
    string name;
    int age;

    Person(string n, int a) {
        name = n;  // First default-construct the string, then assign
        age = a;
    }
};

// 2. Initializer list (more efficient)
class Person {
public:
    string name;
    int age;

    Person(string n, int a) : name(n), age(a) {  // Direct initialization
        // Other code can be written here, but member variables are already initialized
    }
};

Common Errors and Precautions

  1. Constructors Cannot Have Return Types:
   class A {
   public:
       A() { return; }  // Error! Constructors cannot have return types
   };
  1. const Member Variables Must Use Initializer List:
    If a class has a const member variable (unmodifiable), it must be initialized in the initializer list, not assigned inside the constructor body:
   class Test {
   public:
       const int x;  // const member variable
       Test(int val) : x(val) {}  // Correct: use initializer list
       // Test(int val) { x = val; }  // Error! const variables cannot be assigned
   };
  1. Order in Initializer List Does Not Affect Member Variable Declaration Order:
    The initialization order of member variables is determined by their declaration order in the class, not the order in the list:
   class Order {
   public:
       int a;
       int b;
       Order(int x, int y) : b(y), a(x) {  // Although b is listed first, then a
           cout << "a=" << a << ", b=" << b << endl;  // Output: a=x, b=y
       }
   };

Summary

Constructors are a key tool in C++ for initializing member variables when an object is created. Key points:
- Constructor name matches the class name, with no return type;
- Default constructors ensure objects have a reasonable initial state (parameterless or with default parameters);
- Initializer lists are an efficient way to initialize member variables;
- Avoid common errors (e.g., return types in constructors, initialization of const member variables).

Through constructors, we ensure every object has a clear initial state when created, avoiding member variables using random values, and making the code safer and more maintainable.

Xiaoye