重学C++笔记之(五)函数的使用

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第六版》

猜你喜欢

转载自blog.csdn.net/QLeelq/article/details/112360916