C++_Primer_ Study notes_ Chapter 19 (special tools and technologies)

Posted by wayz1229 on Tue, 04 Jan 2022 06:46:57 +0100

Chapter 19 (special tools and technologies)

/1. Control memory allocation

1). Standard memory management mechanisms cannot be applied directly.

  • Some applications need to customize the details of memory allocation, such as using the keyword new to place objects in a specific memory space.
  • In order to achieve this goal, we need to overload the new and delete operators to control the process of memory allocation.

//1. Overload new and delete

1). Although they are overloaded, overloading them is very different from overloading other operators.
2). Understand how new and delete expressions work.

{
    // ---------------new
    string *sp = new string("a value");//Allocates and initializes a string object
    string *arr = string[10];//Allocate and initialize 10 string objects by default
    // The actual operation is carried out in three parts
    // 1. The new expression calls a standard library function named operator new or operator new [], which allocates a large enough, original and unnamed memory space to store specific types of objects or arrays of objects
    // 2. The compiler runs the corresponding constructor to construct these objects and passes in the initial value
    // 3. The object is allocated space and constructed, and the pointer to the object is returned

    // --------------delete
    delete sp;//Destroy * SP and free the memory space pointed to by sp
    delete [] arr;//Destroy the elements in the array and free the corresponding memory space
    // Two steps were actually performed
    // 1. Execute the destructor of the object pointed to by sp or the element in the array pointed to by arr
    // 2. The compiler calls the standard library function named operator delete or operator delete [] to free memory space.     

    // If the application wants to control the process of memory allocation
    // You need to define your own operator new and operator delete functions
    // Even if the definitions of these two functions already exist in the standard library, we can still define our own versions
    // The compiler will not object to this duplicate definition. On the contrary, the compiler will use our customized version instead of the version of the standard library

    // Once the global operator new and operator delete functions are customized, we assume the responsibility of controlling dynamic memory allocation.
    // These two functions must be correct, which is a vital part of the program processing

    // Applications can define operator new and delete in the global scope, or they can define them as member functions
    // When the compiler finds a new or delete expression, it will find the operator function that can be called in the program.
    // How to find?
    // If the allocated (released) object is a class type, the compiler first looks in the scope of the class and its base class. (the scope of the class.)
    // At this time, if the class contains operator new or delete function members, the corresponding expression will discard these members
    // Otherwise, the compiler will find a matching function in the global scope. If a user-defined version is found, it will be used
    // If not, execute the version of the standard library

    // We can use the scope operator to make the new or delete expression ignore the functions defined in the class
    // Directly execute the version used in the global,
    // You can use the scope operator
    // ::new,::delete    
}

3).operator new and operator delete interfaces

{
    // The standard library defines eight overloaded versions of the operator new function and the operator delete function.
    // The first four versions may throw bad_alloc exception
    // The last four do not throw exceptions
    void* operator new(size_t);//Assign an object
    void* operator new[](size_t);//array
    // Destructors are not allowed to throw exceptions. The book is wrong
    void* operator delete(void*) noexcept;//Release an object
    void* operator delete[](void*) noexcept;//array

    // 
    void* operator new(size_t, nothrow_t&) noexcept;
    void* operator new[](size_t, nothrow&) noexcept;
    void* operator delete(size_t, nothrow&) noexcept;
    void* operator delete [] (size_t, nothrow&) noexcept;

    // Type, nothrow_t is a struct defined in the header file new
    // This type does not contain any members
    // The new header file also defines a const object named nothrow, through which users can request the non thrown version of new
    // Like destructors, operator delete does not allow exceptions to be thrown
    // The noexcept exception descriptor must be used to specify that it does not throw an exception

    // The application can customize any of the above function versions, provided that the customized version must be in the global scope or class scope.
    // When we define the above operator functions as members of a class, they are implicitly static
    // We do not need to explicitly declare static, which will not cause errors
    // Because operator new is used before object construction
    // operator delete is used after the object is destroyed
    // So these two members must be static, and
    // No data members can be manipulated
    
    //--------For operator new or new [],
    // Their return value type must be void *, and the first formal parameter type must be size_t. And the formal parameter cannot have a default argument
    // When the compiler calls operator new, it passes the number of bytes required to store objects of the specified type to size_t parameter
    // When [] is called, the space required to store all the elements in the array is passed into the function

    // -----------If we want to customize an operator new function, we can provide it with additional formal parameters
    // At this time, the new expression using these custom functions must use the new positioning mode to pass the arguments to the new formal parameters.
    // Although we can customize operator new with any formal parameter
    //But,
    void* operator new(size_t, void*);
    // It is only available for standard libraries and cannot be redefined by users

    // ------------For operator delete or delete []
    // Their return type must be void
    // The first formal parameter must be void *, and executing a delete expression will call the corresponding operator function
    // And initialize the void * parameter with a pointer to the memory to be released
    // When we define operator delete or delete [] as a member of a class,
    // The function can contain another type called size_ Formal parameters of T
    // At this time, the initial value of the parameter is the number of bytes of the object referred to by the first parameter
    // size_t formal parameters can be used to delete objects in the inheritance system
    // If the base class has a virtual destructor, the number of bytes passed to operator delete will vary depending on the dynamic type of the object indicated by the pointer to be deleted
    // Moreover, the actual version of the operator delete function is also determined by the dynamic type of the object.    

    // ------------
    // operator new and operator delete are different from general operator functions
    // These two functions do not overload the new and delete expressions
    // In fact, we can't customize the behavior of new or delete expressions at all
    // Their execution process is as described before, and we cannot change it
    // The purpose of the operator new or operator delete function provided by us is to change the way of memory allocation.
}

4).malloc function and free function

  • After we define our own global operator new and operator delete, these two functions must allocate memory and free memory in some way.
  • Maybe we just want to use a special custom memory allocator, but these two functions should also meet some test purposes at the same time, that is, whether the way of allocating memory is similar to the conventional way?.
  • We can be functions named malloc and free, which are inherited from the c language and defined in the header file cstdlib
  • malloc, which accepts a size representing the number of bytes to be allocated_ T returns a pointer to the allocated space, or 0 indicates that the allocation of space failed.
  • Free, accept a void *, which is a copy of the malloc return pointer. Free returns the relevant memory to the system. Calling free(0) makes no sense.
{
    // ------Write operator new
    void* operator new(size_t size) {
        if (void *mem = malloc(size))
            return mem;
        else 
            throw bad_alloc();
    }
    // -----------Write operator delete
    void operator delete(void *mem) noexcept {
        free(mem);
    }
}

practice

  • 19.2. Replace the allocator class operation with the custom operator new/delete. It needs to be explicitly pointed out.
  1. By default, the allocator class uses operator new/delete to allocate and free memory space.

//2. Locate new expression

1). Ordinary code calls two ordinary functions of the standard library, operator new and operator delete;. Although they are generally used for new or delete expressions.

  • In earlier versions of c + +, the allocator class was not part of the standard library. If an application wants to separate memory allocation from initialization, it needs to call operator new or operator delete. The behavior of these two functions is very similar to the allocate member and deallocate member of allocator. They are responsible for allocating and freeing memory space, but do not construct and destroy objects.
  • Unlike allocator, we cannot use the construct function to construct objects for the memory space allocated by operator new. Instead, we should use the new form to construct objects. The function of locating new in construct is similar to that in the following versions. Both of them are used to construct an object, but memory is not allocated (different from general new expressions)
  • Locating new provides additional information for the allocation function.
{
    // We can use location new to pass an address. At this time, the form of location new is as follows
    new(place_address) type
    new(place_address) type (initializers)
    new(place_address) type [size]
    new(place_address) type [size] {braced initializer list}

    // Where place_address must be a pointer, and provide a comma separated list of initial values in initializers, which will be used to construct the newly allocated object
    // When called through only one address value, locate new and use operator new(size_t, void *) to "allocate" its memory
    // This is a new version of operator that we cannot customize
    // This function does not allocate any memory. It simply returns the pointer argument, and then the new expression is responsible for initializing the object at the specified address to complete the whole work
    // In fact, locating new allows us to construct objects on a specific, pre allocated memory
    // When only one pointer type argument is passed in, the new expression is located to construct the object, but memory is not allocated. Positioning new is only used to construct objects.

    // ----------------Locate the similarities and differences between new and construct
    // Although it is very similar to locating the construct members of new and allocator in many cases, there is also an important difference between them
    // The pointer we pass to construct must point to the space allocated by the same allocator object
    // However, the pointer passed to locate new does not need to point to the memory allocated by operator new
    // In fact, the pointer passed to locate the new expression does not even need to point to dynamic memory.?
}

2). Explicit destructor call

  • Just like the struct for locating new and allocate, the explicit call to the destructor is similar to using destroy.
{
    string *sp = new string("a value");
    sp->~string();

    // Similar to calling destroy, calling the destructor can clear a given object, but it will not free the space where the object is located. If necessary, we can reuse the space

    // More than enough
}

/2. Runtime type identification

1). This function is implemented by two operators

  1. typeid, which is used to return the type of the expression
  2. dynamic_cast, which is used to safely convert the pointer or reference of the base class to the pointer or reference of the derived class.

2). When we use these two operators for a type of pointer or reference, and the type contains a virtual function, the operator will use the pointer or reference to the dynamic type (actual type) of the bound object.

  • These two operators are particularly suitable for
  • We want to use the pointer or reference of the base class object to perform a derived class operation, and the operation is not a virtual function.
  • Assuming that virtual functions cannot be used, the RTTI(run_time_type_identification) operator can be used. But using it has more potential risks. You must clearly know the target type of the conversion and check whether the type conversion was successfully performed.
  • When using RTTI, you must be more careful. When possible, it is better to define virtual functions rather than directly take over the task of type management.

//1.dynamic_cast operator

1).

{
    // Use form
    dynamic_cast<type*>(e)
    dynamic_cast<type&>(e)
    dynamic_cast<type&&>(e)
    // Where type must be a class type
    // And usually, this type contains virtual functions
    // Article Kinds of forms
    // 1. E must be a valid pointer
    // 2. E must be an lvalue
    // 3. E cannot be an lvalue

    // In all the above forms, the actual type of e must meet any of the following three conditions
    // 1. The type of E is a public derived class of the target type
    // 2. The type of E is the public base class of the target type
    // 3. The type of E is the type of target type

    // If yes, the type conversion may succeed, otherwise the conversion fails
    // Successful conversion also needs to ensure that the object e actually points to is a derived class of pointer type!!!

    // ---------When conversion fails
    // If a dynamic_ If the conversion target of the cast statement is a pointer type and fails, the result is 0
    // If it is a reference and fails, it will throw a bad_ The exception of cast, which is defined in the header file 'typeinfo'


    // -------------Dynamic of pointer type_ cast
    // The following examples are confusing and are not recommended for reference. The translation version is very chaotic
    // The Base class contains at least one virtual function, and Derived is the public Derived class of Base
    // If there is a pointer to Base
    // Then we can convert it into a pointer to Derived at run time. In fact, whether it can succeed or not depends on the actual pointing object of bp.

    if (Derived *dp = dynamic_cast<Derived*>(bp)) {
        // Use dp to point to the Derived object
    } else {
        // bp still points to the Base object, and the Base object pointed to by dp is used
    }

    // If bp points to the Derived object, the above type conversion initializes dp and makes it point to the Derived object pointed by bp,
    // At this point, the code that uses the Derived operation inside the if statement is safe
    // Otherwise, the result of the conversion is 0, and dp with 0 means
    // if statement condition failed

    // ----------If bp points to a base object, can the conversion succeed? may not

    // We can perform dynamic on a null pointer_ Cast, the result is a null pointer of the desired type

    // In the condition, we define dp,
    //In one operation, type conversion and type checking are completed at the same time
    // And dp cannot be accessed outside if
    // Once the conversion fails, even if the subsequent code forgets to make corresponding judgment, it will not touch the unbound pointer.

    // ------------Dynamic of reference type_ cast
    // The error representation of a reference is different from that of a pointer because there is no so-called null reference
    void f(const Base &b) {
        try {
            const Derived &d = dynamic_cast<const Derived&>(b);
        } catch (bad_cast) {
            //Error processing type conversion
        }
    }
}

practice

  • 19.3,
  1. Specify the conditions for successful conversion and take this as the standard.
  2. Ambiguous base class. Compilation failed. An error occurs when initializing the derived class address for the base class pointer.
  3. RTTI option needs to be turned on.
  • 19.5 when we cannot get the source code of the base class, we need to add member functions to the derived class. At this time, it is not feasible to add virtual functions to the base class, so it is impossible to use the base class pointer to call new members; At this point, we can consider adding it to the current class. It doesn't matter whether it is a virtual function or not. When we need to call this function, we use dynamic_cast converts the base class pointer to a pointer of this type and uses this member. Therefore, if you cannot add virtual functions to the base class, you can use dynamic_cast instead of virtual functions.

//2.typeid operator

1).

{
    // It allows the program to ask the expression, what type is your actual object?

    // -------Expression form

    typeid(e);
    // Where e can be the name of any expression or type.
    // The result of the operation is a reference to a const object
    // The type of the object is the standard library type_info or type_ Public derived type of info
    // type_ The info class is defined in the header file typeinfo

    // -----------Rules
    // 1. The top const is ignored
    // 2. If the expression is a reference, typeid returns the type of the object referenced by the reference
    // 3. However, when typeid acts on an array or function, it does not perform pointer conversion

    // When the operand does not belong to class type or is a class that does not contain any virtual functions
    // The typeid operator indicates the static type of the operand
    // When the operation object is the lvalue of a class that defines at least one virtual function, the result of typeid will not be obtained until the operation (that is, dynamic type)

    // -------------Using the typeid operator
    //Use typeid
    // 1. Used to compare whether the types of two expressions are the same
    // 2. Compare whether the type of an expression is the same as the specified type

    Derived *dp = new Derived;
    Base *bp = dp;

    if (typeid(*bp) == typeid(*dp))
        // It actually points to the same type
    if (typeid(*bp) == typeid(Derived))
        // Whether to actually point to the Derived object.

    // typeid should act on the object, so it should be dereferenced
    if (typeid(bp) == typeid(Derived))
        //This check will always fail
        // One is a pointer and the other is a class object, which will never be equal.
        // The code here will never be executed

    // When typeid acts on the pointer, it does not act on the object pointed to by the pointer
    // The static type of the returned pointer is Base *.
    // Base * evaluated at compile time

    // ----------Does the expression evaluate
    // Whether typeid needs runtime checking determines whether the expression will be evaluated
    // The compiler evaluates the expression only if the type contains a virtual function.
    // If the type does not contain a virtual function, typeid returns the static type of the expression. The compiler does not need to evaluate the expression until the type of the expression

    // as
    typeid(*p)
    // If the type referred to by p does not contain a virtual function, p does not have to be a valid pointer because it does not need to be evaluated; Otherwise p must be a valid pointer.
    // If p is a null pointer, bad will be thrown_ Exception for typeID. (when evaluating)    
}

//3. Using RTTI

1). Practical application.

  • Equality operation of classes with inheritance relationship.
  • Scheme, define a set of virtual functions. The equality operation symbol defined in the base class delegates the actual work to the equal virtual function.
  • However, the overrides of derived classes must be the same formal parameters, and the formal parameters are all references to the base class. At this time, equal can only use members of the base class. How to solve it?
  • First, understand that equality can occur only when the types of two objects are the same.
{
    class Base {
        friend bool operator==(const Base&, const Base&);
    public:


    protected:
        virtual bool equal(const Base&) const;
    };
    class Derived : public Base {
    public:

    protected:
        bool equal(const Base&) const;
    };

    bool operator==(const Base &l, const Base &r) {
        // First check whether the type of typeid is consistent
        // If inconsistent, false is returned
        // If consistent, continue to call equal
        return typeid(r) == typeid(l) && l.equal(r);
    }

    //You need to use a conversion to type when designing equal
    // dynamic_cast
    // Due to the need to compare members in derived classes
    // So conversion is inevitable.
    bool Derived::equal(const Base &r) const {
        // At this time, the two types must be the same
        // r must be Derived, so
        // Conversion will not cause problems
        auto r = dynamic_cast<Derived&>(r);
        // ..... Subsequent equality judgment operation
    } 

    // The virtual functions in the base class can be connected and compared
    bool Base::equal(const Base &r) {
        //.... Equal judgment operation
    }
}

//4.type_info class

1). type_ The exact definition of info varies slightly with the compiler. But c + + specifies type_ The definition of info class must be in the header file typeinfo, and at least provide the operations shown in table 19.1. (p735)

  • Besides, because type_info usually appears as a base class, so it also provides a public virtual destructor. When the compiler wants to provide additional type information, it is usually in type_info.
  • type_info has no default constructor, and its copy and move * * are deleted** Therefore, we cannot define objects of this type. Create type_ The only way to the info object is to use the typeid operator.
  • The name member of typeid returns a C-style string representing the type name of the object. For a given type, the return value of name varies from compiler to compiler, and is not necessarily the same as the name used in the program. The only requirement for the return value of name is that if the type is different, the returned string must be different.
{
    int arr[10];
    Derived d;
    Base *p = &d;

    typeid(42).name();//i
    typeid(arr).name();//A10_i    
    typeid(Sales_data).name();//10Sales_data
    typeid(string).name();//Ss
    typeid(p).name();//P4Base
    typeid(*p).name();//7Derived

    // type_info provides additional member functions on some compilers to provide additional information about the types used in the program.
    // See the compiler manual for details
}

practice

  • 19.10. ra in c is a dynamic type and needs to be evaluated. The answer is wrong. The result returned by name should represent the string representing class B.

/3. Enumeration type

1). It allows us to organize a set of integer constants together. Like classes, enumeration types define a new type. It is of literal constant type.
2). There are two kinds of enumerations in C + +,

  1. Limited scope, (introduced by the new c++11 standard)
  2. Unlimited scope
{
    // ---------Define qualification
    enum class open_modes {input, output, append};
    enum struct open_modes {input, output, append};
    // The first is the keyword, enum class or
    // enum struct
    // Then there is the type name and a comma separated list of enumeration members enclosed in curly braces
    // The last is a semicolon
   
    //----------Unlimited definition
    // Omit the keyword class or struct
    // And the name of the enumeration type is optional
    enum color {red, yellow, green};

    enum {floatPrec  = 6, doublePrec = 10, double_doublePrec = 10}; 

    // If the enum is unnamed, we can only define its objects when defining the enum
    // The way is
    enum {red, green} aEnum, anotherEnum;

    // ----------Enumeration members
    // In a scoped enumeration type, the name of the enumeration member follows the general scope guidelines and is inaccessible outside the scope of the enumeration type
    // In an enumeration type that does not limit the scope, the scope of the enumeration member is the same as that of the enumeration type itself
    enum color {red, yellow, green};//Not limited
    enum stoplight {red, yellow, green};//Error, duplicate enumeration member defined
    enum class peppers {red, yellow, green};//Correct, the inside is hidden from the outside
    color eyes = green;//Correct, scopeless enumeration type members are in a valid scope
    peppers p = green;//Error, the enumeration member of peppers is not in a valid scope
    // But color::green is in a valid scope, but the type is wrong

    color hair = color::red;//Correct, explicit access
    peppers p2 = peppers::red;//correct

    // --------Default value
    // By default, enumeration values start at 0 and are incremented by one
    // However, we can specify specific values for one or more enumeration members
    enum class intTypes {
        charType = 8, shortType = 16, intType = 16,
        longType = 32, long_longType = 64
    };
    // Enumeration values are not necessarily unique,
    // If we do not explicitly specify the initial value, the value of the current enumeration type member is equal to the value of the previous enumeration member plus one

    //-----------const attribute
    // The enumeration member is const, so the initial value provided when initializing the enumeration member must be a constant expression 
    // In other words, each enumeration type itself is a constant expression. We can use enumeration type members wherever constant expressions are needed.
    // -------Apply
    // constexpr variable defining enumeration type
    constexpr intTypes charbits = intTypes::charType;

    // switch Statements 
    // Use an enum as the expression of the switch statement
    // Enumeration value as label of case

    // Use the enumeration type as a non type template argument (such as the size of an array)
    // Because the inferred value must be a constant expression. This allows the compiler to instantiate templates at compile time.
    // Initializes a static data member of an enumeration type in the definition of a class
    // Provide the initial value in the class. It is required that the initial value is a constant expression and the variable is a constexpr
    // Out of class initialization is not required.


    // ----------Define and initialize objects of type enum
    // As long as enum has a name, we can define it and initialize it
    // To initialize an enum object or assign a value to it
    // You must use an enum member of a given type or another object of that type
    open_modes om = 2;//Error, 2 is not of this type
    om = open_modes::input;//Correctly, input is one of its members

    // An object or enumeration member of an enumeration type that does not limit the scope is automatically converted to an integer
    int i = color::red;//Implicit conversion
    int j = peppers::red;//Error, scoped will not be implicitly converted

    // -------------Specifies the size of the enum
    // In fact, enum is uniformly represented by an integer type
    // In C++11, we can add a colon after the enum name and the type we want to use

    // enum intValues : unsigned long long {
        charType = 255, shortType = 65535,
        longType = 4294967295Ul,
        long_longType = 18446744073709551615ULL
    };

    // If we do not specify the potential type of enum, by default
    // The scope qualified enum member type is int
    // For enumeration types that do not limit the scope, there is no default type for enumeration members. We only know that the potential type of members is large enough to accommodate enumeration values.

    // If we specify the potential types of enumeration members (including the implicit specification of scoped enum), an error will be raised if a member exceeds the range that the type can accommodate

    // Specifying the potential types of enum allows us to control the types used in different implementation environments
    // Make sure that the generated code is the same when compiled in different environments.

    // ----------Pre declaration

    enum intValues : unsigned long long;//Members that are not scoped must specify a member type
    enum class open_modes;//Qualified members can use the default member type int

    // Before declaring enum, you must specify the type of the member. It can be explicit or implicit.

    // Like other declarations, the declaration and definition of enum must match
    // This means that the member types in all declarations and definitions of enum must be consistent
    // Moreover, we cannot specify an unlimited scope enum in the same context
    // Then declare a scoped enum with the same name
    enum class intValues;//Use default int
    enum intValues;//error
    enum intValues : long;//error


    // --------------Parameter matching and enumeration types
    enum Tokens {INLINE = 128, VIRTUAL = 129};
    void ff(Tokens);
    void ff(int);
    int main() {
        Tokens = curTok = INLINE;
        ff(128);//Exact match ff(int)
        ff(INLINE);//Exact match ff(Tokens)
        ff(curTok);//Exact match ff(Tokens)
        return 0;
    }
    // Note that unrestricted enumerations can be converted to integers
    // Only enum members or objects can be used for initialization or assignment

    // As for the conversion from enumeration to integer, it is determined by the potential type of enumeration

    void newf(unsigned char);
    void newf(int);
    unsigned char uc = VIRTUAL;
    newf(VIRTUAL);      //Call int
    newf(uc);           //unsigned char

    // This is because the largest member of Tokens is 129. This enumeration type can be represented by unsigned char
    // Therefore, many compilers use unsigned char as a potential type of enum
    // However, its objects and enumeration members will receive a commission of int
    // So exactly match the overloaded version of int
}

/4. Class member pointer

1). Member pointers refer to pointers that can point to non static members of a class. Generally speaking, the pointer points to the object of the class, but the member pointer points to a member of the class rather than an object.

  • Class does not belong to any object, so there is no need for special pointers to static members; Pointers to static members are no different from normal pointers.
  • The type of member pointer includes the type of class and the type of member. When initializing such a pointer, we make it point to a member of the class, but do not specify the object to which the member belongs; The object to which the member belongs is not provided until the member pointer is used.
{
    class Screen {
    public:
        typedef std::string::size_type pos;
        char get_char_cursor() const {
            return contents[cursor];
        }
        char get() const;
        char get(pos ht, pos, wd) const;
    private:
        str::string contents;
        pos cursor;
        pos height, width;
    };
}

//1. Data member pointer

1). The difference from ordinary pointer declarations is that,

{
    // You need to include the class to which the member belongs

    // Add classname:: before the declared * to indicate that the currently defined pointer can point directly to the member of classname

    const string Screen::*pdata;
    // Pointer to const string member of Screen class

    // The data member of the constant object itself is also a constant, so our pointer is declared as a pointer to the const string member, and pdata can point to
    // A member of any Screen object, whether or not the object is a constant
    // At this time, the pointer can only read, not write.

    // ----------Initialization and assignment
    pdata = &Screen::contents;
    // When we initialize a member pointer (or assign a value to it), we need to specify the member it refers to
    // The object is non-specific.
    // As indicated above, the pointer points to a specific contents member of a non-specific Screen object

    // Among them, we will take the address operator to act on the members of the Screen class instead of an object of this class in memory
    // In c++11, the easiest way to declare a member pointer is to use auto or decltype
    auto pdata  = &Screen::contents;

    // ------------Using data member pointers

    // When we initialize or assign a member pointer, the pointer does not point to any data
    // The pointer specifies a member but no object
    // We provide object information only when we dereference the member pointer.

    // The pointer also has two member access operators
    // .*
    // ->*
    // We can dereference the pointer and get the members of the object
    Screen myScreen, *pScreen = &maScreen;
    // Dereference pdata to get the contents member of the myScreen object
    auto s = myScreen.*pdata;
    // Dereference to obtain the contents member of the object referred to by pScreen
    s = pScreen->*pdata;

    // The above operation goes through two steps
    // 1. Dereference the member pointer to obtain the required member
    // 2. Then get the specified member of the specified object through the member access operator

    // ------------A function that returns a pointer to a data member
    // Regular class access rules are also valid for member pointers
    // contents is private
    // The use of pdata must be inside a member or friend of the Screen class, or an error will be reported

    //Because data members are generally private, we usually can't get data member pointers directly
    // If a class wants us to access its data members
    // It's best to define a function
    class Screen {
    public:
        // data is a static member
        static const std::string Screen::* data() {
            return &Screen::contents;
        }
    }; 
    
    // Get a member pointer. In this case, you only get a pointer, and there is no actual object
    const string Screen:: *pdata = Screen::data();
    // Get the contents member of myScreen
    auto s = myScreen.*pdata;
}

//2. Member function pointer

1).

{
    // The easiest way is to use an auto keyword
    auto pmf = &Screen::get_cursor;
    // pmf is a pointer that can point to a constant member function of Screen
    // The premise is that the function does not accept any arguments and returns a char
    // That is, the function pointed to by the pointer must be the same as get_ The return type and parameter type of cursor, const or reference, can be consistent

    // When we do not use the auto keyword
    // You need to specify the return type, parameter type, const attribute and reference attribute
    char (Screen:: *pmf2)(Screen::pos, Screen::pos) const;
    pfm2 = &Screen::get;//get pointing to the above function type.

    // If the member function is overloaded, we must explicitly point out the function type and specify which function we want to use. That is, the auto keyword cannot be used at this time.

    //----------Priority
    char Screen:: *p(Screen::pos, Screen::pos) const;
    // An attempt was made to declare a function that returns a pointer to the char data of the Screen class.
    // Because it is an ordinary function and cannot be const, an error is reported.

    // Unlike ordinary pointers, there is no rule for automatic conversion between member functions and member function pointers.
    pmf = &Screen::get;     //Correct, you need to explicitly point out the & operator.
    pmf = Screen::get;      //error

    // -------------Using member function pointers
    // And using dereference operators
    // .*
    // ->*
    // Call function
    Screen myScreen, *pScreen = &myScreen;
    // Note the use of the call operator ()
    char c1 = (pScreen->*pmf)();
    char c2 = (myScreen.*pmf2)(0, 0);
    // () is needed because the calling operator takes precedence over the dereference operator.

    myScreen.*pmf();
    // Equivalent to
    myScreen.*(pmf());
    // Because the calling operator has high priority
    // Therefore, () is essential for both declaration and use
    (C::*pf)(parms);
    (c.*pf)(args);

    // -----------Type alias using member pointers
    // This is easy to understand
    using Action = char (Screen::*)(Screen::pos, Screen::pos) const;

    // This makes it easy to define a pointer to a member function
    Action get = &Screen::get;//get member of Screen when pointing to

    // ------------Function member pointer is used as the return value or formal parameter type of a function
    // Formal parameters can be default arguments
    Screen& action(Screen &, Action = &Screen::get);

    // call
    Screen myScreen;
    action(myScreen);
    action(myScreen, get);
    action(myScreen, &Screen::get);
    // Type aliases make member function pointer types easier to understand and more concise

    // ----------Member function pointer table
    // Similar to the previously defined map, it stores callable objects of the same type
    // string as key and callable object as value
    // Use the function class of the standard library for storage
    // Here, we use member function pointers to achieve the same function.

    // Store the pointer in a function table.
    // If a class contains several function members of the same type, such a table can help us select one of these members
    // For example, the callable types of the following member functions are consistent
    // Return type, formal parameter, const attribute, reference attribute
    class Screen {
    public: 
        // Each function is responsible for the movement of the cursor
        Screen& home();
        Screen& forward();
        Screen& back();
        Screen& up();
        Screen& down();
    };
    // These functions are of the same type
    // We want to define a move function

    class Screen {
    public:
        // Define a member function pointer
        using Action = Screen&(Screen::*)();
        // Specify the specific direction to move
        enum Directions {HOME, FORWARD, BACK, UP, DOWN};
        Screen& move(Directions);
    private:
        static Action Menu[];//Function table
    };
    // Arrays store pointers to each function.
    // move accepts a corresponding enumeration type
    // Operate
    Screen& Screen::move(Directions cm) {
        // Typical error, note the member function pointer
        // No object has been specified.
        return (Menu[cm])();
        // The member function of this object is called
        return (this->*Menu[cm])();
    }

    // -------Use is
    Screen myScreen;
    myScreen.move(Direction::HOME);//Call myscreen home()
    myScreen.move(Direction::DOWN);//Call myscreen down()

    // --------Initialize function table
    // Out of class initialization of static.
    Screen::Action Screen::Menu[] = {
                    &Screen::home,
                    &Screen::forward,
                    &Screen::back,
                    &Screen::up,
                    &Screen::down
                                }; 
}

practice,

  • 19.14. If the type of member function pointer is inconsistent with the type of function to be pointed to, an error is reported; When it is an overloaded function, it will automatically match to a function (the type of member function pointer is explicitly specified).
  • 19.17. When defining a type alias, pay attention to the member function pointer and do not omit the class name; And a function pointer points to a class of functions.

//3. Use member functions as callable objects

1). The member function pointer itself is not a callable object. Therefore, it cannot be directly transmitted to an algorithm.

  • To solve the problem, use the function standard library template to generate a callable object.
{
    function<boo (const string &)> fcn = &string::empty;
    find_if(sv.begin(), sv.end(), fcn);
    // ~~Usually, the object executing the member function is implicitly passed to the this parameter. When we want to use function to generate a callable object for the member function, we must follow the same way, but we don't need to operate p745.~~
    // When a function object contains a pointer to a member function, the function class knows that it must use the correct pointer operator to the member to execute the function call.
    // In other words, we can think in find_if contains code similar to the following form
    if (fcn(*it))
    // It is find_ An iterator within the scope of an if argument, * it is an object within a given scope
    // fcn is passed to find_ Callable object of function type of if

    // Where function will correctly use pointers to member functions
    // Essentially, the function class converts the call to the following form

    // it is an iterator
    if((*it).*p)())//p is a member function pointer inside fcn

    // We must specify the callable objects contained in the object of function according to the required type
    vector<string*> pvec;
    function<bool (const string*)> fp = &string::empty;
    find_if(pvec.begin(), pvec.end(), fp);

    vector<string> sv;
    function<bool (const string&)> fp1 = &string::empty;
    find_if(sv.begin(), sv.end(), fp1);
}
  • From mem_fn generate a callable object
{
    // When using function, the user must explicitly provide the type of callable object,
    // Using the standard library function mem_fn, let the compiler be responsible for inferring the type of callable object without explicit user specification.
    // Both it and function are defined in the header file functional, and a callable object can be generated from the member pointer
    find_if(sv.begin(), sv.end(), mem_fn(&string::empty));
    // Using mem_ FN (& String:: empty) generates a callable object that accepts a string argument and returns a bool value

    // ---------Call form
    auto f = mem_fn(&string::empty);//f accepts a string or a string*
    f(*sv.begin());//What is passed in is a string, which f uses* Call empty
    f(&sv.begin());//What is passed in is a string *, f use - > * to call empty
    // In fact, we can think of mem_ The callable object generated by FN has a pair of overloaded function call operators, one accepting pointer and one accepting object
}
  • Generate a callable object using bind
{
    auto it = find_if(sv.begin(), sv.end(), bind(&string::empty, _1));

    // Similar to function, when we use bind, we must convert the implicit formal parameter (this) representing the execution object in the function to explicit?
    // How to convert? confuse
    // And mem_fn similarly, you can accept both a pointer and an object
    auto f = bind(&string::empty, _1);
    f(*sv.begin());//string, f use* Call empty
    f(sv.begin());//string *, f use - > * to call empty

    // How to implement it? What is meant by making the implicit parameter (this) of the calling object explicit?
}

practice,

  • 19, 18, directly use the member function string::empty of the standard library.

/5. Nested classes

1). A class can be defined inside another class, which is called nested class or nested type.

  • Nested classes are often used to define classes that are part of the implementation, such as the QueryResult class
  • A nested class is an independent class that has little to do with the outer class. In particular, the objects of outer classes and nested classes are independent of each other. The object of the nested class does not contain any members defined by the outer class; The object of the outer class also does not contain any members of the nested class definition.
  • Only the nested class defines a type member for the outer class. And the nested class and the outer class are a nested relationship of scope. On the premise of friends, it is convenient to access names (defining nested functions outside the class requires the limitation of the outer layer, so it enters the scope of the outer layer and reduces the redefinition similar to line_no.).

2). Why define nested classes?

  • The name of the nested class is visible in the outer scope and not outside the outer class scope. Like other nested names, the name of the nested class will not conflict with the same name in other scopes. Do you want to include the outer layer?
  • Are the types of members in nested classes the same as those in non nested classes? Like other classes, nested classes can also use access qualifiers to control external access permissions. The outer class does not have special access to members of nested classes. Similarly, nested classes do not have special access to members of outer classes.
  • A nested class defines a type member in its outer class. Like other members, the access rights of this type are determined by the outer class.
  • The nested class in the public part of the outer class actually defines a type that can be accessed randomly; The type defined by the nested class defined in the protected part can only be accessed by the outer class, its friends and derived classes; The types defined by nested classes defined in the private part of the outer class can only be accessed by the outer class and its friends.

3). Declare a nested class

{
    // -------------Statement
    // TextQuery and QueryResult classes are closely related
    // QueryResult is mainly used as the result of the query function in TextQuery
    // It doesn't make sense to use it as anything else
    // We can define QueryResult as a member of TextQuery

    class TextQuery {
    public:
        class QueryResult;//Defined later
    };

    // Since the QueryResult class is declared as a nested class, we must first declare it for use in
    // Because later query needs it as the return type

    // ----------Define a nested class outside the class
    // Like member functions, nested classes must be declared within the class
    // However, the definition can be outside the class or inside the class

    // Prefix required
    class TextQuery::QueryResult {
        // This is in the TextQuery class
        // There is no need to qualify the formal parameters of QueryResult
        friend std::ostream& print(std::ostream&, const QueryResult&);
    public:
        // A nested class can directly use a member of an outer class without limiting the name of the member
        // But the premise is that the outer class has friends,
        // Because nested classes do not have special access to external classes.
    };

    // A nested class is an incomplete type until it is defined outside the outer class

    // ----------Define members of nested classes
    // The only difference is to indicate nested relationships
    // Qualify inner nested classes with outer classes
    // And the inner class can directly use the members of the outer class without qualification on the premise that it is an outer friend
    TextQuery::QueryResult::QueryResult(string s, shared_ptr<set<line_no>> p,shared_ptr<vector<string>> f) : 
                sought(s), lines(p), file(f) {}
    

    // -------------Static member definitions of nested classes

    // static declarations can only be inside a class
    int TextQuery::QueryResult::static_mem = 1024;
    // Static members declared within nested classes
    // The definition of static members will be outside the scope of TextQuery

    //---------Name lookup for nested class scopes

    // Nested class name lookup follows general rules
    // Note that it is a nested scope
    // However, it should also be noted that nested classes do not have special access rights to external classes

    // Conversely, as a type member of the outer class, the nested class can be directly used by the members of the outer class (no different from other members).
    // It should be noted that when defining member functions outside the class, the return type does not enter the class.!
    // So you still need a type qualifier. 
}

/6.union: a space saving class

1). It is a special class. A union can have many data members, but only one data member can have a value at any time.

  • When we assign a value to a member of the union, the other members of the union become undefined. The storage space allocated to a union object must contain at least its largest data member.
  • Like other classes, union defines a new type.

2). Some features of class are also used for union.

  • However, a union cannot contain reference types. Most other types can be used as its member types.
  • In the new c + + standard, the class type with constructor or destructor can also be used as the member type of union.
  • Union can specify public, protected, private and other protection tags for its members. By default, union is public.
  • Union can define member functions including constructors and destructors. However, a union cannot be used as a base class and cannot inherit from other classes, so a union cannot contain virtual functions.

2).union

{
    // union provides an effective way to easily represent a set of mutually exclusive values of different types
    // -------------Define a union

    union Token {
        char cval;
        int ival;
        double dval;
    };
    // We need to process a set of different types of data
    // An object of Token type has only one member, and the of this member may be any of the above

    // Note that the type name after union is optional

    // -------------Use a union
    // Note that the union name is a type name.
    // In general, union objects are uninitialized
    // We can use curly braces to explicitly initialize a union
    Token first_token = {'a'};//Initializing cval members
    Token last_token;//Uninitialized Token member
    Token *pt = new Token;//Point to an uninitialized Token object

    // If an initial value is provided, it is used to initialize the first member
    // -----------Use the member access operator to assign values to other specified members
    last_token.cval = 'z';
    pt->ival = 42;

    //-------------Attention
    // When assigning a value to a data member of a union, other data members will be in an undefined state
    // Therefore, when we use the union, we must clearly know what type of data the current union stores.
    // If we use the wrong data member or assign a value to the wrong data member
    // Depending on the situation, the program may crash or behave abnormally.

    // -------------Anonymous union
    // It refers to an unnamed union
    // And there is no object declaration between the closing bracket and the semicolon.

// Note that once we define an anonymous Union, the compiler will automatically create an unnamed object for the union.
    union {         
        char cval;
        int ival;
        double dval;
    };  //To define an unnamed object, we can directly access its members

    // In the scope of the definition of anonymous Union, the members of the union can be accessed directly. How to use?
    // An anonymous union cannot contain protected or private members, nor can it define member functions.

    // ------------union with class type members
    // In the early stage, it is not allowed to have class type members with defined constructs or copy control members
    // It is allowed in the new standard. However, if the member type in the union defines its own construction or copy control members, the usage of the union is more complex than that containing only members related to the built-in class

    // When the union contains members of built-in types, we can use ordinary assignment statements to change the value saved by the union
    // However, it is not so simple for a union with special class type members
    // If we want to change the value of union to the value corresponding to the member of class type, we must construct the object of this type
    // Similarly, if we want to change the value of a class type to another value, we must destruct the members of the class type.

    // When there are only built-in type members, the deconstruction and construction of union are completed by the compiler
    // When it is a class type, and the constructor and copy control members are customized, the composition default version of the compiler is deleted

    // If a class contains a union member and the Union member contains a deleted copy control member, the copy control operation corresponding to the class will be deleted

    // ---------------------Managing union members using classes
    // Manage union s with class type members

    class Token {
    public:
        // Because the union defines a string member, the Tokne must define a copy control member
        // The default version is deleted.??
        Token() : tok(int), ival(0) {}  //Default constructor 
        // An argument that is not accepted is a default constructor.
        Token(const Token &t) : tok(t.tok) {
            copyUnion(t);
        }
        Token& operator=(const Token &t);
        ~Token() {
            // string members must be explicitly destructed
            if (tok == STR) sval.~string();
        }
        // Complete the corresponding assignment.
        // Controls the conversion of types.
        Token& operator=(const string&);
        Token& operator=(char);
        Token& operator=(int);
        Token& operator=(double);
    private:
        // Track the type of values stored in the union
        enum {INT, CHAR, DBL, STR} tok;//Discriminant
        union { //Anonymous union
            char cval;
            int ival;
            double dval;
            std::string sval;
        };
        // Each Token object contains an unnamed member of the unnamed union type
        // It can be used directly
        void copyUnion(const Token&);
    };

    // Because our union contains a member that defines a destructor
    // Therefore, we must define a destructor for the union to destroy the string member. Unlike ordinary class type members, class members that are part of a union cannot be destroyed automatically
    // Because the destructor does not know what type of value the union holds, it cannot determine which member should be destroyed

    // Our class destructor will check. If the union stores built-in types, the class destructor will do nothing.

    // -----------------Overload assignment operator
    Token& Token::operator=(int i) {
        if (tok == STR) sval.~string();
        ival = i;
        tok = INT;
        return *this;
    }

    // ----------------The string version is slightly different
    Token& Token::operator=(const string &s) {
        if (tok == STR) 
            sval = s;//If it is a string, it can be assigned directly
        else
            // The location new does not apply for an argument, but is used for assignment
            new(&sval) string(s);//Otherwise, construct one directly
            //  Here is a new location used
        tok = STR;
        return *this;
    }

    // ----------------Manage federated members that require copy control
    // Distinguish the difference between construction and assignment
    // Initialization, the object is an object that has no storage value
    // Assignment, the object may or may not have stored values

    void Token::copyUnion(const Token &t) {
        switch(t.tok) {
            case INT : ival = t.ival; break;
            case CHAR : cval = ...

            // To copy a string, you can construct it using a new positioning expression
            case string : new(&sval) string(t.val); break;
        }
    }
    Token& Token::operator=(const Token&t) {
        // The value type of union obtained for the original union and argument
        // Make three judgments.
        if (tok == STR && t.tok != STR) sval.~string();
        if (tok == STR && t.tok == STR) 
            sval = t.sval;//There is no need to construct a new string           
        else //Note here that our first case also executes the following functions
            copyUnion(t);
        tok = t.tok;            //Update type flag.
        return *this;
    }
}

/7. Local class

1). Class can be defined in a function. Such a class is a local class. A type defined by a local class is visible only within the scope in which it is defined. Unlike nested classes, members of local classes are strictly restricted.

  • All members of a local class, including functions, must be completely defined inside the class. Therefore, the role of local classes is much worse than nested classes.
  • Because it needs to be defined within the class, its member function complexity will not be too high. Usually a few lines.
  • Static data members are also not allowed to be defined in local classes, because such members cannot be defined.?.

2). Local classes cannot use variables in the scope of a function

  • Local classes have many restrictions on access to the names of their outer scopes. Local classes can only access type names, static variables, and enumeration members defined by the outer scope.
  • If a local class is defined inside a function, the ordinary local variables of the function cannot be used by the local class
{
    int a, val;
    void foo(int val) {
        static int si;
        enum Loc {a = 1024, b};

        struct Bar {
            Loc locVal;//Correct, use the local type name
            int barVal;

            void fooBar(Loc l = a) {
                barVal = val;       //Error, val is a local variable of foo, formal parameter
                barVal = ::val;     //Correct, use a global object
                barVal = si;        //Correct, use a static local object
                barVal = b;         //Correct, use an enumeration member
            }
        };
    }
}

3). Regular access protection rules are also applicable to local classes

  • The outer function does not have any access to private members of a local class. Of course, you can declare the outer function as a friend; Or declare it public. The code that has access to local classes in the program is very limited. Local classes have been encapsulated in the function scope, so it is unnecessary to further encapsulate them through information hiding.

4). Local class name lookup

  • The search order of names inside local classes is similar to that of others. When declaring a class member, you must first ensure that the name used is visible in the current position before using it. Note that the return type is a member of a class, which is not available at this time. How to solve it? The order in the class?
  • Search order: local class - > scope of outer function - > scope of outer function. (the default is to define local classes in functions.)

5). Nested local classes

{
    // You can nest a class in a local class
    // At this time, the definition of nested class can appear outside the local class, but
    // The definition of a nested class must be in the same scope as the local class.
    // That is, only in functions
    void foo() {
        class Bar {
        public:
            class Nested;//Declare a class
        };

        // definition
        class Bar::Nested {
            ...
        };
    }
    // The nested class of a local class is also a local class, which must comply with various regulations of the local class.
    // All members of a nested class must be defined inside the nested class.
}

/8. Inherent non portable characteristics

1). To support underlying programming. c + + defines some inherent non portable features. The so-called non portability is a machine specific feature.

  • When we transfer a program with non portable features from one machine to another, we usually need to rewrite the program.
  • The size of arithmetic types is different on different machines, which is a typical example.

//1. Bit domain

1). Class can define its (non static) data members as bit fields, which contain a certain number of binary bits in a bit field.

  • When a program needs to pass binary data to other programs or hardware devices, it usually uses the in place domain.
  • The layout of bit fields in memory is machine related.
  • The type of bit field must be integer or enumeration type. Because the behavior of bit fields stored in signed types is determined by the specific implementation, we usually use unsigned types to save a bit field.
{
    // The bit field declaration is the member name followed by a colon and a constant expression to specify the binary digits occupied by the member.
    typedef unsigned int Bit;
    class File {
        Bit mode: 2;//mode occupies 2 bits and 2 binary bits
        Bit modified: 1;//1
        Bit prot_owner: 3;
        Bit prot_group: 3;
        Bit prot_world: 3;
        // Operations and other data members
    public:
        // The file type is expressed in octal
        enum modes {READ = 01, WRITE, EXECUTE};
        File &open(modes);
        void close();
        void write();
        void isRead() const;
        void setWrite();
    };
    // If possible
    // As far as possible, the bit field is continuously defined within the class to compress the adjacent bits of the same integer, so as to provide storage compression
    // For example, these five bit fields may be stored in the same unsigned int.
    // Whether these bits can be compressed into an integer and how to implement them are machine related.

    // &, cannot act on the bit field, so no pointer can point to the bit field of the class.
    // ---------------Use bit field
    // The form of accessing a bit field is very similar to that of accessing other members of a class
    void File::write() {
        modified = 1;
        // ...
    }
    void File::close() {
        if (modified)
            // ...
            // Save content
    }
    // The built-in bit operator is usually used to operate on bit fields with more than 1 bit
    File& File::open(File::modes m) {
        mode |= READ;//Set to READ
        // Other processing
        // ?
        if (m & WRITE)      //If READ and WRITE are turned on
        // Open a file read-write
        return *this;
    }

    // If a class has a bit field set, it usually defines a series of inline operations to verify and set the value of the bit field
    inline bool File::isRead() {return mode & READ;}
    inline void File::setWrite() {mode |= WRITE;}
}

//2.volatile qualifier

1). Its exact meaning is related to the machine. It can only be understood by reading the compiler documentation. If you want to use volatile programs to remain valid after porting to a new machine or a new compiler, you usually need to make some changes to the program.
2). Programs that directly process hardware often contain elements whose values are controlled by processes other than those directly controlled by the program.

  • For example, a program may contain a variable that is periodically updated by the system clock. When the value of an object may be changed outside program control or detection, the object should be declared volatile.
  • The keyword volatile tells the compiler that such objects should not be optimized.
{
    // The usage is similar to const
    volatile int display_register;//The int value may change
    volatile Task *curr_task;//Point to a volatile object
    volatile int iax[max_size];//Every element in iax is volatile
    volatile Screen bitmapBuf;//Every member is volatile

    // A type can be const or volatile
    // You can also have two properties at the same time,
    // These two qualifiers have little effect on each other.

    // Just like a class can define const member functions, it can also define volatile member functions. At this time, only volatile member functions can be called by volatile objects.

    //------------Pointers and volatile
    // This relationship is consistent with const

    volatile int v;
    int *ip = &v;//Error, volatile pointer must be used
    volatile int *ivp = &v;//correct
    volatile int volatile *vivp = &v;//correct

    // ----------------The composite copy is not valid for volatile objects
    // An important difference between const and volatile is that we cannot initialize volatile objects using synthetic copy / move constructors and assignment operations
    // Or assign values from volatile objects
    // The formal parameters accepted by the synthesized member are non volatile

    // The solution is to define the corresponding function
    class Foo {
        Foo(const volatile Foo &);
        // Assign to non volatile objects
        Foo& operator=(volatile const Foo&);
        // Assign to volatile object
        Foo& operator=(volatile const Foo&) volatile;
    };
    // Does it make sense to copy a volatile object?
    // Related to the purpose of use.??
}

//3. Link indication: extern"C"

1).C + + programs sometimes need to call functions written in other languages. The most common is to call functions written in C language.

  • Like all names, function names in other languages must also be declared in C + +, which must specify the return type and formal parameter list.
  • For functions written in other languages, the compiler checks that they are called in the same way as ordinary C + + functions. But the generated code is different.
  • c + + uses link directives to indicate the language used by any non-c + + function.
  • If we want to use c + + code together with code written in other languages (including c language), we need to have access to the compiler of the language, and the compiler is compatible with the current c + + compiler.

2). Declare a non-C + + function

{
    // Link indication can take two forms,
    // 1. Single
    // 2. Composite
    // A link indication cannot appear inside a class definition or function definition. The same link indicates that it must appear in every declaration of the function

    // How are some c functions declared in the cstring header file
    // Single statement
    extern "C" size_t strlen(const char *);

    // Compound statement
    extern "C" {
        int strcmp(const char*, const char*);
        char* strcat(char*, const char*);
    }

    // The link indication is an extern keyword followed by a string literal
    // Then there is an ordinary function declaration.
    // The compiler should support link directives for the C language
    // In addition, the compiler may also support connection instructions in other languages
    extern "Ada"
    extern "FORTRAN"
}

3). Link indication and header file

{
    // Link indication in composite form
    // Curly braces are added
    // The effect is,
    // 1. Declare several functions at one time and establish multiple links.
    // 2. Bring together multiple declarations used for the link indication

    // The function name declared in curly braces is visible, as if declared outside curly braces.

    // It can also be applied to the entire header file
    // For example, a cstring header file might look like this.
    extern "C" {
        #include <string.h>     
    }
    // When an include indication is placed in curly brackets of a composite link indication,
    // All ordinary function declarations in the header file are considered to be written in the link instruction language
    // The link indication can be nested, so if the header file contains its own function to condense the indication
    // The link of the function is not affected


    // c + + standard libraries inherited from c language can be defined as c functions
    // However, it is not necessary to decide whether to use c or c + + to implement the c standard library.       
}

4). Pointer to extern "C" function

{
    // The language in which the function is written is part of the function type, so for functions defined using link instructions,
    // Each of its declarations requires the same link indication
    // Moreover, pointers to functions written in other languages must use the same link instructions as the function itself

    // pf points to a c function that accepts an int and returns void
    extern "C" void (*pf)(int);

    // When we call function with pf, the compiler finds that the C function is currently invoked.

    // Pointers to c functions are not of the same type as pointers to c + + functions.
    // This is the problem of type mismatch.
    void (*pf1)(int);
    extern "C" void (*pf2)(int);
    pf1 = pf2;      //Wrong.
    pf2 = pf1;      //Wrong.

    // Some compilers accept the first assignment statement as an extension to the language

    // ----------------The link indication is valid for the entire declaration
    extern "C" void f1(void (*)(int));
    // f1 is a c function whose formal parameter is a pointer to the c function
    // extern "c" is not only valid for functions.
    // For its return type, the function pointer type of the formal parameter is as valid.

    // If we want to pass c + + a pointer to a c function
    // Use type alias
    extern "C" typedef void fc(int);
    // f2 is a c + + function whose formal parameter is a pointer to the c function
    void f2(fc *);

    // -------------------Exporting c + + functions to other languages

    // Using link instructions, for function definition, we can use another c + + function in programs written in other languages.
    // The f function can be called by the c program
    extern "C" double f(double d) {/*.....*/}

    // The compiler generates appropriate code in the specified language for the function

    // The return types and parameter types of functions that can be shared by multiple languages are subject to many restrictions
    // For example, we are unlikely to pass an object of a c + + class to a c program, because c programs simply can't understand constructors and destructors
    // And class specific operations

    // ----------------Preprocessor??
    // Sometimes you need to compile the same source file in c and c + +
    //  We can, when compiling the c + + version of the program, the preprocessor defines __ cpluspluls. 
    // Using this variable, we can include some code when compiling c + + programs
    #ifndf __cplusplus
    // In fact, we are compiling c + + programs
    extern "C"
    #endif
    int strcmp(const char*, const char*);




    // ---------------Overloaded functions and link instructions
    // The interaction between link directives and overloaded functions depends on the target language (for example, "C")
    // If the target language supports overloaded functions, the compiler that implements the link indication for that language is likely to also support overloaded functions in C + +. (->c++)

    // c language does not support overloading, so a c link pointer can only be used for a function with the same name.
    // Error, two extern "C" functions have the same name
    // It is impossible to have these two functions at the same time in c language.
    extern "C" void print(int);
    extern "C" void print(double);

    // If one of a set of overloaded functions is a c function
    // The rest must be c + + functions???
    class SmallInt {...};
    class BigINt {...};

    // The C function can be invoked in c++.
    extern "C" double f(double);

    //  Overloaded function group in c + +
    extern SmallInt f(const SmallInt &);
    extern BigNum f(const BigNum &);
    // ... Why add the extern keyword
}

/9. Summary

1). Bit domain and volatile make it easy for programs to access hardware. Link directives make it easy for programs to access functions written in other languages.

Topics: C C++ Programming C++11