[C++ Primer Plus] Chapter 7 Function - C++ Programming Module

7.1 Basic knowledge of functions

7.1.1 Functions that return a value

  1. If the declared return type is double and the function returns an int expression, the int value will be cast to type double.
  2. Return type: Can't be an array, but can be anything else - integers, floats, pointers, even structures and objects! (Interestingly, although C++ functions cannot return arrays directly, they can return arrays as part of structures or objects.
  3. The function ends after executing the return statement. If the function contains multiple return statements (for example, they are in different if else options), the function ends after executing the first return statement encountered.

7.1.2 Function prototype (function declaration)

Why do you need a prototype?
The prototype describes the function's interface to the compiler, that is, it tells the compiler the type (if any) of the function's return value and the type and number of parameters.

Syntax for function prototypes:
double add(double a, double b); or double add(double, double);
The variable names in the prototype do not have to be the same as in the definition, and can be omitted: the variable names in the prototype are equivalent to placeholders, so they don't have to be the same as the variable names in the function definition.

The role of the function prototype:

  1. The compiler correctly handles function return values;
  2. The compiler checks that the correct number of arguments are used;
  3. The compiler checks that the correct parameter types are used. If not, cast to the correct type (if possible).

7.2 Passing function arguments by value

Operations performed by cube(side) will not affect the side data in main( ), because cube( ) uses a copy of side, not the original data.
If two parameters of a function are of the same type, the type of each parameter must be specified separately, and the declarations cannot be combined like regular variables. int add(int a, int b);
(10 * 9)/(2 * 1)and (10 / 2)*(9 / 1), the former will calculate 90/2, giving 45, and the latter will calculate 5*9, giving 45. The two methods give the same result, but the median value (90) of the former is larger than that of the latter. The more factors there are, the greater the difference in median values. When numbers are very large, the strategy of alternating multiplication and division operations prevents intermediate results from exceeding the largest floating-point number.

7.3 Functions and arrays

7.3.1 Functions with array parameters

int sum_arr(int arr[], int n);// Pointer variable arr, the first parameter passes the array name as the address of the first element; the second parameter passes the length of the array

  1. arr is not actually an array, but a pointer!
  2. You can think of arr as an array when writing the rest of the function.
  3. Accepting an array name (address) as an argument, accesses the original array, not a copy of it.
  4. In order to tell the array type and number of elements to the array handler function, pass them in two different parameters instead of trying to use square bracket notation to pass the array length.

arr[i] == *(ar + i)// values ​​in two notations
&arr[i] == ar + i// addresses in two notations
Remember that adding 1 to a pointer (including the array name) actually adds a value equal to the length (in bytes) of the type pointed to by the pointer. For traversing an array, using pointer addition and array subscripting are equivalent.

#include <iostream>

const int ArSize = 8;
int sum_arr(int arr[], int n);  // 指针变量arr

int main()
{
    
    
    int cookies[ArSize] = {
    
    1,2,4,8,16,32,64,128};
    std::cout << cookies << " = array address, ";   // 有些C++实现以十进制而不是十六进制格式显示地址,还有些编译器以十六进制显示地址时,会加上前缀0x。
    std::cout << sizeof cookies << " = sizeof cookies\n";   // 32, sizeof cookies是整个数组的长度
    int sum = sum_arr(cookies, ArSize); // cookies是数组名,而根据C++规则,cookies是其第一个元素的地址,因此函数传递的是地址.
                                        // 接受数组名参数的函数访问的是原始数组,而不是其副本
    std::cout << "Total cookies eaten: " << sum << std::endl;
    sum = sum_arr(cookies, 3);          // a lie, 通过第二个参数获知数组中的元素数量
    std::cout << "First three eaters ate " << sum << " cookies.\n";
    sum = sum_arr(cookies + 4, 4);      // another lie, 提供假的数组起始位置,也可以使用 &cookies[4]
    std::cout << "Last four eaters ate " << sum << " cookies.\n";
    return 0;
}
// return the sum of an integer array
int sum_arr(int arr[], int n)   // 必须显式传递数组长度, arr指针本身并没有指出数组的长度
{
    
    
    int total = 0;
    std::cout << arr << " = arr, ";
    // some systems require a type cast: unsigned (arr)
    std::cout << sizeof arr << " = sizeof arr\n";   // 4, sizeof arr只是指针变量的长度
    for (int i = 0; i < n; i++)
        total = total + arr[i];
    return total;
}

out:

0xe7793ffb50 = array address, 32 = sizeof cookies
0xe7793ffb50 = arr, 8 = sizeof arr
Total cookies eaten: 255
0xe7793ffb50 = arr, 8 = sizeof arr
First three eaters ate 7 cookies.
0xe7793ffb60 = arr, 8 = sizeof arr
Last four eaters ate 240 cookies.

7.3.2 Protecting arrays with const

To prevent the function from modifying the contents of the array unintentionally, the keyword const can be used when declaring the formal parameters:
void show_array(const double ar[], int n);// Using const means that show_array( ) cannot modify the values ​​in the array passed to it.

7.3.3 Functions using ranges of arrays

Specifies the range of elements, which can be done by passing two pointers: one to identify the beginning of the array, and the other to identify the end of the array.

#include <iostream>
const int ArSize = 8;
int sum_arr(const int * begin, const int * end);
int main()
{
    
    
    using namespace std;
    int cookies[ArSize] = {
    
    1,2,4,8,16,32,64,128};
    // some systems require preceding int with static to
    // enable array initialization
    int sum = sum_arr(cookies, cookies + ArSize);
    cout << "Total cookies eaten: " << sum << endl;
    sum = sum_arr(cookies, cookies + 3);        // first 3 elements
    cout << "First three eaters ate " << sum << " cookies.\n";
    sum = sum_arr(cookies + 4, cookies + 8);    // last 4 elements
    cout << "Last four eaters ate " << sum << " cookies.\n";
    return 0;
}
// return the sum of an integer array
int sum_arr(const int * begin, const int * end) 
{
    
    
    const int * pt;
    int total = 0;
    for (pt = begin; pt != end; pt++)
        total = total + *pt;
    return total;
}

7.3.4 Using const pointer parameters

The first way is to make a const pointer point to a const object, which prevents the pointer from being used to modify the pointed value, and the
second way is to declare the pointer itself as const, which prevents changing the location pointed to by the pointer.

// 1、能将常规变量的地址赋给指向const的指针
int age = 39;
const int * pt = &age;	// const在前,不能使用pt指针来修改age的值,但是可以将一个新地址赋给pt
age = 20;				// 可以直接通过age变量来修改age的值
int sage = 80;
pt = &sage;				// const在前,不能使用pt指针来修改指向的值,但是可以将一个新地址赋给pt

int sloth = 3;
const int * ps = &sloth; 		// a pointer to const int,不允许使用ps来修改sloth的值,但允许将ps指向另一个位置
int * const finger = &sloth; 	// a const pointer to int,finger只能指向sloth,但允许使用finger来修改sloth的值

// 2、能将const变量的地址赋给指向const的指针
const float g_earth = 9.80;
const float * pe = &g_earth; // VALID

// 3、不能将const的地址赋给常规指针

7.3.4 Functions and two-dimensional arrays

int sum(int ar2[][4], int size);	// ar2是指针而不是数组, 它指向由4个int组成的数组
int sum(int (*ar2)[4], int size);	// int (*ar2)[4]将声明一个指向由4个int组成的数组的指针

// data是一个数组名,该数组有3个元素。第一个元素本身是一个数组,有4个int值组成。
int data[3][4] = {
    
    {
    
    1,2,3,4}, {
    
    9,8,7,6}, {
    
    2,4,6,8}};	// data的类型是指向由4个int组成的数组的指针。

int total = sum(data, 3);			// 函数调用

int sum(int ar2[][4], int size)		// 函数定义
{
    
    
    int total = 0;
    for (int r = 0; r < size; r++)
        for (int c = 0; c < 4; c++)
            total += ar2[r][c];		// 将ar2看作是一个二维数组的名称使用,ar2[r][c] == *(*(ar2 + r) + c)
    return total;
}

ar2[r][c] == *(*(ar2 + r) + c) 	// same thing
ar2 							// pointer to first row of an array of 4 int
ar2 + r 						// pointer to row r (an array of 4 int)
*(ar2 + r) 						// row r (an array of 4 int, hence the name of an array,
                                // thus a pointer to the first int in the row, i.e., ar2[r]
*(ar2 +r) + c 					// pointer int number c in row r, i.e., ar2[r] + c
*(*(ar2 + r) + c	 			// value of int number c in row r, i.e. ar2[r][c]

7.4 Functions and C-strings

A string is passed as a parameter, but what is actually passed is the address of the first character of the string.
This means that string function prototypes should declare their formal parameters representing strings to be of type char *.
You can use const to prohibit modification of string parameters.

7.4.1 Functions that take C-style strings as arguments

If a string is passed as a parameter to a function, there are three ways to represent the string:

  1. char array;
  2. String constants enclosed in quotes (also known as string literals);
  3. A char pointer to be set to the address of the string.
// 使用一个函数来计算特定的字符在字符串中出现的次数。

#include <iostream>
using namespace std;

unsigned int c_in_str(const char * str, char ch);

int main(void)
{
    
    
    char mmm[15] = "minimum";
    char *wail = "ululate";
    unsigned int ms = c_in_str(mmm, 'm');
    unsigned int us = c_in_str(wail, 'u');

    cout << ms << " m characters in " << mmm << endl;
    cout << us << " u characters in " << wail << endl;

    return 0;
}

unsigned int c_in_str(const char * str, char ch)
{
    
    
    unsigned int count = 0;

    while (*str)    // quit when *str is '\0'
    {
    
    
        if (*str == ch)
            count++;
        str++;      // move pointer to next char
    }
    return count;
}

7.4.2 Functions returning C-style strings

Returns the address of the string

// 定义了一个名为buildstr( )的函数,该函数返回一个指针。
// 该函数接受两个参数:一个字符和一个数字。
// 函数使用new创建一个长度与数字参数相等的字符串,然后将每个元素都初始化为该字符。
// 最后,返回指向新字符串的指针。

#include <iostream>
char * buildstr(char c, int n); // prototype
int main()
{
    
    
    using namespace std;
    int times;
    char ch;
    cout << "Enter a character: ";
    cin >> ch;
    cout << "Enter an integer: ";
    cin >> times;
    char *ps = buildstr(ch, times);
    cout << ps << endl;
    delete [] ps;               // free memory
    ps = buildstr('+', 20);     // reuse pointer
    cout << ps << "-DONE-" << ps << endl;
    delete [] ps;               // free memory
    return 0;
}
// builds string made of n c characters
char * buildstr(char c, int n)
{
    
    
    char * pstr = new char[n + 1];	// 包含n个字符的字符串,需要能够存储n + 1个字符的空间
    pstr[n] = '\0';             // terminate string
    while (n-- > 0)				// 在最后一轮循环开始时,n的值为1。先比较再--
        pstr[n] = c;            // fill rest of string, 最后一个pstr[0]
        // 变量pstr的作用域为buildstr函数内,因此该函数结束时,pstr(而不是字符串)使用的内存将被释放。
    return pstr;	// 返回字符串的地址
}

out:

Enter a character:V
Enter an integer:46
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
++++++++++++++++++++-DONE-++++++++++++++++++++

7.5 Functions and structures

The structure name is just the name of the structure. To get the address of the structure, the address operator &
structure must be used as a parameter to pass to the function. There are three ways to represent the structure:

  1. Pass structures by value. (when the structure is relatively small)
  2. Pass the address of the structure, then use the pointer to access the contents of the structure.
  3. Pass by reference.

7.5.1 Passing and returning structs by value

Example 1: Develop a structure for representing time values, and then develop a function that takes two such structures as arguments and returns a structure representing the sum of the arguments.

// 开发用于表示时间值的结构,然后再开发一个函数,它接受两个这样的结构为参数,并返回表示参数的和的结构。 
// 要将两个时间相加,应首先将分钟成员相加。然后通过整数除法 (除数为60)得到小时值,通过求模运算符(%)得到剩余的分钟数。

// 1、定义结构
struct travel_time
{
    
    
    int hours;
    int mins;
};

// 2、返回两个这种结构的总和的sum( )函数的原型
travel_time sum(travel_time t1, travel_time t2);	// 返回值的类型应为travel_time,两个参数也应为这种类型。
// 开发用于表示时间值的结构,然后再开发一个函数,它接受两个这样的结构为参数,并返回表示参数的和的结构。
// 要将两个时间相加,应首先将分钟成员相加。然后通过整数除法 (除数为60)得到小时值,通过求模运算符(%)得到剩余的分钟数。

#include <iostream>
using namespace std;
struct travel_time
{
    
    
    int hours;
    int mins;
};
const int Mins_per_hr = 60;

travel_time sum(travel_time t1, travel_time t2);
void show_time(travel_time t);

int main(void)
{
    
    
    travel_time day1 = {
    
    5, 45};
    travel_time day2 = {
    
    4, 55};
    travel_time trip = sum(day1, day2);
    cout << "Two-day total: ";
    show_time(trip);
    return 0;
}

travel_time sum(travel_time t1, travel_time t2)
{
    
    
    travel_time total;
    total.mins = (t1.mins + t2.mins) % Mins_per_hr;
    total.hours = t1.hours + t2.hours + (t1.mins + t2.mins) / Mins_per_hr;
    return total;
}

void show_time(travel_time t)
{
    
    
    cout << t.hours << " hours, " << t.mins << " minutes\n";
}

Example 2: Define two structures to represent two different methods of describing locations, and then develop a function to convert one format to another and display the result.

// 定义两个结构,用于表示两种不同的描述位置的方法,然后开发一个函数,将一种格式转换为另一种格式,并显示结果。
// 1、建立直角坐标系的结构
struct rect
{
    
    
    double x; // horizontal distance from origin
    double y; // vertical distance from origin
};

// 2、建立极坐标系得结构
struct polar
{
    
    
    double distance; // distance from origin
    double angle; // direction from origin
};

// 3、显示极坐标的函数
// 将弧度值转换为角度值。这意味着需要将弧度值乘以180/——约为57.29577951
// show polar coordinates, converting angle to degrees
void show_polar (polar dapos)	// 将一个polar结构传递给该函数时,该结构的内容将被复制到dapos结构中
{
    
    
    using namespace std;
    const double Rad_to_deg = 57.29577951;
    cout << "distance = " << dapos.distance;
    cout << ", angle = " << dapos.angle * Rad_to_deg;
    cout << " degrees\n";
}

// 4、编写一个将直角坐标转换为极坐标的函数。
#include <cmath>
polar rect_to_polar(rect xypos) // 该函数接受一个rect参数,并返回一个polar结构
{
    
    
    polar answer;
    answer.distance = sqrt( xypos.x * xypos.x + xypos.y * xypos.y);	// 根据毕达哥拉斯定理,使用水平和垂直坐标来计算距离
    answer.angle = atan2(xypos.y, xypos.x);	// atan2( )函数可根据x和y的值计算角度
    return answer; // returns a polar structure
}
// strctfun.cpp -- functions with a structure argument
#include <iostream>
#include <cmath>
// structure declarations
struct polar
{
    
    
    double distance; // distance from origin
    double angle; // direction from origin
};
struct rect
{
    
    
    double x; // horizontal distance from origin
    double y; // vertical distance from origin
};
// prototypes
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
int main()
{
    
    
    using namespace std;
    rect rplace;
    polar pplace;
    cout << "Enter the x and y values: ";
    while (cin >> rplace.x >> rplace.y) // slick use of cin
    {
    
    
        pplace = rect_to_polar(rplace);
        show_polar(pplace);
        cout << "Next two numbers (q to quit): ";
    }
    // 如果程序在输入循环后还需要进行输入,则必须使用 cin.clear( )重置输入,然后还可能需要通过读取不合法的输入来丢弃它们。
    cout << "Done.\n";
    return 0;
}
// convert rectangular to polar coordinates
polar rect_to_polar(rect xypos)
{
    
    
    using namespace std;
    polar answer;
    answer.distance = sqrt( xypos.x * xypos.x + xypos.y * xypos.y);
    answer.angle = atan2(xypos.y, xypos.x);
    return answer; // returns a polar structure
}
// show polar coordinates, converting angle to degrees
void show_polar (polar dapos)
{
    
    
    using namespace std;
    const double Rad_to_deg = 57.29577951;
    cout << "distance = " << dapos.distance;
    cout << ", angle = " << dapos.angle * Rad_to_deg;
    cout << " degrees\n";
}

7.5.2 Passing the address of a structure

Rewrite the show_polar( ) function to use a pointer to the structure. Three places need to be modified:

  1. When calling a function, pass it the address of the structure (&pplace) instead of the structure itself (pplace);
  2. Declare the formal parameter as a pointer to polar, that is, of type polar *. Since the function should not modify the structure, the const modifier is used;
  3. Since the formal parameter is a pointer and not a structure, the indirect membership operator (->) should be used instead of the membership operator (period).
struct polar	// 结构定义
{
    
    
    double distance; 	
    double angle; 		
};

polar pplace;	// 结构声明

show_polar(&pplace);	// 函数调用

void show_polar(const polar * pda)	// 函数定义
{
    
    
    using namespace std;
    const double Rad_to_deg = 57.29577951;
    cout << "distance = " << pda->distance;
    cout << ", angle = " << pda->angle * Rad_to_deg;
    cout << " degrees\n";
}
// strctptr.cpp -- functions with pointer to structure arguments
#include <iostream>
#include <cmath>
// structure templates
struct polar
{
    
    
    double distance; // distance from origin
    double angle; // direction from origin
};
struct rect
{
    
    
    double x; // horizontal distance from origin
    double y; // vertical distance from origin
};
// prototypes
void rect_to_polar(const rect * pxy, polar * pda);
void show_polar (const polar * pda);
int main()
{
    
    
    using namespace std;
    rect rplace;
    polar pplace;
    cout << "Enter the x and y values: ";
    while (cin >> rplace.x >> rplace.y)
    {
    
    
        rect_to_polar(&rplace, &pplace); // pass addresses
        show_polar(&pplace); // pass address
        cout << "Next two numbers (q to quit): ";
    }
    cout << "Done.\n";
    return 0;
}
// show polar coordinates, converting angle to degrees
void show_polar (const polar * pda)
{
    
    
    using namespace std;
    const double Rad_to_deg = 57.29577951;
    cout << "distance = " << pda->distance;
    cout << ", angle = " << pda->angle * Rad_to_deg;
    cout << " degrees\n";
}
// convert rectangular to polar coordinates
// 函数不返回一个新的结构,而是修改调用函数中已有的结构。
// 因此,虽然第一个参数是const指针,但第二个参数却不是。
void rect_to_polar(const rect * pxy, polar * pda)
{
    
    
    using namespace std;
    pda->distance =
            sqrt(pxy->x * pxy->x + pxy->y * pxy->y);
    pda->angle = atan2(pxy->y, pxy->x);
}

7.6 Functions and string objects

Example: Declare an array of string objects and pass the array to a function to display its contents:

// 声明一个string对象数组,并将该数组传递给一个函数以显示其内容:
#include <iostream>
#include <string>
using namespace std;
const int SIZE = 5;
void display(const string sa[], int n);
int main()
{
    
    
    string list[SIZE]; 	// 数组list的每个元素都是一个string对象
    cout << "Enter your " << SIZE << " favorite astronomical sights:\n";
    for (int i = 0; i < SIZE; i++)
    {
    
    
        cout << i + 1 << ": ";
        getline(cin,list[i]);
    }
    cout << "Your list:\n";
    display(list, SIZE);
    return 0;
}
void display(const string sa[], int n)	// 形参sa是一个指向string对象的指针
{
    
    
    for (int i = 0; i < n; i++)
        cout << i + 1 << ": " << sa[i] << endl;
}

7.7 Functions and array objects

// 1、声明一个array对象
const int Seasons = 4;
const std::array<std::string, Seasons> Snames = {
    
    "Spring", "Summer", "Fall", "Winter"};

// 2、声明一个用array对象做参数的函数
void show(std::array<double, 4> da); 	// da an object
void fill(std::array<double, 4> * pa); 	// pa a pointer to an object
//arrobj.cpp -- functions with array objects (C++11)
#include <iostream>
#include <array>
#include <string>
// constant data
const int Seasons = 4;
const std::array<std::string, Seasons> Snames = {
    
    "Spring", "Summer", "Fall", "Winter"};
// function to modify array object
void fill(std::array<double, Seasons> * pa);
// function that uses array object without modifying it
void show(std::array<double, Seasons> da);
int main()
{
    
    
    std::array<double, Seasons> expenses;
    fill(&expenses);
    show(expenses);
    return 0;
}
void fill(std::array<double, Seasons> * pa)
{
    
    
    using namespace std;
    for (int i = 0; i < Seasons; i++)
    {
    
    
        cout << "Enter " << Snames[i] << " expenses: ";
        cin >> (*pa)[i];
    } 
}
void show(std::array<double, Seasons> da)
{
    
    
    using namespace std;
    double total = 0.0;
    cout << "\nEXPENSES\n";
    for (int i = 0; i < Seasons; i++)
    {
    
    
        cout << Snames[i] << ": $" << da[i] << endl;
        total += da[i];
    }
    cout << "Total Expenses: $" << total << endl;
}

out:

Enter Spring expenses:20
Enter Summer expenses:12
Enter Fall expenses:11.1
Enter Winter expenses:23

EXPENSES
Spring: $20
Summer: $12
Fall: $11.1
Winter: $23
Total Expenses: $66.1

7.8 Calling a function on itself (recursion)

C++ does not allow main( ) to call itself.

7.8.1 Recursion involving a recursive call

// 如果recurs( )进行了5次递归调用,则第一个statements1部分将按函数调用的顺序执行5次,
// 然后statements2部分将以与函数调用相反的顺序执行5次。
// 进入5层递归后,程序将沿进入的路径返回。
void recurs(argumentlist) 
{
    
    
    statements1
    if (test)	// test最终将为false,调用链将断开。
        recurs(arguments);
    statements2
}
// 注意,每个递归调用都创建自己的一套变量,
// 因此当程序到达第5次调用时,将有5个独立的n变量,其中每个变量的值都不同。
// 在Counting down阶段和Kaboom阶段的相同层级,n的地址相同.

#include <iostream>
void countdown(int n);
int main()
{
    
    
    countdown(4); // call the recursive function
    return 0;
}
void countdown(int n)
{
    
    
    using namespace std;
    cout << "Counting down ... " << n << " (n at " << &n << ")" << endl;
    if (n > 0)
        countdown(n-1); // function calls itself
    cout << n << ": Kaboom!" << " (n at " << &n << ")" << endl;
}

out:

Counting down ... 4 (n at 0xc848bffc60)
Counting down ... 3 (n at 0xc848bffc30)
Counting down ... 2 (n at 0xc848bffc00)
Counting down ... 1 (n at 0xc848bffbd0)
Counting down ... 0 (n at 0xc848bffba0)
0: Kaboom! (n at 0xc848bffba0)
1: Kaboom! (n at 0xc848bffbd0)
2: Kaboom! (n at 0xc848bffc00)
3: Kaboom! (n at 0xc848bffc30)
4: Kaboom! (n at 0xc848bffc60)

7.8.2 Recursion with multiple recursive calls

Recursion is useful when you need to keep dividing a job into two smaller, similar jobs.

// 递归函数subdivide( )使用一个字符串,该字符串除两端为 | 字符外,其他全部为空格。
// main函数使用循环调用 subdivide( )函数6次,每次将递归层编号加1,并打印得到的字符串。
// 这样,每行输出表示一层递归。
#include <iostream>
const int Len = 66;
const int Divs = 6;
void subdivide(char ar[], int low, int high, int level);
int main()
{
    
    
    char ruler[Len];
    int i;
    for (i = 1; i < Len - 2; i++)
        ruler[i] = ' ';
    ruler[Len - 1] = '\0';
    int max = Len - 2;
    int min = 0;
    ruler[min] = ruler[max] = '|';
    std::cout << ruler << std::endl;
    for (i = 1; i <= Divs; i++)
    {
    
    
        subdivide(ruler,min,max, i);
        std::cout << ruler << std::endl;
        for (int j = 1; j < Len - 2; j++)
            ruler[j] = ' '; // reset to blank ruler
    }
    return 0;
}
void subdivide(char ar[], int low, int high, int level)
{
    
    
    if (level == 0) // 使用变量level来控制递归层,当level为0时,该函数将不再调用自己。
        return;
    int mid = (high + low) / 2;
    ar[mid] = '|';
    subdivide(ar, low, mid, level - 1);
    subdivide(ar, mid, high, level - 1);    // 函数调用自身时,将把level减1
}

out:

|                                                               |
|                               |                               |
|               |               |               |               |
|       |       |       |       |       |       |       |       |
|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

7.9 Functions and pointers

7.9.1 Function Pointers

The address of a function is the start address of the memory where its machine language code is stored.

  1. Get the address of a function; use the function name
  2. declare a function pointer;
  3. Use function pointers to call functions.
double pam(int);	// 1、函数名pam是这个函数的地址

double (*pf)(int);	// 2、声明一个函数指针pf,(*pf)扮演的角色与函数名相同
pf = pam;			// 2、正确地声明pf后,便可以将相应函数的地址赋给它
                    // pam( )的特征标和返回类型必须与pf相同

double x = pam(4); 		// 3、使用函数名来调用函数
double y = (*pf)(5); 	// 3、使用函数指针来调用函数方法1
double y = pf(5); 		// 3、使用函数指针来调用函数方法2

void estimate(int lines, double (*pf)(int));	// 4、声明一个使用函数指针做参数的函数
estimate(50, pam);		// 5、函数调用时传递函数pam的地址作为参数

Why are pf and (*pf) equivalent?
One school of thought says that since pf is a function pointer and *pf is a function, (*pf)( ) should be used as a function call. Another school of thought says that since a function name is a pointer to that function, a pointer to a function should behave like a function name, and therefore pf( ) should be used as a function call.

// 两次调用estimate( )函数,一次传递betsy( )函数的地址,另一次则传递pam( )函数的地址。
// 在第一种情况下,estimate( )使用betsy( )计算所需的小时数;
// 在第二种情况下,estimate( )使用pam( )进行计算。

#include <iostream>
double betsy(int);
double pam(int);
// second argument is pointer to a type double function that
// takes a type int argument
void estimate(int lines, double (*pf)(int));
int main()
{
    
    
    using namespace std;
    int code;
    cout << "How many lines of code do you need? ";
    cin >> code;
    cout << "Here's Betsy's estimate:\n";
    estimate(code, betsy);
    cout << "Here's Pam's estimate:\n";
    estimate(code, pam);
    return 0;
}
double betsy(int lns)
{
    
    
    return 0.05 * lns;
}
double pam(int lns)
{
    
    
    return 0.03 * lns + 0.0004 * lns * lns;
}
void estimate(int lines, double (*pf)(int))
{
    
    
    using namespace std;
    cout << lines << " lines will take ";
    cout << (*pf)(lines) << " hour(s)\n";
}

out:

How many lines of code do you need?100
Here's Betsy's estimate:
100 lines will take 5 hour(s)
Here's Pam's estimate:
100 lines will take 7 hour(s)

7.9.2 Function pointer, pointer function, function pointer array

double (*pf)(int); 	// (*pf)(int)意味着pf是一个指向函数的指针
double *pf(int); 	// *pf(int)意味着pf( )是一个返回指针的函数
// 1、指针函数原型
// 下面这些指针函数原型表示方法本质上都是一样的:
// *ar 与 ar[] 含义相同,且在函数原型中,可以省略标识符
const double * f1(const double ar[], int n);
const double * f2(const double [], int);
const double * f3(const double *, int);
const double * f1(const double ar[], int n){
    
    } // 定义时不能省略标识符

// 2、声明一个指针,指向这个指针函数
const double * (*p1)(const double *, int) = f1;	// 并初始化
auto p2 = f2;	// 使用C++11的自动类型推断功能

// (*p1) (av, 3)和p2(av, 3)都调用指向的函数(这里为f1()和f2()),并将av和3作为参数。
// 因此,显示的是这两个函数的返回值。返回值的类型为const double *(即double值的地址), 
// 因此在每条cout语句中,前半部分显示的都是一个double值的地址。后半部分是值。
cout << (*p1)(av,3) << ": " << *(*p1)(av,3) << endl;
cout << p2(av,3) << ": " << *p2(av,3) << endl;

// 3、用一个函数指针数组使用这三个指针函数
// 运算符[]的优先级高于*,因此*pa[3]表明pa是一个包含三个指针的数组。
// pa中每个指针都指向(将const double *和int作为参数,并返回一个const double *)的函数。
const double * (*pa[3])(const double *, int) = {
    
    f1,f2,f3};
auto pb = pa;	// 数组名是指向第一个元素的指针,因此pa和pb都是指向函数指针的指针。

// pa[i]和pb[i]都表示数组中的指针,因此可将任何一种函数调用表示法用于它们
const double * px = pa[0](av,3);
const double * py = (*pb[1])(av,3);
// 要获得指向的double值,可使用运算符*
double x = *pa[0](av,3);
double y = *(*pb[1])(av,3);

// 4、创建指向整个数组的指针
// 由于数组名pa是指向函数指针的指针,因此指向数组的指针将是这样的指针,即它指向指针的指针。
(*pd)[3]	// pd是一个指针,它指向一个包含三个元素的数组
const double *(*(*pd)[3])(const double *, int) = &pa;
// 既然pd指向数组,那么*pd就是数组,而(*pd)[i]是数组中的元素,即函数指针
// 简单函数调用:使用(*pd)i来调用函数,而*(*pd)i是返回的指针指向的值。
// 使用指针调用函数的语法:使用(*(*pd)[i])(av,3)来调用函数,而*(*(*pd)[i]) (av,3)是指向的double值。

The difference between the above four ways of playing pa and &pa:

  1. pa is the address of the first element of the array, ie &pa[0], is the address of a single pointer.
  2. &pa is the address of the entire array (that is, a block of three pointers).
  3. Numerically, pa and &pa have the same value, but their types are different:
  4. Difference 1: pa+1 is the address of the next element in the array, and &pa+1 is the address of a 12-byte memory block behind the array pa (here, the address is assumed to be 4 bytes).
  5. Difference 2: To get the value of the first element, you only need to dereference pa once, but you need to dereference &pa twice.**&pa == *pa == pa[0]

7.9.3 Simplification with typedefs

  1. The preprocessor creates type aliases: #define A Bsubstitute A for B
  2. The keyword typedef creates a type alias: typedef A B;replace A with B
// 将别名当做标识符进行声明,并在开头使用关键字typedef
typedef const double *(*p_fun)(const double *, int); 	// p_fun now a type name
p_fun p1 = f1; 	// p1 points to the f1() function

// 然后使用这个别名来简化代码
p_fun pa[3] = {
    
    f1,f2,f3}; 	// pa an array of 3 function pointers
p_fun (*pd)[3] = &pa; 		// pd points to an array of 3 function pointers

7.10 Summary

  1. Functions are the programming modules of C++. To use a function, you must provide a definition and a prototype, and call the function. A function definition is the code that implements the functionality of the function; a function prototype describes the interface of the function: the number and kind of values ​​passed to the function and the return type of the function. A function call causes a program to pass parameters to a function and execute the function's code.
  2. By default, C++ functions pass parameters by value. This means that the formal parameters in a function definition are new variables that are initialized to the values ​​provided by the function call. Therefore, C++ functions preserve the integrity of the original data by using copies.
  3. C++ treats the array name parameter as the address of the first element of the array. Technically, this is still pass by value, since the pointer is a copy of the original address, but the function will use the pointer to access the contents of the original array. The following two declarations are equivalent if and only if they declare the formal parameters of the function: typeName arr[];and typeName * arr;. Both declarations indicate that arr is a pointer to typeName, but when writing function code, you can use arr to access elements as you would an array name: arr[i]. Even when passing pointers, formal parameters can be declared as const pointers to protect the integrity of the original data. Since no information about the length of the array is transmitted when passing the address of the data, the length of the array is usually passed as a separate parameter. Alternatively, two pointers (one pointing to the beginning of the array and the other pointing to the next element after the end of the array) can be passed to specify a range, just like the algorithm used by the STL.
  4. C++ provides three ways to represent C-style strings: character arrays, string constants, and string pointers. Their types are both char* (char pointer), so they are passed to the function as parameters of type char*. C++ uses the null character (\0) to terminate strings, so string functions detect the null character to determine the end of a string.
  5. C++ also provides the string class for representing strings. Functions can accept string objects as parameters and return string objects as values. The method size( ) of the string class can be used to determine the length of the string it stores.
  6. C++ treats structs in exactly the same way as primitive types, which means you can pass structs by value and use them as function return types. However, if the structure is very large, it will be more efficient to pass a pointer to the structure while the function is able to work with the raw data. These considerations also apply to class objects.
  7. C++ functions can be recursive, that is, the function code can include calls to the function itself.
  8. C++ function names have the same effect as function addresses. By taking a function pointer as an argument, you pass the name of the function to call.

7.11 Review Questions

// 2
double doctor(const char *str); // e doctor( )接受一个字符串参数(不能修改该字符串),并返回一个double值。
char * plot(map * pa);          // g plot( )将map结构的指针作为参数,并返回一个字符串。

// 3 编写一个接受3个参数的函数:int数组名、数组长度和一个int值,并将数组的所有元素都设置为该int值。
void function3(int arr[], int size, int value)
{
    
    
    for (int i=0; i < size; i++)
        arr[i] = value;
}

// 4 编写一个接受3个参数的函数:指向数组区间中第一个元素的指针、指向数组区间最后一个元素后面的指针以及一个int值,
// 并将数组中的每个元素都设置为该int值。
void function4(int *begin, int *end, int value)
{
    
    
    for (int *pt = begin, pt != end; pt++)
        *pt = value;
}
int arr[10];
function4(arr, arr+3, 100);	// 函数调用

// 5 编写将double数组名和数组长度作为参数,并返回该数组中最大值的函数。该函数不应修改数组的内容。
double function5(const double arry[], int size)
{
    
    
    double temp = arry[0];
    for (int i=1; i < size; i++)
        if (temp < arry[i])
            temp = arry[i];
    return temp;
}

// 6 为什么不对类型为基本类型的函数参数使用const限定符?
// 因为传递的是实参的副本,不改变实参本身的值。

// 7 C++程序可使用哪3种C-风格字符串格式?
char site[7] = {
    
    'R', 'U', 'N', 'O', 'O', 'B', '\0'};
char str[] = "hello world"; // 字符数组
char *pt = "hello world";   // 字符指针

// 8 编写一个函数将字符串中所有的c1都替换为c2,并返回替换次数。
int replace(char * str, char c1, char c2)
{
    
    
    int count = 0;
    while (*str)
    {
    
    
        if ((*str) == c1)
        {
    
    
            *str = c2;
            count++;
        }
        str++;
    }
    return count;
}

// 11 函数judge( )的返回类型为int,它将这样一个函数的地址作为参数:将const char指针作为参数,并返回一个int值。
int judge(int (*pf)(const char *)); // judge()参数是一个函数指针

// 12
struct applicant {
    
    
    char name[30];
    int credit_ratings[3];
};
// a.编写一个函数,它将application结构作为参数,并显示该结构的内容。
void function12a(application a)
{
    
    
    cout << a.name << endl;
    for (int i=0; i<3; i++)
        cout << a.credit_ratings[i] << endl;
}
// b.编写一个函数,它将application结构的地址作为参数,并显示该参数指向的结构的内容。
applicant pplace;	    // 结构声明
show_polar(&pplace);    // 函数调用
void  function12b(const applicant * pa)	// 函数定义
{
    
    
    cout << pa->name << endl;
    for (int i=0; i<3; i++)
        cout << pa->credit_ratings[i] << endl;
}

// 13 函数f1()和f2()的原型如下:
void f1(applicant * a);
const char * f2(const applicant * a1, const applicant * a2);

void (*p_f1)(applicant * a);            // *p_f1 现在是函数指针
typedef void (*p_f1)(applicant * a);    // *p_f1 现在是函数指针类型,和f1函数的类型一样
p_f1 p1=f1;                             // 将p1声明为指向f1的指针
p_f1 ap[5];                             // 将ap声明为一个数组,它包含5个类型与p1相同的指针;

const char * (*p_f2)(const applicant * a1, const applicant * a2);
typedef const char * (*p_f2)(const applicant * a1, const applicant * a2);
p_f2 p2=f2;
p_f2 (*pa)[10];     // 将pa声明为一个指针,它指向的数组包含10个类型与p2相同的指针。

9. What is the meaning of the expression *"pizza"? What about "taco"[2]?

The behavior of the character constant is the same as the array name
"pizza" is the address of the first element, so using the * operator will get the value of the first element, that is, the character p "
taco" is the address of the first element, "taco" [2] is the value of the second element, the character c.

10. C++ allows structures to be passed by value, as well as addresses of structures. If glitz is a struct variable, how to pass it by value? How to pass its address? What are the pros and cons of these two approaches?

  1. To pass it by value, just pass the struct name glitz. To pass its address, use the address-of operator &glitz.
  2. Passing by value will automatically preserve the original data, but at the cost of time and memory. Pass-by-address saves time and memory, but does not protect the original data unless const qualifiers are used on the function parameters.
  3. Also, passing by value means that regular struct member notation can be used, but passing a pointer requires the use of the indirect member operator.

7.12 Programming Exercises

// 5. 定义一个递归函数,接受一个整数参数,并返回该参数的阶乘。
// 前面讲过,3的阶乘写作3!,等于3*2!,依此类推;而0!被定义为 1。通用的计算公式是,如果n大于零,则n!=n*(n−1)!。
// 在程序中对该函数进行测试,程序使用循环让用户输入不同的值,程序将报告这些值的阶乘。

#include <iostream>
using namespace std;
long long recurs(int n);
int main(void)
{
    
    
    cout << "Please enter a number for factorial:";
    int num;
    long long result;
    while (cin>>num)
    {
    
    
        result = recurs(num);
        cout << num << "! = " << result << endl;
        cout << "Please enter a number against:";
    }
    return 0;
}

long long recurs(int n)
{
    
    
    if (n == 0)
        return 1;
    else
        return n * recurs(n-1);
}

// 7. 修改程序清单7.7中的3个数组处理函数,使之使用两个指针参数来表示区间。
// fill_array( )函数不返回实际读取了多少个数字,而是返回一个指针,该指针指向最后被填充的位置;
// 其他的函数可以将该指针作为第二个参数,以标识数据结尾。

#include <iostream>
const int Max = 5;
// function prototypes
double *fill_array(double *begin, double *end);
void show_array(double *begin, double *end);
void revalue(double r, double *begin, double *end);
int main()
{
    
    
    using namespace std;
    double properties[Max];
    double *pa = fill_array(properties, properties+Max);
    show_array(properties, pa);
    if (pa - properties > 0)
    {
    
    
        cout << "Enter revaluation factor:";
        double factor;
        while (!(cin >> factor)) // bad input
        {
    
    
            cin.clear();
            while (cin.get() != '\n')
                continue;
            cout << "Bad input; Please enter a number:";
        }
        revalue(factor, properties, pa);
        show_array(properties, pa);
    }
    cout << "Done.\n";
    cin.get();
    return 0;
}
double *fill_array(double *begin, double *end)
{
    
    
    using namespace std;
    double temp;
    double *pt;
    for (pt = begin; pt != end; pt++)
    {
    
    
        cout << "Enter value #" << (pt - begin) + 1 << ":";
        cin >> temp;
        if (!cin) // bad input
        {
    
    
            cin.clear();
            while (cin.get() != '\n')
                continue;
            cout << "Bad input; input process terminated.\n";
            break;
        }
        else if (temp < 0) // signal to terminate
            break;
        else
            *pt = temp;
    }
    return pt;
}
// the following function can use, but not alter,
// the array whose address is ar
void show_array(double *begin, double *end)
{
    
    
    using namespace std;
    for (double *pt = begin; pt != end; pt++)
    {
    
    
        cout << "Property #" << (pt - begin) + 1 << ": $";
        cout << *pt << endl;
    }
}
// multiplies each element of ar[] by r
void revalue(double r, double *begin, double *end)
{
    
    
    for (double *pt = begin; pt != end; pt++)
        *pt *= r;
}

Guess you like

Origin blog.csdn.net/qq_39751352/article/details/126671080