Chapter 6 Function Design
The function is the basic functional unit of the C / C program, and its importance is self-evident. Function design is easy to cause this function to be misuse, so the function of the function is not enough. This chapter focuses on the interface design and internal implementation of the function.
Two elements of the function interface are parameters and return values. In the C language, there are two ways to pass the parameters and return values: Value Pass By Value and Packing By Pointer. The C language has more reference delivery (pass by reference). Since the nature of the reference passed, the use of the way is transmitted, and the initiator is often confused, it is easy to cause confusion, please read the 6.6 "reference and pointer comparison".
6.1 rules of parameters
l [Rules 6-1-1] The writing of parameters should be complete, do not save the type of parameters and omit the parameter name. If the function does not have a parameter, fill it with VoID.
E.g:
Void setValue (int width, int hotht); // Good style
Void setValue (int, int); // bad style
Float getValue (void); // Good style
Float getValue (); // bad style
l [Rules 6-1-2] The parameter naming is appropriate, the order is reasonable.
For example, writing a string copy function StringCopy, it has two parameters. If the parameter name is started with STR1 and STR2, for example
Void stringcopy (char * str1, char * STR2);
So, it is difficult to figure out to copy Str1 to STR2, or just poured.
You can make the parameter name more meaningful, such as strsource and strDestination. This can be seen from the name, you should copy the strsource to strDestination.
There is also a problem, the two parameters should this be the one before? The order of the parameters should follow programmers' habits. In general, the destination parameters should be placed in front, and the source parameters should be placed behind.
If the function is declared as:
Void stringcopy; char * strdestination;
Others may not think about the following form when using others:
Char Str [20];
Stringcopy (STR, "Hello World"); // Parameter sequence reverse
l [Rules 6-1-3] If the parameter is a pointer, it is only used for input, then CONST should be added before the type to prevent this pointer from being accidentally modified in the function.
E.g:
Void stringcopy (char * strdestination, const char * strsource);
l [Rules 6-1-4] If the input parameter passes an object in a value transfer, it should be transferred to the "const" mode, so that the temporary object constructor and the destructive process are saved, thereby increasing efficiency.
2 [Recommendation 6-1-1] Avoiding the function of too much parameters, the number of parameters is controlled within 5. If the parameters are too much, it is easy to make the parameter type or order when used.
2 [Recommendation 6-1-2] Do not use the type and number of uncertain parameters.
C Standard Library Function Printf is a typical representative using uncertain parameters, its prototype:
INT Printf (const chat * format [, argument] ...);
This style of function has lost strict type safety inspections at compile.
6.2 Return value rules
l [Rule 6-2-1] Do not omit the type of return value. C language, where you do not add the type of function, you will automatically press the integer. Don't do this, it is easy to be misunderstood as a Void type.
The C language has a strict type security check that does not allow the above situation. Since the C program can call the C function, in order to avoid confusion, it is necessary to have a type of C / C function. If the function does not return value, it should be declared as a Void type.
l [Rules 6-2-2] Function Name and Return Value Types are unable to conflict in semantics.
A typical representative of this rule is a C standard library function getchar.
E.g:
Char C;
C = getchar ();
IF (c == EOF)
...
According to the GetChar name, the variable C is declared as a CHAR type is a natural thing. But unfortunately, getChar is indeed not a char type, but the int type, its prototype is as follows:
INT getchar (void);
Since C is a char type, the value range is [-128, 127], if the value of the macro EOF is outside the value of char, then the IF statement will always fail, this "danger" people generally get it! The responsibility of this error is not in the user, is the function getchar misleading the user.
l [Rules 6-2-3] Do not return the normal values and the error flags together. Normal values are obtained with output parameters, and the error flag returns with the return statement.
Review the above example, why do the designers of the C standard library function be declared as a confused int type? Will he be so stupid?
Under normal circumstances, getChar does return to a single character. But if getchar hits the file end flag or a read error, it must return a flag EOF. To distinguish between normal characters, you have to define EOF as a negative number (usually negative 1). Therefore, the function getchar is an int type.
In actual work, we often encounter the above problems. In order to avoid misunderstandings, we should separate the normal values and error signs. That is, the normal value is obtained with the output parameters, and the error flag returns with the return statement.
The function getchar can be rewritten into BOOL getchar (char * c);
Although gechar is flexible than getchar, such as Putchar (GetChar ()); however, if getchar is wrong, what is its flexibility?
2 [Recommendation 6-2-1] Sometimes the function does not need to return value, but in order to increase flexibility, such as support chain expression, you can add a return value.
For example, string copy functions STRCPY prototype:
Char * STRDEST, Const Char * strsrc);
The STRCPY function copies the strsrc to the output parameter strDest, and the return value of the function is STRDEST. Do not do more, you can get the following flexibility:
Char Str [20];
INT Length = Strlen (Str, "Hello World");
2 [Recommendation 6-2-2] If the return value of the function is an object, some occasions can be improved with "reference delivery" replacement "value" can improve efficiency. Some occasions can only be used to deliver "without" reference delivery ", otherwise it will be wrong.
E.g:
Class String
{...
// Assignment function
String & Operate = (const string&);
// Add function, if there is no Friend modification, only one right parameter Friend String Operate (Const String & S1, Const String & S2);
Private:
CHAR * M_DATA;
}
String's assignment function operate = The implementation 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; // Returns * this reference, no copy process
}
For assignment functions, the String object should be returned by "reference delivery". If the function is passed correctly, although the function is still correct, because the RETURN statement is copied to the external storage unit of the returned value, it adds unnecessary overhead to reduce the efficiency of the assignment function. E.g:
String A, B, C;
...
A = b; // If you use "value transfer", you will generate a * this copy
A = b = c; // If you use "value transfer", you will generate twice * this copy
The implementation of the String additive 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 = new char [S1.DATA) Strlen (S2.DATA) 1];
STRCPY (Temp.Data, S1.DATA);
STRCAT (TEMP.DATA, S2.DATA);
Return Temp;
}
For additive functions, the String object should be returned by "value delivery". If you switch to "reference delivery", the function return value is a "reference" pointing to the local object TEMP. Since Temp is automatically destroyed at the end of the function, the return "reference" will result in invalid. E.g:
C = a b;
At this time, A B does not return to the expected value, and C is not there, and there is a hidden danger.
6.3 Rules for internal implementation
Different functions have different internally realization, it seems that it is not possible to agree on "internal implementation". But according to experience, we can strictly control the quality of the function in the "entrance" and "exit" of the function body.
l [Rules 6-3-1] In the "entrance" of the function, check the validity of the parameters.
Many program errors are caused by illegal parameters, we should fully understand and correctly use "assert" to prevent such errors. See "Using Assessment" in Section 6.5.
l [Rule 6-3-2] In the "exit" of the function body, check the correctness and efficiency of the Return statement.
If the function has a return value, the "exit" of the function is a returnite statement. Don't underestimate the Return statement. If the return statement is not written, the function is either error, or it is low.
Note is as follows:
(1) RETURN statement cannot return "pointer" or "reference" to "stack memory", because the internal existence is automatically destroyed. E.g
Char * func (void) {
Char str [] = "Hello World"; // STR's memory is on the stack
...
Return str; // will result in errors
}
(2) To figure out the "value", "pointer" is still "reference".
(3) If the function return value is an object, consider the efficiency of the Return statement. E.g
Return String (S1 S2);
This is the syntax of the temporary object, representation "Create a temporary object and return it." Don't think that it is equivalent to "first creation of a local object Temp and returns it" is equivalent, such as
String Temp (S1 S2);
Return Temp;
Essentially, three things will occur. First, the TEMP object is created and is initialized; then the copy constructor copies the TEMP to the external storage unit that saves the return value; finally, TEMP is destroyed at the end of the function (call destructor). However, "Creating a temporary object and returning it" is different. The compiler creates the temporary object directly and initializes the cost of copying and sectulating in the external storage unit, and improving efficiency.
Similarly, we don't want
Return Int (x y); // Create a temporary variable and return it
Write
INT TEMP = X Y;
Return Temp;
Due to internal data types such as int, float, Double variables do not have constructor and destructive functions, although the "temporary variable syntax" does not increase how much efficiency, but the program is more simple and easy to read.
6.4 Other recommendations
2 [Recommendation 6-4-1] The function of the function is single, do not design multi-purpose functions.
2 [Recommendation 6-4-2] The size of the function body is small, and the control is controlled within 50 lines of code.
2 [Recommendation 6-4-3] Try to avoid the function with the "memory" function. The same input should produce the same output.
A function with the "memory" function, its behavior may be unpredictable because its behavior may depend on some "memory state". Such functions are neither easy to understand and are not conducive to testing and maintenance. In the C / C language, the STIC local variable of the function is the "memory" memory of the function. It is recommended to use the Static partial variable as possible unless it is necessary.
2 [Recommendation 6-4-4] Not only should you check the validity of the input parameters, but also check the effectiveness of variables in the function in the function by other routes, such as global variables, file handles, etc.
2 [Recommendation 6-4-5] The return value for error processing must be clear, so that the user does not easily ignore or misunderstand the error.
6.5 use assertions
The program is generally divided into debug version and release version, and the Debug version is used for internal debugging, and the Release version is issued to the user.
Assert is a macro that works only on the Debug version, it is used to check the situation that "should not" happen. Example 6-5 is a memory replication function. During the run, if the parameter of Assert is a false, the program will abort (generally show the dialogue, which means where ASSERT is triggered).
Void * Memcpy (void * pvto, const void * pvfrom, size_t size)
{
Assert ((PVTO! = null) && (pvfrom! = null); // Using assertions
Byte * PBTO = (byte *) PVTO; / / Prevent the address of the PVTO
Byte * pbfrom = (byte *) PVFROM; / / Prevent the address of the PVFROM
While (size -> 0)
* PBTO = * PBFROM ; Return PVTO;
}
Example 6-5 Copying the memory blocks that do not overlap
Assert is not a macro of a rush. In order not to cause a difference between the Debug version and the Release version, Assert should not generate any side effects. So Assert is not a function, but a macro. Programmers can see Assert as a honey-impaired test means that can be safely used in any system state. If the program terminates at Assert, it is not to say that the function containing the Assert has an error, but the caller has an error, and Assert can help us find the cause of the error.
I rarely compare the assertion of the procedure, but I don't know if the role of the assert is more depressed. You have been a lot of time, not to troubleshoot, but just to figure out what this error is. Sometimes, programmers can occasionally design errors. So if you can't figure out what is checked, it is difficult to judge that the error is in the program, or it appears in the assertion. Fortunately, this problem is very good, just add a clear annotation. This is an obvious thing, but there are very few programmers. This is better than a person in the forest, seeing a "dangerous" big brand on the tree. But what is dangerous? Do you want to fall? Is there a waste well? Is there a beast? This warning card is difficult to play an active effect unless it tells people "dangerous". It is difficult to understand that it is often overlooked by programmers or even deleted. [MAGUIRE, P8-P30]
l [Rules 6-5-1] Use asserts to capture illegal conditions that should not occur. Do not confuse the difference between illegal conditions and error conditions, the latter inevitably exist and must be processed.
l [Rule 6-5-2] At the entrance to the function, the validity of the assertion check parameters (legitimacy) is used.
l [Recommendation 6-5-1] When writing a function, it is necessary to perform repeated exam, and ask yourself: "What assumes I plan?" Once the assumption is determined, it is necessary to check the assumptions using the assertion.
l [Recommendation 6-5-2] General textbooks encourage programmers to perform anti-wrong design, but remember that this programming style may hide errors. When the error is performed, if the "impossible" thing does occur, you should use the assertion to conduct alarm.
6.6 Reference and pointer comparison
Quote is the concept in C , and beginners are easy to confuse the references and pointers. In the program, N is a reference to M, and m is a reference (REFERENT).
Int m;
INT & n = m;
n is equivalent to the alias (nickname), any operation to N is the operation of M. For example, some people named Wang Xiaoyao, his nickname is "three hair". Saying how "San Mao" is, in fact, it is three four four. So n is neither a copy of M, nor pointing to M's pointers, in fact, N is M itself.
Some rules referenced are as follows:
(1) The reference must be initialized while being created (the pointer can be initialized at any time).
(2) There is no NULL reference, the reference must be associated with the legal storage unit (the pointer can be null).
(3) Once the reference is initialized, the reference is not changed (the pointer can change the object you refer to time).
In the following example program, K is initialized to i's reference. Statement k = j does not modify K into J. The value of K is changed to 6. Since K is a reference to i, the value of i has also become 6.
INT i = 5;
INT J = 6;
INT & K = I;
K = j; // k and i have the value of 6;
The above program looks like playing text games, does not reflect the value of reference. The main function of the reference is to transfer the parameters and return values of the function. In the C language, there are three ways to pass the parameters and return values: value transfer, pointer delivery, and reference delivery.
The following is an example program for "value delivery". Since X in the FUNC1 function body is a copy of the external variable N, the value of the X does not affect N, so N's value is still 0.
Void func1 (int x)
{
X = x 10;
}
...
INT n = 0;
Func1 (n);
COUT << "n =" << n << endl; // n = 0
The following is an example program for "pointer". Since x in the FUNC2 function is pointed to the external variable N, the content that changes the pointer will cause the value of N to change, so the value of n is 10.
Void func2 (INT * X)
{
(* x) = (* x) 10;
}
...
INT n = 0;
FUNC2 (& n);
COUT << "n =" << n << endl; // n = 10
The following is an example program that "reference delivery". Since X in the FUNC3 function body is referenced by the external variable N, X and N are the same thing, and the change X is equal to changing N, so N is the value of 10.
Void Func3 (INT & X)
{
X = x 10;
}
...
INT n = 0;
FUNC3 (N);
COUT << "n =" << n << endl; // n = 10
Compare the above three sample programs, the nature of "reference delivery" is found, "pointer delivery", and writing method is like "value transfer". In fact, anything "pointer" that can be done can also do, why should I "quote"?
The answer is "Doing the right tool to work with appropriate tools."
The pointer can operate how things in memory without restraint, although the pointer is powerful, it is very dangerous. Just like a knife, it can be used to cut trees, paper cuts, manicure, haircut, etc., who dares to use it?
If you only need to borrow a "alias" of an object, then use "reference", not to use "pointer" to avoid accidents. For example, someone needs a certificate that I originally opened the official seal on the document. If you give him the key to him, then he gains the right.