7.6 函数和结构

为结构编写函数比为数组编写函数要简单得多。虽然结构变量和数组一样,都可以存储多个数据项,但在涉及到函数时,结构变量的行为更接近于基本的单值变量。也就是说,与数组不同,结构将其数据组合成单个实体或数据对象,该实体被视为一个整体。前面讲过,可以将一个结构赋给另外一个结构。同样,也可以按值传递结构,就像普通变量那样。在这种情况下,函数将使用原始结构的副本。另外,函数也可以返回结构。与数组名就是数组第一个元素的地址不同的是,结构名只是结构的名称,要获得结构的地址,必须使用地址运算符&。在C语言和C++中,都使用符号&来表示地址运算符;另外,C++还使用该运算符来表示引用变量,这将在第8章讨论。

使用结构编程时,最直接的方式是像处理基本类型那样来处理结构;也就是说,将结构作为参数传递,并在需要时将结构用作返回值使用。然而,按值传递结构有一个缺点。如果结构非常大,则复制结构将增加内存要求,降低系统运行的速度。出于这些原因(同时由于最初C语言不允许按值传递结构),许多C程序员倾向于传递结构的地址,然后使用指针来访问结构的内容。C++提供了第三种选择——按引用传递(将在第8章介绍)。下面介绍其他两种传递方式,首先介绍传递和返回整个结构。

7.6.1 传递和返回结构

当结构比较小时,按值传递结构最合理,下面来看两个使用这种技术的示例。第一个例子处理行程时间。有些地图指出,从Thunder Falls到Bingo城需要3小时50分钟,而从Bingo城到Gotesquo需要1小时25分钟,对于这种时间,可以使用结构来表示——一个成员表示小时值,另一个成员表示分钟值。将两个时间加起来需要一些技巧,因为可能需要将分钟值转换为小时。例如,前面列出的两个时间的总和为4小时75分钟,应将它转换为5小时15分钟。下面开发用于表示时间值的结构,然后再开发一个函数,它接受两个这样的结构为参数,并返回表示参数的和的结构。

定义结构的工作很简单:

struct travel_time
{
    int hours;
    int  mins;
}

接下来,看一下返回两个这种结构的总和的sum()函数的原型。返回值的类型应为travel_time,两个参数也应为这种类型。因此,原型应如下所示:

 travel_time sum(travel_time t1,travel_time t2); 

要将两个时间相加,应首先将分钟成员相加。然后通过整数除法(除数为60)得到小时值,通过求模运算符(%)得到剩余的分钟数。程序清单7.11在sum()函数中使用了这种计算方式,并使用show_time()函数显示travel_time结构的内容。

程序清单7.11 travel.cpp

// travel.cpp -- 使用结构的函数
#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.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";
}

其中,travel_time就像是一个标准的类型名,可以用来声明变量、函数的返回类型和函数的参数类型。由于total和t1变量是travel_time结构,因此可以对它们使用句点成员运算符。由于sum()函数返回travel_time结构,因此可以将其用作show_time()函数的参数。由于在默认情况下,C++函数按值传递参数,因此函数调用show_time(sum(trip,day3))将执行函数调用sum(trip,day3),以获得其返回值。然后,show_time()调用将sum()的返回值(而不是函数自身)传递给show_time()。下面是该程序的输出:

Two-day total: 10 hours, 40 minutes
Three-day total: 15 hours, 12 minutes 

7.6.2 另一个处理结构的函数示例

前面介绍的有关函数和C++结构的大部分知识都可用于C++类中,因此有必要介绍另一个示例。这次要处理的是空间,而不是时间。具体地说,这个例了将定义两个结构,用于表示两种不同的描述位置的方法,然后开发一个函数,将一种格式转换为另一种格式,并显示结果。这个例子用到的数学知识比前一个要多,但并不需要像学习数学那样学习C++。

假设要描述屏幕上某点的位置,或地图上某点相对于原点的位置,则一种方法是指出该点相对于原点的水平偏移量和垂直偏移量。传统上,数学家使用x表示水平偏移量,使用y表示垂直偏移量。x和y一起构成了直角坐标(rectangular coordinates)。可以定义由两个坐标组成的结构来表示位置:

struct rect
{
    double x;             // 水平偏移量
    double y;             // 垂直偏移量
};

另一种描述点的位置的方法是,指出它偏离原点的距离和方向。传统上,数学家从正水平轴开始按逆时针方向度量角度。距离和角度一起构成了极坐标(polar coordinates)。可以定义另一个结构来表示这种位置:

struct polar
{
    double distance;      // 离开原点的距离
    double angle;         // 偏离原点的方向角度
};

下面来创建一个显示polar结构的内容的函数。C++库(从C语言借鉴而来)中的数学函数假设角度的单位为弧度,因此应以弧度为单位来测量角度。但为了便于显示,我们将弧度值转换为角度值。这意味着需要将弧度值乘以180/π一一约为57.29577951。该函数的代码如下:

// 显示极坐标,并将弧度转换为角度
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";
}

请注意,形参的类型为polar。将一个polar结构传递绘该函数时,该结构的内容将被复制到dapos结构中,函数随后将使用该拷贝完成工作。由于dapos是一个结构,因此该函数便用成员运算符句点来标识结构成员。

接下来,让我们试着再前进一步,编写一个将直角坐标转换为极坐标的函数。该函数接受一个rect参数,并返回一个polar结构,这需要使用数学库中的函数,因此程序必须包含头文件cmath(在较旧的系统中为math.h)。另外,在有些系统中,还必须命令编译器载入数学库。可以根据勾股定理,使用水平和垂直坐标来计算距离:

distance = sqrt(x*x + y*y) 

数学库中的atan2()函数可根据x和y的值计算角度:

angle = atan2 (y,x) 

还有一个atan()函数,但它不能区分180度之内和之外的角度。

有了这些公式后,便可以这样编写该函数:

// 将直角坐标转换为极坐标
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;      // 返回一个极坐标结构
}

编写好函数后,程序的其他部分编写起来就非常简单了。程序清单7.17列出了程序的代码。

程序清单7.12 strctfun.cpp

// strctfun.cpp -- 使用结构为参数的函数
#include <iostream>
#include <cmath>

// 结构声明
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()
{
    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): ";
    }
    cout << "Done.\n";
    return 0;
}

// 将直角坐标转换为极坐标
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;      // 返回一个极坐标结构
}

// 显示极坐标,并将弧度转换为角度
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";
}

下面是该程序的运行情况:

Enter the x and y values: 30 40
distance = 50, angle=53.1301 degrees
Next two numbers(q to quit): -100 100
distance = 141.421 angle=135 degrees
Next two numbers(q to quit):q

程序说明程序清单7.12中的两个函数已经在前面讨论了,因此下面复习一下该程序如何使用cin来控制while循环:

while (cin >> rplace.x >> rplace.y) 

前面讲过,cin是istream类的一个对象。抽取运算符(>>)被设计成使得cin>>rplacc.x也是一个istream对象。正如第11章将介绍的,类运算符是使用函数实现的。使用cin>>rplace.x时,程序将调用一个函数,该函数返回一个istream值。将抽取运算符用于cin>>rplace.x对象(就像cin>>rplace.x>>rplace.y这样),也将获得一个istream对象。因此,整个while循环的测试表达式的最终结果为cin,而cin被用于测试表达式中时,将根据输入是否成功,被转换为bool值true或false。例如,在程序清单7.12中的循环中,cin期望用户输入两个数字,如果用户输入了q(前面的输出示例就是这样做的),cin>>将知道q不是数,从而将q留在输入队列中,并返回一个将被转换为fasle的值,导致循环结束。

请将这种读取数字的方法与下面更为简单的方法进行比较:

for(int i = 0;i < limit; i++)
{
    cout<<"Enter value#”<< (i+1) << ":";
    cin>>temp;
    if(temp < 0)
        break;
    ar[i] = temp;
}

要提早结束该循环,可以输入一个负值。这将输入限制为非负值。这种限制符合某些程序的需要,但通常需要一种不会将某些数值排除在外的、终止循环的方式。将cin>>用作测试条件消除了这种限制,因为它接受任何有效的数字输入。在需要使用循环来输入数字时,别忘了考虑使用这种方式。另外请记住,非数字输入将设置一个错误条件,禁止进一步读取输入。如果程序在输入循环后还需要进行输入,则必须使用cin.clear()重置输入,然后还可能需要通过读取不合法的输入来丢弃它们。程序清单7.7演示了这些技术。

7.6.3 传递结构的地址

假设要传递结构的地址而不是整个结构以节省时间和空间,则需要重新编写前面的函数,使用指向结构的指针。首先来看一看如何重新编写show_polar()函数。需要修改三个地方:

完成上述修改后,该函数如下所示:

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";
}

接下来对rect_to_polar进行修改。由于原来的rect_to_polar函数返回一个结构,因此修改工作更复杂些。为了充分利用指针的效率,应使用指针,而不是返回值。为此,需要将两个指针传递给该函数,第一个指针指向要转换的结构,第二个指针指向存储转换结果的结构。函数不返回一个新的结构,而是修改调用函数中已有的结构。因此,虽然第一个参数是const指针,但第二个参数却不是。也可以像修改函数show_polar()修改这个函数。程序清单7.13列出了修改后的程序。

程序清单7.1.3 strctptr.cpp

// strctptr.cpp -- 使用结构指针为参数的函数
#include <iostream>
#include <cmath>

// 结构
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()
{
    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);        // 传递地址
        cout << "Next two numbers (q to quit): ";
    }
    cout << "Done.\n";
    return 0;
}

// 显示极坐标,并将弧度转换为角度
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";
}

// 将直角坐标转换为极坐标
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.13的行为与程序清单7.12相同。它们之间的差别在于,程序清单7.12使用的是结构副本,而程序清单7.13使用的是指针,让函数能够对原始结构进行操作。

文件下载(已下载 381 次)

发布时间:2014/6/16 下午8:13:09  阅读次数:3889

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号