Classes

author: Ir1d, cjsoft, Lans1ot, JasonkayZK A class is an extension of a struct: in addition to member data, it can also have member functions.

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

In C++, the struct keyword also defines a class. The definition of struct above comes from C. For historical reasons, C++ retains and extends struct.

Defining a Class

A class is defined using the keyword class or struct; class is used as the example below.

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 very similar to using struct. The example defines a class named Object. This class has two member data items, weight,value, and defines an array e of this type after }.

Defining a pointer to struct is the same as defining a pointer to a class.

Access Specifiers

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

  • public: All members after this access specifier can be accessed publicly. In simple terms, they can be accessed both inside and outside the class.
  • protected: Members after this access specifier can be accessed by members inside the class, derived classes, or friends, but cannot be accessed outside the class.
  • private: Members after this access specifier can only be accessed by members inside the class or friends, 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 qualify a function or class. This allows the qualified entity to access the private (private) or protected (protected) members of the class without becoming a member function or member class. In simple terms, anything marked with this class's friend marker can access private or protected member data.

Derived class (derived class): C++ allows one class to be used as a base class, from which a derived class can be derived. The derived class inherits the member variables and member functions of the base class according to specific rules. This can improve code reuse.

A derived class has an “is” relationship with its base class. For example, a cat (derived class) “is” a mammal (base class).

The difference between private and protected above can be understood as follows: a derived class can access the protected elements of the base class (and public elements as well), but cannot access private elements.

Accessing and Modifying Member Data

The method is the same as for struct.

  • For a variable, use the . symbol.
  • For a pointer, use the -> symbol.

Member Functions

A member function is, as its name suggests, a function contained in a class.

Common examples of member functions
vector.push_back();
set.insert();
queue.empty();
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 one function that prints the member data of Object and another that changes the member data weight.

As with ordinary functions, member functions can also be declared first and defined later, as on line fourteen (the declaration) and after line seventeen (the definition).

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

Operator Overloading

What is overloading?

C++ allows a programmer to specify different definitions for functions or operators with the same name. This is called overloading (overload).

If two or more of the kinds and number of parameters of same-named functions differ pairwise, these same-named functions are treated as different functions.

Note that if two same-named functions differ only in their return types, they cannot be overloaded; the compiler will reject compilation!

If no ambiguity occurs at the call site (that is, when calling some same-named functions, the called function cannot be uniquely determined from the kinds and number of supplied arguments; this often occurs for functions with default arguments), the compiler determines which function to call from the supplied arguments.

a process above is called overload resolution.

Operator overloading can partially replace functions and simplify code.

Here is an example of operator overloading:

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; the specific implementation is given later.

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

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

/* Overload defined inside the class */ return_type operator<symbol>(parameters){...}

/* Declaration inside the class, definition outside */ return_type ClassName::operator<symbol>(parameters){...}

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

For more information, see item 4 in “References”.

Operators that can be overloaded
+       -       *       /       %       ^       &
|       ~       !       =       <       >       +=
-=      *=      /=      %=      ^=      &=      |=
<<      >>      >>=     <<=     ==      !=      <=
>=      &&      ||      ++      --      ,       ->*
->      ()      []      new     new []  delete  delete []

Setting Initial Values When Instantiating Variables

To perform this operation, a default constructor (Default constructor) must be defined.

class ClassName {
  ... ClassName(...)... { ... }
};

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

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

This example defines the default constructor of Object. When we instantiate a variable of type Object, this function initializes all its member data to 0.

If there is no explicit constructor, the compiler considers the class to have an implicit default constructor. In other words, if no constructor is defined, the compiler automatically generates a default constructor and initializes members according to their types (the same as when defining variables of built-in types).

In this situation, member data is uninitialized, and accessing an uninitialized variable has undefined behavior (that is, its returned value is unknown).

If custom initial values are needed, another constructor can be defined (or overloaded).

Defining (or overloading) constructors

In general, a default constructor takes no parameters, which distinguishes it from a constructor. The definitions of constructors and default constructors are much the same; they differ only in the number of parameters.

Constructors can be overloaded (the first one is of course called a definition). Note that if a constructor has already been defined, the compiler will no longer generate a no-argument default constructor. This may cause an attempt to construct a variable by the default method to fail to compile (that is, when no initialization arguments are supplied).

When using C++11 or later, {} can be used to initialize variables.

About {}

Initialization with {} uses the lightweight proxy object std::initializer_list for initialization.

The initialization steps are roughly as follows:

  1. Try to find a default constructor whose parameters include std::initializer_list; if one exists, call it (after the call, do not perform the searches below, and so on).
  2. Try to place the elements in {} into the other constructor parameters; if the parameters can be filled in order (default parameters also count), call that default constructor.
  3. If there are no private member data items, try assigning values outside the class in element-definition order or subscript order.

a process above is only a simplified version of the complete process; see “Reference 9” for details.

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 such as the following is written:

class Node {
 public:
  int var;

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

Node a = 1;

This seems very illogical: an int type cannot be converted to a node type. However, the compiler does not issue an error.

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

In most cases, however, the programmer wants the compiler to report an error. In this case, the explicit keyword can be added before the constructor. This tells the compiler that the call must be explicit.

class Node {
 public:
  int var;

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

In other words, node a=1 reports an error, but node a=node(1) does not, because the latter explicitly calls the constructor. Of course, most people would not write the latter code, but this example is enough to illustrate the role of explicit.

In programming contests, however, “strengthening code-standard requirements” is commonly used to avoid this kind of situation and prevent it at the source.

Destruction

This is unavoidable. Every variable proceeds to destruction when its scope ends.

However, for a pointer that already points to dynamically allocated memory, destroying the pointer does not automatically release the memory it points to; dynamic memory must be released manually.

The same issue arises if a struct's member data contains a pointer. A destructor is needed to manually release dynamic memory.

The destructor (Destructor) is called when the variable is destroyed. Its overloading method is the same as that of a constructor, but ~ must be placed before it.

The default destructor is generally sufficient for programming contests; normally, we overload the destructor only when member data contains pointers.

class Object {
 public:
  int weight;
  int value;
  int* ned;

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

  ~Object() { delete ned; }
};

Assigning to Class Variables

By default, assignment follows the rules for assigning corresponding member data. 类名称() or 类名称{} can also be used as temporary variables for assignment.

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

In addition, by default, assignment performs a shallow copy between corresponding elements. If member data contains a pointer, the member pointers of the two variables have the same address after assignment.

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

To solve pointer-related issues or perform additional operations, the corresponding constructors need to be overloaded.

For more information about constructors (constructor), see item 6 in “References”.

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

This content comes from OI Wiki: Classes. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.