Skip to content

Classes

A class is an extension of a structure, which can have not only member elements but also member functions.

In object-oriented programming (OOP), an object is an instance of a class, i.e., a variable.

In C++, the struct keyword also defines a class; the definition of structure mentioned earlier comes from C. Due to certain historical reasons, C++ retains and extends struct.

Defining a Class

Classes are defined using the keyword class or struct. The following examples use class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class ClassName {
  ...
};

// Example:
class Object {
 public:
  int weight;
  int value;
} e[array_length];

const Object a;
Object b, B[array_length];
Object *c;

This is similar to using struct. This example defines a class named Object. The class has two member elements: weight and value; and after the }, an array e of this type is defined.

Defining pointers to a class is the same as for struct.

Access Specifiers

Unlike the example in struct, this example includes public, which is an access specifier.

  • public: Members following this access specifier can be accessed publicly, meaning they can be accessed both inside and outside the class.
  • protected: Members following this access specifier can be accessed by members inside the class, derived classes, or friends, but cannot be accessed from outside the class.
  • private: Members following this access specifier can only be accessed by members inside the class or friend members, and cannot be accessed from outside the class or from derived classes.

For struct, all members are public by default. For class, all members are private by default.

Basic Concepts of Friends and Derived Classes

Friend (friend): Use the friend keyword to modify a function or class. This allows the modified entity to access the private (private) or protected (protected) members of the class without becoming a member function or member class. In simple terms, any entity marked with friend for this class can access its private or protected members.

Derived class (derived class): C++ allows using a class as a base class and deriving a derived class from it. The derived class inherits (according to specific rules) member variables and member functions from the base class. This improves code reusability.

A derived class represents an "is" relationship. For example, a cat (derived class) "is" a mammal (base class).

Regarding the difference between private and protected, a derived class can access protected elements of the base class (same as public), but cannot access private elements.

Accessing and Modifying Member Element Values

The method is the same as for struct.

  • For variables, use the . symbol.
  • For pointers, use the -> symbol.

Member Functions

Member functions, as the name suggests, are functions contained within a class.

Common Examples of Member Functions
1
2
3
vector.push_back();
set.insert();
queue.empty();

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Class_Name {
  ... type Function_Name(...) { ... }
};

// Example:
class Object {
 public:
  int weight;
  int value;

  void print() {
    cout << weight << endl;
    return;
  }

  void change_w(int);
};

void Object::change_w(int _weight) { weight = _weight; }

Object var;

This class has a function to print the member elements of Object and a function to change the member element weight.

Similar to functions, member functions can also be declared first and defined later, as in line 14 (declaration) and after line 17 (definition).

To call the print member function of var, use var.print().

Overloading Operators

What is Overloading?

C++ allows programmers to specify different definitions for functions or operators with the same name. This is called overloading.

If the parameter types or numbers of overloaded functions differ in one or more ways, these functions with the same name are considered distinct.

Note: If two functions with the same name differ only in their return type, they cannot be overloaded, and the compiler will refuse to compile!

If no ambiguity arises during the call (i.e., when calling certain overloaded functions, it is impossible to uniquely determine which function to call based on the provided parameter types and numbers, which often occurs with functions having default parameters), the compiler will determine which function to call based on the parameters provided.

This process is called overload resolution.

Overloading operators can partially replace functions and simplify code.

Below is an example of overloading operators.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Vector {
 public:
  int x, y;

  Vector() : x(0), y(0) {}

  Vector(int _x, int _y) : x(_x), y(_y) {}

  int operator*(const Vector& other) const { return x * other.x + y * other.y; }

  Vector operator+(const Vector&) const;
  Vector operator-(const Vector&) const;
};

Vector Vector::operator+(const Vector& other) const {
  return Vector(x + other.x, y + other.y);
}

Vector Vector::operator-(const Vector& other) const {
  return Vector(x - other.x, y - other.y);
}

// Lines 4 and 5 assign values to x and y; see the implementation details later.

This example defines a vector class and overloads the *, +, and - operators to represent vector dot product, vector addition, and vector subtraction, respectively.

The template for overloading operators can be roughly divided into the following parts.

1
2
3
/*Overloading inside class definition*/ return_type operator symbol(parameters){...}

/*Declaration inside class definition, definition outside*/ return_type class_name::operator symbol(parameters){...}

For custom classes, if certain operators are overloaded (generally only the < comparison operator needs to be overloaded), corresponding STL containers or algorithms can be used, such as sort.

For more information, refer to "Reference 4".

Operators That Can Be Overloaded
1
2
3
4
5
6
+       -       *       /       %       ^       &
|       ~       !       =       <       >       +=
-=      *=      /=      %=      ^=      &=      |=
<<      >>      >>=     <<=     ==      !=      <=
>=      &&      ||      ++      --      ,       ->*
->      ()      []      new     new []  delete  delete []

Setting Initial Values When Instantiating Variables

To achieve this, you need to define a default constructor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class ClassName {
  ... ClassName(...)... { ... }
};

// Example:
class Object {
 public:
  int weight;
  int value;

  Object() {
    weight = 0;
    value = 0;
  }
};

This example defines the default constructor for Object, which initializes all member elements to 0 when we instantiate a variable of type Object.

If no explicit constructor is defined, the compiler assumes the class has an implicit default constructor. In other words, if no constructor is defined, the compiler automatically generates a default constructor and initializes member elements based on their types (similar to defining built-in type variables).

In this case, member elements are uninitialized, and accessing uninitialized variables results in undefined behavior (i.e., it is unknown what value will be returned).

If custom initialization values are needed, you can define (or overload) constructors.

About Defining (or Overloading) Constructors

Generally, a default constructor takes no parameters, which distinguishes it from other constructors. The definition of constructors and default constructors is similar, differing only in the number of parameters.

Constructors can be overloaded (though the first definition is simply called defining). Note that if a constructor is defined, the compiler will no longer generate a parameterless default constructor. This may cause compilation to fail when attempting to construct variables using the default method (i.e., without providing initialization parameters).

When using C++11 or later, you can use {} to initialize variables.

About {}

Using {} for initialization involves the lightweight proxy object std::initializer_list.

The initialization steps are roughly as follows:

  1. Attempt to find a default constructor with std::initializer_list in its parameters; if found, call it (after this, no further searches are performed, same below).
  2. Attempt to fill the elements in {} into other constructor parameters. If the parameters can be filled in order (including default parameters), call that default constructor.
  3. If there are no private member elements, attempt to assign values in order of element definition or subscript order outside the class.

The above process is a simplified version of the complete process. For details, refer to "Reference 9".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Object {
 public:
  int weight;
  int value;

  Object() {
    weight = 0;
    value = 0;
  }

  Object(int _weight = 0, int _value = 0) {
    weight = _weight;
    value = _value;
  }

  // the same as
  // Object(int _weight,int _value):weight(_weight),value(_value) {}
};

// the same as
// Object::Object(int _weight,int _value){
//   weight = _weight;
//   value = _value;
// }
//}

Object A;        // ok
Object B(1, 2);  // ok
Object C{1, 2};  // ok,(C++11)

About Implicit Type Conversion

Sometimes code like the following is written:

1
2
3
4
5
6
7
8
class Node {
 public:
  int var;

  Node(int _var) : var(_var) {}
};

Node a = 1;

This seems illogical, as an int type cannot be converted to a node type. However, the compiler does not give an error prompt.

The reason is that during assignment, 1 is first used as a parameter to call node::node(int), and then the default copy function is called for assignment.

However, in most cases, the programmer would want the compiler to report an error. In such cases, the explicit keyword can be added before the constructor. This tells the compiler that explicit invocation is required.

1
2
3
4
5
6
class Node {
 public:
  int var;

  explicit Node(int _var) : var(_var) {}
};

This means node a = 1 will cause an error, but node a = node(1) will not, because the latter explicitly calls the constructor. Of course, most people would not write the latter code, but this example suffices to illustrate the role of explicit.

However, in algorithm competitions, the common practice to avoid such situations is to "strengthen code standardization," preventing it at the source.

Destruction

This is an unavoidable issue. Every variable will be destroyed when it goes out of scope.

However, for pointers that already point to dynamically allocated memory, the pointer does not automatically release the memory it points to when destroyed; manual release of dynamic memory is required.

If a structure's member elements include pointers, the same problem arises. A destructor is needed to manually release dynamic memory.

The destructor will be called when the variable is destroyed. The overloading method is similar to constructors but requires a ~ prefix.

The default destructor is usually sufficient for algorithm competitions. Typically, we only overload the destructor when member elements include pointers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Object {
 public:
  int weight;
  int value;
  int* ned;

  Object() {
    weight = 0;
    value = 0;
  }

  ~Object() { delete ned; }
};

Assigning Values to Class Variables

By default, assignment follows the rules for assigning corresponding member elements. You can also use class_name() or class_name{} as temporary variables for assignment.

The former only calls the copy constructor, while the latter calls the default constructor before calling the copy constructor.

Additionally, by default, assignments perform shallow copy between corresponding elements. If member elements include pointers, after assignment, the member pointers of both variables will have the same address.

1
2
3
4
// A, tmp1, tmp2, tmp3 are of type Object
tmp1 = A;
tmp2 = Object(...);
tmp3 = {...};

To resolve pointer issues or perform more operations, you need to overload the corresponding constructors.

For more on constructors, refer to "Reference 6".

References

  1. cppreference class
  2. cppreference access
  3. cppreference default_constructor
  4. cppreference operator
  5. cplusplus Data structures
  6. cplusplus Special members
  7. C++11 FAQ
  8. cppreference Friendship and inheritance
  9. cppreference value initialization