结构体作为函数的输入与输出
本节阅读量:
考虑用3个松散变量表示的员工:
1
2
3
4
5
6
7
8
|
int main()
{
int id { 1 };
int age { 24 };
double wage { 52400.0 };
return 0;
}
|
如果要将此员工传递给函数,必须传递三个变量:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#include <iostream>
void printEmployee(int id, int age, double wage)
{
std::cout << "ID: " << id << '\n';
std::cout << "Age: " << age << '\n';
std::cout << "Wage: " << wage << '\n';
}
int main()
{
int id { 1 };
int age { 24 };
double wage { 52400.0 };
printEmployee(id, age, wage);
return 0;
}
|
虽然传递3个单独的员工变量还不算太糟,但如果函数需要传递多个员工变量,独立传递每个变量就非常容易出错。此外,如果向员工添加新属性(例如名称),现在必须修改所有函数声明、定义和函数调用,以接受新的参数!
通过引用传递结构体
与单个变量相比,使用结构体的一大优势是,可以将整个结构体传递给函数。结构体通常通过引用传递(通常是常量引用),以避免进行复制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#include <iostream>
struct Employee
{
int id {};
int age {};
double wage {};
};
void printEmployee(const Employee& employee) // 注: 通过引用传递
{
std::cout << "ID: " << employee.id << '\n';
std::cout << "Age: " << employee.age << '\n';
std::cout << "Wage: " << employee.wage << '\n';
}
int main()
{
Employee joe { 14, 32, 24.15 };
Employee frank { 15, 28, 18.27 };
// 打印 Joe 的信息
printEmployee(joe);
std::cout << '\n';
// 打印 Frank 的信息
printEmployee(frank);
return 0;
}
|
在上面的示例中,将整个Employee传递给printEmployee()(一次用于joe,一次用于frank)。
上述程序输出:
1
2
3
4
5
6
7
|
ID: 14
Age: 32
Wage: 24.15
ID: 15
Age: 28
Wage: 18.27
|
因为传递的是整个结构体对象(而不是单个成员变量),所以无论有多少个成员变量,都只需要一个参数。而且,如果决定向Employee结构体中添加新成员变量,也不必更改函数声明或函数调用!新成员变量将自动包含在内。
结构体作为返回值
考虑这样的情况:有一个函数需要返回三维空间中的一个点。这样的点有3个属性:x坐标、y坐标和z坐标。但函数只能返回一个值。那么如何将所有3个坐标返回给用户呢?
一种常见的方法是返回结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#include <iostream>
struct Point3d
{
double x { 0.0 };
double y { 0.0 };
double z { 0.0 };
};
Point3d getZeroPoint()
{
// 创建一个临时变量并返回
Point3d temp { 0.0, 0.0, 0.0 };
return temp;
}
int main()
{
Point3d zero{ getZeroPoint() };
if (zero.x == 0.0 && zero.y == 0.0 && zero.z == 0.0)
std::cout << "The point is zero\n";
else
std::cout << "The point is not zero\n";
return 0;
}
|
这将打印:
结构体通常按值返回,避免返回悬空引用。
在上面的getZeroPoint() 函数中,创建了一个新的命名对象(temp),以便可以返回它:
1
2
3
4
5
6
|
Point3d getZeroPoint()
{
// 创建一个临时变量并返回
Point3d temp { 0.0, 0.0, 0.0 };
return temp;
}
|
对象名称temp在这里没有提供任何有用的说明。
因此可以通过返回临时(未命名/匿名)对象来稍微改进函数:
1
2
3
4
|
Point3d getZeroPoint()
{
return Point3d { 0.0, 0.0, 0.0 }; // 返回一个未命名的 Point3d
}
|
在这种情况下,将构造临时Point3d,将其复制回调用方,然后在表达式末尾销毁。这里比上面更加简洁(一行替代两行,并且不需要判断temp是否被多次使用)。
返回类型推导
在函数具有显式返回类型(例如Point3d)的情况下,甚至可以在return语句中省略该类型:
1
2
3
4
5
6
|
Point3d getZeroPoint()
{
// 因为函数声明中已经指定返回类型
// 因此这里无需再指定
return { 0.0, 0.0, 0.0 }; // 返回一个未命名的 Point3d
}
|
还要注意,在这种情况下,如果想要返回一个全零的结构体,可以使用空大括号来返回Point3d:
1
2
3
4
5
|
Point3d getZeroPoint()
{
// 空大括号,来进行值初始化
return {};
}
|
结构体是一个很重要的代码组件
结构体很有用,类(C++和面向对象编程的核心)也直接构建在这里介绍的概念之上。充分理解结构体(特别是数据成员、成员选择和默认成员初始化)将使您更容易过渡到类。
习题
问题 1
你在运营一个网站,想要计算广告收入。请编写一个程序,允许输入3项数据:
- 观看了多少条广告。
- 点击广告的用户所占的百分比。
- 每次点击的平均收入。
把这3个值存放在一个结构体中。把这个结构体传给另一个函数并打印各项的值。这个打印函数还应当打印出当天的收入(把3个成员相乘)。
显示提示
如果你把百分比存成整数,那么在计算当天收入时还需要除以100。
查看答案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
#include <iostream>
// 首先定义我们的 Advertising 结构体
struct Advertising
{
int adsShown {};
double clickThroughRatePercentage {};
double averageEarningsPerClick {};
};
Advertising getAdvertising()
{
Advertising temp {};
std::cout << "How many ads were shown today? ";
std::cin >> temp.adsShown;
std::cout << "What percentage of ads were clicked on by users? ";
std::cin >> temp.clickThroughRatePercentage;
std::cout << "What was the average earnings per click? ";
std::cin >> temp.averageEarningsPerClick;
return temp;
}
void printAdvertising(const Advertising& ad)
{
std::cout << "Number of ads shown: " << ad.adsShown << '\n';
std::cout << "Click through rate: " << ad.clickThroughRatePercentage << '\n';
std::cout << "Average earnings per click: $" << ad.averageEarningsPerClick << '\n';
// 下面这一行为了缩短长度而拆成多行
// 需要把 ad.clickThroughRatePercentage 除以 100,因为它是百分比而不是倍数
std::cout << "Total Earnings: $"
<< (ad.adsShown * ad.clickThroughRatePercentage / 100 * ad.averageEarningsPerClick) << '\n';
}
int main()
{
// 声明一个 Advertising 结构体变量
Advertising ad{ getAdvertising() };
printAdvertising(ad);
return 0;
}
|
问题 2
创建一个用于表示分数的结构体。该结构体应当有一个整数分子成员和一个整数分母成员。
编写一个函数从用户处读入一个Fraction,并用它读入两个分数对象。再编写一个函数把两个Fraction相乘,并以Fraction的形式返回结果(不需要约分)。另外编写一个函数打印分数。
程序的输出应与下面一致:
Enter a value for the numerator: 1
Enter a value for the denominator: 2
Enter a value for the numerator: 3
Enter a value for the denominator: 4
Your fractions multiplied together: 3/8
两个分数相乘时,结果的分子是两个分子的乘积,结果的分母是两个分母的乘积。
查看答案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
#include <iostream>
struct Fraction
{
int numerator{ 0 };
int denominator{ 1 };
};
Fraction getFraction()
{
Fraction temp{};
std::cout << "Enter a value for numerator: ";
std::cin >> temp.numerator;
std::cout << "Enter a value for denominator: ";
std::cin >> temp.denominator;
std::cout << '\n';
return temp;
}
constexpr Fraction multiply(const Fraction& f1, const Fraction& f2)
{
return { f1.numerator * f2.numerator, f1.denominator * f2.denominator };
}
void printFraction(const Fraction& f)
{
std::cout << f.numerator << '/' << f.denominator << '\n';
}
int main()
{
Fraction f1{ getFraction() };
Fraction f2{ getFraction() };
std::cout << "Your fractions multiplied together: ";
printFraction(multiply(f1, f2));
return 0;
}
|
问题 3
在上一题的答案中,为什么getFraction()是按值返回,而不是按引用返回?
查看答案
因为局部变量temp是一个Fraction对象,它会在函数结束时离开作用域。如果按引用返回temp,就会把一个悬空引用返回给调用者。