近期准备求职面试。
将C++中面向对象部分的基础知识复习整理一下。
Class
In C++, a class is used to define your own abstract data type.
Class Definition and Declaration
In simple terms, a class defines a new type and a new scope.
Class Definition: Overview
Class Members
Each class can have no members or define multiple members; members can be data, functions, or type aliases.
A class can contain several public, private, and protected parts.
Access Specifiers
- public: Members defined with the public access specifier can be accessed by all code of that type; users of the class and derived classes can also access them.
- private: Members defined with the private access specifier can be accessed by other members of the class but not by users of the class or its derived classes.
- protected: Members defined with the protected access specifier can be thought of as a mix of public and private; like private, users of the class cannot access them directly, but like public, protected members can be accessed by derived classes that use the class as a base.
Constructors
When creating a class type, the compiler automatically uses a constructor to initialize objects.
Constructor: Defines how to initialize the class member functions. (C++ Primer 4th, 2.3.3-2, P43)
Additionally: C++ supports two types of initialization: (C++ Primer 4th, 2.3.3-1, P42)
- Copy initialization: uses syntax with the equal sign (=).
- Direct initialization: uses syntax by placing the initialization expression in parentheses ().
1 | int ival = 1024; // Copy initialization |
Member Functions
Inside a class, it is mandatory to declare member functions, but defining member functions is optional.
Functions defined inside a class are implicitly considered inline. (C++ Primer 4th, 12.1.1-3, P369)
Inline functions: Expand in-line at the call site, avoiding function call overhead. Since calling a function is much slower than directly executing an expression. (C++ Primer 4th, 7.6, P221)
Calling a function has to do a lot of work:
- Save registers before calling and restore them upon return;
- Copy arguments;
- The program must transfer to a new location to execute.
Member functions defined outside the class must indicate that they are within the class’s scope.
You can use the :: scope operator to specify:
1 | class testClass { |
Member functions have an additional implicit argument that binds the function to the object—this pointer.
When we define:
1 | testClass obj; |
Calling the printHelloWorld function of the obj object means that any reference to members of the testClass inside the printHelloWorld function refers to members of the obj object.
const Member Function
Adding the keyword const after the parameter list allows the member function to be declared as constant.
const members cannot change the data members of the object they operate on.
The const keyword must appear in both the declaration and definition; if it appears only once, a compilation error will occur.
1 | // We can add the following member function declaration under the public access specifier in the testClass. |
The role of const is as described by the function name :)
Data Abstraction and Encapsulation
The concepts behind classes are data abstraction and encapsulation.
Data abstraction is a programming (design) technique that depends on the separation of interface and implementation.
Through data abstraction, programmers using a type only need to understand the type’s interface; they can think abstractly about what the type does rather than concretely about how it works.
Encapsulation is a technique that groups lower-level elements to form new, higher-level entities.
Functions are a form of encapsulation: the detailed behaviors executed by the function are encapsulated within this larger entity, which is the function itself. The encapsulated elements hide their implementation details—the function can be called, but its executed statements cannot be accessed.
The standard library type vector embodies both data abstraction and encapsulation. Arrays are conceptually similar to vectors but are neither abstract nor encapsulated. You can directly manipulate arrays by accessing the memory storing them.
Access Specifiers Implementing Abstraction and Encapsulation
In C++, access specifiers are used to define the abstract interface of classes and to implement encapsulation.
Note: A class can have no access specifier or multiple access specifiers.
In access specifiers, we have briefly introduced the differences among three types of access specifiers.
A single access specifier can occur as many times as needed. Each access specifier specifies the access level for the subsequent member definitions. This specification remains valid until the next access specifier is encountered or the right brace of the class definition is reached.
Members can be defined before any access specifier appears.
The access level of members defined after the class’s opening brace but before the first access specifier depends on how the class is defined.
- Using class definition: Members defined before the first access specifier are private.
- Using struct definition: Members defined before the first access specifier are public.
Benefits of Data Abstraction and Encapsulation
Data abstraction and encapsulation provide two significant advantages:
- Avoid unintended user-level errors occurring inside the class that could potentially disrupt the state of the object.
- Over time, the class implementation can be improved based on requirements or defect reports without changing the user-level code.
Changing the class definitions in the header files can effectively change the text of the program in the resource files that include those headers, so when a class changes, any code using that class must be recompiled.
More on Class Definitions
Multiple Data Members of the Same Type
The declaration of a class’s data members is similar to that of ordinary variable declarations.
If a class has multiple data members of the same type, these members can be specified in a single member declaration, with the sequence of declarations from left to right. (The order of definitions of members is necessary in the constructor.)
Using typedef to Simplify Classes
As in the code
1 | class testClass { |
Member Functions Can Be Overloaded
Member functions can be overloaded. However, overloading operators has special rules and is an exception.
Member functions can only overload the other member functions of the same class.
Member functions of a class are unrelated to ordinary member functions and functions declared in other classes, nor can they overload them.
Overloaded member functions adhere to the same rules as ordinary functions: two overloaded members must not have parameters of the same quantity and type. The function matching process used for non-member overloaded functions also applies to calls of overloaded member functions.
1 | class testClass { |
Assuming we define an object of that class as classTemp, how we call the get function will determine which version of get to use:
1 | testClass classTemp; |
Explicitly Specify Inline Member Functions
Defining member functions within a class will automatically treat them as inline.
This means that when they are called, the compiler will attempt to expand the function inline. You can also explicitly declare a member function as inline.
1 | class testClass { |
A member can be defined as inline within the body of the class as part of its declaration. It can also be specified as inline when defined outside the class. Both are valid. A benefit of defining inline outside the class is that it makes the class easier to read.
Class Declaration and Class Definition
Once the right brace is encountered, the class definition ends. Once a class is defined, we know all the class members and the storage space required for that class.
A class can only be defined once in a given source file.
Putting class definitions in header files ensures that the class is defined in the same way in every implementation file that uses the class.
Use header guards to avoid multiple inclusions (ensuring that the class definition appears only once).
1 |
|
You can also declare a class without defining it.
1 | class testClass; |
This declaration becomes a forward declaration; after the declaration but before the definition, the class testClass is an incomplete type, meaning testClass is known as a type, but its members are not known.
Incomplete types can only be used in limited ways
- Cannot define objects of that type.
- Can be used to define references or pointers to the class type.
- Can be used to declare that type as a parameter type or return type of a function.
Before creating an object of a class, the class must be fully defined.
The class must be defined, not just declared, so that the compiler can allocate the appropriate storage space for the class’s objects.
Similarly, the class must be defined before accessing its members via references or pointers.
Forward declarations of classes are generally used to write mutually dependent classes.
Use Class Declarations for Class Members
As mentioned earlier, only data members can be specified as that class type when the class definition has appeared beforehand. If the class type is an incomplete type, then data members can only be pointers or references to that class type.
Since a class cannot have data members of its own type until the class definition body is completed, a class cannot have data members of its own type. However, as soon as the class name appears, it can be considered declared. Thus, class data members can be pointers or references to their own type.
1 | class testClass { |
Class Objects
Defining a class means defining a type. Once a class is defined, you can define objects of that type. When defining an object, memory space is allocated for it, but (generally speaking) storage is not allocated when defining the type (after the right brace of the class and before the semicolon).
1 | class testClass { |
There are two ways to define instances of a class type:
- Use the class name directly as the type name. (Introduced in C++)
- Specify the keyword class or struct followed by the class name. (Inherited from C)
1 | testClass item1; |
Why does a class end with a semicolon?
The class definition ends with a semicolon. The semicolon is required because a list of object definitions can follow the class definition. Definitions must end with a semicolon.
However, defining object definitions as part of the class definition is a bad idea as it makes operations harder to understand.
Implicit this Pointer
Member functions of a class have an additional implicit parameter, which is a pointer pointing to the class object. This implicit pointer is called this, and it is bound to the object that calls the member function.