创建自己的函数时,必须理清3个方面——定义、提供原型以及调用。
#include <iostream>
void simple();
int main()
{
using namespace std;
cout << "main() will call the simple () function:\n";
simple();
//执行函数simple()时,将停止执行main()函数中的代码,等函数simple()执行完毕后,再继续执行main()函数中的代码
cout << "main() is finished with the simple () function.\n";
return 0;
}
void simple()
{
using namespace std;
cout << " I'm but a simple function.\n";
}
main() will call the simple () function:
I'm but a simple function.
main() is finished with the simple () function.
定义函数
定义格式略,与c语言相仿,这里需要注意几个点:
(1)对于有返回值的函数,必须使用返回语句,返回值本身可以是变量、常量,或者表达式,但是类型必须是函数头声明的类型或者可以转化成其类型。
(2)c++对于返回值的类型有一定的限制,不能时数组,但可以是其他任何类型——整数、浮点数、指针,甚至可能是结构和对象。(不能直接返回数组,但是可以将数组作为结构对象或者对象组成部分来返回)。
(3)函数在执行返回语句后结束。如果函数包含多条返回语句(例如,它们位于不同的ifelse选项中),则函数在执行遇到的第一条返回语句后结束。
函数原型
#include <iostream>
void cheers(int);
//需要函数原型的原因:能提高效率,
//如果没有函数原型,在主函数中使用它时,编译器会搜索文件剩余部分时将必须停止对main()函数的编译。
//函数原型是一条语句,必须以分号结尾,函数原型不要求提供变量名(可以包括,也可以不包括),有类型列表即可,
double cube(double x);
//原型中的变量名相当于占位符,因此不必和函数定义中的变量名相同。
int main()
{
using namespace std;
cheers (5);
cout << "Give me a number: ";
double side;
cin >> side;
double volume = cube(side);
//由于cube()有返回值,因此main()可以将其用在赋值语句中。
cout << "A " << side << " -foot cube has a volume of ";
cout << volume << " cubic feet.\n";
cheers(cube(2));//在c++中可以自动将被传递的参数强制转化成期望的类型
//(当然前提是两者的转化是有意义的,例如这里就不能转化成指针或者结构)。这里2传递后被转化成2.0
return 0;
}
void cheers(int n)
{
using namespace std;
for (int i = 0; i < n; i++)
cout << " Cheers!";
cout << endl;
}
double cube(double x)
{
return x * x * x;
}
Cheers! Cheers! Cheers! Cheers! Cheers!
Give me a number: 8
A 8 -foot cube has a volume of 512 cubic feet.
Cheers! Cheers! Cheers! Cheers! Cheers! Cheers! Cheers! Cheers!
c++函数原型和ANSI原型
(1)在c++中函数原型必不可少(在ANSI C中是可选的)
(2)在c++中,括号为空与在括号中使用关键字void是等效的(也就是函数没有参数),在ANSI C中,括号为空意味着不支持参数(也就是会在后面定义参数列表,不是没有参数)。
在c++中,函数原型不指定参数列表时应使用省略号。例如,void say_bye(…);但一般仅当与接受可变参数的C函数(如printf())交互时才需要这样做。
原型的功能
(1)编译器正确处理函数返回值
(2)编译器检查使用的参数数目是否正确
(3)编译器检查使用的参数类型是否正确,如果正确,这转化成正确的类型。
当传递的参数与函数原型中规定的类型不匹配时,在c中,会导致一些错误,例如如果函数需要的是一个int值(16位),但是程序员传递了一个double值(64位),则程序只检查64中的前16位,并将它们解释成一个int值。但c++可以自动将传递的值转化成原型中指定的类型,条件是两个都输算术类型。
但是自动类型转化不能避免所有的错误,例如,将8.33E27传递给一个int类型的函数就不行,因为这样大的值不能正确转化成int类型,当较大的值转化成较小的类型时,有些编译器就会发出警告,指出会丢失数据。
并且仅当有意义时,才会数据转化,例如,原型不会将整数转化成结构或者指针。
函数参数和按值传递
用于接收传递值的变量被称为形参,传递给函数的值被称为实参。
c++使用参数来表示实参,使用参量来表示形参。
#include <iostream>
using namespace std;
void n_chars(char, int);
//原型中的变量名不必与定义中的变量名相同,并且可以省略。
//另外,就算函数两个参数的类型相同,则必须分别指定每个参数的类型,
//不能组合在一起,例如viod fufu(float a, float b)不能写成viod fufu(float a, b)
int main()
{
int times;
char ch;
cout << "Enter a character: ";
cin >> ch;//这里使用cin,而不是cin.get(ch)或者ch = cin.get()来读取一个字符,
//原因是cin>>可以跳过所有的空格和换行符,若使用cin.get()的话,当用于在此处键入字母并且按下enter(会生成一个换行符)时,
//当下面第18行输入一个数字时,cin.get()会读取这个换行符,当然可以使用编程的方法避免,但是最简单的还是使用cin>>。
while (ch != 'q')
{
cout << "Enter an integer: ";
cin >> times;
n_chars(ch, times);//按值传递,这里函数将创建变量c和n,并将传递ch和times的值赋值给c和n,使用的是变量ch和times的副本,不是原始的ch和times的数据。
cout << "\nEnter another character or press the"
" q-key to quit: ";
cin >> ch;
}
cout << "The value of times is " << times << ".\n";
cout << "Bye\n";
}
void n_chars(char c, int n)//函数可以有多个参数,在调用函数时,只需使用逗号隔开
{
while (n-- > 0)
cout << c;
}
Enter a character: w
Enter an integer: 50
wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
Enter another character or press the q-key to quit: a
Enter an integer: 20
aaaaaaaaaaaaaaaaaaaa
Enter another character or press the q-key to quit: q
The value of times is 20.
Bye
// lotto.cpp -- probability of winning
#include <iostream>
// Note: some implementations require double instead of long double
long double probability(unsigned numbers, unsigned picks);//unsigned int 简称unsigned
int main()
{
using namespace std;
double total, choices;
cout << "Enter the total number of choices on the game card and\n"
"the number of picks allowed:\n";
while ((cin >> total >> choices) && choices <= total)
{
cout << "You have one chance in ";
cout << probability(total, choices); // compute the odds
cout << " of winning.\n";
cout << "Next two numbers (q to quit): ";
}
cout << "bye\n";
// cin.get();
// cin.get();
return 0;
}
// the following function calculates the probability of picking picks
// numbers correctly from numbers choices
long double probability(unsigned numbers, unsigned picks)
{
long double result = 1.0; // here come some local variables
long double n;
unsigned p;
for (n = numbers, p = picks; p > 0; n--, p--)
result = result * n / p ;
return result;
}//probability函数使用了两种局部变量(result、p和n),形参(numbers和picks)
//形参和局部变量的区别就是形参就是从调用probability()函数那里获得自己的值,而其他变量时从函数中获得自己的值。
Enter the total number of choices on the game card and
the number of picks allowed:
50 6
You have one chance in 1.58907e+07 of winning.
Next two numbers (q to quit): q
bye
函数和一维数组
函数和数组的结合,只需要使用循环将所有的数组元素积累起来,这是一个很常见的任务。
#include <iostream>
const int ArSizes = 8;
int sum_arr(int arr[], int n);//或者int sum_arr(int * arr, int n)
//在c++中,当且仅当用于函数头和函数原型时,int arr[]和int * arr含义一致,都意味着arr是一个指针。
//但是使用数组表示法(int arr[])可提醒用户,arr不仅指向int,还指向int数组的第一个int,
//所以当需要指针指向第一个元素时,使用数组表示法,(当然也可以使用指针表示法,但为了区别第7行的内容,一般使用数组表示法)
//当需要指针指向独立的一个值时,使用指针表示法,
int main()
{
using namespace std;
int cookies[ArSizes] = {1, 2, 4, 8, 16, 32, 64, 128};
int sum = sum_arr(cookies, ArSizes);
//将数组名作为参数传递给函数,数组名是数组第一个元素的地址,因此此时函数传递的地址,cookies的类型是int *,
//因此将上面的函数原型替换成int sum_arr(int * arr, ArSize)也对,
//在c++中,当且仅当用于函数头和函数原型时,int arr[]和int * arr含义一致
cout << "Total cookies eaten: " << sum << "\n";
cout << sizeof(cookies);//使用sizeof()函数将得到整个数组的长度,这里是4×8=32,以字节为单位
cout << &cookies << endl;//数组名cookies是数组第一个元素的地址,但对数组名运用取地址符返回的是整个数组的地址,这里返回的就是一个32字节内存块的地址。
cout << &cookies[0];//数组第一个元素的地址,注意区分上行。
return 0;
}
int sum_arr(int arr[], int n)
//为了使函数通用,此处不限于特定长度的数组,方括号指出arr[]这是一个数组,arr是数组名,是一个指针
{
int total = 0;
for (int i = 0; i < n; i++)
total = total + arr[i];
return total;
}
Total cookies eaten: 255
320x61fdf0
0x61fdf0
将数组作为参数的意义
好处:(1)将数组地址作为参数可以节省复制整个数组所需的时间和内存。如果数组很大,则使用的拷贝的系统开销很大,程序不仅需要更多的计算机内存,还需要花费时间来复制大块的数据。
坏处:(1)在c中,使用原始数据会增加破坏数据的风险,但是在c++中使用const限定符可以解决这种问题。
// arrfun2.cpp -- functions with an array argument
#include <iostream>
const int ArSize = 8;
int sum_arr(int arr[], int n);
// use std:: instead of using directive
int main()
{
int cookies[ArSize] = {1,2,4,8,16,32,64,128};
// some systems require preceding int with static to
// enable array initialization
std::cout << cookies << " = array address, ";
// some systems require a type cast: unsigned (cookies)
std::cout << sizeof (cookies) << " = sizeof cookies\n";
//sizeof(cookies)是整个数组的长度。
int sum = sum_arr(cookies, ArSize);
std::cout << "Total cookies eaten: " << sum << std::endl;
sum = sum_arr(cookies, 3);
//由于sum_arr只能通过第二个参数来确定数组的长塑,所以可以“欺骗”,这第二个参数是3,意味着此处只计算数组前三个元素的和
std::cout << "First three eaters ate " << sum << " cookies.\n";
sum = sum_arr(cookies + 4, 4); // another lie
//注意此处在sum_arr函数内部输出的地址不同,输出的是数组第五个元素的地址。
std::cout << "Last four eaters ate " << sum << " cookies.\n";
// std::cin.get();
int b = 7;
int * p = &b;
std::cout << sizeof(p) << std::endl;
std::cout << sizeof(b) << std::endl;
return 0;
}
// return the sum of an integer array
int sum_arr(int arr[], int n)
{
int total = 0;
std::cout << arr << " = arr, ";
// some systems require a type cast: unsigned (arr)
std::cout << sizeof(arr) << " = sizeof arr\n";
//sizeof(arr)其中arr是指针变量,在64位操作系统使用8个字节来存储地址
//因为在该函数arr只是一个指针变量,本身没有指出数组的长度,所以在必须显式的传递数组的长度。
for (int i = 0; i < n; i++)
total = total + arr[i];
return total;
}
int total = 0;
std::cout << arr << " = arr, ";
// some systems require a type cast: unsigned (arr)
std::cout << sizeof arr << " = sizeof arr\n";
for (int i = 0; i < n; i++)
total = total + arr[i];
return total;
}
0x61fdf0 = array address, 32 = sizeof cookies
0x61fdf0 = arr, 8 = sizeof arr
Total cookies eaten: 255
0x61fdf0 = arr, 8 = sizeof arr
First three eaters ate 7 cookies.
0x61fe00 = arr, 8 = sizeof arr
Last four eaters ate 240 cookies.
8
4
// arrfun3.cpp -- array functions and const
#include <iostream>
const int Max = 5;
// function prototypes
int fill_array(double ar[], int limit);
void show_array(const double ar[], int n); // don't change data
void revalue(double r, double ar[], int n);
int main()
{
using namespace std;
double properties[Max];
int size = fill_array(properties, Max);
show_array(properties, size);
if (size > 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, size);
show_array(properties, size);
}
cout << "Done.\n";
// cin.get();
// cin.get();
return 0;
}
int fill_array(double ar[], int limit)
{
using namespace std;
double temp;
int i;
for (i = 0; i < limit; i++)
{
cout << "Enter value #" << (i + 1) << ": ";
cin >> temp;
if (!cin) // bad input
{
cin.clear();
//cin.clear函数的作用清除cin流错误状态,该函数默认的状态微是ios::goodbit(就是把"坏的"变成"好的")
//同理,ci.clear(ios::failbit)清除cin流,并设置failbit状态位(就是把“好的”变成“坏的”)。
while (cin.get() != '\n')
continue;// 把错误的输入字符用cin.get()清出输入流,从新再来接受
cout << "Bad input; input process terminated.\n";
break;
}
else if (temp < 0) // signal to terminate
break;
ar[i] = temp;
}
return i;
}
// the following function can use, but not alter,
// the array whose address is ar
void show_array(const double ar[], int n)
//c++函数使用普通参数时,按照c++按值传递数据,函数使用的是数据副本,可以对数据实现自动保护。
//但接收数组名的函数将使用原始数据,上面的fill_array()就是直接接收的数据名,能够修改原始数据,
//这里show_array()函数在声明形参时使用关键字const(),该声明表明了指针ar指向的是常量数据,这就意味着不能是用ar修改数据。
//这里需要注意的是:并不意味着原始数据必须是常量,只是意味着不能在show_array()函数中使用ar来修改数据。
//show_array()将数组视为只读数据。
{
using namespace std;
for (int i = 0; i < n; i++)
{
cout << "Property #" << (i + 1) << ": $";
cout << ar[i] << endl;
}
}
// multiplies each element of ar[] by r
void revalue(double r, double ar[], int n)
{
for (int i = 0; i < n; i++)
ar[i] *= r;
}
Enter value #1: 10000
Enter value #2: 52220
Enter value #3: 89552
Enter value #4: 25475
Enter value #5: 55466
Property #1: $10000
Property #2: $52220
Property #3: $89552
Property #4: $25475
Property #5: $55466
Enter revaluation factor: 0.9
Property #1: $9000
Property #2: $46998
Property #3: $80596.8
Property #4: $22927.5
Property #5: $49919.4
Done.
使用数组区间的函数
除了使用将指向数组起始处的指针作为第一个参数,把数组的长度作为第二个参数以外,还可以指定元素区间,这可以通过两个指针来完成,第一个指针标识数据的开头,另一个标识数组的结尾。标识数据结尾的参数是指向最后一个元素后面的指针。
// arrfun4.cpp -- functions with an array 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)
//这里计算大的数组元素的总和,因为最后一个元素的指针是cookies+ArSize-1,那么后面的那个元素的指针是cookies+ArSize
cout << "Total cookies eaten: " << sum << endl;
sum = sum_arr(cookies, cookies + 3); // first 3 elements
//这里计算的是数组前3个元素的总和,计算到cookies+3-1的元素。
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";
// cin.get();
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;
}
Total cookies eaten: 255
First three eaters ate 7 cookies.
Last four eaters ate 240 cookies.
指针和const
有两种方式可以将const关键字用于指针:(1)让指针指向一个常量对象,这样可以防止使用指针来修改所指向的值 (2)将指针本身声明成常量,这样可以防止改变指针指向的位置。
一级间接关系时:指针赋值的情况有(1)常规变量的地址赋值给常规指针 (2)const变量的地址赋值给指向const的指针(3) 将常规变量赋值地址给指向const的指针(例如下面代码)
注意:不能将const变量的地址赋值给常规指针;
综上:数据类型本身不是指针,则可以将const数据或者非const数据的地址赋值给指向const的指针,但是const数据的地址禁止赋给非const指针。
数组同理,不能将常量数组的地址赋值给非常量指针。
#include <iostream>
int main()
{
using namespace std;
int age = 30;
const int * pt = &age;
//这里需要注意的是:pt声明并不意味着它指向的值是一个常量,只是对于pt而言,这个值是常量。
//*pt += 1;
//这里不合法,pt指向age,而age不是const,可以直接直接通过age来修改age的值,但是不能使用pt指针来修改它。
//cin >> *pt;
age = 20;//合法
//*pt = 20;//error
const float g_earth = 9.80;
const float * pe = &g_earth;
//这种情况就是把const变量的地址赋值给指向const的指针,这种情况下,不能使用g_earth来修改其值,也不能使用pe来修改其值。
const float g_moon = 1.63;
//float * pm = &g_moon;
//这种情况就是把const变量的地址赋值给常规指针,c++禁止这样做,
//原因是如果将g_moon的地址赋值给pm,则可以使用pm来修改g_moon的值,这违背了const变量的定义。
int height = 170;
int * py = &height;
const int * pl = py;
//将指针指向指针,此处是一级间接关系,在此处将非const指针(py的值,也就是变量height的地址)赋值给const指针(pl)是允许的。
cout << *pl;
//下面讨论二级间接关系时候的状态
const int **pp2;
int * p1;
const int n = 13;
/*pp2 = &p1;//error
*pp2 = &n;
*p1 = 10;*/
//当指针涉及到二级间接关系时,会出现混乱,这里如果合法,则意味着可以通过修改*pl的值来修改const变量n的值
return 0;
}
使用const的好处
(1)将指针参数声明为指向常量数据的指针理由有二:第一,这样可避免由于无意间修改数据而导致的编程错误;第二,使用const使得函数能够处理const和非const实参,否则将只能接收非const数据。
(2)应该尽可能将形参声明为指向const的指针。
int sloth = 3;
const int * ps = &sloth;
//不允许通过ps来修改sloth的值,可以将新地址赋值给ps
int * const finger = &sloth;
//此处const关键字的位置不同,这种格式可以使得finger只能指向sloth,但允许finger来修改sloth的值
简而言之,就是finger和*ps都是const,但是ps和*finger不是。
double trouble = 2.58;
const double * const stick = &trouble;
//此处声明了一个指向const对象的const指针,其中stick只指向trouble,并且stick不能用来修改trouble的值,
//简言之,就是stick和*stick都是const。
函数和二维数组
与一维数组一样,编写二维数组作为参数的函数,数组名依旧被视为是指针,相应的形参是一个指针,声明这个指针案例如下:
#include <iostream>
int sum(int (*ar2)[4], int size);
//这里的括号时不能少的,少了的话为int sum(int *ar2[4], int size)声明的是一个由4个指向int的指针组成的数组(指针组成的数组)
//并非是一个由4个int组成的数组的指针(数组的指针)
//或者是另一种形式
//int sum(int ar2[][4], int size);
int main()
{
using namespace std;
int data [3][4] = {{1,2,3,4}, {9,8,7,6}, {2,4,6,8}};//三行四列
int total = sum(data, 3);//这里是3是因为data是一个数组,它有三个元素,一个元素是一个数组,由4个int值组成。
cout << total << endl;
return 0;
}
//ar2是指针不是数组,它指向的是4个int组成的数组,所以两种方式都指定了列数(4),
//故没有把列数作为独立的函数参数进行传递,由于指定了列数,所以sum函数只接受4列组成的数组。
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 + r) + c)
return total;
}
60
函数和C风格字符串
将字符串作为参数传递给函数,表示字符串的方式有3种。
(1)char数组;例如:char ghost[15] = "galumphing";
(2)用引号括起来的字符串常量(也称字符串字面值)
(3)被设置为字符串常量的地址的char指针;例如char * str = “galumphing”;
上面的这三种类型都是char指针(准确来说是char*),所以可以将其作为字符串处理函数的参数。将字符串作为参数来传递,但实际上传递的是字符串的第一个字符的地址。这就意味着字符串函数原型应将其表示为字符串的形参声明成为char*类型。
注:字符串有内置的结束字符(末尾的空值字符),但是不以空值字符结尾的char数组只是数组,而不是字符串。这意味着不必将字符串长度作为参数传递给函数,而函数可以使用循环依次检查字符串中的每个字符,直到遇到末尾的空字符为止。
将c风格字符串作为参数的函数
// strgfun.cpp -- functions with a string argument
#include <iostream>
unsigned int c_in_str(const char * str, char ch);
int main()
{
using namespace std;
char mmm[15] = "minimum"; // string in an array
// some systems require preceding char with static to
// enable array initialization
char *wail = "ululate"; // wail points to string
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;
// cin.get();
return 0;
}
// this function counts the number of ch characters
// in the string str
unsigned int c_in_str(const char * str, char ch)
//C_int_str()函数不应修改字符串的内容,编译器将捕获这种错误。
//也可以在函数头使用数组表示法,例如unsigned int c_int_str(const char str[], char ch)
//也就是说这里的形参不一定非得是数组名,也可以是其他形式的指针。
{
unsigned int count = 0;
while (*str) // quit when *str is '\0'
{
if (*str == ch)
//str最初指向的是字符串的第一个字符,因此*str表示的是第一个字符,
count++;
str++; // move pointer to next char
//表达式str++将指针没增加一个字节,使之指向字符串中的下一个字符,最后当指向结尾的空值字符,使得*str等于0,结束循环。
}
return count;
}
3 m characters in minimum
2 u characters in ululate
返回c风格字符串的函数
注意:函数是无法返回一个字符串的,但是可以返回字符串的地址。
// strgback.cpp -- a function that returns a pointer to char
#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
//这里必须使用delete释放该字符串占用的内存,因为ps指向的是由new分配的内存
ps = buildstr('+', 20); // reuse pointer
cout << ps << "-DONE-" << ps << endl;
delete [] ps; // free memory
// cin.get();
// cin.get();
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--,意味先使用这个值然后再递减,所以pstr[0]是最后被设置成c的元素,
//就相当于是从后往前来填充字符串,这样可以避免使用额外的变量
pstr[n] = c; // fill rest of string
return pstr;
//函数结束后,pstr(不是字符串)使用的内存将被释放,但是在此处返回了pstr的值,
//因此可以在主函数中的指针ps来访问新建的字符串。
}
Enter a character: a
Enter an integer: 5
aaaaa
++++++++++++++++++++-DONE-++++++++++++++++++++
函数和结构
在涉及函数时,结构将其数据组合成单个实体或者数据对象,该实体被视为一个实体。
(1)可以将结构赋值给另一个结构
(2)可以像普通变量那样按值传递结构,此时使用的是函数的副本
(3)函数可以返回结构,与数组名不一样的是,数组名是数组的第一个元素的地址,但是结构名就仅仅是结构的名称,要想获得结构的地址,就必须使用取地址符&。
当结构比较小时,按值传递最合理;
// travel.cpp -- using structures with functions
#include <iostream>
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()
{
using namespace std;
travel_time day1 = {5, 45}; // 5 hrs, 45 min
travel_time day2 = {4, 55}; // 4 hrs, 55 min
travel_time trip = sum(day1, day2);//按值传递
cout << "Two-day total: ";
show_time(trip);
travel_time day3= {4, 32};
cout << "Three-day total: ";
show_time(sum(trip, day3));
// cin.get();
return 0;
}
travel_time sum(travel_time t1, travel_time t2)
{
travel_time total;//total、t1和t2都是travel_time结构
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)
{
using namespace std;
cout << t.hours << " hours, "
<< t.mins << " minutes\n";
}
Two-day total: 10 hours, 40 minutes
Three-day total: 15 hours, 12 minutes
传递结构的地址
传递结构的地址可以节省时间和空间,
// 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);
//将形参声明为指向polar的指针,即*polar类型,由于函数不应该修改结构,因此使用了const修饰符
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); // 调用函数时,将结构的地址而不是结构本身传递给它
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
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);
}
Enter the x and y values: 58 40
distance = 70.4557, angle = 34.5923 degrees
Next two numbers (q to quit):
虽然C风格字符串和string对象的用途几乎相同,但是与数组相比,string对象和结构更相似,比如说可以将结构作为完整的实体传递给函数,也可以将一个对象作为完整的实体进行传递,如果需要多个字符串,可以声明一个string对象数组,而不是一个二维char数组。
// topfive.cpp -- handling an array of string objects
#include <iostream>
#include <string>
using namespace std;
const int SIZE = 5;
void display(const string sa[], int n);
int main()
{
string list[SIZE]; // an array holding 5 string object
cout << "Enter your " << SIZE << " favorite astronomical sights:\n";
for (int i = 0; i < SIZE; i++)
{
cout << i + 1 << ": ";
getline(cin,list[i]);//读取一行字符(字符间有空格),函数的基本格式:getline(cin, str, '结束符')
//结束符默认是回车符。
}
cout << "Your list:\n";
display(list, SIZE);
// cin.get();
return 0;
}
void display(const string sa[], int n)//形参是sa是一个指向string对象的指针,因此sa[i]是一个string对象
{
for (int i = 0; i < n; i++)
cout << i + 1 << ": " << sa[i] << endl;
}
Enter your 5 favorite astronomical sights:
1: M13
2: M14
3: M15
4: M16
5: M16
Your list:
1: M13
2: M14
3: M15
4: M16
5: M16
函数和array数组
类对象是基于结构的,可按值将对象传递给函数,这种情况下,函数处理的是原始对象的副本;当然也可以传递指向对象的指针,这让函数可以操作原始对象。
//arrobj.cpp -- functions with array objects
#include <iostream>
#include <array>
#include <string>
const int Seasons = 4;
const std::array<std::string, Seasons> Snames =
{"Spring", "Summer", "Fall", "Winter"};
//此处的const array对象Snames是在所有函数之前声明的,因此可在后面的任何函数中使用它
void fill(std::array<double, Seasons> * pa);//pa是指向array<double, 4> 类型的指针
void show(std::array<double, Seasons> da);
int main()
{
std::array<double, 4> expenses;//expense的类型是array<double, 4>
fill(&expenses);
show(expenses);
// std::cin.get();
// std::cin.get();
return 0;
}
void fill(std::array<double, Seasons> * pa)
{
for (int i = 0; i < Seasons; i++)
{
std::cout << "Enter " << Snames[i] << " expenses: ";
std::cin >> (*pa)[i];
}
}
void show(std::array<double, Seasons> da)
{
double total = 0.0;
std::cout << "\nEXPENSES\n";
for (int i = 0; i < Seasons; i++)
{
std::cout << Snames[i] << ": $" << da[i] << '\n';
total += da[i];
}
std::cout << "Total: $" << total << '\n';
}
Enter Spring expenses: 85
Enter Summer expenses: 98
Enter Fall expenses: 56.2
Enter Winter expenses: 54.1
EXPENSES
Spring: $85
Summer: $98
Fall: $56.2
Winter: $54.1
Total: $293.3
递归
函数可以自己调用自己,但在c++中,main()函数不允许自己调用自己。这种功能被称为递归。
包含一个递归调用的递归
void recurs(argumentlist)
{
statemental
if (test)//test是false时,链被断开。
recurs(arguments)
statements2;
}
当if语句时true时,每个recurs()调用都将执行statements1,然后再调用recurs(),不会执行statement1,当if语句时false,当前调用将执行statements2。
当前调用结束后,程序将控制权将返回给调用它的recurs(),而recurs将执行其statements2部分,然后结束,并将控制权返回给前一个调用,依此类推。
// recur.cpp -- using recursion
#include <iostream>
void countdown(int n);
int main()
{
countdown(4); // call the recursive function
// std::cin.get();
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;
}
Counting down ... 4 (n at 0x61fe00)
Counting down ... 3 (n at 0x61fdd0)
Counting down ... 2 (n at 0x61fda0)
Counting down ... 1 (n at 0x61fd70)
Counting down ... 0 (n at 0x61fd40)
0: Kaboom!( n at 0x61fd40)
1: Kaboom!( n at 0x61fd70)
2: Kaboom!( n at 0x61fda0)
3: Kaboom!( n at 0x61fdd0)
4: Kaboom!( n at 0x61fe00)
注:每个递归调用都创建自己的一套变量,因此当函数到达第5次调用时,将有5个独立的n变量,其中每个变量的值都不同,相同层级地址相同。
包含多个递归调用的递归
在需要将一项工作不断分成两项小的、类似的工作时,递归非常有用。
// ruler.cpp -- using recursion to subdivide a ruler
#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
}
// std::cin.get();
return 0;
}
void subdivide(char ar[], int low, int high, int level)
{
if (level == 0)
return;
int mid = (high + low) / 2;
ar[mid] = '|';
subdivide(ar, low, mid, level - 1);
subdivide(ar, mid, high, level - 1);
}
| |
| | |
| | | | |
| | | | | | | | |
| | | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
在该程序中,函数subdivide使用变量level来控制层级。函数调用自身时,把level减去1。当level未零时,该函数将不在调用自己。subdivide()调用自己两次,一次是针对左边那部分,一次是右边。最初的中点被用作一次调用的右端点和另一次调用的左端点。调用次数乘几何级数增长。
函数指针
函数也有地址,函数的地址是存储在其机器语言代码的内存的开始地址。利用这个可以编写将另一个函数的地址作为参数的函数。这样第一个函数将能够找到第二个函数,并且运行它。与直接调用另一个函数相比,它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。
函数指针的基础知识
获取函数的地址:函数名就是其地址,例如think()这个函数,think就是该函数的地址。要将函数作为参数进行传递,必须传递函数名,注意:要区分传递的是函数的地址还是返回值,
process(think())//传递的是函数的返回值
thought(think)//传递的是函数的地址
声明函数指针
声明指向某种类型的指针时,必须指定指针指向的类型。故声明指向函数的指针时,也必须指定指针指向的函数类型。也就是声明应指定函数的返回类型以及函数的特征标(参数列表)。 也就是说,声明应像函数那样指出有关函数的信息。例:
double pam(int)//函数原型
double (*pf) (int)//这里与pam()声明类似,这里将pam替换成(*pf),
由于pam是函数,所以(*pf)也是函数,pf是函数指针,
技巧就是要声明指向特定类型的指针,可以首先编写这种函数的原型,
比如第一行代码,再用(*pf)代替函数名,这样pf就是这类函数的指针。
这里回顾之前的知识点:使用括号将*pf括起来是因为括号的优先级比*要高,所以*pf(int)意味着pf()是一个返回指针的函数,而(*pf)(int)意味着pf是一个指向函数的指针。
double pam(int);
double (*pf) (int);//这里声明了一个和第一行函数原型类型一致的函数指针,
//pf指向的函数接受一个int参数,并返回是double值的函数
pf = pam;//将相关的函数的地址赋值给它,
//另外需要注意pam的特征标必须和pf一致,否则编译器拒绝这种赋值
使用指针来调用函数
(*pf)扮演的角色与函数名相同,把它看做是函数名即可。
double pam(int);
double (*pf) (int);
pf = pam//pf现在也指向pam函数
double x = pam(4);
double y = (*pf) (5);//使用函数指针来调用函数
double y = pf(5)//也可以像使用函数名那样使用pf
pf和(*pf)等价的原因:
两种学派:
(1)pf是函数指针,而*pf是函数,所以可以使用(*pf)( )用作函数调用;
(2)函数名是指向该函数的指针,指向函数的指针的行为应与函数名相似,故可以使用pf()用作函数调用使用。
函数指针的深入了解
在函数原型中,参数列表const double ar[]和const double*ar含义完全相同,在函数原型中,可省略标识符,可以将上面两者简化成const double[]和const double *,但是函数定义时必须提供标识符,也就是必须使用const double ar[]或者const double *ar。
下面是比较复杂的函数原型和其指针的表示
函数原型:const double * f1(const double ar[], int n);//该函数原型表明返回值是const double *类型。
对应的函数指针声明:const double * (*p1) (const double *, int ) = f1;//要声明其指针,假定使用的指针名为p1,只需使用(*p1)替换f1即可。这里还同时对其进行了初始化。
同时声明多个函数指针,形式为函数指针数组:const double * (*pa[3]) (const double *, int) = {f1, f2, f3};//[3]放置位置的说明:首先pa是一个包含3个元素的数组,所以是pa[3],运算符[]的优先级高于*,所以*pa[3]表明是一个包含三个指针的数组。然后其他部分,特征标为const double *,int,返回值类型是const double *。所以pa是一个包含三个指针的数组,其中每个指针都指向以const double *和int作为参数,并且返回const double*的函数。
声明完相关数组后如何调用:const double *px = pa[1](av, 3);或const double *py = (*pa[1])(av, 3);
关于auto
使用c++11的自动类型判断
例如:
auto p2 = f1;
c++会声明自动类型的变量,根据被赋值的类型自动推导变量的类型
但自动类型推断只能用于单值初始化,不能用于初始化列表(比如上面函数指针数组)但是可以通过上面的pa来声明同类型的指针。pb与pa同类型,都是包含三个函数指针的数组。
auto pb = pa;
使用typedof进行简化
创建类型别名:
typedef double real;//使real成为double的别名
同样可以使用其简化指针类型的声明
typedef const double * (*p_fun) (const double *, int )
p_fun p1 = f1;//p1指向f1函数
p_fun pa[3] = {f1, f2, f3};//pa是一个包含三个函数指针的数组
p_fun (*pd)[3] = &pa;//指针pd指向pa
这里将别名作为标识符进行声明,并在开头使用关键字typedef。可以将p_fun声明为函数指针类型的别名。