C++ Primer 第六章 函数

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

  1. top-level const on parameters are ignored
void fcn(const int i)

We can call fcn passing it either a const int or a plain int.


void fcn(const int i) { /* fcn can read but not write to i */ }
void fcn(int i) { /* . . . */ } // error: redefines fcn(int)

Because top-level consts are ignored, we can pass exactly the same types to either version of fcn.
The second version of fcn is an error.

6.2.4. Array Parameters

begin() and end() in Array C++ STL

// despite appearances, these three declarations of print are equivalent
// each function has a single parameter of type const int*
void print(const int*);
void print(const int[]); // shows the intent that the function takes an array
void print(const int[10]); // dimension for documentation purposes (at best)

int i = 0, j[2] = {0, 1};
print(&i); // ok: &i is int*
print(j); // ok: j is converted to an int* that points to j[0]
  • We cannot copy an array, and when we use an array it is (usually) converted to a pointer .
  • If we pass an array to print, that argument is automatically converted to a pointer to the first element in the array; the size of the array is irrelevant.

There are three common techniques used to manage pointer parameters.

  • Using a Marker to Specify the Extent of an Array
void print(const char *cp)
{
if (cp) // if cp is not a null pointer
	while (*cp) // so long as the character it points to is not a null character
		cout << *cp++; // print the character and advance the pointer
}
  • Using the Standard Library Conventions
void print(const int *beg, const int *end)
{
// print every element starting at beg up to but not including end
while (beg != end)
	cout << *beg++ << endl; // print the current element
							// and advance the pointer
}

int j[2] = {0, 1};
// j is converted to a pointer to the first element in j
// the second argument is a pointer to one past the end of j
print(begin(j), end(j)); 
  • Explicitly Passing a Size Parameter
void print(const int ia[], size_t size)
{
	// const int ia[] is equivalent to const int* ia
	// size is passed explicitly and used to control access to elements of ia
	for (size_t i = 0; i != size; ++i) {
		cout << ia[i] << endl;
	}
}

int j[] = { 0, 1 }; // int array of size 2
print(j, end(j) - begin(j));

Array Reference Parameters

// ok: parameter is a reference to an array; the dimension is part of the type
// We may call this function only for an array of exactly ten ints:
void print(int (&arr)[10])
{
	for (auto elem : arr)
		cout << elem << endl;
}

int i = 0, j[2] = {0, 1};
int k[10] = {0,1,2,3,4,5,6,7,8,9};
print(&i); // error: argument is not an array of ten ints
print(j); // error: argument is not an array of ten ints
print(k); // ok: argument is an array of ten ints

Passing a Multidimensional Array

Because we are dealing with an array of arrays, that element is an array, so the pointer is a pointer to an array.
The size of the second (and any subsequent) dimension is part of the element type and must be specified:

// matrix points to the first element in an array whose elements are arrays of ten ints
//declares matrix as a pointer to an array of ten ints
void print(int (*matrix)[10], int rowSize) { /* . . . */ }

// equivalent definition
void print(int matrix[][10], int rowSize) { /* . . . */ }

6.2.5. main: Handling Command-Line Options

prog -d -o ofile data0
// argv[0] contains the program’s name, not user input.
int main(int argc, char *argv[]) { ... }

// equivalent definition
int main(int argc, char **argv) { ... }
argv[0] = "prog"; // or argv[0] might point to an empty string
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;

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.

1

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 or nonconst version of a given type; such consts 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 the preprocessor 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 &);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值