一、复习函数的基本知识
库函数
自定义函数
先写段程序,复习下自定义函数
#include <iostream>
void simple(void);
using namespace std;
int main(void)
{
cout << "main() will call the somple() function " << endl;
simple();
return 0;
}
void simple(void)
{
cout << "I'm simple but a function." << endl;
}
自定义函数的三个部分:
函数定义,函数声明,函数调用,缺一不可
1:定义函数
函数分为有返回值和没有返回值的
没有返回值就是void函数,通用格式如下:
void functionName (parameterList)
{
statement(s)
return; //可写可不写,没返回值一般不写
}
其中的paramenterList是指传递给函数的参数类型和数量,例如:
void cheers (int n)
{
for(int i = 0; i <n ; i++)
cout << "Cheers!";
cout << endl;
}
这里的int意思就是,在调用cheers函数的时候,应该给它一个Int值作为参数传递给它
有返回值的函数将生成一个值,并将它返回给调用函数,通用格式如下:
typeName functionName(paramterList)
{
statements;
return value;
}
对于有返回值的函数,必须使用返回语句,以便把值返回给调用函数。
值本身可以是常量、变量、表达式,只是其结果的类型必须为typeName类型或者可以被转换为typeName(比如说我们返回类型是double,但是返回一个int表达式,int会被强转为double类型)
C++对于返回值的类有限制:不能是数组,但是可以是其他任何类型,比如结构对象等(但是可以把数组作为结构或对象组成部分来返回)
函数在执行返回语句后结束。如果函数包含多条返回语句,则函数在执行遇到第一条返回语句后结束。
int bigger (int a , int b)
{
if(a>b)
return a;
else
return b;
}
2:函数原型和函数调用
看如下程序,体会自定义有返回值和没有返回值的函数
#include <iostream>
using namespace std;
void cheers(int n);//声明
double cube(double x);
int main(void)
{
cheers(5);//调用
cout << "Give me a number: " << endl;
double side;
cin >> side;
double volume = cube(side);
cout << side << " side cube = " << volume << endl;
cheers(cube(2)); //自定义函数调用自定义函数,并进行double转int的操作
return 0;
}
void cheers(int n) //无返回值
{
for (int i = 0; i < n; i++)
{
cout << "Cheers!" << endl;
}
}
double cube(double x) //有返回值
{
x = x * x * x;
return x;
}
结果:
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Give me a number:
3
3 side cube = 27
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
*
1)自定义无返回值cheers,自定义有返回值cube
2)把输入作为cube的参数,返回double类型
3)自定义函数cheers引用自定义函数cube
1)为什么需要原型?
就是头文件下面为什么要再声明一下
函数原型描述了函数到编译器的结构,也就是它将函数的返回值类型及参数类型数量告诉编译器
cube()函数完成计算后,把返回值放到了指定位置——可能是寄存器,可能是内存,然后调用函数(main())从这个为之取得返回值
C++编译器并不会在文件中进一步查找,这样效率太低了,所以最好能提供原型
2)原型的语法
函数原型并不需要提供变量名,有类型列表有足够了。比如对于cheer()原型,我们只提供了参数类型:
void cheers(int);
比方说我们去饭店吃饭,只需要打电话告诉饭店,我们有几个人,我们吃什么菜,我们有几个人,而不需要把每个人的名字告诉饭店一个道理
所以说比方说下面的原型:
double cube(double x)写成double cube (double) 一样是可以编译的!
3)原型的功能
要确保以下几点:
·编译器正确处理函数返回值;
·编译器检查使用的参数数目是否正确
·编译器检查使用的参数类型是否正确
但是如果是较大的数据类型转换为较小类型时,有些编译器会发出警告,指出数据可能丢失
比如把2.33E27转为int类型,这种值就不会被正确转换
二、函数参数和按值传递
C++通常按值传递参数,这意味着将数值参数传递给函数,而后者将其赋给新的变量
比如:double volume = cube (side);
→ side是一个变量,前面定义为5
cube的函数头如下:double cube (double x)
→ 函数被调用时,该函数创建一个新的名为x的double变量,把它初始化为5
→ 这样cube()执行的操作将不会影响main()中的数据。
→ cube使用的是side的副本,而不是原来的数据
用于接收传递值得变量被称为形参,比如这里double 的x
传递给函数的值被称为实参,比如原来的side
1:多个参数
1)函数内定义两个变量,哪怕是相同类型,也要写全
void fifi (float a, float b)
2)与一个参数一样,原型中的变量名不必与定义中的变量名相同,甚至可以省略,但是最好写上,至少知道人家是干嘛用的
见如下程序:演示修改形参并不会影响调用函数中的数据
#include <iostream>
using namespace std;
void n_chars(char c, int n);
int main(void)
{
char ch;
int times;
cout << "Please enter a character: ";
cin >> ch; //cin.get()也行
while(ch != 'q')
{
cout << "Enter a integer: ";
cin >> times;
n_chars(ch,times);
cout << endl;
cout << "Enter another character or press q_key to quie: " << endl;
cin >> ch;
}
return 0;
}
void n_chars(char c, int n)
{
while (n-- >0)
{
cout << c;
}
}
结果:
Please enter a character:
1
Enter a integer: 10
1111111111
Enter another character or press q_key to quie:
q
*
1)声明了ch和times分别作为n_chars的两个参数,然后通过函数返回内容
2:另外一个接收两个参数的函数
题目如下:
美国许多州都采用某种纸牌游戏来发行彩票,让参与者从卡片中选择一定数目的选项。
例如:从51个数字中选取6个,随后管理者随机抽取6个数。如果参与者选择的数字与这6个完全相同,赢得几百万美金。函数计算中奖概率
首先,必须从51个数中选取6个数,而中奖的概率为1/R,R的计算公式为:
选择6个数时,分母为6!,分子是连续6个整数的乘积,从51开始连续递减,可以采用for循环计算
long double result = 1.0;
for(n = numbers , p = picks ; p > 0 ; n-- , p--)(numbers是一共从多少个数中选择)
result = result * n / p;(pick是选取数的个数)
程序:
#include <iostream>
using namespace std;
long double probability(unsigned int numbers,unsigned int picks);
int main(void)
{
unsigned int total,choices;
cout << "Enter the total number of choices on the game card and the number of picks allowed: " << endl;
while((cin >> total >> choices) && choices <= total)
{
cout << "You have one chance in " << probability(total,choices) << " of winning";
cout << "Please enter next two number(q to quit)";
}
cout << "Bye";
return 0;
}
long double probability(unsigned int numbers,unsigned int picks)
{
double n,p;
long double result = 1.0;
for(n = numbers, p = picks; p > 0; n--,p--)
result = result * (n / p);
return result;
}
结果(输出是R,概率是1/R):
Enter the total number of choices on the game card and the number of picks allowed:
49
6
You have one chance in 1.39838e+07 of winningPlease enter next two number(q to quit)100
10
You have one chance in 1.73103e+13 of winningPlease enter next two number(q to quit)
*
1)这个程序就演示了可以在函数中使用两种局部变量。
首先是形参(numbers和picks);其次是其他局部变量(result、n、p)
形参和局部变量的主要区别是,形参从调用函数那里获得自己的值,而其他变量是从函数中获得自己的值
2)注意可以这样写:
while((cin >> total >> choices) && choices <= total),代表如果输入成功
三、函数和数组
如何把函数和数组结合起来?
假设使用一个数组来记录家庭野餐中每人吃了多少甜品,现在要统计总数,来使用函数实现
我们返回值部分不能为数组,但是参数部分可以是
比如以下声明:
int sum_arr ( int arr[] , int n )
这里的arr看起来是一个数组,其实它是一个指针,但是函数编写时,可以把它看成是数组
下面程序将演示如同使用数组名那样使用指针
#include <iostream>
using namespace std;
const int ArSize = 8;
int sum_arr(int arr[],int n);
int main(void)
{
int cookies[ArSize] = {1,2,4,8,16,32,64,128};//数组定义好了
int sum = sum_arr(cookies,ArSize); //函数返回总数
cout << "Total cookies eaten: " << sum << endl;
return 0;
}
int sum_arr(int arr[],int n)//它用了两个参数,一个是数组,一个是数组数量
{
int total = 0;
for(int i = 0; i < n; i++)//很明显,我们用数组的数量和求和
{
total += arr[i];
}
return total;
}
结果:Total cookies eaten: 255
1:函数如何使用指针处理数组
大多数情况下,C++和C一样,也将数组名视为指针,它将数组名解释为第一个元素的地址:
cookies == & cookies [0]
但是也有一些例外,比如
1)数组声明使用数组名来标记存储位置
2)对数组名使用sizeof将得到整个数组的长度(字节为单位)
3)&用于数组名时,返回整个数组的地址
再来看这个函数调用:int sum = sun_arr(cookies , ArSize)
cookies是数组名,而C++规定它是第一个元素的地址,因此函数传递的是地址。所以cookies类型必须是int指针,即int *。所以正确的函数头应该是:
in sun_arr ( int * arr , int n)
在函数头或函数原型中,int * arr和int arr[]含义相同,它们都意味着arr是一个int指针
arr[i] == *(ar + i)
&arr [i] == ar + i
将指针(数组名+1)意味着加上了一个与指针指向的类型的长度相等的值,就是指针偏移
2:将数组作为参数意味着什么?
sum_arr把cookies的第一个元素的地址和数组中的元素传递给了sum_arr函数。
sum_arr函数将cookies的地址赋给指针变量arr,将ArSize赋给int变量n
函数在传递数组的时候,使用的是原来的数组
看下面的函数,这回我们使用指针来演示上面的程序
#include <iostream>
using namespace std;
const int ArSize = 8;
int sum_arr(int arr[],int n);
int main(void)
{
int cookies[ArSize] = {1,2,4,8,16,32,64,128};//数组定义好了
cout << "array adress: " << &cookies << endl;
cout << "size of cookies: " << sizeof(cookies) << endl; //32
int sum = sum_arr(cookies,ArSize); //函数返回总数
cout << "Total cookies eaten: " << sum << endl;
sum = sum_arr(cookies,3);
cout << "3 cookies eaten: " << sum << endl;
return 0;
}
int sum_arr(int arr[],int n)//它用了两个参数,一个是数组,一个是数组数量
{
int total = 0;
cout << "arr adress: " << arr << endl;
cout << "size of arr: " << sizeof arr << endl;//这里返回的不是数组,而是指针,8个字节,int *占用8字节
for(int i = 0; i < n; i++)//很明显,我们用数组的数量和求和
{
total += arr[i];
}
return total;
}
结果:
array adress: 0x3cebbff7d0
size of cookies: 32
arr adress: 0x3cebbff7d0
size of arr: 8
Total cookies eaten: 255
arr adress: 0x3cebbff7d0
size of arr: 8
3 cookies eaten: 7
*
1)
cout << "size of cookies: " << sizeof(cookies) << endl; 这里打印的是数组的大小
2)cout << "arr adress: " << arr << endl;
我们sum_array的地址和cookies地址完全一样,所以我们知道sum_array其实就是使用了cookies的地址来进行运算
3)cout << "size of arr: " << sizeof arr << endl;//这里返回的不是数组,而是指针,8个字节,int *占用8字节
3:更多数组函数示例
先看一个案例:考虑对房地产数组执行的操作。两个基本操作是,将值读入到数组中和显示数组的内容。
另外添加另一个操作:重新评估每种房地产的值。(所有房地产都以相同的比率增加或减少)
#include <iostream>
using namespace std;
const int Max = 5;
int fill_array(double arr[],int limit);
void show_array(const double arr[],int n);
void revalue(double r,double arr[], int n);
int main(void)
{
double properties[Max];//5个double类型数组
int size = fill_array(properties,Max);//添加数组的方法
show_array(properties,size);
if(size > 0)
{
cout << "Enter revaluation factor: ";
double factor;
while(!(cin >> factor))
{
cin.clear();
while ((cin.get() != '\n'))
{
continue;
}
cout << "Bad input : input process terminated." << endl;
}
revalue(factor,properties,size);
show_array(properties,size);
}
cout << "Done!\n";
cin.get();
cin.get();
return 0;
}
int fill_array(double arr[],int limit)//
{
double temp;
int i;
for (i = 0; i < Max; i++)
{
cout << "Enter value #" << i + 1 << ":";
cin >> temp;
if(!cin)
{
cin.clear();
while (cin.get() != '\n')
{
continue;
}
cout << "Bad input : input process terminated." << endl;
break;
}
else if(temp < 0)
break;
else
arr[i] = temp;
}
return i;
}
void show_array(const double arr[],int n)//加上const以后,就不能通过指针来修改对象了
{
for (int i = 0; i < n; i++)
{
cout << "Property #" << i+1 << " : $";
cout << arr[i] << endl;
}
}
void revalue(double r,double arr[], int n)//r是比例系数
{
for(int i = 0; i < n ; i++)
{
arr[i] *= r;
}
}
结果:
输入:
Enter value #1:1000
Enter value #2:2000
Enter value #3:3000
Enter value #4:4000
Enter value #5:5000
显示:
Property #1 : $1000
Property #2 : $2000
Property #3 : $3000
Property #4 : $4000
Property #5 : $5000
修改因子:
Enter revaluation factor: 2
Property #1 : $2000
Property #2 : $4000
Property #3 : $6000
Property #4 : $8000
Property #5 : $10000
Done!
*程序说明:
1)填充数组
该函数指定两个参数,一个是数组名,另一个是指定读取的最大元素数,该函数返回实际读取的元素数,还要注意判断它输入是否正确和输入是否大于0这两个条件,然后使用for循环,创建临时值,挨个放进去
2)显示数组
这个就是循环+显示数组元素
3)修改数组
它需要给函数传递三个参数,分别是因子,数组和元素数目,没有返回值
4:使用数组区间和函数
除了上面例子中提供数组中的数据类型、数组的起始位置和数组中元素数量,还有另一种方法:
即指定元素区间(range),这可以通过传递两个指针来完成:一个指针标识数组开头,另一个指针标识数组的尾部,看下例(还是饼干):
#include <iostream>
using namespace std;
const int ArSize = 8;
int sum_arr(const int * begin, const int * end);
int main(void)
{
int cookies[ArSize] = {1,2,4,8,16,32,64,128};
int sum = sum_arr(cookies, cookies + ArSize);
cout << "Total cookies eaten: " << sum << endl;
return 0;
}
int sum_arr(const int * begin, const int * end)
{
int total = 0;
const int * pt;
for(pt = begin; pt != end; pt++)
total += *pt;
return total;
}
结果:Total cookies eaten: 255
*
1)定义了指针的开头和指针的结尾,求和就用指针内容
2)调用函数的开头结尾位置采用cookies和cookies + ArSize
5:指针和const
三个const的用法
①const int *pt
②int *const pt;
③const int *const pt;
①const int *pt等效于 int const *pt
使用const,就不能通过指针修改数组了,比如如下程序:
int main(void)
{
int n =10;
int *pt = &n;
cout << "1)n= " << n << endl;
*pt = 20;//通过指针修改了n的对象
cout << "2)n= " << n << endl;
return 0;
}
1)n= 10
2)n= 20
但是如果加上const,就不能通过指针来修改了
int main(void)
{
int n =10;
const int *pt = &n;
cout << "1)n= " << n << endl;
*pt = 20;//通过指针修改了n的对象
cout << "2)n= " << n << endl;
return 0;
}
结果如下:
报错说*pt是只读,所以不能通过const修改后的指针来修改原值
但是指针还是可以指向其他地方的,如下:
#include <iostream>
using namespace std;
int main(void)
{
int n =10;
int m = 100;
const int *pt = &n;
cout << "1)n= " << n << endl;
//*pt = 20;//通过指针修改了n的对象
pt = &m;
cout << "*pt = " << *pt << endl;
cout << "m = " << m << endl;
return 0;
}
还是可以指向其他位置,比如m
1)n= 10
*pt = 100
m = 100
②int * const pt;
#include <iostream>
using namespace std;
int main(void)
{
int n =10;
int *const pt = &n;
cout << "1)n= " << n << endl;
*pt = 20;
cout << "2)n = " << n << endl;
return 0;
}
这时候把*放到了const的前面,就可以通过pt指针修改原n值
结果:
1)n= 10
2)n = 20
再把pt指针指向m
#include <iostream>
using namespace std;
int main(void)
{
int n =10;
int *const pt = &n;
cout << "1)n= " << n << endl;
*pt = 20;
cout << "2)n = " << n << endl;
pt = &m;
return 0;
}
这时候就报错了,说明*const pt可以修改指向数值的值,但是只能指向一个值,不能指向任意变量
③const int *const pt;
这就说明了,你既不能通过指针修改内容,也不能指向其他位置
尽可能使用const
将指针参数声明为指向常量数据的指针有两条理由:
·这样可以避免由于无意间修改数据而导致的编程错误
·使用const使得函数能够处理const和非const实参,否则将只能接收非const数据
如果条件允许,则应将指针形参声明为指向const的指针
如果函数形参是const,那么不管函数是const或者非const,都能进行调用
如果函数形参是非const,那么如果函数是const,就不能进行调用
所以在使用的时候,最好给形参设置成const指针!
四、函数和二维数组
1:
跟一维数组类似,假设
int date[3] [4] = {{1,2,3,4},{5,6,7,8},{2,4,6,8}};
int total =sum(data , 3),sum函数就是数组名+数组中的元素
data是一个数组名,有三个元素,第一个元素本身是数组,有4个int组成,因此data类型是指向由4个int组成的数组的指针,正确的原型如下:
int sum(int(*ar2)[4], int size))(指针的数组)
这声明了将4个指向int的指针组成的数组
而不是由一个指向由4个int组成的数组的指针
如:int ar2[4],(这是数组的指针)
二维数组函数写成下面这样子,可读性更强;
int sum(int ar2[] [4] , int size);
2:二维数组遍历
因此二维数组的遍历就需要循环嵌套,外层访问行,内层访问列
int sum(int are[][4],int size)
{
int total = ;
for(int r = 0; r < size; r++)
{
for(int c = 0;c <4; c++)
{
total += ar2[r][c];
}
}
return total;
}
3:提取元素
可以用数组表示法的原因是:ar2指向数组的第一个元素,因此ar+r指向编号为r的元素,又因为元素本身又是数组,随意ar2[r]是由4个int组成的数组的名称,所以ar2[r][c]是4个int组成的数组中的一个元素(最简单的方法),必须对指针执行两次才能解除引用,如下:
ar2[r][c] == *(*(ar2+r)+c)
先对行指针+r,利用*取出行元素,就是数组,然后对数组名+c,就是针对这个数组偏移,然后再*取出元素
五、函数和C风格字符串
1:表示字符串的三种方法
·char数组 char ghost[15] = "galloping";
·用引号括起来的字符串常量(字符串字面值)
·被设置为字符串的地址的char指针 char * str = "galumping"
看如下例子:使用一个函数来计算特定的字符再字符串中出现的次数
#include <iostream>
using namespace std;
unsigned int c_in_str(const char *str, char ch);
int main(void)
{
char mmm[15] = "minimum";//第一种字符数组的方式
const char * wail = "ululate"; //第二种字符指针方式 //char * wail = (char *"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))
{
if(*str == ch)
count++;
str++;
}
return count;
}
3 m characters in minimum
2 u characters in ululate
2:返回C风格字符串的函数
函数无法返回字符串,但可以返回字符串的地址,这样效率更高
下面定义一个buildstr()函数,返回一个指针,接收两个参数,一个字符一个数字。函数使用new创建一个长度和数字参数相等的字符串,然后将每个元素都初始化为该数组
#include <iostream>
using namespace std;
char * buildstr(char c, int n);
int main(void)
{
char ch;
cout << "Enter a character: ";
cin >> ch;
int times;
cout << "Enter a integer: ";
cin >> times;
char *ps = buildstr(ch,times);
cout << ps << endl;
delete[] ps;
ps = buildstr('+',20);
cout << ps << "-Done-" << ps << endl;
delete[]ps;
return 0;
}
char * buildstr(char c, int n)
{
char * pstr = new char[n+1];//new创建数组
pstr[n] = '\0';//最后一个留给空字符
for(int i = 0 ; i < n; i++)
{
pstr[i] = c;
}
return pstr;
}
结果:
Enter a character: V
Enter a integer: 20
VVVVVVVVVVVVVVVVVVVV
++++++++++++++++++++-Done-++++++++++++++++++++
*
1)要注意new字符串的时候多留一位给我们的空格
2)new完了要记得delete
六、函数和结构
结构变量的行为更接近基本的单值变量,在这种情况下,函数将使用原始结构的副本,另外,函数也可以返回结构
与数组名就是数组的第一个元素的地址不同,结构名只是结构的名称,要获得结构的地址,必须使用地址运算符&
如果结构非常大,则赋值结构将增加内存要求,所以许多C程序员进行函数传递和返回,更喜欢指针来访问结构体的内容
C++提供了第三种选择——按引用类型
1:传递和返回结构
看如下程序:
#include <iostream>
using namespace std;
const int Mins_per_hr = 60;
struct travel_time
{
int hours;
int mins;
};
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 days :" << trip.hours << " hours , " << trip.mins << " mins." << endl;//这样写还是有些复杂,再定义一个shouwtime函数
travel_time day3 = {4,32};
cout << "Three days :" ;
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; // (t1.mins + t2.mins) / Mins_per_hr会自动取整的
return total;
}
void show_time(travel_time t)
{
cout << t.hours << " Hours, " << t.mins << "Minutes." << endl;//没返回值
}
结果:
Two days :10 hours , 40 mins.
Three days :15 Hours, 12Minutes.
*这里所有的参数类型都是结构体
2:另一个处理结构的函数示例
定义两个结构,用于表示两种不同的描述位置的方法,然后开发一个函数,将一种格式转换为另一种格式,并显示结果
#include <iostream>
#include <cmath>
using namespace std;
struct polar
{
double distance;
double angle;
};
struct rect
{
double x;
double y;
};
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
int main(void)
{
rect rplace;
polar pplace;
cout << "Enter the x and y value: ";
while (cin >> rplace.x >> rplace.y)
{
pplace = rect_to_polar(rplace);//将直角坐标系转为极坐标系,输入的一定是直角坐标系新建的
show_polar(pplace);//要显示极坐标系,参数肯定是极坐标系
cout << "Next two numbers(q to quit):";
}
return 0;
}
polar rect_to_polar(rect xypos)
{
polar answer;
answer.distance = sqrt(xypos.x * xypos.x + xypos.y * xypos.y);
answer.angle = atan2(xypos.y,xypos.x);//atan2返回弧度,还需要转化为角度
return answer;
}
void show_polar(polar dapos)
{
const double Rad_to_deg = 57.29577951;
cout << "Distance = " << dapos.distance << endl;
cout << "Angel = " << dapos.angle * Rad_to_deg << "degree" << endl;
}
结果:
Enter the x and y value: 30
40
Distance = 50
Angel = 53.1301degree
Next two numbers(q to quit):-100
100
Distance = 141.421
Angel = 135degree
Next two numbers(q to quit):q
*
1)函数引用结构体类型,结构体类型应该放在最前面,不然报错
2)while (cin >> rplace.x >> rplace.y),如果输入成功
3:传递结构的地址
可以对上述的程序进行修改
#include <iostream>
#include <cmath>
using namespace std;
struct polar
{
double distance;
double angle;
};
struct rect
{
double x;
double y;
};
void rect_to_polar(const rect *pxy , polar * pda);
void show_polar(const polar *pda);
int main(void)
{
rect rplace;
polar pplace;
cout << "Enter the x and y value: ";
while (cin >> rplace.x >> rplace.y)
{
//pplace = rect_to_polar(rplace);
rect_to_polar(&rplace,&pplace);//两个值,第一个是直角坐标系的指针,第二个是极坐标系的指针
//show_polar(pplace);
show_polar(&pplace);//直接用地址就行了,没有返回值
cout << "Next two numbers(q to quit):";
}
return 0;
}
void rect_to_polar(const rect *pxy , polar *pda)//定义两个地址,只需要把地址换了就行
{
pda->distance = sqrt(pxy->x*pxy->x + pxy->y*pxy->y);
pda->angle = atan2(pxy->y, pxy->x);//atan2返回弧度,还需要转化为角度
}
void show_polar(const polar *pda)
{
const double Rad_to_deg = 57.29577951;
cout << "Distance = " << pda->distance << endl;
cout << "Angel = " << pda->angle * Rad_to_deg << "degree" << endl;
}
结果:
Enter the x and y value: 30
40
Distance = 50
Angel = 53.1301degree
Next two numbers(q to quit):-100
100
Distance = 141.421
Angel = 135degree
Next two numbers(q to quit):q
*
1)函数内部传递的是指针,参数是指针,没有返回值
七、函数和string对象
string对象和结构更为相似,例如,可以将一个结构赋给另一个结构,也可以将一个对象赋给另一个对象。可以将结构作为完整实体传递给函数
下面的程序声明了一个string对象数组,并将该数组传递给一个函数显示内容:
#include <iostream>
using namespace std;
const int SIZE = 5;
void display(const string sa[], int n);
int main(void)
{
string list[SIZE];
cout << "Enter " << SIZE << " favorite food: " << endl;
for (int i = 0; i < SIZE; i++)
{
cout << i+1 << ": ";
getline(cin,list[i]);
}
cout << "Your list" << endl;
display(list,SIZE);
return 0;
}
void display(const string sa[], int n)
{
for (int i = 0; i < n; i++)
{
cout << i+1 << ": " << sa[i] << endl;
}
}
结果:
Enter 5 favorite food:
1: break
2: milk
3: icecr
4: saled
5: cake
Your list
1: break
2: milk
3: icecr
4: saled
5: cake
八、函数与array对象
假设 要使用一个array对象存储一年四个季度的开支,看如下程序:
#include <iostream>
#include <string>
#include <array>
using namespace std;
const int Seasons = 4;
const array<string,Seasons> Snames = {"Spring","Summer","Fall","Winter"};
void fill(array<double,Seasons> *pa);
void show(array<double,Seasons> da);
int main(void)
{
array<double,Seasons> expenses;
fill(&expenses);//把指针填充到array里面
show(expenses);
return 0;
}
void fill(array<double,Seasons> *pa)
{
for (int i = 0; i < Seasons; i++)
{
cout << "Enter " << Snames[i] << " expensens: ";
cin >> (*pa)[i];//pa是array对象的指针,需要转化成值再进行操作
}
}
void show(array<double,Seasons> da)
{
double total = 0.0;
cout << "EXPENSENS: " << endl;
for (int i = 0; i < Seasons; i++)
{
cout << Snames[i] << ": $" << da[i] << endl;
total += da[i];
}
cout << "Total expenses: " << total << endl;
}
结果:
Enter Spring expensens: 212
Enter Summer expensens: 256
Enter Fall expensens: 208
Enter Winter expensens: 244
EXPENSENS:
Spring: $212
Summer: $256
Fall: $208
Winter: $244
Total expenses: 920
*cin >> (*pa)[i];//pa是array对象的指针,需要转化成值再进行操作
九、递归
C++函数又一个特点——可以自己调用自己(但是C++不允许main()调用自己),这种功能被称为递归。
1:包含一个递归调用的递归
如果递归函数调用自己,则被调用的函数也将调用自己,这将无限循环下去,除非代码又包含终止的内容,一般放在if语句中,如下:void类型的递归函数recurs()代码如下:
void recurs(argumentlist)
{
statements1
if(test)
resurs(arguments)
statements2
}
看如下的例子:
#include <iostream>
using namespace std;
void countdown(int n);
int main(void)
{
countdown(4);
return 0;
}
void countdown(int n)
{
cout << "counting down.... " << n << endl;
if(n>0)//更新条件,递归退出条件
{
countdown(n-1);
}
cout << n << ": Kandom" << endl;
}
结果:
counting down.... 4
counting down.... 3
counting down.... 2
counting down.... 1
counting down.... 0
0: Kandom
1: Kandom
2: Kandom
3: Kandom
4: Kandom
*
1)当n递减到0的时候才开始执行下面的代码:cout << n << ": Kandom" << endl;
2)第二个部分将于与函数调用相反的顺序执行5次!
3)每次调用递归都会创建自己的一套变量,因此当程序到第五次调用时,将有5个独立的n变量,其中每个变量的值都不同,对程序如下修改!
#include <iostream>
using namespace std;
void countdown(int n);
int main(void)
{
countdown(4);
return 0;
}
void countdown(int n)
{
cout << "counting down.... " << n << "(n at adress: " << &n << ")" << endl;
if(n>0)//更新条件,递归退出条件
{
countdown(n-1);
}
cout << n << ": Kandom" << "(n at adress: " << &n << ")" << endl;
}
结果如下:
counting down.... 4(n at adress: 0x1d3adffc10)
counting down.... 3(n at adress: 0x1d3adffbe0)
counting down.... 2(n at adress: 0x1d3adffbb0)
counting down.... 1(n at adress: 0x1d3adffb80)
counting down.... 0(n at adress: 0x1d3adffb50)
0: Kandom(n at adress: 0x1d3adffb50)
1: Kandom(n at adress: 0x1d3adffb80)
2: Kandom(n at adress: 0x1d3adffbb0)
3: Kandom(n at adress: 0x1d3adffbe0)
4: Kandom(n at adress: 0x1d3adffc10)
2:包含多个递归调用的递归
在需要将一项工作不断分为两项较小、类似的工作,递归很有用。递归方法有时被称为分而治之策略,比如对标尺找到两端,找到终点并标记,然后同样的操作给标尺的左右两部分继续用。
见如下程序:
#include <iostream>
using namespace std;
const int Len = 66;
const int Divs = 6;
void subdivide(char ar[], int low, int high,int levels);
int main(void)
{
char ruler[Len];//字符数组
for(int i = 0; i < Len; i++)
ruler[i] = ' ';//清空数组
int min = 0; //数组边界开始
int max = Len - 2; //数组边界结束
ruler[Len-1] = '\0';
ruler[min] = ruler[max] = '|';
for(int i = 1; i < Divs; i++)
{
subdivide(ruler,min,max,i);//这样使用递归好像就可以了
cout << ruler << endl;
}
return 0;
}
void subdivide(char ar[], int low, int high,int levels)//levels是为了控制递归退出
{
if(levels == 0)
return;
int mid = (low + high) / 2;
ar[mid] = '|';
subdivide(ar,low,mid,levels-1);
subdivide(ar,mid,high,levels-1);
}
结果:
| | |
| | | | |
| | | | | | | | |
| | | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
*
1)刚开始是两边两条斜杠,第二次变成三条,第三次变成5条,一直递归下去,
2)将递归次数写进了形参,然后形成判断条件
十、函数指针
与数据项相似,函数也有地址。函数的地址是存储机器语言代码的内存的开始地址。我们可以编写一个将另外一个函数的地址作为参数的函数,这样第一个函数能找到第二个函数并运行,特殊的是,它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数
1:函数指针基础知识
比如我们要设计一个estimate()函数来估算指定行数的代码需要的时间,并希望不同的此恒许愿都将使用该函数。我们将程序员要使用的算法函数地址传递给esitmate(),为此必须:
·获取函数地址
·声明一个指针
·使用函数指针调用函数
1)获取函数地址
使用函数名即可,不用参数,比如说think()是函数,则think是该函数的地址
2)声明函数指针
比如:函数如下
double pan(int);则指针如下
double (*pf)(int);
写的时候直接把原型中的函数名用(*Name)替换了就行了
*一定要加(),不然就是函数名
double (*pf)(int);这时函数指针
double *pf(int),这就是一个函数了
3)使用指针调用函数
现在(*pf)扮演的角色就与函数名相同,因此使用(*pf)时,可将它看作是函数名即可!
例如:
double pam(int);
double (*pf)(int);
pf = pam;
2:函数指针示例
下面的程序演示如何使用函数指针,两次调用estimate()函数,依次传递besty()函数的地址,另一次传递pam()函数的地址。如下
#include <iostream>
using namespace std;
double Rick(int lines);
double Jack(int lines);
void estimate(int lines, double(*pf)(int));
int main(void)
{
int code;
cout << "How many lines of code do you need?\n";
cin >> code;
cout << "Here is Rick's estimate: " << endl;
estimate(code,Rick);
cout << "Here is Jack's estimate: " << endl;
estimate(code,Jack);
return 0;
}
double Rick(int lines)
{
return lines * 0.05;
}
double Jack(int lines)
{
return (0.03 * lines) + (0.0004 * lines * lines);
}
void estimate(int lines, double(*pf)(int))
{
cout << lines << " Lines code will take " << (*pf)(lines) << " hours." << endl;
}
结果:
How many lines of code do you need?
100
Here is Rick's estimate:
100 Lines code will take 5 hours.
Here is Jack's estimate:
100 Lines code will take 7 hours.
*
可以看到在最后一行代码我们声明了和Rick,Jack类型相同的函数指针*pf,使用的时候直接用名字参数即可: (*pf)(lines) << " hours." << endl;
3:深入讨论函数指针
看下例:
#include <iostream>
using namespace std;
const double *f1(const double *ar,int n);
const double *f2(const double ar[],int n);
const double *f3(const double ar[],int n);
int main(void)
{
double av[3] = {1112.3,1542.6,2227.9};
//part1: p1是指向函数的指针
const double *(*p1)(const double *,int) = f1;//第一种方法,使用传统方法进行给指针赋值
auto p2 = f2; //第二种方法:使用auto方法,让编译器自动判断p2的类型
cout << "PART1:--------------------" << endl;
cout << "Adress Value" << endl;
cout << (*p1)(av,3) << ": " << *((*p1)(av,3)) << endl; //(*p1)(av,3) == f1(av,3),第二个是把该函数内容再显示出来
cout << p2(av,3) << ": " << *p2(av,3) << endl;//第二种方法,直接把指针名称写出来就行了
//part2: p2是由指针构成的数组
const double *(*pa[3])(const double *,int) = {f1,f2,f3};//指针数组的内容是函数,注意它的声明方法
auto pb = pa;//这样pb也是函数指针数组
cout << "PART2:--------------------" << endl;
cout << "Adress Value" << endl;
for(int i = 0;i < 3; i++)
cout << pa[i](av,3) << ": " << *pa[i](av,3) << endl;//pa[1]就是f1了,加*就是它里面的内容
for (int i = 0; i < 3; i++)
{
cout << pb[i](av,3) << ": " << *pb[i](av,3) << endl;
}
//part3:
//(pc)pd 是指针,指向了一个由函数指针构成的数组
auto pc = &pa;//pc是指向由三个函数指针作为元素的数组pa的指针
const double *(*(*pd)[3])(const double *,int) = &pa;//传统方法,首先pd是指针,就是*pd,然后*pd是3个元素数组指针,就是*(*pd)[3],最后每个元素都是函数指针,就有了这个
cout << "PART3:--------------------" << endl;
cout << "Adress Value" << endl;
cout << (*pc)[0](av,3) << ": " << *(*pc)[0](av,3) << endl;
const double *pdb = (*pd)[1](av,3);
cout << pdb << ": " << *pdb << endl;
cout << (*pd)[2](av,3) << ": " << *(*pd)[2](av,3) << endl;
cout << (*(*pd)[2])(av,3) << ": " << *(*(*pd)[2])(av,3) << endl;//这两种写法其实是一样的
return 0;
}
const double *f1(const double *ar,int n)
{
return ar;
}
const double *f2(const double ar[],int n)
{
return ar + 1;
}
const double *f3(const double ar[],int n)
{
return ar + 2;
}
结果:
PART1:--------------------
Adress Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
PART2:--------------------
Adress Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
PART3:--------------------
Adress Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
0xf0e7dff6b0: 2227.9
*总结一下
1)
(*pa[2])(av,3) ≠ *pa[2](av,3),括号优先级不一样
pa[2](av,3)来说,pa[2]是指针数组第三个元素,就是函数指针,最后*,就是第三个函数的值
(*pa[2])(av,3)来说,(*pa[2])就是第三个元素函数指针所指向的函数指针,然后赋值,出来的还是地址
2)
对于函数指针来说*pf和pf其实是等价的
3)
请注意pa和&pa的区别(pa是数组),pa是数组第一个元素的地址,即&pa[0],而&pa是整个数组(三个指针)的地址,从数字上说它们相同,但是它们类型不同。
比如pa+1是下一个元素的地址,而&pa+1是后面12个字节内存块的地址。
还有个差别,要得到第一个元素的值,只需要对pa解除即可,而&pa解除两次引用
**&pa == *pa == pa[0]
4)
用auto解放了复杂的声明过程
const double *(*pa[3])(const double *,int) = {f1,f2,f3};
auto pb = pa;//这样pb也是函数指针数组
5)
还可以用typedef进行简化(并没有定义新的东西,而是起了一个别名,第五章说过)
比如:typedef int n,这里就把n变成int类型了
比如:typedef double real;这里real就变成了double类型了,而不是变量
实例如下:
typedef const double *(*p_fun) (const double *,int),可以直接用p_fun类型了
p_fun p1 = f1;
p_fun pa[3] ={f1,f2,f3};
十一、复习题和编程练习
1:复习题:
1:使用函数的三个步骤?
定义,调用,声明
2:请创建与下面的描述匹配的函数原型
a:igor()没参数,没返回值
void igor(void);
b:tofu()接收一个int参数,且返回一个float
float tofu(int)
c:mpg()接收两个double,返回一个double
double mpg(double,double)
d:summation()将long数组名和数组长度作为参数,返回一个long
long arr[n];
long summation(long [],int)
e:doctor()接受一个字符串参数(不能修改),并返回double
double doctor(const char *str)
f:ofcourse()将boss结构作为参数,无返回值
void ofcourse(boss bs) 形参名可写可不写
g:plot将map结构的指针作为参数,返回字符串
char *plot(map *pt)
3:编写一个接受3个参数的函数,:int数组名、数组长度、和一个int值 ,并将数组所有元素都设置为该int值
void Set(int ar[], int n, int x)
{
for(i = 0;i<n;i++)
{
ar[i] = x;
}
}
4:编写一个接受三个参数的函数:指向数组区间中第一个元素的指针、指向数组区间最后一个元素后面的指针以及一个int值,并将数组中的每个元素都设置为该int值
void Set(int *begin,int *end , int x)
{
for (int *pt = begin; pt != end ; pt ++ )
{
*pt = x;
}
}
*注意循环条件
5:编写一个double数组名和数组长度作为参数,并返回该数组中最大值的函数。该函数不应该修改数组内容。
double Set(const double ar[],int len)
{
double max = arr[0];
for(int i = 1; i < size; i++)
{
if(max < arr[i]);
max = arr[i];
}
return max;
}
6:为什么不对基本类型的函数参数使用const?
我们的C++一般是按值传递参数,而不像数组和结构这种,整体复制,修改副本其实对原来的值不影响,但是如果用指针了就必须加const
7:C++使用哪三种C风格的字符串
char数组。char ch[] = "Hello";
字符串。"hello;";
可以指向字符串首字符的指针。char * pt = "Hello.指的是地址
8:编写一个函数,原型如下:
该函数将字符串中所有的c1都替换为c2,并返回替换次数
int replace(char * str, char c1,char c2)
int replace(char * str,char c1,char c2)
{
int count;
while(*str)
{
if(*str == c1)
{
*str = c2;
count++;
}
str++;
}
return count;
}
9:表达式*"pizza"代表:字符串首地址,取出p
"taco[2]"呢,taco是数组,第三个元素,双引号就是首地址
10:C++允许按值传递结构,也允许传递结构的地址。如果glitz是一个结构变量,如何按值传递它?如何传递地址?两种方法有何利弊?
地址传递不能保护原始数据;但是按值传递内存消耗大
11:函数judge()返回类型是int,它将这样一个函数地址作为参数:将const char指针作为参数,并返回int值,写该函数原型
int judge(int (*pf) (const char));
12:结构如下:
struct applicant
{
char name[30];
int ratings[3];
}
a:编写一个显示该结构体内容的函数,参数就是结构体
void show(applicant ap)
{
for (int i = 0; i < 2; i++)
{
cout << ap.name[i] << ap.ratings[i] << endl;
}
}
b.将该结构的地址作为参数,显示参数所指向结构的内容
void show(applicant *ap)
{
for (int i = 0; i < 2; i++)
{
cout << ap->name << ap->ratings[i] << endl;
}
}
13:假设f1()和f2()原型如下:
void f1(applicant * a);
const char * f2(const applicant * a1,const applicant * a2);
1)请将p1和p2分别声明为指向f1和f2的指针;
2)将ap声明为数组,它包含5个类型与p1相同的指针
3)将pa声明为一个指针,它指向的数组包含10个类型与p2相同的指针
使用typedef方法来实现。
1)
typedef void *(pf_1)(applicant * a);//使用typedef直接变成类型
pf_1 p1 = f1;
typedef char const char *(*p_f2)(const applicant *a1,const applicant *a2);
p_f2 p2 = f2;
2)pf_1 ap[5];
3p_f2 (*pa) [10];