Access control of C + + classes

Private and public

There are methods and member variables in a class. After the public keyword is identified, the methods and variables under public become public functions. After the private keyword is identified, the methods and member variables under the private keyword become private. By default, if public is not declared, all methods and members in the class are private. If private is not declared, all methods and members in struct are public.

Friends

In the previous article, we will print, read and other non sales_ The global function of the data class is declared as sales_ The friend function of the data class, so print and read can access sales_ Private member of the data class. Let's recall sales again_ Data class.

class Sales_data
{
public:
    //The default construction is implemented through default
    // Sales_data() = default;
    //Show implementation default constructs
    Sales_data() : bookNo(""), units_sold(0), revenue(0.0) {}
    // copy structure, according to Sales_data type object constructs a new object
    Sales_data(const Sales_data &sa);
    Sales_data(const std::string &s) : bookNo(s) {}
    Sales_data(const std::string &s, unsigned n, double p)
        : bookNo(s), units_sold(n), revenue(p * n) {}
    Sales_data(std::istream &is);
    //Return book number
    std::string isbn() const { return bookNo; }
    //Get average unit price
    double avg_price() const;
    //Put a sales_ Merge data object into current class object
    Sales_data &combine(const Sales_data &);
    friend std::ostream &print(std::ostream &, const Sales_data &);
    friend std::istream &read(std::istream &, Sales_data &);

private:
    //Book number
    std::string bookNo;
    //sales volume
    unsigned units_sold = 0;
    //income
    double revenue = 0.0;
};

Packaging has two important advantages:
·Ensure that user code does not inadvertently break the state of the encapsulated object.
·The specific implementation details of the encapsulated class can be changed at any time without adjusting the user level code.

Friend's statement

The declaration of a friend only specifies the access permission, not a function declaration in the usual sense. If we want the user of the class to call a friend function, we must declare the function in addition to the friend declaration. In order to make friends visible to the users of the class, we usually place the declaration of friends and the class itself in the same header file (outside the class). Therefore, our Sales_data header file should provide independent declarations for read, print and add (except for the friend declarations inside the class). Therefore, I declare these functions in the header file of Sales_data class

// Sales_ Nonmember interface of data
extern Sales_data add(const Sales_data &, const Sales_data &);
extern std::ostream &print(std::ostream &, const Sales_data &);
extern std::istream &read(std::istream &, Sales_data &);

Hide type definitions

We can define a new type in the class. This type hides the internal implementation from the outside, and the outside does not know what the type is.

class Screen
{
public:
    typedef std::string::size_type pos;

private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
};

Members used to define types must be defined before being used. This is different from ordinary members. The specific reasons will be explained in 7.4 Section 1 (page 254) explains. Therefore, type members usually appear at the beginning of the class.

inline member function

The so-called inline function is a means to expand at compile time and reduce runtime overhead. It can be declared through the inline keyword, or you can specify inline before defining the function in the cpp file of the class. Of course, if the member function of a class is implemented in the class header file, it is also an inline function. We improve the Screen class and implement the inline function in the above three ways

class Screen
{
public:
    typedef std::string::size_type pos;
    //Because Screen has another constructor
    //So you need to implement a default constructor
    Screen() = default;
    // cursor is initialized to 0
    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}
    //Read characters at cursor
    char get() const
    {
        //Implicit inlining
        return contents[cursor];
    }

    //Show inline
    inline char get(pos ht, pos wd) const;
    //Can be made inline later
    Screen &move(pos r, pos c);

private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
};

We can explicitly declare member functions with inline as part of the declaration inside the class. Similarly, we can modify the definition of functions with inline keyword outside the class: Although we do not need to specify inline at the same time in the declaration and definition, it is actually legal. However, it is best to specify inline only where it is defined outside the class, which makes the class easier to understand.

Overloaded member function

Class member functions also support overloading. As long as the function name is the same, the parameter list is different.

mutable property

If a member variable is specified with the mutable attribute, the member variable can be modified regardless of whether the object is const or whether the member function is const.
We define a mutable member variable access for Screen_ CTR, and a const member function some_member, and modify access in this function_ CTR variable.

class Screen
{
public:
    typedef std::string::size_type pos;
    //Because Screen has another constructor
    //So you need to implement a default constructor
    Screen() = default;
    // cursor is initialized to 0
    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}
    //Read characters at cursor
    char get() const
    {
        //Implicit inlining
        return contents[cursor];
    }

    //Show inline
    inline char get(pos ht, pos wd) const;
    //Can be made inline later
    Screen &move(pos r, pos c);
    void some_member() const;

private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
    //Even in a const object, access_ctr can also be modified
    mutable size_t access_ctr;
};

Implement some_ A member variable of member

void Screen::some_member() const
{ //Access can also be modified in the const function_ ctr
    ++access_ctr;
}

call chaining

When we return * this through the internal member function, that is, the class object itself, we can continue to call its internal member function in a chain. For example, we implement two set functions through overloading

Screen &Screen::set(char c)
{
    contents[cursor] = c;
    return *this;
}
Screen &Screen::set(pos r, pos col, char ch)
{
    //Set new value at given position
    contents[r * width + col] = ch;
    return *this;
}

call chaining

    Screen::pos row = 3;
    Screen::pos col = 4;
    Screen screen(3, 4, 'c');
    screen.move(2, 3).set('#');

* this returned from const member is a constant pointer. If we implement a const function of display,

const Screen &Screen::display(ostream &os) const
{
    os << "width is " << width << " "
       << "height is " << height << endl;
    return *this;
}

If the compiler is called in the following chain, an error will be reported because display returns const screen & type

 screen.display(cout).move(2, 3).set('#');

Therefore, we can implement chained calls through overloading to implement a display function that returns const screen & type and a display function that returns screen & type,
These two display functions call do internally_ Display function, because const function can only call const function, we first implement display function, which is a const function

void Screen::do_display(ostream &os) const
{
    os << "width is " << width << " "
       << "height is " << height << endl;
}

Then implement two overloaded display functions

const Screen &Screen::display(ostream &os) const
{
    do_display(os);
    return *this;
}

Screen &Screen::display(ostream &os)
{
    do_display(os);
    return *this;
}

In this way, the compiler will dynamically select the version of display according to the type

    Screen screen(3, 4, 'c');
    screen.display(cout).move(2, 3).set('#');
    const Screen cscreen(2, 1, ' ');
    cscreen.display(cout);

Class types and declarations

class First
{
    int memi;
    int getMem();
};

struct Second
{
    int memi;
    int getMem();
};

We have defined two types above, and the following assignment will report an error, because although the members in the class are the same, different classes are different types

    First obj1;
    //Compilation error. obj1 and obj2 are not of the same type
     Second obj2 = obj1;

We can declare the class first without defining the class

class Bags;

This declaration is sometimes called forward declaration. It introduces the name Screen into the program and indicates that Bags is a kind type. For type Bags, it is an incomplete type before it is defined after its declaration, that is, at this time, we know that Bags is a class type, but we don't know which members it contains.

Incomplete types can only be used in very limited scenarios:
You can define a pointer or reference to this type, or you can declare (but cannot define) a function with an incomplete type as a parameter or return type.

Data members cannot be declared as this kind of type until the class is defined. We must first complete the class definition before the compiler can know how much space is required to store the data member.
Because a class is defined only after all classes are completed, the member type of a class cannot be the class itself.
However, once the name of a class appears, it is considered declared (but not yet defined), so a class is allowed to contain references or pointers to its own type:

class Link_screen
{
    Screen window;
    Link_screen *next;
    Link_screen *prev;
};

Friend classes and member functions

If A class A can be declared as A friend of another class B, class A objects can access the private members of class B objects.

class Screen
{
public:
    // Window_mgr can access the private part of the Screen class
    friend class Window_mgr;
    //Other parts of the Screen class
};

Window_mgr class can access the private members of Screen class and declare window forward through class_ Mgr class.
Next, we define Window_mgr class

class Window_mgr
{
public:
    //The number of each screen in the window
    using ScreenIndex = std::vector<Screen>::size_type;
    //Resets the specified Screen to blank by number
    void clear(ScreenIndex);

private:
    std::vector<Screen> screens{Screen(24, 80, ' ')};
};

void Window_mgr::clear(ScreenIndex i)
{
    // s is a reference to Screen, pointing to the Screen we want to clear
    Screen &s = screens[i];
    //Clear screen
    s.contents = string(s.height * s.width, ' ');
}

You can also have member functions as friends

class Screen
{
public:
    // Window_mgr can access the private part of the Screen class
    friend void Window_mgr::clear(ScreenIndex);
    //Other parts of the Screen class
};

·First define Window_mgr class, which declares the clear function, but cannot define it. Screen must be declared before clear uses the members of screen.
·Next, define the Screen, including the friend declaration for clear.
·Finally, clear is defined so that it can use the members of Screen.

Classes and nonmember functions do not have to be declared before their friend declarations. When a name first appears in a friend declaration, we implicitly assume that the name is visible in the current scope. However, the friend itself is not necessarily declared in the current scope

Source code link https://gitee.com/secondtonone1/cpplearn
My official account, thank you.

Keywords: C++ Back-end

Added by pck76 on Mon, 03 Jan 2022 17:22:25 +0200