C++: Class and Object Supplement - Initialization List, Static Members, Friends, Anonymous Objects

Table of contents

introduction

1. Initialization list

1.1 Assignment inside the constructor

1.2 Using initialization lists

1.3 Precautions

1.4 explicit keyword

Two, static members

2.1 Concept

2.2 Scenarios

2.3 Features

3. Friends

3.1 Concept

3.2 Grammar

3.2.1 Friend functions

3.2.2 Friend classes

3.3 Features

4. Anonymous objects

4.1 Concept

4.2 Grammar

4.3 Examples

4.4 Purpose


 

introduction

Classes and objects are important concepts in C++ programming, but there are some advanced features that require a deeper understanding. This blog will introduce four topics: initialization list , static members , friends , anonymous objects . These features allow us to design and use classes and objects more flexibly, improving code efficiency and maintainability.

1. Initialization list

1.1 Assignment inside the constructor

When creating an object, the compiler calls the constructor to give each member variable in the object an appropriate initial value.

class Date {
public:
    Date(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }

private:
    int _year;
    int _month;
    int _day;
};

After the above constructor is called, the member variables in the object will get the initial value , but it must be strictly distinguished, the statement in the constructor body can only be called initial value assignment , not initialization . This is because the initialization can only be done once when the object is created , and the statements in the constructor body can be assigned multiple times during the execution of the constructor. Therefore, the initialization list we will learn is an efficient way to achieve the actual initialization of object member variables .

1.2 Using initialization lists

The initialization list is a mechanism used in C++ to initialize class member variables in the constructor . It allows explicit initialization of member variables via colons (:) after the constructor's parameter list , with each "member variable" followed by an initial value or expression enclosed in parentheses . This method is only executed once when the object is created, which is more efficient and recommended than using assignment statements to initialize member variables in the constructor body .

The syntax for an initialization list is as follows:

ClassName::ClassName(parameters) : member1(value1), member2(value2), ..., memberN(valueN) {
    // 构造函数体
}

Among them, ClassNameis the name of the class, parametersis the parameter list of the constructor, and member1, member2, ..., memberNare the member variables of the class, value1, value2, ..., valueNare the corresponding initial values ​​of the member variables.

1.3 Precautions

 Each member variable can only appear once in the initialization list (initialization can only be initialized once).

②  For member variables of const or reference type, the initialization list is the only way to initialize .

Because assignment is performed inside the constructor, and the two objects here can only be initialized and cannot be modified later, so only the initialization list can be used.

class MyClass {
private:
    int x;
    const int y;
    double& z;
public:
    // 初始化列表在这里进行成员变量的初始化
    MyClass(int a, int b, double& c) : x(a), y(b), z(c) {
        // 构造函数体
    }
};

int main() {
    int num = 42;
    double val = 3.14;
    MyClass obj(10, num, val);
    // obj.x 被初始化为 10
    // obj.y 被初始化为 num (42)
    // obj.z 被初始化为 val (3.14)
    return 0;
}

③ A member of a custom type (and the class has no default constructor) must also use an initialization list.

class A {
public:
    A(int a)
            : _a(a) {}

private:
    int _a;
};

class B {
public:
    B(int a, int ref)
            : _aobj(a) {}

private:
    A _aObj;  // 没有默认构造函数
}
    

If the initialization list is not used at this time, it will not be possible to create an object of class B when instantiating it, because the constructor of class B is called during instantiation, and the member variable of class A does not have a suitable default constructor. Only the constructor with parameters is displayed in the initialization list just work.

④ The order in which member variables are declared in the class is the order in which they are initialized in the initialization list, regardless of their order in the initialization list .

class A {
public:
    A(int a)
        : _a1(a), _a2(_a1) {
    }

    void Print() {
        cout << _a1 << " " << _a2 << endl;
    }

private:
    int _a2;
    int _a1;
};

int main() {
    A aa(1);
    aa.Print();
}

A. Output 1 1 B. Program crashes C. Compilation fails D. Output 1 random value

The answer is D.

Parse:

We see that the initialization list first initializes the member variable _a1, and then initializes _a2, but in fact the order of initializing member variables is only related to the order of declaration in the class! ! ! We declare _a2 first, so in the initialization list, initialize _a2 first, but at this time the value of _a1 is a random value, because the class does not process the built-in type by default, so its value is a random value And initialize _a2, and then use the parameter a to initialize _a1. From this we can get the answer D.

⑤ Try to use the initialization list to initialize, because no matter whether you use the initialization list or not, for custom type member variables, you must first use the initialization list to initialize.

The above paragraph means that when we instantiate a class object, no matter whether we use the initialization list to assign values ​​to the member variables, the compiler will first use the initialization list to initialize. So the question is, if I don't call it, it will be used automatically, so what will be the result? The result of automatic use is: the value of the built-in member variable is not processed as a random value, and the member variable of the class type is initialized with the default constructor.

When we operate in the body of a function, we can only assign it, not initialize it. If you want to use a parameterized constructor of a class type, etc., you must use an initialization list! ! ! There are also cases mentioned above where an initializer list must be used.

In addition, the initialization list initialization and the assignment in the function body can be mixed, but it should be noted that, regardless of whether the member variable is displayed or not used, it will go through the initialization list.

In short, it is recommended to use the initialization list to initialize variables! ! !

1.4 explicit keyword

Constructors can not only construct and initialize objects, but also have the function of type conversion for a single parameter or a constructor with default values ​​except for the first parameter, which has no default value.

Let's look at a piece of code:

class MyClass {
public:
    MyClass(int x) : _x(x) {}

private:
    int _x;
};

int main() {
    MyClass obj1 = 42; // 隐式类型转换
    MyClass obj2(42);  // 使用显式构造函数进行对象的创建

    return 0;
}

Are you a little confused about the instantiation of obj1?

How can an int type object be equivalent to a class type object? ? This is not the same as our cognition!

Let me describe the implicit type conversions that take place here:

Generally, implicit type conversion will generate temporary variables in principle. Here, first use the constructor to initialize an anonymous temporary variable MyClass(42) (now readers only need to understand that there are anonymous objects here), and then use the default generated copy constructor Initializing obj1 is the code we see, but this method is relatively inefficient, so most modern compilers will optimize the process, only call the constructor once to directly instantiate the obj1 object, and pass 42 to the formal parameter x instance change.

Let's verify it:

 This is the result of running the above code, which is consistent with the above explanation.

There is also a constructor with parameters that can also be counted in this case, that is, a constructor with a default value except for the first parameter without a default value.

 The principle of this code is the same as above.

From this we introduce the explicit keyword

explicitIs a keyword in C++, used to modify the single-argument constructor, its function is to prevent the compiler from performing implicit type conversion. Normally, when we have only one parameter in the constructor, the compiler will automatically perform type conversion, converting the parameter type into an object of the class, thereby creating a temporary object. explicitThis implicit automatic type conversion can be prevented by using the keyword.

Using explicitthe keyword can avoid some unexpected type conversions, which enhances the readability and safety of the code. It is typically used for classes that wish to explicitly state that they only accept explicit constructor calls.

Two,  static members

2.1 Concept

staticIs a keyword in C++, used to define static member variables and static member functions. Static members are associated with the class itself, not with objects of the class. This means that static members are shared among all objects of the class , rather than each object having its own copy. Static members exist throughout the lifetime of the class until the end of the program .

2.2 Scenarios

Question: Implement a class and count how many class objects are created in the program

Idea: We can define a global variable and increment it inside the constructor!

Disadvantage: This global variable may be modified elsewhere in the program!

So let's take a look at the correct idea (readers can see how to use it first):

#include <iostream>

class Counter {
public:
    Counter() {
        count++; // 每次创建对象时递增计数
    }

    static int getCount() {
        return count;
    }

private:
    static int count; // 静态成员变量用于计数
};

int Counter::count = 0; // 静态成员变量初始化

int main() {
    Counter obj1;
    Counter obj2;
    Counter obj3;

    std::cout << "Number of objects created: " << Counter::getCount() << std::endl; // 输出:Number of objects created: 3

    return 0;
}

In the above example, we created a Counterclass named with a static member variable countand a constructor. Whenever the object is created, the constructor is called automatically, where it is incremented count. This way, every time an object is created, the number of created objects is automatically counted.

In mainthe function, we create three Counterobjects, and then Counter::getCount()get the number of created objects through the static member function, and output the result. The output result is 3, which means that a total of three Counterobjects have been created in the program.

We declare a static member variable count in the class and initialize it outside the class . There is only the default constructor in the instance. When instantiating the object, the count variable in the constructor ++, the static variable belongs to the class, not Class objects, static members exist throughout the lifetime of the class until the end of the program. Among them, the getCount function is a static member function, which is called to obtain the number of class declaration objects.

To explain, static member variables and functions belong to classes, not objects. There is no this pointer in static member functions. They can be called through class name::variable/function , or through class objects.

2.3 Features

  1. All objects share: Static member variables are shared among all objects of the class. For all objects, they all point to the same memory location and are stored in the static area.

  2. In-class declaration, out-of-class initialization : Static member variables must be declared inside the class, but initialized outside the class, usually static member variables are initialized outside the class.

  3. Can be accessed by class name: Since static members are associated with a class rather than an object, static members can be accessed by class name without object .

  4. Does not occupy object space: Since a static member is associated with a class, not an object, it does not occupy the object's memory space .

  5. Static member function: A static member function is a function that is associated with a class rather than an object. It can only access the static member variables and other static member functions of the class, but cannot access ordinary member variables and non-static member functions, because it does not have this pointer. Static member functions can be called directly by the class name without creating an object.

  6. Access permissions for static members: Static member variables and static member functions have the same access level as access permissions for classes. If a member function is private, then only member functions of the class can access it , even if it is a static member function, it cannot be called by the class name.

  7. Static members and dynamic memory allocation: Static member variables do not occupy the memory space of the object, so it does not affect the size of the object. However, if the static member variable is a pointer to dynamically allocated memory, then the memory block pointed to by this pointer is located on the heap, so it needs to be released manually, otherwise it may cause a memory leak.

  8. Static members are also members of a class, subject to public, protected, private access qualifiers.

  9. Static member functions cannot call non-static member functions, because there is no this pointer , and non-static member functions can call static member functions of the class.

3. Friends

3.1 Concept

Friend (friend) is a feature in C++ that allows a class to declare other classes or non-member functions as its own friends, thereby allowing these friends to access the private members and protected members of the class. Friend provides a A way to break out of encapsulation, sometimes for convenience. But friends will increase the degree of coupling and destroy the encapsulation, so friends should not be used more often.

3.2 Grammar

The friend declaration syntax is carried out inside the class, and the friend relationship is established by declaring other classes or functions as friends in the class. Friend declarations use friendthe keyword.

class ClassName {
public:
    // 成员函数和成员变量的声明

    friend ReturnType FriendFunctionName(Parameters); // 友元函数的声明

    friend class FriendClassName; // 友元类的声明
};

Explain the use of friends in detail through two examples

3.2.1 Friend functions

#include <iostream>

class A {
public:
    A(int value) : _a(value) {}

    // 友元函数声明
    friend void showAValue(const A &objA);

private:
    int _a;
};

// 友元函数定义,访问类A的私有成员
void showAValue(const A &objA) {
    std::cout << "A's value: " << objA._a << std::endl;
}

int main() {
    A objA(10);

    // 调用友元函数,访问类A的私有成员
    showAValue(objA); // 输出:A's value: 10
    return 0;
}

In the above example, we defined class A. A friend function is declared in class A showAValue, which allows the function to access private members of class _aA.

In mainthe function, we create an object of class A objA, and then call showAValuethe function to access the private members of class A.

3.2.2 Friend classes

#include <iostream>

class A; // 前向声明类A,用于在类B中声明友元类

class B {
public:
    B(int value) : _b(value) {}

    void showAValue(const A& objA); // 类A的引用作为参数

private:
    int _b;
};

class A {
public:
    A(int value) : _a(value) {}
    friend class B; // 声明B为A的友元类
    
private:
    int _a;
};

void B::showAValue(const A& objA) {
    std::cout << "A's value: " << objA._a << std::endl; // 在B的成员函数中访问A的私有成员
}

int main() {
    A objA(10);
    B objB(20);

    objB.showAValue(objA); // 输出:A's value: 10

    return 0;
}

In the above example, we defined two classes: Class A and Class B. A friend function is declared in class B showAValuewhose parameter is a reference to class A. Declare class B as a friend class in class A so that showAValuefunctions can access private members of class _aA.

In mainthe function, we create an object of class A objAand an object of class B objB, and then call showAValuethe function to output _athe value of the private member of class A. Since showAValuethe function is a member function of class B, and class B is declared as a friend class in class A, it can access the private members of class A.

3.3 Features

  • Friend functions can access private and protected members of a class, but not member functions of the class
  • Friend functions cannot be modified with const
  • Friend functions can be declared anywhere in the class definition, not restricted by class access qualifiers
  • A function can be a friend function of multiple classes
  • The principle of calling a friend function is the same as that of a normal function

explain the second point

Friend functions cannot use constthe modifier. In C++, a friend function is a non-member function independent of the class. Although it can access the private members and protected members of the class declared as a friend, it cannot be declared as a member function const.

constconstA member function refers to a member function declared as in a class , which means that the member function will not modify the member variables ( non- mutablemember variables ) of the class. Such member functions can constbe called on class objects to ensure that the object's state is not modified.

Because a friend function is not a member function of a class, it has no constand volatilemodifiers . A friend function is not restricted to constthe semantics of a class object, so it can modify private and protected members of the class without constrestriction.

4. Anonymous objects

4.1 Concept

An anonymous object is an unnamed temporary object created in C++. It is not assigned any variable name, can only be used once in the created expression, and will be destroyed immediately afterwards.

Anonymous objects are often used to simplify code and temporary calculations, they do not need to be named, because they only exist in the expression in which they are created, and will be destroyed after the expression ends, thus saving memory and resources.

4.2 Grammar

The syntax of an anonymous object is very simple. It omits the naming of the object when creating the object and uses the temporary object directly. An anonymous object can only be used once in the expression it was created, after which it will be destroyed immediately.

ClassType(); // 创建匿名对象

In the above syntax, ClassTypeis the name of the class to be created, followed by parentheses that call the constructor of the class to create the object.

4.3 Examples

#include <iostream>

class Point {
public:
    Point(int x, int y) : _x(x), _y(y) {}

    int getX() const { return _x; }
    int getY() const { return _y; }

private:
    int _x;
    int _y;
};

void displayPoint(const Point& p) {
    std::cout << "Point: (" << p.getX() << ", " << p.getY() << ")" << std::endl;
}

int main() {
    // 创建匿名对象并直接传递给函数
    displayPoint(Point(2, 3));

    // 作为函数返回值
    Point p1(1, 1);
    Point p2(2, 2);
    Point sum = Point(p1.getX() + p2.getX(), p1.getY() + p2.getY());
    displayPoint(sum);

    // 临时计算
    int result = Point(4, 5).getX() + Point(6, 7).getY();
    std::cout << "Result: " << result << std::endl;

    return 0;
}

In the above example, we defined a Pointclass representing two-dimensional coordinate points. Then in mainthe function we demonstrate in different situations using anonymous objects:

  1. In displayPointthe function, directly create and pass an anonymous object for displaying the coordinates of the point.
  2. When calculating the sum of two points, use the anonymous object as the function return value without defining additional variables.
  3. In the temporary calculation, the member function of the anonymous object is directly used for calculation.

With anonymous objects, we can simplify the code and avoid defining unnecessary temporary variables, thus improving the conciseness and readability of the code. But be aware that since the lifetime of an anonymous object is limited to the expression it is in, it should not be used outside of its lifetime.

4.4 Purpose

  1. As a function return value: In some cases, a function can return a temporary anonymous object without defining a variable to receive the return value.
  2. As a function parameter: You can directly pass an anonymous object as a parameter of a function without defining a variable to store the object before calling the function.
  3. Ad-hoc calculations: In some expressions, anonymous objects can be used directly for calculations without having to explicitly name and define variables.

Guess you like

Origin blog.csdn.net/weixin_57082854/article/details/132134775