文章目录
1. 函数的基础知识
函数不能返回数组类型,可以返回其他任何类型,不过数组可以包含在结构或对象中返回。
用于接收传递的变量被称为实参(argument),传递给函数的值被称为实参(parameter)。
2. 函数和数组
2.1 数组属于值传递(指针)
它属于值传递,不过它的值是地址,所以可以改变地址中的值。
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()
{
int cookies[ArSize] = {1,2,4,8,16,32,64,128};
int sum = sum_arr(cookies, ArSize);
cout<<sum<<endl;
}
int sum_arr(int arr[], int n)
{
int total = 0;
for(int i = 0; i < n; i++)
{
total += *arr;
arr++;
}
// for(int i = 0; i < n; i++)
// {
// total += arr[i];
// }
return total;
}
输出结果:
255
上面的函数原型还可以使用指针代替:
int sum_arr(int *arr, int n);
2.2 使用const来保护数组
我们知道数组传递的是指针,所以在函数中数组中的元素容易被修改。我们可以添加const关键字来进行保护。
void show_array(const double ar[], int n);
ar指向的是常量数据,这就意味着不能使用ar修改该数据。也就是说可以使用ar[0],但是它不能被改变。
2.3 指针和const
有两种不同的方式将const关键字用于指针。
- 第一种:是让指针指向一个常量对象,这样防止使用该指针来修改所指向的值。
- 第二种:是让指针本身声明为常量,防止改变指针指向的位置。
(1)第一种情况
int age = 39;
const int * pt = &age;
pt指向一个const int,因此不能使用pt来修改这个值,但是age他自己可以变化。还可以将const变量的地址赋值给const的指针。例如:
const float g_earth = 9.8;
const float * pe = &g_earth;
这种情况,利用指针和本身的g_earth都不能改变变量的值。再来看以下例子是不允许的:
const float g_moon = 1.63;
float *pm = &g_moon;//不允许;
将指针参数声明为常量数据的指针有两条理由:
- 这样可以避免由于无意间修改数据而导致的编译错误。
- 使用const使得函数能够处理const和非const实参,否则将只能接收非const数据。
(2)第二种情况
上面的指针只能防止修改指针所指向的数据,而不能防止修改它的指向。也就是说可以给指针一个新的地址。如果想要指针本身不被修改,这可以像这样:
int sloth = 3;
const int *ps = &sloth;//指向的数据不能变,指向const int
int *const finger = &sloth;//一个常量指针,指向了int
上面的finger指针不能变,但是可以通过指针修改指向的值sloth。
我们还可以综合以上两个,使得指针和指向的数据都不能变。
double trouble = 2.0E30;
const double * const stick = &trouble;//指针和指向的数据都不能变
3. 函数和二维数组
对二维数组也是一样,二维数组的形参也是一个指针。假设代码形式为
int data[3][4] = {{1,2,3,4}, {9, 8, 7, 6},{2, 4, 6, 8}};
int total = sum(data, 3);
它有两种函数原型的形式:
int sum(int (*ar2)[4], int size);//括号是必须的
int sum(int ar2[][4], int size)
上式表示ar2为指针类型,它指向4个int组成的数组(可以形容成行指针?)。因此,指针类型指定了列数。虽然ar2是指针,我们也可以像一维数组一样,使用二维数组对元素进行访问。
4. 函数和C-风格字符串
C-风格字符串由一系列字符组成,以空值字符结尾。如果要将字符串作为参数传递给函数,则表示字符串的方式有三种。
- char数组
- 被引用括起来的字符串常量(也称字符串字面值)
- 被设置为字符串的地址的char指针。
上述三种选择的类型都是char指针,也就是char*。三种类型举例如下:
char ghost[15] = "galloping";
char* str = "galloping";
int n1 = strlen(ghost);//数组
int n2 = strlen(str);//C-风格字符串
int n3 = strlen("gamboling");//字符串常量
由于C-风格字符串的结束符已知,所以没必要传递字符串长度。
#include<iostream>
using namespace std;
unsigned int c_in_str(const char * str,char ch);
int main()
{
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)
{
if(*str ==ch)
count++;
str++;
}
return count;
}
输出:
还可以返回C-风格的地址:
char* buildstr(char c, int n);
5. 函数和结构
传递结构时,更接近于基本的单值变量,也就是说把结构当做一个整体。结构传递是按值传递的,如果结构比较大的时候,赋值结构将增加内存要求,降低系统运行的速度。出于这些原因C语言程序员倾向于传递结构体的地址,C++中提供了第三种选择,按引用传递,这将在后续讲。
5.1 值传递
时间相加的例子:
#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()
{
travel_time day1 = {5, 45};
travel_time day2 = {4, 55};
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));
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";
}
5.2 传递结构的地址
直角坐标转极坐标的例子:
#include<iostream>
#include<cmath>
using namespace std;
struct polar
{
double distance;
double angle;
};
struct rect
{
double x;
double y;
};
void rectToPolar(const rect* pxy,polar* pda);
void showPolar(const polar* pda);
int main()
{
rect rPlace;
polar pPlace;
cout<<"Enter the x and y values: ";
while(cin>>rPlace.x>>rPlace.y)
{
rectToPolar(&rPlace, &pPlace);
showPolar(&pPlace);
cout<<"Next two numbers(q to quit):";
}
cout<<"Done.\n";
return 0;
}
void showPolar(const polar *pda)
{
const double Rad_to_deg = 57.29577951;
cout<<"distance = "<<pda->distance;
cout<<", angle = "<<pda->angle * Rad_to_deg;
cout<<" degrees\n";
}
void rectToPolar(const rect *pxy, polar *pda)
{
pda->distance =sqrt(pxy->x*pxy->x + pxy->y *pxy->y);
pda->angle = atan2(pxy->y,pxy->x);
}
6. 函数和string对象
string对象与结构更相似。例如,可以将一个对象赋给另一个对象,可以将对象作为完整的实体进行传递。如果需要多个字符串,可以声明一个string对象数组,而不是二维char数组。
下面的例子为输入5句话,然后输出:
#include<iostream>
#include<string>
using namespace std;
const int SIZE = 5;
void display(const string sa[], int n);
int main()
{
string list[SIZE];
cout<<"Enter "<<SIZE<<"sentence.\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)
{
for(int i = 0; i < n; i++)
cout<< i + 1<<": "<< sa[i]<<endl;
}
7. 函数和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()
{
array<double, Seasons> expenses;
fill(&expenses);
show(expenses);
return 0;
}
void fill(array<double, Seasons> *pa)
{
for(int i = 0; i < Seasons; i++)
{
cout<<"Enter "<<Snames[i]<< " expenses:";
cin>>(*pa)[i];
}
}
void show(array<double, Seasons> da)
{
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;
}
8. 函数递归
C++函数可以调用函数自己(与C语言不同的是,C++不允许main()调用自己),这种结构称为递归。
8.1 一个递归
下面的例子为5次函数调用,注意进去和出来的顺序。每次调用的时候都会创建新的n,可以看到n的地址不一样。
#include<iostream>
using namespace std;
void countDown(int n);
int main()
{
countDown(4);
return 0;
}
void countDown(int n)
{
cout<<"Counting down ..."<<n
<<"(\"n\" address is:"<<&n<<")"<<endl;
if(n > 0)
countDown(n - 1);
cout<<n<<":kaboo!\n";//调用结束,原路返回!
}
输出:
8.2 包含多个递归调用
递归方法有时候也被成为分而治之策略(divide-and-conquer strategy)。
#include<iostream>
using namespace std;
const int Len = 66;
const int Divs = 6;//6层
void subdivide(char ar[], int low, int high, int level);
int main()
{
char ruler[Len];
for(int i = 1;i < Len -2;i++)
ruler[i] = ' ';//输入空格
int max = Len -2;//其余两端为'|'
int min = 0;
ruler[min] = 0;
ruler[min] = ruler[max] = '|';
cout<<ruler<<endl;
for(int i = 1;i <=Divs; i++)
{
//i决定了分几分,也就是调用几次
subdivide(ruler, min, max, i);
cout<<ruler<<endl;
for(int j = 1;j < Len - 2;j++)
ruler[j]= ' ';
}
}
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);//右半部分
}
9. 函数指针
9.1 函数指针的基础知识
函数名即为函数的地址。 注意传递函数地址和传递函数返回值的区别,假设现在有think()函数,则他们的区别为:
process(think);//传递函数地址
thought(think());//传递函数的返回值
process()调用使得process()函数能够在内部调用think()函数。thought()调用首先调用think()函数,然后将think()的返回值传递给thought()函数。
声明函数指针,以下pam是函数,所以(*pf)也是函数。
double pam(int);//函数原型
double (*pt)(int);//指针类型
一定要加括号,如果不加括号,就变成了返回指针的函数。
使用函数指针来调用函数有两种格式,这两种格式都可以使用。
double pam(int);
double (*pf)(int);
pf = pam;
double x = pam(4);//第一种情况
double y = (*pf)(5);//第二种情况
9.2 使用举例
主要看estimate()函数,传递的函数名,因此我们可以在estimate的内部进行函数调用。
#include<iostream>
using namespace std;
double betsy(int);
double pam(int);
void estimate(int lines, double (*pf)(int));
int main()
{
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))
{
cout<<lines<<" lines will take ";
cout<<(*pf)(lines)<<" hours\n";
}
9.3 使用typedef进行简化
除了使用auto外,C++还提供了其他简化声明工具。
typedef const double *(*p_fun)(const double*,int);
p_fun p1 = f1;//p1指向了f1函数
总览目录
上一篇:(四)循环和分支语句
下一篇:(六)函数探幽
文章参考:《C++ Primer Plus第六版》