C ++ starts from scratch (10)
-- What is a class?
The previous article explains that the structure only defines the memory layout. class can be written before the type definition operator, that isClassType (class for short), which is no different from the structure (only a small difference, as described in the next article, actually, C ++ is extended from C. The class is a very important concept proposed by C ++. It only retains the struct keyword for compatibility with C. However, the small difference mentioned in the brackets above is enough to show that the designers of C ++ define different semantics for structures and classes.
For the time being, we can first consider that the long progress of the structure of the class is more than the concept of member functions (although the structure can also have member functions). Before learning about member functions, we should first look at a semantic requirement.
Operations and resources
The program is mainly composed of operations and operated resources, and the operator is the CPU, which is normal, but sometimes there are some needs, it must be shown that a resource has operated on another resource (temporarily called operator). For example, in a game, it is often used to map monsters to Attack Players. Generally, this operation requires the operator to modify the operator or use the information recorded by the operator to complete the operation. For example, the attacking power of the monster determines the status of the player after the attack. This Semantics indicates that the operator has certain functions. To implement the preceding semantics, as previously mentioned in ing, the ing between monsters and players is structured as follows:
Struct monster {float life; float attack; float defend ;};
Struct player {float life; float attack; float defend ;};
The above attack operations can be mapped to void monsterattackplayer (monster & Mon, player & PLA );. Note that the function name is expected to be used to represent the operator, but it is similar to the previous article to name the cross-river scheme SLN, because the semantics should be represented by the Type rather than the function name. Therefore, C ++ provides the concept of member functions.
Member Functions
As before, writing the declaration statement of a function in a type definition operator defines a member function, as shown below:
Struct ABC {long a; void AB (long );};
The above defines a ing element -- the first variable ABC: A, the type is long ABC:; and declares a ing element -- the second function ABC: AB, the type is void (ABC:) (long ). The Type modifier ABC: modifies the ABC: AB function here, indicating that it is the offset type of the function type, that is, a relative value. But because it is a function, the meaning is different from the variable, that is, it still maps the address in the memory (the address of the Code), but because it is an offset type, it is relative, therefore, function operators such as ABC: AB (10) cannot be applied to it );. Here, it will be wrong, because ABC: AB is relative. Its relative content is not a memory address as a member variable, but a parameter of the structure pointer type. The parameter name is set to this, this is forcibly defined and will be explained later.
Note that because its name is ABC: AB, and the above is just to declare it, to define it, it is still the same as the previous function definition, as shown below:
Void ABC: AB (long d) {This-> A = D ;}
Note that the name of the above function is ABC: AB, but like the member variables mentioned earlier, long ABC: A; cannot be written directly ;, in this case, you cannot directly write the Definition Statement of the function (at least the name of the function is ABC: AB does not comply with the identifier rules). Instead, you must first define the custom type through the type definition character, write the statement later.
Note that the keyword "this" is used above. Its type is ABC *, which is automatically generated by the compiler. That is, the preceding function definition is actually equivalent to void ABC: AB (ABC * This, long d) {This-> A = D ;}. The reason why the declaration of this parameter should be omitted is that the compiler is responsible for reflecting the semantics mentioned above (meaning of members) in the code, which is also called ABC :: AB is the offset type of the function type. It is relative to this parameter:
Abc a, B, C; A. ABC: AB (10); B. ABC: AB (12); C. AB (14 );
The above uses the member operator to call ABC: AB. Note that after execution,. a, B. A and C. the values of a are 10, 12, and 14 respectively, that is, ABC: AB is called three times, but the value of this parameter three times is different through the member operator, then, the member variable A of the three ABC variables can be modified. Note that a. ABC: AB (10); is written above. Like member variables, A. AB (10) can also be used because the left and right types must correspond to each other );. Note that When ABC: AB is defined above, this-> A = D is written in the function body. The same as above, because the type must correspond to each other, that is, this must be a pointer of the corresponding custom type. Therefore, you can omit this-> writing, and void ABC: AB (long d) {A = D;} is returned ;}.
Note that the function of the member operator is not to return a number of the corresponding member variable type when it is a member variable, but to return a number of the function type, however, the difference is that this function type cannot be expressed in syntax, c ++ does not provide any keyword or type modifier to express the returned type (VC provides the _ thiscall type modifier internally for representation, but it is still not usable when writing code, but used internally by the compiler ). That is to say, when the right side of the member operator is a number of the function type offset type, a number of the function type is returned (indicating that it can be applied to the function operator ), the function type is the type given in the offset type, but this type cannot be displayed. A. AB returns a number, which is a function type. In VC, its type is void (_ thiscall ABC:) (long ), however, this type is invalid in C ++.
C ++ does not provide keywords like _ thiscall to modify the type, because this type requires the compiler to encounter function operators and member operators, such as. AB (10); transmits the address on the left of the member operator as the first parameter of the function call, and then transmits the other parameters given in the function operator. That is, this type provides the compiler with some information to generate the correct code for the function operator and member operator at the same time, instead of modifying numbers (modifying numbers requires dealing with all situations ). That is, the type is used to modify numbers, and this type cannot modify numbers. Therefore, C ++ does not provide a keyword similar to _ thiscall.
As before, because ABC: AB maps an address rather than an offset value, it can be ABC: AB; but it cannot be ABC: ;, because the latter is the offset value. Based on the type match, it is easy to know that there are also:
Void (ABC: * p) (long) = ABC: AB; or void (ABC: * p) (long) = & ABC: AB;
Then there is: void (ABC: ** pp) (long) = & P; (C. ** pp) (10.0f );. Brackets are added because the priority of function operators is higher. Recall that the previous article said that the conversion of pointer types only changes the type and the value remains unchanged (the next article describes the changes in the value). Therefore, the following code can be used, and this Code is meaningless, here, we only need to deepen our understanding of member functions.
Struct ABC {long a; void AB (long );};
Void ABC: AB (long d)
{
This-> A = D;
}
Struct AB
{
Short A, B;
Void ABCD (short tem1, short tem2 );
Void ABC (long tem );
};
Void AB: ABCD (short tem1, short tem2)
{
A = tem1; B = tem2;
}
Void AB: ABC (long tem)
{
A = short (TEM/10 );
B = short (TEM-TEM/10 );
}
Void main ()
{
Abc a, B, C; AB D;
(C. * (void (ABC: *) (long) & AB: ABC) (43 );
(B. * (void (ABC: *) (long) & AB: ABCD) (0xabcdef12 );
(D. * (void (AB: *) (short, short) ABC: AB) (0 xabcd, 0xef12 );
}
After the above execution, C. A is 0x00270004, B. A is 0x0000ef12, d. A is 0 xabcd, and D. B is 0 xFFFF. For function calls of C, because the address mapped by AB: ABC is directly converted to the type and used directly, the program will jump to AB :: A = short (TEM/10) at ABC; start execution, and the parameter TEM maps the first address of the memory for passing parameters, and then interpreted with the long type to get the tem as 43, and then executed. Note that B = short (TEM-TEM/10); actually this-> B = short (TEM-TEM/10); and the value of this is the address corresponding to C, but here it is considered as the AB * type (because it is in the function AB: ABC), so this-> B is normal (the ABC structure does not contain the member variable "B ), the offset of B is 2, so after the preceding statement is executed, the result 39 is stored in the address of C and the memory corresponding to 2 is added, in addition, the 16-bit binary data is stored in the short type. Do the same thing for a = short (TEM/10); so the final C. A value is 0x0027004 (decimal 39 to hexadecimal 0x27 ).
Similarly, for B's call, the program will jump to AB: ABCD, but when B's call code is generated, record the parameter 0xabcdef12 in the memory of the passed parameter in the long format, and then jump to AB: ABCD. However, when AB: ABCD is compiled, the corresponding addresses of the tem1 and tem2 parameters are mapped based on the two short types. Therefore, it is easy to think that the value of tem1 is 0xef12, the value of tem2 is 0 xabcd, but this is not the case. How to pass parameters is determined by the previously mentioned function call rules. The specific implementation details of function calls are described in C ++ from (15th, here, we only need to know that the member function ing is still the address, and its type determines how to use it, which will be explained later.
Description
I have already explained the meaning of declaration. Here, due to the new definition syntax of the member function definition rules, the meaning of declaration must be reconsidered. Note that you can place the definition of a function before the definition of the main function, and you do not need to declare that function. If you define a variable, you do not need to declare that variable. This means that the definition statement has the declaration function, but the Definition Statement of the above member function does not have the declaration function. The following describes the true meaning of the Declaration.
Declaration is a statement that requires the compiler to generate ing elements. The so-called ing element is the variables and functions described earlier. There are only three columns (or three fields ): type column, name column, and address bar (this column of the member variable type contains the offset value ). That is, the compiler generates a ing element every time it sees the declaration statement, and leaves the corresponding address bar blank, and then leaves some information to tell the connector -- this. OBJ file (the file generated after the compiler compiles the source file, for VC is. OBJ file) requires some symbols, find these symbols and then modify and improve this. OBJ file.
In retrospect, the symbol is a string used for communication between the compiler and the connector. Note that the symbol has no type, because the connector is only responsible for finding and perfecting the symbol (because the address bar of some ing elements is still empty. OBJ file), no syntax analysis, there is no type.
The definition requires the compiler to fill in the address bar that was previously declared not written. That is to say, the address corresponding to a variable is only known when it is defined. Therefore, the actual allocation of memory on the stack is completed by the definition of variables, so declared variables do not allocate memory. Note that the definition is the address required to generate the ing element. Therefore, the definition shows the address of the ing element it generates, if no ing element exists in the ing table of the compiler (that is, the variable table or function table used to record the ing element in the compiler, that is, if no declaration has been made for the corresponding element, the compiler will report an error.
But I only wrote one variable or Function Definition Statement before. Is it still normal and there is no error? Actually, it is very simple. You only need to regard the Declaration and definition as a statement, except that the information provided to the compiler is different. For example, void ABC (float); and void ABC (float) {}, the compiler treats them the same. The former provides the function type and type name, so the compiler only fills in the ing element name and type columns. The compiler cannot enter the address bar because only ";" is followed and no code for this function ing is provided. The latter gives the function name, type, and ing code (empty compound statement). Therefore, the compiler obtains all the information to be filled in and then fills in the information in the three columns, the result shows that the definition statement completes the declaration function.
For variables, such as long ;. Same as above, the type and name are provided here, so the compiler fills in the two columns: type and name. However, the variable corresponds to the first address of a memory block on the stack, this first address cannot be expressed in the Code (the previous function writes a compound statement after the function declaration to show the address of the code corresponding to the corresponding function ), it must be obtained through computation within the compiler. Therefore, it is hard to specify that the above writing is counted as the definition of a variable, and the declaration of a variable must be preceded by extern. That is, the compiler will perform internal computation to get the corresponding address and fill in all the information of the ing element.
It is inevitable that the above seems to be confused because of the emergence of custom types. Consider the definition of member variables, such:
Struct ABC {long a, B; double C ;};
The preceding examples show the types -- long ABC:, long ABC:, and double ABC:; and the names -- ABC: A, ABC: B, and ABC: C; given the address (that is, offset)-0, 4, and 8. Because it is a structure-defined type, the offset of each member variable can be obtained through this statement. The above three pieces of information can be filled in all the information of the ing element, so the above can be counted as a Definition Statement. For member functions, see the following:
Struct ABC {void AB (float );};
The preceding example shows the type -- void (ABC:) (float); The name is -- ABC: AB. However, because no address is provided, you cannot fill in all the information of the ing element. Therefore, the above is the declaration of the member function ABC: AB. As mentioned above, you only need to give the address, and you don't have to worry about whether it is a definition or declaration, so you can do this:
Struct ABC {void AB (float ){}};
The type and name are given above, and the address is given at the same time. Therefore, all information of the ing element can be filled in completely, which is the definition. The usage above has its own particularity, which is described later. Note: If the ABC: AB Definition Statement is followed by the following statement, it will be incorrect:
Struct ABC {void AB (float ){}};
Void ABC: AB (float ){}
The above error will be reported for a simple reason, because the latter is only defined, it only provides the ABC: AB address information, but the address bar in the ing element has already been filled in, the compiler will repeat the definition. Let's look at the definition of the member function separately. It provides the void type (ABC:) (float), the name ABC: AB, and the address, but why does it only provide the address? First, the name ABC: AB does not comply with the identifier rules, and the type modifier ABC: Must be added through the type definition character, this has been described multiple times before. Therefore, the above information is: given an address, which is the address of the ing element of the type void (ABC:) (float) and ABC: AB. The result compiler looks for such a ing element. If yes, enter the corresponding address bar. Otherwise, an error is reported, indicating that only one void ABC: AB (float) {} is incorrect, you must first declare the corresponding ing element through the type definition character. That is, the definition mentioned above only fills the address bar and does not generate ing elements.
Role of statement
The role of definition is obvious. It is used for meaningful ing (name-to-address), but what is the purpose of declaration? It only generates the name of the type pair. Why do we need the name of the type pair? It just tells the compiler not to make an error saying that the variable or function is not defined? Everything has its own meaning. Let's take a look at the following code.
Extern "C" Long ABC (long a, long B );
Void main () {long c = ABC (10, 20 );}
Assume that the above Code is written in a. cpp and the file a. obj is compiled and generated. However, according to the previous instructions, the connection will be incorrect because the symbol _ ABC cannot be found. Because the address bar corresponding to name_abc is still empty. Add a new source file B. cpp to the project where a. cpp is located in VC, and write the following code.
Extern "C" float ABC (float a) {return ;}
Compile and connect. Now there is no problem, but I believe you have seen the problem. -- the declaration of function ABC does not match the defined type, but the connection is successful?
Note that the preceding connection description indicates that there is no connection type, just a symbol. Use extern "c" to make. OBJ requires the _ ABC symbol, while B. CPP provides the _ ABC symbol, and the rest is that the connector splits B. put the address of _ ABC in OBJ to. OBJ to improve. OBJ, connect to. OBJ and B. OBJ.
So what is the above result? Because we need to consider the implementation details of the function, this is explained in "C ++ from the very beginning (15 th)". Here we only need to notice one thing: the compiler can still generate code even if there is no address to implement the function of the function operator-function call. This is because the type and name must be given at the same time during declaration, because the type tells the compiler how to generate code to implement this operator when an operator involves a ing element. That is to say, the numeric multiplication of two Char Types is different from the code generated by the multiplication of two long types. The function calling code of long ABC (long); is different from that of void ABC (float. That is, the different numeric types used by operators will lead to different code generated by the compiler.
So why should we put the ABC definition in B. cpp? Because the compilation between source files is independent, if it is placed in a. CPP, the compiler will find that such a ing element already exists, but the type does not match, and an error will be reported. Put it in B. cpp, so that the connector can improve a. obj. Then there will be no type, just the symbol. Continue.
Struct ABC {long a, B; void AB (long tem1, long tem2); void ABCD ();};
Void main () {abc a; A. AB (10, 20 );}
As mentioned above, although ABC: AB is not defined here, it can still be compiled successfully without any problems. Assume that the above Code is in a. cpp, add B. cpp, and write the following code in it.
Struct ABC {float B, A; void AB (long tem1, long tem2); long ABCD (float );};
Void ABC: AB (long tem1, long tem2) {A = tem1; B = tem2 ;}
The function ABC: AB is defined here. As mentioned earlier, the function definition here is only defined, therefore, you must write the type definition character "{}" before it to let the compiler generate a ing element. However, we should note that the bitwise of the member variables is replaced here, so that B maps 0 and a maps 4, and the types of A and B are changed to float, and. the definition in CPP is quite different. But there is no problem, the compilation connection is successful,. AB (); after execution. A Is 0x41a00000,. B is 0x41200000, and * (float *) &. A is 20, * (flaot *) &. B is 10.
Why? This is because the compiler only follows type matching in the source file currently compiled, and all the ing elements generated by compiling other source files are invalid when compiling another source file. Therefore, the Declaration binds the type and name, and the name represents the number of the address type associated with the type, the compilation and generation of all operators operating on this number in the subsequent code will be affected by the type of this number. That is, Declaration tells the compiler how to generate code. It is not only a syntax statement that describes variables or functions, but is indispensable.
Note that the ABC: ABCD member functions in the above two files have different declarations, and the entire project (that is,. CPP and B. in CPP) there is no definition of ABC: ABCD, but the connection can still be compiled successfully, because the Declaration does not tell the compiler what is already there, but how to generate code.
Header file
As described above, if there is a custom type ABC. CPP, B. CPP and C. to use it in CPP, it must be in. CPP, B. CPP and C. in CPP, use the "{}" type definition character before ABC to re-define the custom type. If you accidentally write different definitions in A. cpp and B. cpp as shown in the preceding figure, a difficult-to-find error will occur. Therefore, C ++ provides a pre-compiled command to help you.
Precompiled commands are the commands executed before compilation. They are interpreted and executed by the Pre-compiler. The pre-compiler is another program. Generally, the compiler vendors merge the program into the C ++ compiler and only provide one program. In this example, the precompiled instruction contains the instruction -- # include, in the format of # include <File Name>. Note that precompiled commands must occupy a single line, and <File Name> is a file name enclosed by double quotation marks or angle brackets, for example: # include "ABC. C ", # include" C:/ABC. DSW "or # include <C:/abc.exe>. Its function is very simple, that is, the file corresponding to the file name written in quotation marks or angle brackets is in ANSI or MBCS format (for details about these two formats, refer to C ++ from scratch (v, and the contentIntactReplace it with the # include location, for example, the ABC content of the file below.
Struct ABC {long a, B; void AB (long tem1, long tem2 );};
The preceding a. cpp can be changed:
# Include "ABC"
Void main () {abc a; A. AB (10, 20 );}
B. cpp can be changed:
# Include "ABC"
Void ABC: AB (long tem1, long tem2) {A = tem1; B = tem2 ;}
At this time, it will not appear in B. in CPP, the custom ABC definition is written incorrectly, resulting in incorrect results (. A Is 0x41a00000,. B is 0x41200000), and then. AB (10, 20); after execution,. A is 10,. B is 20.
Note that double quotation marks are used to enclose the file name. It indicates that when only one file name or relative path is included and the full path is not given, first, search for the Directory of the source file to be compiled at this time, and then search for the compiler's customized include directory (such as: C:/program files/Microsoft Visual Studio. NET 2003/vc7/include), which usually contains the header file of the SDK that comes with the compiler (the SDK will be described in "C ++ from scratch (18 ), if not found, an error is returned. (Note that the compiler generally provides some options to search for the specified directory in addition to the preceding directory. Different compilers have different settings, this is not a table ).
If it is enclosed by Angle brackets, it indicates that the self-defined include directory of the compiler is searched first, and then the directory where the source file is located. Why is it different? Only to prevent conflict between the file name and the file name under the compiler's include directory, because once the file is found, the subsequent directory will not be searched.
Therefore, in General C ++ code, if you want to use a custom type, all the custom type definitions are packed in two files respectively. For the above structure ABC, two files, ABC. H and ABC. CPP, where ABC. H is called the header file, while ABC. CPP is called the source file. The Declaration is put in the header file, while the definition is put in the source file, the content of ABC. H is the same as that of ABC, and the content of ABC. cpp is the same as that of B. cpp. Then, whenever the structure ABC is used in a source file in the project, ABC is included at the beginning of the source file. h, which is equivalent to bringing all the relevant declarations of the structure ABC into the compilation of that file, such as the previous. CPP declares the structure ABC by including ABC at the beginning.
Why do we need to generate an ABC. cpp? If the Definition Statement of ABC: AB is also put in ABC. h, then. CPP should use ABC, C. CPP also uses ABC, so. CPP contains ABC. h. Because of the definition of ABC: AB, a symbol is generated? AB @ ABC @ qaexjj @ Z (for VC); same as C. this symbol must also be generated during CPP compilation. When two identical symbols appear during the connection, the connector cannot determine which one to use and an error is returned. Therefore, ABC. cpp is defined specifically, and the definition of ABC: AB function is put into ABC. obj. In this way, only one symbol is generated and no error is reported during connection.
Note the above struct ABC {void AB (float ){}};. If you put this in ABC. in H, because the ABC: AB function has been given in the type specifier, two identical symbols will appear in the same way, and the connection fails. To avoid this problem, C ++ requires that the function defined in the previous example is an inline function, which is directly written into the function definition in the Type Definition operator. For more information, see the next section.
Member meaning
The above describes the meaning of a member function from the perspective of syntax. If it is dizzy, it doesn't matter. Implementation doesn't mean it can't be used, what matters to programmers is the ability to use the language rather than understanding the language (although the latter is also very important ). The following describes the member semantics.
At the beginning of this article, we propose a kind of semantics-the function of a resource, and the custom type of C ++ is added with the member operator ". the use of "and"-> "easily shows a semantic-subordination in code. For example, a. B and C. D indicate B of A and D of C respectively. To map the functions of a resource to C ++, you should map the resource to a custom type, and its functions are mapped to member functions of this custom type, for example, the monsters and players mentioned at the beginning are as follows:
Struct player {float life; float attack; float defend ;};
Struct monster {float life; float attack; float defend; void attackplayer (player & PLA );};
Player player; monster A; A. attackplayer (player );
The semantics above is very obvious. The operations executed by the Code are monster a attacking the player, and player. Life represents the player's life. Assume that monster: attackplayer is defined as follows:
Void MONSTER: attackplayer (player & PLA)
{
Pla. Life-= attack-PLA. Defend;
}
The semantics above is very obvious: the method by which a monster can attack a player is to subtract its own attack power from the defensive power of the attacked player. The semantics is very clear and the code is readable. As in the original statement:
Void monsterattackplayer (monster & Mon, player & PLA)
{
Pla. Life-= mon. Attack-PLA. Defend;
}
The semantics of the Code: the monster attacking player is an operation. This operation requires two resources, respectively, the monster type and the player type. This Semantics does not show what we were going to do, but an explanation of the monster's attack function (this will start from scratch in C ++ (12) ), it is more suitable for the performance of cash register. For example, the cashier is responsible for collecting money. When a customer buys something at the counter, the salesperson opens a ticket and the customer receives the ticket to pay for it. Here, the cashier needs to operate on two resources-money and documents. In this case, the money collection should be mapped to the above function rather than the member function, because in this algorithm, there is no need to map the cashier to a custom type, that is, we don't care who is responsible for the Cashier's work, but how it is done.
At this point, we have introduced half of the custom type content. With this content, we can compile code that can reflect more complex semantics. The second half of the custom type content will be described in the next article, their proposal can be considered as a semantic need, so the next article will explain how the remaining content reflects the semantics, but it still needs to explain how they are implemented.