C++ Primer 第六章 函数
- 6.1. Function Basics
- Parameters and Arguments
- 6.2. Argument Passing
- 6.2.3. const Parameters and Arguments
- 6.2.4. Array Parameters
- 6.2.6. Functions with Varying Parameters
- initializer_list Parameters
- Ellipsis Parameters
- 6.3. Return Types and the return Statement
- List Initializing the Return Value
- Returning a Pointer to an Array
- Using a Trailing Return Type
- Using decltype
- 6.4. Overloaded Functions
- Defining Overloaded Functions
- Overloading and const Parameters
- 6.5. Features for Specialized Uses
- 6.5.1. Default Arguments
- Default Argument Declarations
- 6.5.2. Inline and constexpr Functions
- constexpr Functions
- Put inline and constexpr Functions in Header Files
- 6.5.3. Aids for Debugging
- The assert Preprocessor Macro
- The NDEBUG Preprocessor Variable
- 6.6. Function Matching
- Function Matching with Multiple Parameters
- Argument Type Conversions
- 6.7. Pointers to Functions
- Using Function Pointers
- Function Pointer Parameters
- Returning a Pointer to Function
- Using auto or decltype for Function Pointer Types
6.1. Function Basics
Parameters and Arguments
Arguments are the initializers for a function’s parameters.
Although we know which argument initializes which parameter, we have no guarantees about the order in which arguments are evaluated.
6.2. Argument Passing
6.2.3. const Parameters and Arguments
We cannot pass a const object, or a literal, or an object that requires conversion to a plain reference parameter.
6.2.4. Array Parameters
We cannot copy an array, and when we use an array it is (usually) converted to a pointer .
6.2.6. Functions with Varying Parameters
initializer_list Parameters
std::initializer_list
std::initializer_list<T>
We can write a function that takes an unknown number of arguments of a single type by using an initializer_list parameter.
An initializer_list is a library type that represents an array of values of the specified type.
This type is defined in the initializer_list header.
Like a vector, initializer_list
is a template type.
When we define an initializer_list
, we must specify the type of the elements that the list will contain:
initializer_list<string> ls; // initializer_list of strings
initializer_list<int> li; // initializer_list of ints
Unlike vector, the elements in an initializer_list
are always const values; there is no way to change the value of an element in an initializer_list
.
void error_msg(initializer_list<string> il)
{
for (auto beg = il.begin(); beg != il.end(); ++beg)
cout << *beg << " " ;
cout << endl;
}
When we pass a sequence of values to an initializer_list parameter, we must enclose the sequence in curly braces:
// expected, actual are strings
if (expected != actual)
error_msg({"functionX", expected, actual});
else
error_msg({"functionX", "okay"});
A function with an initializer_list
parameter can have other parameters as well.
void error_msg(ErrCode e, initializer_list<string> il)
{
cout << e.msg() << ": ";
for (const auto &elem : il)
cout << elem << " " ;
cout << endl;
}
if (expected != actual)
error_msg(ErrCode(42), {"functionX", expected, actual});
else
error_msg(ErrCode(0), {"functionX", "okay"});
Ellipsis Parameters
Ellipsis parameters are in C++ to allow programs to interface to C code that uses a C library facility named varargs
.
An ellipsis parameter may appear only as the last element in a parameter list and may take either of two forms:
void foo(parm_list, ...);
void foo(...);
6.3. Return Types and the return Statement
Calls to functions that return references are lvalues; other return types yield rvalues.
char &get_val(string &str, string::size_type ix)
{
return str[ix]; // get_val assumes the given index is valid
}
int main()
{
string s("a value");
cout << s << endl; // prints a value
get_val(s, 0) = 'A'; // changes s[0] to A
cout << s << endl; // prints A value
return 0;
}
List Initializing the Return Value
As in any other return, the list is used to initialize the temporary that represents the function’s return.
If the list is empty, that temporary is value initialized.
vector<string> process()
{
// . . .
// expected and actual are strings
if (expected.empty())
return {}; // return an empty vector
else if (expected == actual)
return {"functionX", "okay"}; // return list-initialized vector
else
return {"functionX", expected, actual};
}
Returning a Pointer to an Array
Type (*function(parameter_list))[dimension]
int (*func(int i))[10];
-
func(int) says that we can call func with an int argument.
-
(*func(int)) says we can dereference the result of that call.
-
(*func(int))[10] says that dereferencing the result of a call to func yields an array of size ten.
-
int (*func(int))[10] says the element type in that array is int.
Using a Trailing Return Type
Trailing returns can be defined for any function, but are most useful for functions with complicated return types, such as pointers (or references) to arrays.
A trailing return type follows the parameter list and is preceded by ->
.
// fcn takes an int argument and returns a pointer to an array of ten ints
auto func(int i) -> int(*)[10];
Using decltype
int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
// returns a pointer to an array of five int elements
decltype(odd) *arrPtr(int i)
{
return (i % 2) ? &odd : &even; // returns a pointer to the array
}
The return type for arrPtr
uses decltype
to say that the function returns a pointer to whatever type odd
has.
The only tricky part is that we must remember that decltype does not automatically convert an array to its corresponding pointer type.
6.4. Overloaded Functions
Functions that have the same name but different parameter lists and that appear in the same scope are overloaded.
void print(const char *cp);
void print(const int *beg, const int *end);
void print(const int ia[], size_t size);
Defining Overloaded Functions
It is an error for two functions to differ only in terms of their return types.
// each pair declares the same function
Record lookup(const Account &acct);
Record lookup(const Account&); // parameter names are ignored
typedef Phone Telno;
Record lookup(const Phone&);
Record lookup(const Telno&); // Telno and Phone are the same type
Overloading and const Parameters
- A parameter that has a top-level const is indistinguishable from one without a top-level const:
Record lookup(Phone);
Record lookup(const Phone); // redeclares Record lookup(Phone)
Record lookup(Phone*);
Record lookup(Phone* const); // redeclares Record lookup(Phone*)
- On the other hand, we can overload based on whether the parameter is a reference (or pointer) to the
const
ornonconst
version of a given type; suchconsts
are low-level:
// functions taking const and nonconst references or pointers
// have different parameters
// declarations for four independent, overloaded functions
Record lookup(Account&); // function that takes a reference to Account
Record lookup(const Account&); // new function that takes a const reference
Record lookup(Account*); // new function, takes a pointer to Account
Record lookup(const Account*); // new function, takes a pointer to const
6.5. Features for Specialized Uses
6.5.1. Default Arguments
typedef string::size_type sz; // typedef see § 2.5.1 (p. 67)
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
window = screen(, , '?'); // error: can omit only trailing arguments
window = screen('?'); // calls screen('?',80,' ')
- If a parameter has a default argument, all the parameters that follow it must also have default arguments.
Default Argument Declarations
Although it is normal practice to declare a function once inside a header, it is legal to redeclare a function multiple times.
However, each parameter can have its default specified only once in a given scope.
Any subsequent declaration can add a default only for a parameter that has not previously had a default specified.
// no default for the height or width parameters
string screen(sz, sz, char = ' ');
we cannot change an already declared default value:
string screen(sz, sz, char = '*'); // error: redeclaration
but we can add a default argument as follows:
string screen(sz = 24, sz = 80, char); // ok: adds default
6.5.2. Inline and constexpr Functions
In an inline function, the compiled code is “in line” with the other code in the program.That is, the compiler replaces the function call with the corresponding function code.
With inline code, the program doesn’t have to jump to another location to execute the code and then jump back.
Inline functions thus run a little faster than regular functions, but they come with a memory penalty.
// inline.cpp -- using an inline function
#include <iostream>
// an inline function definition
inline double square(double x) { return x * x; }
int main()
{
using namespace std;
double a, b;
double c = 13.0;
a = square(5.0);
b = square(4.5 + 7.5); // can pass expressions
return 0;
}
Note:
The inline specification is only a request to the compiler. The compiler may choose to ignore this request.
constexpr Functions
constexpr specifier
A constexpr function is a function that can be used in a constant expression.
A constexpr function is defined like any other function but must meet certain restrictions:
- The return type and the type of each parameter in a must be a literal type, and the function body must contain exactly one return statement:
constexpr int new_sz() { return 42; }
constexpr int foo = new_sz(); // ok: foo is a constant expression
-
In order to be able to expand the function immediately, constexpr functions are implicitly inline.
-
A constexpr function body may contain other statements so long as those statements generate no actions at run time.
-
A constexpr function is permitted to return a value that is not a constant:
// scale(arg) is a constant expression if arg is a constant expression
constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }
int arr[scale(2)]; // ok: scale(2) is a constant expression
int i = 2; // i is not a constant expression
int a2[scale(i)]; // error: scale(i) is not a constant expression
Put inline and constexpr Functions in Header Files
Unlike other functions, inline and constexpr functions may be defined multiple times in the program.
As a result, inline and constexpr functions normally are defined in headers.
6.5.3. Aids for Debugging
The assert Preprocessor Macro
assert(expr);
-
assert is a preprocessor macro.
-
A preprocessor macro is a preprocessor variable that acts somewhat like an inline function.
-
The assert macro takes a single expression, which it uses as a condition:
-
It evaluates
expr
and if the expression is false (i.e., zero), then assert writes a message and terminates the program. If the expression is true (i.e., is nonzero), then assert does nothing. -
The assert macro is defined in the cassert header.
-
As we’ve seen,
preprocessor
names are managed by thepreprocessor
not the compiler. -
As a result, we use preprocessor names directly and do not provide a using declaration for them.
The NDEBUG Preprocessor Variable
The behavior of assert depends on the status of a preprocessor variable named NDEBUG.
If NDEBUG is defined, assert does nothing.
By default, NDEBUG is not defined, so, by default, assert performs a run-time check.
$ CC -D NDEBUG main.C # use /D with the Microsoft compiler
has the same effect as writing #define NDEBUG at the beginning of main.C.
void print(const int ia[], size_t size)
{
#ifndef NDEBUG
// _ _func_ _ is a local static defined by the compiler that holds the function's name
cerr << _ _func_ _ << ": array size is " << size << endl;
#endif
// ...
In addition to _ _func_ _
, which the C++ compiler defines, the preprocessor defines four other names that can be useful in debugging:
_ _FILE_ _ string literal containing the name of the file
_ _LINE_ _ integer literal containing the current line number
_ _TIME_ _ string literal containing the time the file was compiled
_ _DATE_ _ string literal containing the date the file was compiled
We might use these constants to report additional information in error messages:
if (word.size() < threshold)
cerr << "Error: " << _ _FILE_ _
<< " : in function " << _ _func_ _
<< " at line " << _ _LINE_ _ << endl
<< " Compiled on " << _ _DATE_ _
<< " at " << _ _TIME_ _ << endl
<< " Word read was \"" << word
<< "\": Length too short" << endl;
Error: wdebug.cc : in function main at line 27
Compiled on Jul 11 2012 at 20:50:03
Word read was "foo": Length too short
6.6. Function Matching
void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6); // calls void f(double, double)
Function Matching with Multiple Parameters
f(42, 2.56);
In this case, the viable functions are f(int, int)
and f(double, double)
.
The compiler will reject this call because it is ambiguous: Each viable function is a better match than the other on one of the arguments to the call.
Note:
Casts should not be needed to call an overloaded function. The need for a cast suggests that the parameter sets are designed poorly.
Argument Type Conversions
Conversions are ranked as follows:
1. An exact match
- The argument and parameter types are identical.
- The argument is converted from an array or function type to the corresponding pointer type. (covers function pointers.)
- A top-level const is added to or discarded from the argument.
2. Match through a const conversion
Record lookup(Account&); // function that takes a reference to Account
Record lookup(const Account&); // new function that takes a const reference
const Account a;
Account b;
lookup(a); // calls lookup(const Account&)
lookup(b); // calls lookup(Account&)
(1) We cannot bind a plain reference
to a const
object.
(2) We can use b to initialize a reference to either const
or nonconst
type.
However, initializing a reference to const
from a nonconst
object requires a conversion. Hence, the nonconst
version is preferred.
3. Match through a Promotion or Arithmetic Conversion
(1) The small integral types always promote to int or to a larger integral type.
void ff(int);
void ff(short);
ff('a'); // char promotes to int; calls f(int)
(2) All the arithmetic conversions are treated as equivalent to each other.
The conversion from int
to unsigned int
, for example, does not take precedence over the conversion from int
to double
:
void manip(long);
void manip(float);
manip(3.14); // error: ambiguous call
6.7. Pointers to Functions
Using Function Pointers
When we use the name of a function as a value, the function is automatically converted to a pointer.
Function Pointer Parameters
// third parameter is a function type and is automatically treated as a pointer to function
void useBigger(const string &s1, const string &s2,
bool pf(const string &, const string &));
// equivalent declaration: explicitly define the parameter as a pointer to function
void useBigger(const string &s1, const string &s2,
bool (*pf)(const string &, const string &));
Type aliases, along with decltype, let us simplify code that uses function pointers:
// compares lengths of two strings
bool lengthCompare(const string &, const string &);
// Func and Func2 have function type
typedef bool Func(const string&, const string&);
typedef decltype(lengthCompare) Func2; // equivalent type
// FuncP and FuncP2 have pointer to function type
typedef bool(*FuncP)(const string&, const string&);
typedef decltype(lengthCompare) *FuncP2; // equivalent type
Both Func
and Func2
are function types, whereas FuncP
and FuncP2
are pointer types.
It is important to note that decltype returns the function type; the automatic conversion to pointer is not done.
Returning a Pointer to Function
As with arrays, we can’t return a function type but can return a pointer to a function type.
using F = int(int*, int); // F is a function type, not a pointer
using PF = int(*)(int*, int); // PF is a pointer type
PF f1(int); // ok: PF is a pointer to function; f1 returns a pointer to function
F f1(int); // error: F is a function type; f1 can't return a function
F *f1(int); // ok: explicitly specify that the return type is a pointer to function
int (*f1(int))(int*, int);
auto f1(int) -> int (*)(int*, int);
Using auto or decltype for Function Pointer Types
string::size_type sumLength(const string&, const string&);
string::size_type largerLength(const string&, const string&);
// depending on the value of its string parameter,
// getFcn returns a pointer to sumLength or to largerLength
decltype(sumLength) *getFcn(const string &);