Functions are the basic functional units of C ++/C Programs. Their importance is self-evident. The minor disadvantage of function design can easily lead to incorrect use of the function. Therefore, it is not enough to make the function correctly. This chapter focuses on the interface design and internal implementation rules of functions.
Two elements of a function interface are parameters and return values. In C, function parameters and return values are transmitted in two ways: pass by value and pass by pointer ). Pass by reference is added to the C ++ language ). Because the nature of reference transfer is like pointer transfer, but the usage is like value transfer, beginners are often confused and prone to confusion. Please read section 6.6 "Comparison of reference and pointer" first ". 6.1 parameter rules LRule 6-1-1]Parameters must be completely written. Do not worry about writing only the parameter type and omitting the parameter name. If the function has no parameters, use void. For example: Void setvalue (INT width, int height); // good style Void setvalue (INT, INT); // bad style Float getvalue (void); // good style Float getvalue (); // bad style LRule 6-1-2]The parameter name should be appropriate and the order should be reasonable. For example, compile the stringcopy function, which has two parameters. If the parameter names start with str1 and str2, for example Void stringcopy (char * str1, char * str2 ); It is difficult to figure out whether to copy str1 to str2. The parameter names can be more meaningful, such as strsource and strdestination. In this way, we can see that strsource should be copied to strdestination. There is another question: Which of the two parameters should be the first one after? The order of parameters must follow the programmer's habits. Generally, the target parameter should be placed first, and the source parameter should be placed later. If you declare a function: Void stringcopy (char * strsource, char * strdestination ); When using it, others may write the following form without thinking about it: Char STR [20]; Stringcopy (STR, "Hello World"); // The Order of parameters is reversed. LRule 6-1-3]If the parameter is a pointer and only used for input, you should add const before the type to prevent the pointer from being accidentally modified in the function body. For example: Void stringcopy (char * strdestination, const char * strsource ); LRule 6-1-4]If the input parameter is passed as a value, you should use the "const &" method to transfer the object. This saves the construction and analysis processes of the temporary object and improves the efficiency. 2.[Recommended 6-1-1]Avoid too many parameters in a function, and set the number of parameters to less than 5. If there are too many parameters, the parameter type or sequence may be incorrect during use. 2.[Recommended 6-1-2]Do not use parameters with uncertain types or numbers. The C standard library function printf is a typical example of using uncertain parameters. Its prototype is: Int printf (const chat * Format [, argument]…); This type of function loses strict type security checks during compilation. 6.2 return value rules LRule 6-2-1]Do not omit the type of the returned value. In C language, all functions without type descriptions are automatically processed by integer type. This method is easy to misunderstand as the void type. The C ++ language has strict type security checks and does not allow the above situations. Since C ++ programs can call C functions, to avoid confusion, it is required that any c ++/C function must have a type. If the function does not return a value, the void type should be declared. LRule 6-2-2]The function name and return value type cannot conflict in semantics. A typical violation of this rule is the c Standard library function getchar. For example: Char C; C = getchar (); If (C = EOF) ... According to the getchar name, it is natural to declare the variable C as the char type. Unfortunately, getchar is not a char type but an int type. Its prototype is as follows: Int getchar (void ); Because C is of the char type and the value range is [-128,127], if the macro EOF value is out of the char value range, the IF statement will always fail, this kind of "dangerous" is generally expected! The fault in this example is not caused by the user. The getchar function misleads the user. LRule 6-2-3]Do not mix the normal value with the error flag to return the result. The normal value is obtained using the output parameter, and the error mark is returned using the return statement. In the previous example, why should the C standard library function designers declare getchar as a confusing int type? Will he be so stupid? Normally, getchar returns a single character. However, if getchar encounters a file end mark or a read error, it must return an EOF flag. To distinguish it from a normal character, we have to define the EOF as a negative number (usually negative 1 ). Therefore, the getchar function is of the int type. In our practical work, we often encounter the above difficult problems. To avoid misunderstanding, we should separate the normal value from the error mark. That is, the normal value is obtained using the output parameter, and the error mark is returned using the return statement. The function getchar can be rewritten to bool getchar (char * C ); Although gechar is more flexible than getchar, such as putchar (getchar (), what is its flexibility if getchar is used incorrectly? 2.[Recommended 6-2-1]Sometimes a function does not need to return values, but to increase flexibility, such as supporting chained expressions, you can add return values. For example, the prototype of the string copy function strcpy: Char * strcpy (char * strdest, const char * strsrc ); The strcpy function copies strsrc to the output parameter strdest, And the return value of the function is strdest. In this case, the following flexibility can be achieved: Char STR [20]; Int length = strlen (strcpy (STR, "Hello World ")); 2.[Recommended 6-2-2]If the return value of a function is an object, in some cases, replacing "value transfer" with "reference transfer" can improve the efficiency. In some cases, you can only use "value transfer" instead of "reference transfer". Otherwise, an error occurs. For example: Class string {... // Value assignment function String & operate = (const string & other ); // Add function. If there is no friend modifier, only one parameter on the right is allowed. Friend string operate + (const string & S1, const string & S2 ); PRIVATE: Char * m_data; } The implementation of the string value assignment function operate = is as follows: String & string: operate = (const string & other) { If (this = & other) Return * this; Delete m_data; M_data = new char [strlen (other. Data) + 1]; Strcpy (m_data, other. data ); Return * This; // The returned result is a reference of * This, without the need to copy the process. } For the value assignment function, the string object should be returned using the "reference transfer" method. If the "value transfer" method is used, although the function is still correct, but because the return statement needs to copy * this to the external storage unit that saves the returned value, unnecessary overhead is added, this reduces the efficiency of the value assignment function. For example: String A, B, C; ... A = B; // If "value transfer" is used, a * This copy will be generated. A = B = C; // If "value transfer" is used, two * This copies will be generated. The implementation of the string addition function operate + is as follows: String operate + (const string & S1, const string & S2) { String temp; Delete temp. Data; // temp. Data is a string containing only '\ 0' Temp. Data = newchar [strlen (s1.data) + strlen (s2.data) + 1]; Strcpy (temp. Data, s1.data ); Strcat (temp. Data, s2.data ); Return temp; } For Addition functions, the string object should be returned using the "value transfer" method. If "reference transfer" is used, the return value of the function is a "Reference" pointing to the local object temp ". Because temp is automatically destroyed at the end of the function, the returned "Reference" is invalid. For example: C = A + B; At this time, A + B does not return the expected value, and C does not get anything, causing hidden risks. 6.3 internal implementation rules of functions Different functions have different internal implementations, and it seems impossible to reach an agreement on internal implementations. However, based on experience, we can strictly control the "ENTRANCE" and "exit" of the function body to improve the function quality. LRule 6-3-1]Check the parameter validity at the "ENTRANCE" of the function body. Many program errors are caused by Invalid parameters. We should fully understand and correctly use assert to prevent such errors. For details, see section 6.5 "Use assertions ". LRule 6-3-2]Check the correctness and efficiency of the Return Statement at the "exit" of the function body. If a function returns a value, the "exit" of the function is the return statement. We should not underestimate the return statement. If the return statement is not well written, the function either fails or is inefficient. Note: (1) The return statement cannot return "Pointer" or "Reference" pointing to "stack memory" because the function body is automatically destroyed when it ends. For example Char * func (void) { Char STR [] = "Hello World"; // the memory of STR is on the stack. ... Return STR; // Error } (2) Determine whether the returned result is "value", "Pointer", or "Reference ". (3) If the return value of a function is an object, the efficiency of the return statement should be considered. For example Return string (S1 + S2 ); This is the syntax of a temporary object, indicating "Creating a temporary object and returning it ". Do not think it is equivalent to "first create a local object temp and return its results", such String temp (S1 + S2 ); Return temp; Otherwise, the above Code will have three things. First, the temp object is created and initialized at the same time. Then, the copy constructor copies temp to the external storage unit that stores the returned values. Finally, temp is destroyed at the end of the function (the Destructor is called ). However, the process of "Creating a temporary object and returning it" is different. The Compiler directly creates and initializes the temporary object in an external storage unit, saving the copy and destructor fees, improved efficiency. Similarly, we do not want Return int (x + y); // create a temporary variable and return it Write Int temp = x + y; Return temp; Because the internal data types such as int, float, and double do not have constructor and destructor, although the "Syntax of temporary variables" does not increase much efficiency, the program is more concise and easy to read. 6.4 other suggestions 2.[Recommended 6-4-1]A single function is required. Do not design a multi-purpose function. 2.[Recommended 6-4-2]The size of the function body should be small and should be controlled within 50 lines of code as much as possible. 2.[6-4-3 is recommended.]Do not use the "Memory" function. The same input should generate the same output. For a function with the "Memory" function, its behavior may be unpredictable, because its behavior may depend on a certain "memory state ". Such functions are hard to understand and are not conducive to testing and maintenance. In C/C ++, the static local variable of the function is the memory of the function. We recommend that you use less static local variables unless necessary. 2.[Recommended 6-4-4]Check not only the validity of input parameters, but also the validity of variables entering the function body through other channels, such as global variables and file handles. 2.[Recommended 6-4-5]The returned values for error handling must be clear, so that the user cannot easily ignore or misunderstand the error. |