C++-Classes and Objects (Part 2)

One, const member function

Generally, when an object of a custom type is passed as a parameter, it is passed by reference, which can reduce a copy construction, and at the same time add const to modify it, so as not to change the entity by reference.

class A
{
    
    
public:
    A(int x = 0)
        : _a(x)
    {
    
    
    }
    void Print()
    {
    
    
        cout << _a << endl;
    }

private:
    int _a;
};
void func(const A &a)
{
    
    
    a.Print();
}
int main()
{
    
    
    A a1;
    return 0;
}

In this case, if we call the Print() function through the formal parameter a, an error will occur. The reason is that there is a magnification of permissions here, because there is a default parameter when calling a member function, which is the this pointer, but the this pointer of a is modified by const, that is to say, theconst *thispassed on*this, which is an obviousMagnification of authorityproblem, in order to solve this problem, the concept of const member function must be introduced.

The const-modified "member function" is called a const member function. The const-modified class member function actually modifies the implicit this pointer of the member function, indicating that any member of the class cannot be modified in the member function. And const is added after the formal parameter list of the member function.

class A
{
    
    
public:
    A(int x = 0)
        : _a(x)
    {
    
    
    }
    void Print() const
    {
    
    
        cout << _a << endl;
    }

private:
    int _a;
};

Consider the following four questions:

  • Can a const object call a non-const member function?
    The answer is no.
    Passing const *this to *this will magnify permissions.
  • Can a non-const object call a const member function?
    The answer is yes.
    *this can be passed to const *this, here is the reduction of permissions.
  • Can const member functions call other non-const member functions?
    The answer is no.
    In the const member function, const is modified by *this, if it calls other non-const member functions, the authority will be enlarged.
  • Can non-const member functions call other const member functions?
    The answer is yes.
    *this is allowed to be passed to const *this, here is the narrowing of permissions.

Second, talk about the constructor

1. Initialization list

It was mentioned before that the instantiation of a class is when the object is defined, but when are the member variables inside the object defined?
Because some variables must be initialized when they are defined, such as const int p; int& a; the
initialization list is where each member variable is defined,initialization list: Starts with a colon, followed by a comma-separated list of data members, each "member variable" followed by an initial value or expression enclosed in parentheses. For example like this:

class A
{
    
    
public:
    A(int a = 0, int b = 0, int c = 0)
        : _a(a), _b(b), _c(c)
    {
    
    
    }
    void Print() const
    {
    
    
        cout << _a << endl;
    }

private:
    int _a;
    int _b;
    int _c;
};

When calling the constructor, the contents in the initialization list will be carried out first, and then the contents inside the constructor { } will be carried out. The contents in { } are actually assignments to the defined objects, and the initialization list is the member variable definition initialization The place.
Notice:

  • Each member variable can only appear once in the initialization list (initialization can only be initialized once)
  • reference member variableconst member variable, a custom type member variable andwhen there is no default constructor, must be initialized using an initializer list.
  • Regardless of whether you have explicitly written the initialization list, the compiler will first use the initialization list to initialize. If there is no explicit write, then the compiler will look to see if there is a default value for the member variable.
  • member variables in the classdeclaration order, which is in the initializer listinitialization order
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();
}

Since _a2 is defined before _a1, _a2 will be initialized first in the initialization list, so it will be initialized to a random value, and _a1 will be initialized to 1.

2, 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.

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

private:
    int _a;
};
int main()
{
    
    
    A a1 = 8;
    return 0;
}

The 8 here will call the constructor of class A, convert the implicit type to type A, and then initialize a1 through copy construction, where the compiler will optimize it into a constructor (described later).

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

private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    
    
    Date d1 = 2023;
    return 0;
}

Implicit type conversion will also occur here. In order to prevent this type conversion, you can modify the member function with explicit to prohibit the implicit type conversion of the constructor.

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

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

Three, static members

Class members declared static are called static members of the class, member variables modified with static are called static member variables; member functions modified with static are called static member functions.

characteristic:

  • Static member variables must be defined and initialized outside the class. The static keyword is not added when defining, and the class is only declared.
class A
{
    
    
public:
    A(int a = 1)
        : _a(a)
    {
    
    
    }

private:
    int _a;
    static int _b;
};
int A::_b = 1;
  • Static members are shared by all class objects, not belonging to a specific object and stored in the static area.
  • Static members of a class can be passed class name::static member (provided it is under public scope) or object.static member.
  • Static member functions do not have a hidden this pointer and cannot access any non-static members.
  • Static members are also members of a class, subject to public, protected, private access qualifiers.

think:

  • Realize a class, calculate how many objects are created in the program?

Analysis: Since the constructor or copy construction is called when creating an object, it is enough to do something about these two functions. You can define a counter, and add 1 to the counter every time you call these two functions. Some people will think of using global variables as counters, but this approach is not very safe, because anyone can change the global functions.
So we use static member variables as this counter, static idiom variables are not owned by an object, but belong to the entire class.

class A
{
    
    
public:
    A()
    {
    
    
        _count++;
    }
    A(const A &a)
    {
    
    
        _count++;
    }

private:
    static int _count;
};
void func()
{
    
    
    A a1;
    A a2(a1);
    A a3;
}
int main()
{
    
    
    func();
    return 0;
}

In the above case, we have called a function and created many objects inside the function, but we cannot access _count out of this function, unless we create another object, use it to access _count, and finally _count value minus 1.
In fact, this is not necessary. We only need to define a static member function under the public scope of the class, and the function is to return the value of _count, so that we can specify the scope of the class to directly access this member function, no longer need Create a new object.

class A
{
    
    
public:
    A()
    {
    
    
        _count++;
    }
    A(const A &a)
    {
    
    
        _count++;
    }
    static int sum()
    {
    
    
        return _count;
    }

private:
    static int _count;
};
void func()
{
    
    
    A a1;
    A a2(a1);
    A a3;
}
int main()
{
    
    
    func();
    cout << A::sum() << endl;
    return 0;
}
  • Can a static member function call a non-static member function?

Since there is no default parameter of this pointer in the static member function, it cannot call the non-static member function.

  • Can a non-static member function call a static member function of a class?

Static member functions belong to the entire class and can be accessed through non-static member functions.

Four, friend

Friends provide a way to break out of encapsulation, and sometimes convenience. But friends will increase the degree of coupling and destroy the encapsulation, so friends should not be used more often.
Friends are divided into: friend function and friend class

1. Global functions as friends

We talked about operator overloading before, but when we overload the << operator with a member function, there will be problems because the stream insertion (<<) operator has two operands, and when it is overloaded with a member function , the this pointer acts as the first operand, and the second operand is an object of the ostream class, so the following situations will appear when using it:
a1 << cout

  • ostream class
    insert image description here

cout is actually an object of the ostream class, the reason why he can usecout <<This form prints data, in fact he has done operator overloading + function overloading.

To solve the above situation, we use global functions for operator overloading:

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

private:
    int _a;
};
ostream &operator<<(ostream &_cout, const A &a)
{
    
    
    cout << a._a << endl;
}

But this way of writing is wrong, and the private members of the class cannot be directly accessed outside the class, so we need to use this function as a friend function of class A.

class A
{
    
    
    friend ostream &operator<<(ostream &_cout, const A &a);

public:
    A(int a = 0)
        : _a(a)
    {
    
    
    }

private:
    int _a;
};
ostream &operator<<(ostream &_cout, const A &a)
{
    
    
    cout << a._a << endl;
}

illustrate:

  • 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 (it has no this pointer)
  • 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

2. Classes as friends

All member functions of a friend class can be friend functions of another class, and can access non-public members of another class.

  • The friendship relationship is one-way and not exchangeable.
class B;
class A
{
    
    
    friend class B;
    friend ostream &operator<<(ostream &_cout, const A &a);

public:
    A(int a = 0)
        : _a(a)
    {
    
    
    }

private:
    int _a;
};
class B
{
    
    
public:
    B()
    {
    
    
    }

private:
    int _b;
};

B is a friend class of A, and all member functions of class B can access private members of class A, but class A is not a friend class of B, and member functions of class A cannot access private members of class B.

  • Friend relationship cannot be transmitted
    If C is a friend of B and B is a friend of A, it cannot be explained that C is a friend of A.
  • Friend relationships cannot be inherited.

3. Member functions as friends

class B;
class A
{
    
    
    friend void B::func(const A &a);
    friend ostream &operator<<(ostream &_cout, const A &a);

public:
    A(int a = 0)
        : _a(a)
    {
    
    
    }

private:
    int _a;
};
class B
{
    
    
public:
    B()
    {
    
    
    }
    void func(const A &a)
    {
    
    
        cout << a._a << endl;
    }

private:
    int _b;
};

Notice: Writing like this will fail to compile. The compiler will only look up when compiling, and will not look down. When the compiler reaches the friend void B::func(const A &a); line, it does not I don't know there is this function, so I have to declare this function before this, but the member function is declared in the class, so the definition of class B should be placed above class A.

class A;
class B
{
    
    
public:
    B()
    {
    
    
    }
    void func(const A &a)
    {
    
    
        cout << a._a << endl;
    }

private:
    int _b;
};

class A
{
    
    
    friend void B::func(const A &a);
    friend ostream &operator<<(ostream &_cout, const A &a);

public:
    A(int a = 0)
        : _a(a)
    {
    
    
    }

private:
    int _a;
};

Five, inner class

== Concept: == If a class is defined inside another class, this inner class is called an inner class. The inner class is an independent class, it does not belong to the outer class, let alone access the members of the inner class through the object of the outer class. Outer classes do not have any privileged access to inner classes.
== Note: == The inner class is the friend class of the outer class, see the definition of the friend class, the inner class can access all members of the outer class through the object parameters of the outer class. But the outer class is not a friend of the inner class.
characteristic:

  • The inner class can be defined in the public, protected, and private of the outer class.
  • Note that the inner class can directly access the static members in the outer class without the object/class name of the outer class.
class A
{
    
    
private:
    static int k;
    int h;

public:
    class B // B天生就是A的友元
    {
    
    
    public:
        void foo(const A &a)
        {
    
    
            cout << k << endl;   // OK
            cout << a.h << endl; // OK
        }
    };
};
int A::k = 1;
int main()
{
    
    
    A::B b;
    b.foo(A());
    return 0;
}
  • sizeof(outer class) = outer class, has nothing to do with inner class.
cout << sizeof(A) << endl;

insert image description here
The only variable that belongs to class A is h.

Six, anonymous objects

An anonymous object means that when a class is instantiated to define an object, it is not necessary to write the name of the object.

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

private:
    int _a;
};

int main()
{
    
    
    A a1();
    return 0;
}

I said before that the object of defining a1 in this way uses a full default constructor, which is wrong, and it will conflict with the function declaration (function name a1, return value type A, parameter is empty).
But it is possible to create anonymous objects this way.

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

private:
    int _a;
};

int main()
{
    
    
    A();
    return 0;
}

== Note: == The declaration cycle of the anonymous object is on this line, and the destructor will be called after this line.
insert image description here

7. Compiler optimization when copying objects

In the process of passing parameters and returning values, generally the compiler will do some optimizations to reduce the copying of objects, which is still very useful in some scenarios.

class A
{
    
    
public:
    A(int a = 0)
        : _a(a)
    {
    
    
        cout << "A(int a)" << endl;
    }
    A(const A &aa)
        : _a(aa._a)
    {
    
    
        cout << "A(const A& aa)" << endl;
    }
    A &operator=(const A &aa)
    {
    
    
        cout << "A& operator=(const A& aa)" << endl;
        if (this != &aa)
        {
    
    
            _a = aa._a;
        }
        return *this;
    }
    ~A()
    {
    
    
        cout << "~A()" << endl;
    }

private:
    int _a;
};
void f1(A aa)
{
    
    
}
A f2()
{
    
    
    A aa;
    return aa;
}
  • pass by value
int main()
{
    
    
    A aa1;
    f1(aa1);
    cout << endl;
    return 0;
}

insert image description here

A construction occurs when aa1 is created, and a copy construction occurs when passing parameters by value.

int main()
{
    
    
    f1(1);
    f1(A(2));
    return 0;
}

The first method: 1. First, an implicit type conversion will occur, and the constructor will be called once. The object converted from the implicit type will be passed to the formal parameter for a copy construction, but because it occurs in one line, the compiler will optimize it into a construction .
The second way: Create an anonymous object and pass parameters directly. First, there will be a constructor, and then a copy construction, but the compiler will optimize it into a construction.
insert image description here

  • Return by value
A f2()
{
    
    
    A aa;
    return aa;
}
int main()
{
    
    
    A aa2 = f2();
    return 0;
}

The aa object creates a construction, passes a value and returns a copy construction, initializes aa2 with the return value and another copy construction, but the compiler will optimize it to one construction + one copy construction.

A f2()
{
    
    
    return A();
}
int main()
{
    
    
    A aa2 = f2();
    return 0;
}

If you pass an anonymous object to return, the compiler will optimize it to a construction.

insert image description here
Summarize

  • Receive the return value object, try to copy the structure to receive, do not assign to receive
  • When returning an object in a function, try to return an anonymous object
  • Try to use const& to pass parameters
    Notice
    The optimization made by different compilers may also be different, and the newer the compiler, the greater the optimization. It is normal if the optimization made by your compiler is different from the optimization in my example.

Eight, understand classes and objects again

Physical computers in real life do not know, computers only know data in binary format. If you want the computer to recognize
entities in real life, users must describe the entities through some object-oriented language, and then write programs to create
objects before the computer can recognize them. For example, if you want the computer to recognize the washing machine, you need:

  1. The user first needs to abstract the reality of the washing machine entity—that is, to understand the washing machine at the level of human thought, what attributes the washing machine has, and what functions it has, that is, a process of abstract cognition of the washing machine
  2. After 1, people have a clear understanding of the washing machine in their minds, but the computer is still unclear at this time. To make the computer recognize the washing machine in people's imagination, people need to pass some kind of object-oriented language ( For example: C++, Java, Python, etc.) describe the washing machine with classes and input it into the computer
  3. After 2, there is a washing machine class in the computer, but the washing machine class only describes the washing machine object from the perspective of the computer. Through the washing machine class, each specific washing machine object can be instantiated. At this time, the computer can wash the washing machine What is it.
  4. The user can use the washing machine object in the computer to simulate the washing machine entity in reality.
    In the class and object stage, everyone must realize that a class describes a certain type of entity (object), describes which attributes and methods the object has, and forms a new custom type after the description is completed , only with this custom type can instantiate a specific object

insert image description here

Guess you like

Origin blog.csdn.net/Djsnxbjans/article/details/129049659