Object-Oriented Programming – Classes
- Procedural Programming: A process-centered programming paradigm
- Object-Oriented Programming (OOP): A programming paradigm centered around objects
- Encapsulation, Inheritance, Polymorphism
Basic Concepts of Classes and Objects
- A class encapsulates data and methods that share common characteristics, distinguishing them by access levels, to describe or abstract an entity
- Access levels in a class are: private, protected, public
- If no access modifier is specified, the default is private
- Member functions of a class can be declared within the class and defined outside of it
- A class can be defined in a separate file, and its header file can be included when needed
- Constructor of a class: Initializes object data and is automatically called by the compiler. If no constructor is explicitly declared, a default empty constructor is provided
- The function name is the same as the class name
- It has no return value
- It can have parameters and can be overloaded
- It is written under public access
- Destructor of a class: Automatically called when the object’s lifecycle ends to perform cleanup, a default empty destructor is provided
- The destructor is called first, then the object space is released
- The function name is the same as the class name, prefixed with the tilde (~)
- Generally, an empty destructor suffices; if a class has pointer members, an explicit destructor must be declared to manually release all pointer memory
- Destructors are managed in a stack manner, last in, first out
- C++ does not recommend initializing member variables when defining a class.
// Define a class
class Test {
private:
int num;
// int num = 10; // Only supported in higher versions of C++
protected:
int n2;
public:
int n3;
Test() {
num = 1;
n2 = 2;
n3 = 3;
}
Test(int pn) {
num = pn;
}
void getNum() {
return num; // Can directly access variables in the current scope without access restrictions
}
void show(void) {
cout << n << n2 << n3 << endl;
}
bool compareObj(Test & obj);
~Test() { // Destructor
cout << 'Destructor' << endl;
}
};
// Implementing class methods outside the class
bool Test::compareObj(Test & obj) {
if (num == obj.getNum()) { // Directly access num since it's in the Test scope
return true;
}
return false;
}
// Instantiate an object through the class
Test tobj; // Implicitly calls the no-argument constructor
tobj.show(); // Calls the public method of the instance
Test tobj2 = Test(); // Using the no-argument constructor
Test tobj3 = Test(10); // Using the parameterized constructor
Test tobj5(10); // Implicitly using the parameterized constructor
// When a class has only one member, implicit conversion can be used to generate an object
class Test2 {
int n;
public:
Test2(int pn) {
n = pn;
}
};
Test2 tobj4 = 100; // Will convert to Test2 tobj4 = Test2(100);
// Destructors are managed in a stack manner, last in, first out,
// In the same scope: first constructed, last destructed
// Local objects are destructed first, global objects last
Test2 ob1(1);
void fn() {
Test2 ob2(2);
{
Test2 ob3(3);
}
Test2 ob4(4);
}
// Constructor call order: 1 construct 2 construct 3 construct 4 construct
// Destructor call order: 3 destruct 4 destruct 2 destruct 1 destruct
// Overall order: 1 construct 2 construct 3 construct 3 destruct 4 construct 4 destruct 2 destruct 1 destruct // An object's memory is released immediately when its block scope ends
- Copy Constructor
- Essentially, it is also a constructor
- Called when an old object initializes a new object
- If a copy constructor is not explicitly declared, the compiler will automatically provide a default copy constructor (performing shallow copy)
- The copy constructor name is the same as the class name, with a parameter of a constant reference to the class
- When actively implementing a copy constructor, all attributes must be copied; otherwise, attributes will have random values
- If the class has pointers pointing to heap memory, the copy function must be actively implemented to copy the heap content
class Test3 {
int n;
public:
Test3() {
n = 1;
}
Test3(const Test3 & ob) {
n = ob.n;
cout << 'Copy Constructor' << endl;
}
};
Test3 ob();
// Various situations for copy constructor
Test3 ob1 = ob; // Object initializes a new object - calls copy constructor
Test3 fn(Test3 pob) {}
fn(ob); // Passed as a function parameter - calls copy constructor
Test3 fn2() {
Test3 ob2;
return ob2;
}
// (In VS editor) As a function return value, when calling this function, the copy constructor will be executed to generate an anonymous object stored in a temporary area. If an external variable receives it, it will be assigned to the external variable; otherwise, it will be released immediately
// (In QtCreator, Linux) As a function return value, if an external variable receives the function return value, the copy constructor will not be called, and the function's ob2 will not be released; instead, the content of ob2 will be directly assigned to variable ob3, thus saving the temporary storage process and improving performance
Test3 ob3 = fn();
Initialization List
- Object members: Data members defined in a class are generally of basic data types, but can also be class objects, referred to as object members
- When a class has object members
- The initialization order is: first call the constructor of the object members, then call the class constructor; the destructor order is the opposite
- By default, the no-argument constructor of object members is called
- When using the parameterized constructor of object members, an initialization list must be used
class A {
int an;
A() {}
A(int a) {
an = a;
}
};
class B {
A oa;
A oa2;
int n;
public:
B() {}
B(int a, int b) : oa(a), oa2(b) { // Initialization list, passing parameter a to A's parameterized constructor
}
};
Explicit Keyword
- C++ provides the explicit keyword to prohibit implicit conversions through constructors; constructors declared as explicit cannot be used in implicit conversions
- Explicit modification applies to single-parameter constructors or constructors where all parameters except the first have default values
class Test {
public:
explicit Test(int n) {}
};
// Test ob = 1; // After the constructor is modified with explicit, this statement can no longer be used
Test ob(1);
Array of Class Objects
- An array of objects is essentially an array where each element is an object
class A {
public:
A() {}
A(int n) {}
};
A arr[5]; // When creating elements, each object element will call the no-argument constructor. Before the program ends or the array is released, the destructors of the object elements will be called
A arr1[2] = {A(10), A(20)}; // When initializing the array, a parameterized constructor can be used
Dynamic Object Creation
- In C, dynamic objects are created using functions like malloc or realloc
- Type casting is required
- Only object space is created; constructors are not automatically executed, requiring the programmer to handle it
- When using free to release space, destructors are not called, requiring the programmer to handle it
- In C++, dynamic objects are created
- Using the new operator, which automatically calls the constructor to initialize when allocating memory for the object in the heap
- No type casting is required
- When deleting dynamic objects, destructors are called first, then memory is released
class A {
public:
A() {}
A(int a) {}
void fn() {}
};
A *oa = new A;
A *oa1 = new A(10);
oa->fn();
delete oa;
delete oa1;
// Dynamic object array
A *oarr = new A[20];
delete [] oarr;
Static Members
- Members modified with the static keyword are static members; regardless of how many objects of this class are created, there is only one copy of the static member, which is shared by all objects of this class
- Static members modified with static belong to the class, not to the object
- Static members modified with static must allocate space when defining the class
- Static members modified with static must be defined in the class and initialized outside the class
class Data {
private:
static int spn; // Private static member variable
public:
int a;
static int b; // Static member variable
static void fn() { // Static member function
spn = 10; // Static member functions can only operate on static member variables
}
};
int Data::b = 100; // Initialize static member data
Data ob;
cout << ob.b << endl; // 100 Access static member through object
cout << Data::b << endl; // 100 Can directly access static member through class name
ob.b = 200;
Data ob2;
cout << ob2.b << endl; // 200 Static member data is shared; modifying one object affects others
Singleton Design Pattern
- The Singleton pattern is a commonly used software design pattern. Its core structure contains a special class called Singleton, which ensures that there is only one instance of a class in the system, and that instance is easily accessible from the outside, facilitating control over the number of instances and conserving system resources. If you want to ensure that only one object of a certain class exists in the system, the Singleton pattern is the best solution.
// Define a class for the Singleton pattern
class SingleTest {
private: // 1. Prevent external instantiation of this class by privatizing the constructor
SingleTest() {}
SingleTest(const SingleTest & ob) {}
~SingleTest() {}
private: // 2. Define a static pointer variable to hold the address of the unique instance
static SingleTest * const p;
public: // 3. Define a static member function to get the address of the unique instance
static SingleTest * getSingleTest() {
return p;
}
void fn() {}
};
SingleTest * const SingleTest::p = new SingleTest; // 4. Instantiate the unique object
// Usage
SingleTest *stp = SingleTest::getSingleTest();
stp->fn();