C ++ starts from scratch (7)
-- What is a function?
The content earlier than this article is a basic foundation. Theoretically, you only need to write the content mentioned above to write almost any program that only operates on memory, that is, the content described later in this article can be implemented by yourself using the previous content, but it is relatively complicated and complicated.
This article will begin with a more in-depth discussion of the very meaningful functions proposed by C ++. Most of them are the same as the previous switch statements and are a technical implementation, but more importantly, it provides the semantic concept. Therefore, this article will mainly explain their respective uses in terms of the semantics they provide, rather than the implementation principle (but it will still explain the implementation principle ). To clearly describe these functions, readers are required to compile and generate a program using VC at least, because it is best for many examples to compile and observe the execution results to deepen understanding (especially the concepts of declaration and type ). For this reason, if you do not use VC or other compilers to compile code, please refer to other documents to learn how to use VC for compilation. For subsequent examples, the following describes some preliminary knowledge.
Prerequisites
After writing C ++ code, how can we compile the compiler? Write c ++ code in a text file, and then pass the file name of the text file to the compiler as the input parameter of the compiler, that is, the compiler compiles the file corresponding to the given file name. In VC, these programming environments (that is, a software that provides a lot of convenient software development functions) Help us) to uniformly manage the source files with C/C ++ code. To let VC know which files are source files (because there may be other types of files such as resource files), after C ++ code is written in a text editor, save it as. C or. CPP (C plus) text file. The former indicates C code, while the latter indicates C ++ code. By default, VC can use different compilation syntaxes to compile the source files based on different source files.
As mentioned in the previous article, every statement in C ++ is executed from top down, and each statement corresponds to an address. Is the address corresponding to the first statement in the source file 0? Of course not. Just like allocating memory on the stack, you can only get the relative offset value. The actual physical address varies with the operating system, for example, in windows, the code can even have no physical address, and the physical address of the code can change at any time.
When you want to write a program with a slightly normal point, you will find that a source file is generally not enough. You need to use multiple source files to write code. How to connect source files? C ++ stipulates that all statements that generate code should be placed in functions rather than written directly in text files. As I will immediately explain after the function, we only need to know that the function is equivalent to a shell. It uses a pair of "{}" to enclose the code and then divides the code into sections, each piece of code is identified by the unique identifier of the function name in the project. To connect each piece of code, you only need to use the function name. The "generate code" mentioned above refers to expression statements and command statements. Although defining statements may also generate code, the code generation is special, yes, it can be directly written in the source file (described in "C ++ from scratch (10)"), that is, it does not need to be included in a pair.
Where do programs start from? C ++ enforces that a function named main should be defined in the source file, and the code starts to run from this function. It should be noted that C ++ is implemented by the compiler, and its provision is very far-fetched, therefore, many compilers provide additional program entry point definition syntax (the program entry point is the function to be executed at the beginning), such as VC. To compile DLL files, there should be no main function; in order to write Win32-based programs, winmain should be used instead of main; and VC actually provides more flexible means to actually let programs start to execute from any function, not necessarily the previous winmain and Main, which are described in C ++ from scratch (19th.
For subsequent instructions, you should know that the program starts to run from the main function, as follows:
Long A; void main () {short B; B ++;} Long C;
The preceding statements actually start with long A; and long C;, but you don't have to worry about them. Actually, meaningful statements start with short B.
Function)
The robot is used to weld the welding points on the car frame, and three-dimensional coordinates of the welding points are given. The robot moves the welding gun to an accurate position by controlling the motors of each joint. Once the program for controlling the movement of the welding Gun is compiled, it will require the robot to weld the 200 points on the frame, and the coordinates of the 200 points can be simply given, then, call the previously compiled mobile program for 200 times, instead of repeatedly writing code for each move. The above mobile program can be represented by a function.
A function is a ing element. Like a variable, it associates an identifier (that is, a function name) with an address, and also has a type associated with it, called the return type of a function. The difference between a function and a variable is that the address associated with the function must be the address of the Code, just like the label described above, but unlike the label, c ++ defines a function as a type, and the number is a pure binary number, that is, the address corresponding to the function name can be modified by the Type modifier so that the compiler can generate the correct code to help programmers implement the above functions.
Since the compiler does not allocate memory when defining a function, the reference modifier "&" is no longer used. Similarly, the definition of the array modifier "[]" can also know that it cannot act on the function. Only the pointer modifier "*" can be left, because the function name corresponds to a number of the address type of a function type.
The reason why the previous mobile program can be called 200 times in different ways is that it is flexible in writing and can change its running effect according to different situations (points in different locations. In order to pass the information used to describe the situation to the mobile Program (that is, the coordinate of the point), it must be done in the East and West. In C ++, this is implemented using parameters, c ++ provides a type modifier called the function modifier "()". Before describing the function modifier, let's first understand What abstract declarative is ).
Declare a variable long A; (this looks the same as defining variables, which will be described later), where long is a type, used to modify the address corresponding to the variable name. After the variable name is removed from the declaration (that is, the preceding statement), the rest is called an abstract declaration. For example: Long * a, & B = * a, c [10], (* D) [10];, the declared modifiers for variables A, B, C, and D are long *, long &, long [10], and long (*) [10].
The function modifier is followed by the function name. There are zero or multiple abstract declarations in parentheses to indicate the type of the parameter. They are separated by commas. Parameters are memory (mapped by parameter names) used to transmit necessary information to the code at the address corresponding to the function name for corresponding functions. Declare a function as follows:
Long * ABC (long *, long &, long [10], long (*) [10]);
The above declares a function ABC, whose type is long * (long *, long &, long [10], long (*) [10]). indicates the code starting from the corresponding address of the function. Four parameters must be provided in sequence. The type is as above and the return value type is long *. The above ABC type is actually an abstract declarative, so it can also be as follows:
Long AB (long *, long &, long [10], long (*) [10]), short, long &);
The preceding mobile program can be declared as follows:
Void move (float X, float y, float Z );
The parameter name is added when the declaration modifier is written to indicate the ing of the corresponding parameter. However, because this is the declaration of the function, the above parameter name does not actually generate any ing, because this is the declaration of the function, not the definition (the declaration will be described later ). The parameter name is a semantic representation, indicating that the first, second, and third parameters represent the X, Y, and zcoordinate values respectively.
The preceding response type is void. As mentioned above, void is a special numeric type provided by C ++. It is only used to ensure the rigor of the syntax, that is, a number is returned after any function is executed (as described later). For a function that does not return a number, the return type can be defined as void, which ensures the rigor of the syntax. It should be noted that any type of number can be converted to void, that is, void (234); or void ();.
Note that none of the above function modifiers can be abstract modifiers, namely void ABC ();. It is equivalent to void ABC (void);, indicating that the ABC function has no parameters and does not return values. Then their abstract declaration is void () or void (void), which can be as follows:
Long * ABC (long * (), long (), long [10]);
It can be seen from the meaning of the function modifier that it is the same as the reference modifier. The type cannot be modified repeatedly, that is, it cannot be void a () (long);, Which is meaningless. Similarly, because of the type modifier's modifier order from left to right, it is normal to have: void (* pA )(). Assume that a variable definition Statement (which can also be considered as a declaration statement, which is described later) requires the compiler to allocate a 4-byte space on the stack, map this address to Pa. Its type is no parameter, and the return value type is the pointer of the void function. What is the purpose? It will be explained later.
Function Definition
Let's take a look at the function definition. For the previous robot control program, you can write it as follows:
Void move (float X, float y, float Z)
{
Float temp;
// Move the gun based on the values of X, Y, and Z
}
Int main ()
{
Float X [200], Y [200], Z [200];
// Place the coordinates of the 200 points in the array x, y, and z.
For (unsigned I = 0; I <200; I ++)
Move (X [I], Y [I], Z [I]);
Return 0;
}
The above defines a function move, whose corresponding address is the address of the Definition Statement float temp;, but the compiler actually generates some additional code (called the function prefix -- Prolog, in C ++ from the beginning (15) to obtain the parameter value or other work (such as exception handling), so move will correspond to the float temp; A previous address. The Type modifier after moving is a little different from the previous one. It just adds the variable name so that it is not an abstract declarative. Its function is to let the compiler generate a ing, bind the added variable name and the memory address that transmits the corresponding information to form the so-called parameter. For this reason, you can write as follows: void move (float X, float, float Z ){}. Because the variable name is not bound to the second parameter, the second parameter cannot be used. This will be explained in the future.
The definition of a function is the same as that of the previous function declaration, except that a composite statement must be written immediately after it (it must be a composite statement, that is, a statement enclosed ), the address of the compound statement will be bound to the function name. However, due to the function prefix mentioned above, the actual address of the function name is before the address of the compound statement.
To call a given function, C ++ provides the function operator "()", followed by a number of function types, put numbers and numbers of the corresponding types, so the above move (X [I], Y [I], Z [I]) is to use the function operator, use the values of X [I], Y [I], and Z [I] as the parameters, record the address of the current location, and jump to the address corresponding to move to continue execution, when the result is returned from the move operation, the system redirects to the function call location based on the previously recorded position and continues the subsequent code execution.
Function operators are operators, so they also need to return numbers, that is, the return value of the function, which can be as follows:
Float AB (float X) {return x * X;} int main () {float c = AB (10); Return 0 ;}
A function AB is defined first, and it returns a float number. The return statement is used to specify the return value of the function, and the subsequent number must be the return value type of the corresponding function, when the return type is void, return can be written directly ;. Therefore, the above C value is 100, and the value returned by the function operator is the number returned by the expression x * X in the AB function, while AB (10) uses 10 as the value of the parameter X of the AB function, therefore, x * x returns 100.
Since we have also explained that the function can have pointers, and compare the function with the variable, you can directly write the function name, such as AB ;. The above will return the number of the address type corresponding to AB, and then calculate the number of this address type. The function type should be used to explain the memory content corresponding to the corresponding address, considering the meaning of the function, it will be meaningless, so it does not do anything. directly return the binary number corresponding to the number of this address type, which is equivalent to the pointer type mentioned above. Therefore, it can be as follows:
Int main () {float (* PAB) (float) = AB; float c = (* PAB) (10); Return 0 ;}
The above defines a pointer PAB, whose type is float (*) (float). At first, assign the corresponding address of AB to it. Why didn't I write PAB = & AB; but PAB = AB ;? As we have already said, the number of the address type of the function type will not do anything. The effect is the same as that of the number of the pointer type, so PAB = AB; no problem, and PAB = & AB; is no problem. It can be considered that the number compiler of the address type of the function type will be implicitly converted to a number of the pointer type, so it can be (* PAB) (10); or (* AB) (10); because the latter compiler performs implicit type conversion.
Because the function operator is connected with a number, float c = AB (10); that is, C is 10000. You should also note that the function operator allows the compiler to generate some code to pass the parameter value and jump to the corresponding address to continue executing the code, so the following is acceptable:
Long AB (long x) {If (x> 1) return x * AB (X-1); else return 1 ;}
It indicates that when the value of X is greater than 1, the number returned by X-1 is used as the parameter, and then jumps to the address corresponding to AB, that is, if (x> 1) the address corresponding to the repeat operation. Therefore, if long c = AB (5);, C is the factorial of 5. If you cannot understand the above, we will explain in detail how the function is implemented and the so-called stack overflow problem later.
Now we should understand the significance of the main function. it just creates a ing so that the connector can define the entry address of the program, that is, the corresponding address of the main function. The above function move is defined before the function main. If you move the definition of move to the following main, an error will occur, saying that the function move has not been defined. Why? Because the compiler only compiles from top down and only compiles once. What should I do with the above problem? It is described later.
Overload Functions
If you only want to move the X and Y coordinates, you must write another function as follows to avoid moving the zcoordinate:
Void move2 (float X, float y );
It is changed to move2 in order not to conflict with the name of the previous move function, but move2 also indicates moving, but it has to change a name, which seriously affects semantics. In order to better show the semantics of the source code, that is, the meaning of this Code, C ++ proposed the concept of heavy-load functions.
A overload function indicates multiple functions with the same name but different parameter types and numbers. As follows:
Void move (float X, float y, float Z) {}and void move (float X, float y ){}
The above two overload functions are defined. Although the function names are the same, they are actually two functions. The same function names indicate that they have the same semantics-the programs that move the gun, but they only move in different ways, the former moves in three-dimensional space, while the latter moves in a water plane. When move (12, 43); is called, and move (23, 5, 12) is called. However, it must be a different parameter. It cannot be a different return value. That is, an error is reported as follows:
Float move (float X, float y) {return 0;} and void move (float a, float B ){}
Although the returned values are different, the compiler still considers the function defined above to be the same, which means that the function is defined repeatedly. Why? Because when writing a function operator, the type of Return Value of the function cannot be obtained, that is, float a = move (1, 2). Although it can be introduced as the former, it can also be moved (1, 2), so that you cannot know which function should be used. It should also be noted that although the above parameter names are different, they are all the same. The parameter names only indicate the mapped addresses in the scope of the function, which will be described later. If it is changed to the following, there is no problem:
Float move (float X, float y) {return 0;} and void move (float a, float B, float c ){}
Pay attention to the following issues:
Float move (float X, char y); float move (float a, short B); move (10,270 );
The above compiler will report an error, because here 270 is considered as an int in the calculation of function operators, that is, an integer. It can be converted to Char or short, the result compiler cannot determine which function should be used. Therefore, move (10, (char) 270 );.
Description and definition
The Declaration tells the compiler some information to assist the compiler in syntax analysis and avoid compiler errors. The definition is to tell the compiler to generate some code that will be used by the connector. That is, the Declaration is for the compiler and the definition is for the connector. This description is vague. Why do we have to make a declaration and definition here? This is because C ++ agrees to split the program into several segments and write them in different files respectively. The Compiler mentioned above only compiles the program from the top down and only compiles each file once.
When the compiler compiles a program, only the source files are compiled one by one and the corresponding intermediate files are generated separately (for VC. OBJ file), and then all intermediate files are connected by the connector to form an executable file. The problem is that the compiler is compiling. in the CPP file, the variables A and B are defined in the definition statement, while B is being compiled. CPP, it is found that the code using a and B, such as a + = B;, but in B. if no definition statements A and B are found in CPP, the compiler reports an error. Why? If no error is reported, as defined in A. cpp, how does one compile B. cpp before compiling a. cpp? If the source file compilation sequence is specific, the compilation flexibility will be greatly reduced. Therefore, C ++ also stipulates: Compile. all things (variables, functions, etc.) defined in CPP are compiled in B. when CPP is used, all counts are not counted, and A is not compiled. the same as CPP. So what should I do if B. cpp uses the variables defined in A. cpp? To this end, C ++ proposed the concept of declaration.
Therefore, the variable declaration long A is to tell the compiler that there is such a variable. Its name is a and its type is long. Its corresponding address is unknown, but you can mark it first, that is, mark all the places in the subsequent code that use this variable to tell the connector to find a variable named a in all the intermediate files during connection, then, modify all the marked places and put the address corresponding to. In this way, this file uses the variables defined in another file.
So the declaration of long A is to tell the compiler that there is such a variable A. Therefore, when using a in subsequent code, do not report an error saying that A is not defined. The same is true for functions, but the problem is that the function declaration and function definition are easily different, because the function definition must be followed by a composite statement, but the variable definition and variable Declaration are exactly the same, then how does the compiler identify variable definitions and variable declarations? The compiler considers long A as a variable definition. To identify the variable declaration, the modifier extern proposed by C ++ can be used.
A modifier is used in a declaration or Definition Statement to modify this declaration or definition to provide certain information to the compiler. It is always followed by or after a declaration or definition statement, for example:
Extern long a, * pA, & RA;
The preceding Declaration (not defined) contains three variables A, Pa, and RA. Because extern indicates the external meaning, the above is considered to tell the compiler that there are three external variables, A, Pa, and Ra. Therefore, it is considered as a declaration statement, therefore, no memory is allocated. Similarly, for functions, it is the same:
Extern void ABC (long); or extern long AB (short B );
The above extern is equivalent to not writing, because the compiler can determine the above is a function declaration based on the final ";", and the provided "external" information is meaningless to the function, the compiler ignores this vulnerability. Extern actually specifies the modifier of the identifier after it is modified. The actual modifier should be extern "C" or extern "C ++ ", the identifiers declared by C language and C ++ language respectively.
C ++ is a strong type language, that is, it requires strict type matching principles, in order to achieve the function overload function described above. That is, several functions with the same name can be implemented for overloading because they are not actually the same name, but are modified by their respective parameter types and numbers. For example, void ABC (), * ABC (long), ABC (long, short); in VC, their names are changed to "? ABC @ yaxxz ","? ABC @ yapaxj @ Z ","? ABC @ yaxjf @ Z ". The names of the three variables declared by extern long a, * pA, & RA; also change accordingly. A @ 3ja ","? Pa @ 3paja ","? RA @ 3aaja ". The above is called the identifier modifier of the C ++ language style (different compiler modifier formats may be different ), the identifier modification in the C language style is simply to add "_" before the identifier (the C style modification must be the same for different compilers ). For example, extern "C" long a, * pA, & RA; is changed to _ A, _ Pa, and _ Ra. The above extern "C" Void ABC (), * ABC (long), ABC (long, short); will report an error because the C style is used, if you only add an underscore before the function name, three identical symbols are generated.
Why cannot I have the same symbol? Why do I need to change the identifier? Not only is it because of the previous function overload. Unlike an identifier, a symbol can be composed of any character. It is a means of communication between the compiler and the connector. An identifier is only a means of identification provided at the C ++ language level. The reason why we need to change the identifier instead of using it as a symbol is that the compiler itself and the connector still have some information to pass, and this information needs to be identified by a symbol, the identifier written by the user may conflict with the internal symbols used by the compiler. Therefore, it must be modified on the identifier defined by the programmer before being used as a symbol. Since all characters can be used as symbols, the compiler does not allow its own internal symbols to use characters that are not usable by identifiers, such as the "?", That's not enough? Because some C/C ++ compilers and connectors can communicate with each other without any character, they must also be an identifier, therefore, in the C language style, the prefix "_" is added to distinguish between the symbols defined by the programmer and those defined by the compiler. That is, you can use "?" As the symbol is VC, maybe other compilers do not support it, but other compilers must support identifiers with the prefix. In this way, you can use multi-party code to achieve code reuse in a larger scope. This is detailed in C ++ from (18.
When writing extern void ABC (long);, is it extern "C" or extern "C ++ "? In VC, if the source file where the above Code is located has the extension. cpp to indicate that it is C ++ source code, it will be interpreted as the latter. If it is. C, it will be interpreted as the former. However, in VC, you can modify the default settings by modifying the project options. And extern long a; is the same as above.
Therefore:
Extern "C ++" Void ABC (), * ABC (long), ABC (long, short );
Int main () {ABC ();}
The first sentence above tells the compiler that subsequent code may use these three functions, so that the compiler should not report errors. If the above program is placed in a. cpp of a VC project, no errors will occur when compiling a. cpp. But when connected, the compiler will say the symbol "? ABC @ yaxxz "is not found, because this project contains only one file, and the connection only connects. OBJ and other necessary library files (which will be described later ). The connector searches for the symbol "? The address corresponding to ABC @ yaxxz is not found, so an error is returned. In other words, the main function uses void ABC (); defined outside a. cpp, but the definition of this function is not found. It should be noted that if it is written as int main () {void (* pA) = ABC;}, an error will still be reported, because ABC is equivalent to an address, here, we need to calculate the value of this address (even if Pa is not used), so the same error is reported.
To eliminate the preceding errors, you should define the void ABC () function. CPP, such as after the main function, you can also generate a new one. add the CPP file to the project. the definition function ABC in the CPP file. So you can:
Extern "C ++" Void ABC (), * ABC (long), ABC (long, short );
Int main () {ABC ();} void ABC (){}
If you think you have understood the differences between the Declaration and definition and the meaning of the statement, I bet there is a 50% possibility that you have not really understood the meaning of the statement, due to space limitations, I will explain the true meaning of the statement in "C ++ from scratch (10)". If you are a person with some C/C ++ programming experience, the sample given at that time should be 50% likely to surprise you.
Call rules
Calling Rules refer to how function parameters are transmitted, how return values are transmitted, and how the above function name identifiers are modified. It is not language-level content, because it represents how the compiler implements functions, and each compiler has its own processing method for how to implement the functions. In VC, three types of modifiers are defined to inform the compiler how to implement functions, including: __cdecl, _ stdcall, and _ fastcall. The three methods have different parameters, function return value transmission methods, and function name modification methods. When exceptions are described later, the specific implementation methods of the functions are explained one by one. Since they are type modifiers, the following modifiers can be used:
Void * _ stdcall ABC (long), _ fastcall de (), * (_ stdcall * PAB) (long) = & ABC;
Void (_ fastcall * de) () = de;
Variable Scope
Void move (float a, float B); is the same as void move (float X, float y, that is, variable names A and B have no significance here. That is to say, the range of the variables A and B is limited only to the function body (composite statement when the function is defined) in the previous move, similarly, the valid range of X and Y is only in the moving function body. This is called the scope of a variable.
//// A. cpp //////
Long E = 10;
Void main ()
{
Short A = 10;
E ++;
{
Long E = 2;
E ++;
A ++;
}
E ++;
}
The effective range of the First E above is the entire. in the CPP file, while the valid range of A is within the main function, and the valid range of E in the main function is within its pair. That is, after e ++ is executed at the end of the previous step, long e = 2; the defined variable e is no longer available, that is, it is released. Long E = 10; the defined e value is 12, and a value is 11.
That is to say, "{}" can be nested and included in layers. Without "{}", a scope is generated, the variables defined in "{}" are valid only in "{}", and "{}" is invalid, which is equivalent to being not defined.
Why? That is to better reflect the semantics. A layer of "{}" indicates a stage. When executing this stage, variables with the same semantics as the previous stage, such as sorting, may be required. Some variables are only useful at a certain stage, and it makes no sense after this stage. The following example:
Float a [10];
// Assign a value to array
For (unsigned I = 0; I <10; I ++)
For (unsigned J = 0; j <10; j ++)
If (A [I] <A [J])
{
Float temp = A [I];
A [I] = A [J];
A [J] = temp;
}
The temp above is called a temporary variable, and its scope is only in the braces after if (a [I] <A [J]), because it represents a stage, the program has entered the phase of exchanging array elements, and temp is meaningful only when elements are exchanged, used for exchanging auxiliary elements. If temp is defined at the beginning, it indicates that temp is valid during the search of array elements. This is not semantically correct, although the definition at the beginning will not affect the result, but you should constantly ask yourself-Can this code be avoided? What is the significance of this code? However, performance may be affected due to the relationship between scopes, which is described in C ++ from scratch (10.
The next article will illustrate how to write c ++ code with known algorithms to help readers achieve the most basic requirements of programmers-to get algorithms and code.