7.1 函数基本知识
7.1.1 有返回值的函数
- 如果声明的返回类型为double,而函数返回一个int表达式,则该int值将被强制转换为 double类型。
- 返回类型:不能是数组,但可以是其他任何类型——整 数、浮点数、指针,甚至可以是结构和对象!(有趣的是,虽然C++函 数不能直接返回数组,但可以将数组作为结构或对象组成部分来返回。
- 函数在执行返回语句后结束。如果函数包含多条返回语句(例如, 它们位于不同的if else选项中),则函数在执行遇到的第一条返回语句后结束。
7.1.2 函数原型(函数声明)
为什么需要原型?
原型描述了函数到编译器的接口,也就是说,它将函数返回值的类型(如果有的话)以及参数的类型和数量告诉编译器。
函数原型的语法:
double add(double a, double b);
或者double add(double, double);
原型中的变量名不必与定义中的变量名相同,而且可以省略:原型中的变量名相当于占位符,因此不必与函数定义中的变量名相同。
函数原型的作用:
- 编译器正确处理函数返回值;
- 编译器检查使用的参数数目是否正确;
- 编译器检查使用的参数类型是否正确。如果不正确,则转换为正确的类型(如果可能的话)。
7.2 按值传递函数参数
cube(side)执行的操作将不会影响main( )中的side数据,因为 cube( )使用的是side的副本,而不是原来的数据。
如果函数的两个参数的类型相同,则必须分别指定每个参数的类型,而不能像声明常规变量那样,将声明组合在一起。int add(int a, int b);
(10 * 9)/(2 * 1)
和(10 / 2)*(9 / 1)
,前者将计算90/2,得到45,后者将计算为5*9,得到45。这两种方法得到的结果相同,但前者的中间值(90)大于后者。因子越多,中间值的差别就越大。当数字非常大时,交替进行乘除运算的策略可以防止中间结果超出最大的浮点数。
7.3 函数和数组
7.3.1 带数组参数的函数
int sum_arr(int arr[], int n);
// 指针变量arr,第一个参数传递数组名作为第一个元素的地址;第二个参数传递数组的长度
- arr实际上并不是数组,而是一个指针!
- 在编写函数的其余部分时,可以将arr看作是数组。
- 接受数组名(地址)作为参数,访问的是原始数组,而不是其副本。
- 为将数组类型和元素数量告诉数组处理函数,请通过两个不同的参数来传递它们,而不要试图使用方括号表示法来传递数组长度。
arr[i] == *(ar + i)
// values in two notations
&arr[i] == ar + i
// addresses in two notations
记住,将指针(包括数组名)加1,实际上是加上了一个与指针指向的类型的长度(以字节为单位)相等的值。对于遍历数组而言,使用指针加法和数组下标是等效的。
#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 用const保护数组
为防止函数无意中修改数组的内容,可在声明形参时使用关键字const:
void show_array(const double ar[], int n);
// 使用const意味着show_array( )不能修改传递给它的数组中的值。
7.3.3 使用数组区间的函数
指定元素区间(range),这可以通过传递两个指针来完成:一个指针标识数组的开头,另一个指针标识数组的尾部。
#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 使用const指针参数
第一种方法是让const指针指向一个const对象,这样可以防止使用该指针来修改所指向的值,
第二种方法是将指针本身声明为const,这样可以防止改变指针指向的位置。
// 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 函数和二维数组
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 函数和C-字符串
将字符串作为参数来传递,但实际传递的是字符串第一个字符的地址。
这意味着字符串函数原型应将其表示字符串的形参声明为 char *类型。
可以使用const 来禁止对字符串参数进行修改。
7.4.1 将C-风格字符串作为参数的函数
字符串作为参数传递给函数,则表示字符串的方式有三种:
- char数组;
- 用引号括起的字符串常量(也称字符串字面值);
- 被设置为字符串的地址的char指针。
// 使用一个函数来计算特定的字符在字符串中出现的次数。
#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 返回C-风格字符串的函数
返回字符串的地址
// 定义了一个名为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 函数和结构
结构名只是结构的名称,要获得结构的地址,必须使用地址运算符&
结构作为参数传递给函数,则表示结构的方式有三种:
- 按值传递结构。(当结构比较小时)
- 传递结构的地址,然后使用指针来访问结构的内容。
- 按引用传递。
7.5.1 按值传递结构和返回结构
例1:开发用于表示时间值的结构,然后再开发一个函数,它接受两个这样的结构为参数,并返回表示参数的和的结构。
// 开发用于表示时间值的结构,然后再开发一个函数,它接受两个这样的结构为参数,并返回表示参数的和的结构。
// 要将两个时间相加,应首先将分钟成员相加。然后通过整数除法 (除数为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";
}
例2:定义两个结构,用于表示两种不同的描述位置的方法,然后开发一个函数,将一种格式转换为另一种格式,并显示结果。
// 定义两个结构,用于表示两种不同的描述位置的方法,然后开发一个函数,将一种格式转换为另一种格式,并显示结果。
// 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 传递结构的地址
重新编写show_polar( )函数,使用指向结构的指针。需要修改三个地方:
- 调用函数时,将结构的地址(&pplace)而不是结构本身(pplace)传递给它;
- 将形参声明为指向polar的指针,即polar *类型。由于函数不应该修结构,因此使用了const修饰符;
- 由于形参是指针而不是结构,因此应间接成员运算符(->),而不是成员运算符(句点)。
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 函数和string对象
例:声明一个string对象数组,并将该数组传递给一个函数以显示其内容:
// 声明一个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 函数和array对象
// 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 调用自身的函数(递归)
C++不允许main( )调用自己。
7.8.1 包含一个递归调用的递归
// 如果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 包含多个递归调用的递归
在需要将一项工作不断分为两项较小的、类似的工作时,递归非常有用。
// 递归函数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 函数和指针
7.9.1 函数指针
函数的地址是存储其机器语言代码的内存的开始地址。
- 获取函数的地址; 使用函数名
- 声明一个函数指针;
- 使用函数指针来调用函数。
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的地址作为参数
为何pf和(*pf)等价呢?
一种学派认为,由于pf是函数指针,而 *pf 是函数,因此应将(*pf)( )用作函数调用。另一种学派认为,由于函数名是指向该函数的指针,指向函数的指针的行为应与函数名相似,因此应将pf( )用作函数调用使用。
// 两次调用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 函数指针、指针函数、函数指针数组
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值。
以上四种玩法pa和&pa之间的差别:
- pa是数组第一个元素的地址,即&pa[0],是单个指针的地址。
- &pa是整个数组(即三个指针块)的地址。
- 从数字上说,pa和&pa的值相同,但它们的类型不同:
- 差别1:pa+1为数组中下一个元素的地址,而&pa+1为数组pa后面一个12字节内存块的地址(这里假定地址为4字节)。
- 差别2:要得到第一个元素的值,只需对pa解除一次引用,但需要对&pa解除两次引用。
**&pa == *pa == pa[0]
7.9.3 使用typedef进行简化
- 预处理器创建类型别名:
#define A B
用 A 替代 B - 关键字 typedef 创建类型别名:
typedef A B;
用 B 替代 A
// 将别名当做标识符进行声明,并在开头使用关键字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 总结
- 函数是C++的编程模块。要使用函数,必须提供定义和原型,并调用该函数。函数定义是实现函数功能的代码;函数原型描述了函数的接口:传递给函数的值的数目和种类以及函数的返回类型。函数调用使得程序将参数传递给函数,并执行函数的代码。
- 在默认情况下,C++函数按值传递参数。这意味着函数定义中的形参是新的变量,它们被初始化为函数调用所提供的值。因此,C++函数通过使用拷贝,保护了原始数据的完整性。
- C++将数组名参数视为数组第一个元素的地址。从技术上讲,这仍然是按值传递的,因为指针是原始地址的拷贝,但函数将使用指针来访问原始数组的内容。当且仅当声明函数的形参时,下面两个声明才是等价的:
typeName arr[];
和typeName * arr;
。这两个声明都表明,arr是指向typeName的指针,但在编写函数代码时,可以像使用数组名那样使用arr来访问元素:arr[i]。即使在传递指针时,也可以将形参声明为const指针,来保护原始数据的完整性。由于传递数据的地址时,并不会传输有关数组长度的信息,因此通常将数组长度作为独立的参数来传递。另外,也可传递两个指针(其中一个指向数组开头,另一个指向数组末尾的下一个元素),以指定一个范围,就像STL使用的算法一样。 - C++提供了3种表示C-风格字符串的方法:字符数组、字符串常量和字符串指针。它们的类型都是char*(char指针),因此被作为char*类型参数传递给函数。C++使用空值字符(\0)来结束字符串,因此字符串函数检测空值字符来确定字符串的结尾。
- C++还提供了string类,用于表示字符串。函数可以接受string对象作为参数以及将string对象作为返回值。string类的方法size( )可用于判断其存储的字符串的长度。
- C++处理结构的方式与基本类型完全相同,这意味着可以按值传递结构,并将其用作函数返回类型。然而,如果结构非常大,则传递结构指针的效率将更高,同时函数能够使用原始数据。这些考虑因素也适用于类对象。
- C++函数可以是递归的,也就是说,函数代码中可以包括对函数本身的调用。
- C++函数名与函数地址的作用相同。通过将函数指针作为参数,可以传递要调用的函数的名称。
7.11 复习题
// 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、表达式*"pizza"的含义是什么?“taco” [2]呢?
字符常量的行为与数组名相同
"pizza"为第一个元素的地址,因此使用*运算符将得到第一个元素的值,即字符p
"taco"为第一个元素的地址,“taco”[2]为第二个元素的值,即字符c。
10、C++允许按值传递结构,也允许传递结构的地址。如果glitz是一个结构变量,如何按值传递它?如何传递它的地址?这两种方法有何利弊?
- 要按值传递它,只要传递结构名glitz即可。要传递它的地址,请使用地址运算符&glitz。
- 按值传递将自动保护原始数据,但这是以时间和内存为代价的。按地址传递可节省时间和内存,但不能保护原始数据,除非对函数参数使用了const限定符。
- 另外,按值传递意味着可以使 用常规的结构成员表示法,但传递指针则必须使用间接成员运算符。
7.12 编程练习
// 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;
}