Skip to main content

4 posts tagged with "C++"

View All Tags

· 21 min read
CheverJohn

虽然我上面开了两篇博客准备一锅把函数端掉,但是当我想到数组哈,我觉得还是有必要对它好好搞一番。为什么呢?因为当我们在形参中放一个数组时,要开始注意了昂,这个数组是按照数组基本的操作传递值的,它是以指针的方式运转的!!!一提到指针哈,就得好好琢磨琢磨了,毕竟我江某人如今的观点是C++最重要的有两个:指针和STL库。

要我说,之前讲的两章,着实是对函数基础知识的总结。而我们日常使用中,是不会这么简单的。比如说我们企业中要计算某个项目中每个用户所购买的东西之和。我们很容易想到每个数组可以索引指向一个顾客,要计算总共有多少个东西被买掉了,我们可以使用循环来计算出总和。这不是不可以哈。但是呢,我们其实可以用一个函数来实现它。我们可以在声明函数时,放入一个数组形状的形参,如下:

void function( int arr[], int a)

很明显,这里边arr就是咱们即将要传递的数组,[]里边是空的,说明我们待会要传递的数组的长度是需要额外设置的。但是我再强调一点哈,这个arr实际上并不是数组,而是一个指针!但是呢,我们在编写函数的时候,是可以将arr看做是数组的。

example

下面是我写的一个小example

#include <iostream>
const int ArSize = 8;

int sums(int arr[], int n);

int main()
{
using namespace std;
int counts[ArSize] = { 1, 2, 4, 8, 16, 32, 64,128};

int sum = sums(counts, ArSize);
cout << "Total counts: " << sum << "\n";
return 0;
}

int sums(int arr[], int n){
int total = 0;
for(int i = 0; i < n; i++)
total = total + arr[i];
return total;
}

接下来我来详细讲一讲哈,其实我们当初在学习数组的时候就知道,数组名是可以当做指针来用的,数组名指向该数组的第一个元素的地址。但是呢,这边在函数中讨论数组和指针的话。我得把需要注意的几个点额外说一哈

  1. 数组声明使用数组名来表示存储位置
  2. 对数组使用sizeof得到的是整个数组的长度,举个例子哈,int aaa[8],int是4个字节,对这个数组使用sizeof的话,我们得到的长度为32位字节
  3. 如果我们使用取地址符&的话,我们得到的也会是一个长度为32字节的内存块的地址。

arr是咱们的数组名,根据C++规则,arr指代的是第一个元素的地址。 所以咱们的函数传递的也是地址哈。元素的类型是int,那么咱们的指针也应该是int类型的。因此,我们可以使用int *来表示。

int sum = sums(int * arr, int n)

易知,int * arr 替换了int arr[],这两个的含义是相同的。

但是呢,其实这两种表达方法也是有区别的,

数组表达式(int arr[])提醒咱们的程序员,arr不仅指向int,还指向了int数组的第一个int。当指针指向数组的第一个元素时,本书使用了数组表达法;

指针表达式可以用在当我们指针指向一个独立的值,而不是第一个值的时候。

!!!要记住只有在这边是可以的等价的,在其他地方都是不等价的哦。例如我们不能再函数体中将两者相替换。

经过我们的挖掘后,知道了arr数组名实际上是一个指针的事实后,我们也可以 用方括号数组表示法来访问数组元素。无论arr是指针还是数组名,表达式arr[3]都指的是数组的第4 个元素。

这边总结出两个式子,希望能记住:

arr[i] == *(ar + i)
&arr[i] == ar + i

在强调一点,指针加一的意义,指的是加上一个与指针指向的类型的长度。例如上文中的32字节内存块。对于遍历数组而言,使用指针加法和数组下标是等效的。

数组作为参数有啥意义呢?

讲得更加清楚一点,实际上数组内容并没有传递给函数,而是将数组的地址、包含的元素类型以及元素数目提交给了函数。有了这些信息后,函数便可以使用原来的数组。传递常规变量时,函数将使用该变量的拷贝;但传递数组时,函数将使用原来的数组

一种是拷贝了原始数据,并进行操作,一种是使用指针,直接操作原始数据。都实现了函数的值传递。但是我想两种方法肯定是有利有弊的呀。继续往下分析。

数组名与指针对应是否是好事呢?

先说回答哈,确实是一件好事。将数组地址作为参数可以节省复制整个数组所需要的时间和内存。如果数组很大的话。则使用拷贝的系统开销将非常大;程序不仅需要更多的计算机内存,还需要花费时间来复制大块的数据。

但是呢,有利有弊哈,我们使用指针其本质上时使用了原始数据,增加了破坏数据的风险。不过不怕,C++可以解决它,ANSI C也可以解决它,那就是const限定符了。稍后我在写例子哈。

这边再写一个例子,用于演示咱们的指针是如何运转的:

#include <iostream>
const int ArSize = 8;
int sums(int arr[], int n);
int main(){
int things[ArSize] = {1,2,4,8,16,32,64,128};
std::cout << things << " = arr address, ";

std::cout << sizeof(things) << " = sizeof things\n";
int sum = sums(things, ArSize);
std::cout << "Total things: " << sum << std::endl;
sum = sums(things, 3);
std::cout << "First tree people buy " << sum << " things.\n";
sum = sums(things + 4, 4);
std::cout << "Last four people buy " << sum << " things.\n";
return 0;
}

int sums(int arr[], int n){
int total = 0;
std::cout << arr << " = arr, ";
std::cout << sizeof(* arr) << " = sizeof arr\n ";
for (int i = 0; i < n; i++)
total += arr[i];
return total;
}

这边的地址值和数组的长度会随着系统的变化而变化哈,如果你和你的小伙伴们运行出不一样的结果,不要诧异哦!此外,有些C++实现 的是以十进制而不是十六进制格式显示地址哈,所以不要太大惊小怪,显得见识浅薄了些。还有一些编译器以十六进制显示地址时,会加上前缀0x呢。

代码说明:

首先我这边things和arr指向了同一个地址。但是sizeof(things)的值为32,而sizeof(arr)为4(是我电脑上运行的结果哈)。这是由于sizeof(things)是整个数组的长度,而sizeof(arr)只是指针变量的长度。顺道加一个知识点,这也是必须显式传递数组长度,而不能在sums()中使用sizeof(arr)的原因;指针本身并没有之处数组的长度。

因为咱们的sums()只能通过第二个参数获知数组中的元素数量,我们可以对函数做修改。例如,程序第二次使用该函数时,这样调用它:

sum = sums(things, 3);

通过告诉函数things有3个元素,可以让它计算前3个元素的总和。

也可以提供假的数组起始位置:

sum = sums(things + 4, 4);

由于things是第一个元素的地址,因此things+4是第五个元素的地址。这条语句将计算数组第5、6、7、8个元素的总和。请注意输出中第三次函数调用选择将不同于前两个调用的地址赋给arr的。

Attention!

我们可以数组类型和元素数量告诉数组处理函数,通过两个不同的参数来传递他们:

int sums(int * arr, int n)

而不要试图使用方括号表示法来传递数组长度:

int sums(int arr[size])

各种例子,来更深入了解数组函数

案例

假设要使用一个数组来记录房地产的价值。

思路分析

首先要明确使用哪种类型。当然double的取值范围比int和long大,并且提供了足够多的有效位数来精确地表示这些值。

接下来必须决定数组元素的数目。(这边不考虑动态数组)如果房地产数目不超过5个,则可以使用一个包含5个元素的double数组。

考虑操作:两个基本的操作,一、将值读入到数组中和显示数组内容。二、重新评估每种房地产的值。

简单起见,我们规定房地产以相同比率增加或者减少。

1.填充数组

顾客不止一个,所以我们可以做多个数组,房产得有上限,毕竟我这边不搞花里胡哨的动态数组。所以函数定义为:

int fill_array(double arr[], int limit);

该函数接受两个参数,一个是数组名,另一个指定了要读取的最大元素数;该函数返回实际读取的元素数。例如,如果使用该函数处理一个包含了5个元素的数组,则将5作为第二个参数。如果只输入3个值,则该函数将返回3.

可有循环连续地将值读入到数组中,但是我们该如何提早结束循环呢?有两种思路,一、使用一个特殊值来指定输出结束。由于所有的属性不为负,我们可以使用负值来指出输入结束。二、该函数应对错误输入做出反应,如停止输入等。代码如下:

int fill_array(double arr[], int limit){
using namespace std;
double temp;
int i;
for (i = 0; i < limit; i++)
{
cout << "Enter value # " << (i + 1) << ": ";
cin >> temp;
if (!cin)
{
cin.clear();
while (cin.get() != '\n')
continue;
cout << "Bad input; input process terminated.\n";
break;
}
else if (temp < 0)
break;
arr[i] = temp;
}
return i;
}

上面函数可以判断输入是否出错,比如说负值啦等等。如果输入正确的话,则循环将会在读取最大数目的值后结束。循环完成的最后一项工作后,将i加1,因此循环结束后,i将比最后一个数组索引大1,即等于填充的元素数目。然后,函数返回这个值。

2.显示数组及用const保护数组

不是啥高大的东西,就是显示元素的数组,但是最重要的东西应该是const保护数组。

当我们用数组名表示指针传递值时,会导致原始数据受到威胁。这个时候我呼应了上文中所要讲的方法const保护数组不被修改。

为了防止函数无意中修改数组的内容,我们可以在声明形参时使用关键字const:

void show_array(const double arr[], int n);

该声明表明,指针arr指向的是常量数据。这意味着不能使用arr修改该数据,也就是说,可以使用值,但是不会修改。咳咳,这并不是意味着原始数组必须是常量,而只是意味着不能在show_array()函数中使用arr来修改数据。因此该函数将数组视为只读数据。 如果你要在函数中给原数组赋值的话,是会报错的。

show_array函数代码如下:

void show_array(const double arr[], int n){
using namespace std;
for (int i = 0; i < n; i++)
{
cout << "Property #" << (i + 1) << ": $";
cout << arr[i] << endl;
}

}

3.修改数组

实现的功能是对数组中每个元素与同一个重新评估因子相乘。需要给函数传递3个参数:因子、数组和元素数目。该函数不需要返回值,因此代码如下:

void show_array(const double arr[], int n){
using namespace std;
for (int i = 0; i < n; i++)
{
cout << "Property #" << (i + 1) << ": $";
cout << arr[i] << endl;
}

}

这个就和上一个函数不一样了,这边是必须要修改值的,所以不能加const

4.组合代码解出题目

#include <iostream>
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(){
using namespace std;
double properties[Max];

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; Please enter a number: ";
}
revalue(factor, properties, size);
show_array(properties, size);
}
cout << "Done.\n";
cin.get();
cin.get();
return 0;
}

int fill_array(double arr[], int limit){
using namespace std;
double temp;
int i;
for (i = 0; i < limit; i++)
{
cout << "Enter value #" << (i + 1) << ": ";
cin >> temp;
if(!cin)
{
cin.clear();
while(cin.get() != '\n')
continue;
cout << "Bad input; input process terminated.\n";
break;
}else if (temp < 0)
break;
arr[i] = temp;
}
return i;
}


void show_array(const double arr[], int n){
using namespace std;
for (int i = 0; i < n; i++){
cout << "Property #" << (i + 1) << ": $";
cout << arr[i] << endl;
}
}

void revalue(double r, double arr[], int n){
for(int i = 0; i < n; i++)
arr[i] *= r;
}

5.程序说明

回顾一下整个过程。我们首先考虑的是通过数据类型和设计适当的函数来处理数据,然后讲这些函数组合成一个程序。有时这个也称为自下而上的程序设计(bottom-up programming),因为设计过程是从组建到整体进行。这种方法非常适合于OOP——它首先强调的是数据表示和操纵。

以前的过程性编程倾向于从上而下的程序设计,首先指定模块化设计方案,然后在研究细节,

最终产品都是模块化程序,也就是我们最后得到的东西都是模块化的东西,据我目前的经验来看,当代程序的思路都是模块化!

6.数组处理函数的常用编写方式

总结一下数组处理函数无非就两种情况

情况一:

void f_modify(double ar[], int n);

情况二:

void _f_no_change(const double ar[], int n);

再扯几句哈,函数原型是可以省略变量名的,也可以将返回类型作指定,比如这边就指定了void。

7.使用数组区间的函数

上面我们讲数组和函数的时候,用的是传统的C++方法,将指向数组起始处的指针作为一个参数,将数组长度作为第二个参数(指针指出数组的位置和数组类型),这样便给函数提供了找到所有数据所需要的信息。

我们处理数组的C++函数,必须将数组中的数据种类、数组的起始位置和数组中元素数量给函数。

还有一种给函数提供所需信息的方法是,即指定元素区间,这可以通过传递两个指针来完成:一个指针表示数组的开头,另一个指针标识数组的尾部。(C++标准模板库STL中将区间方法广义化了,STL方法使用“ 超尾”概念来指定区间,也就是说,对于数组而言,标识数组结尾的参数将是指向最后一个元素后面的指针。举个例子:

double elboud[20];

指针elboud和elboud+20定义了区间。唉,其实就是数组名+多少个(数字)从而做出区间,写个小例子便于理解:

#include <iostream>
const int ArSize = 8;
int sums(const int * begin, const int * end);
int main(){
using namespace std;
int things[ArSize] = {1, 2, 4, 8, 16, 32, 64, 128};

int sum = sums(things, things + ArSize);
cout << "Total things eaten: " << sum << endl;
sum = sums(things, things + 3);
cout << "First three people buy " << sum << " things.\n";
sum = sums(things + 4, things + 8);
cout << "Last four people buy " << sum << " things.\n";
return 0;
}

int sums(const int * begin, const int * end){
const int * pt;
int total = 0;

for(pt = begin; pt != end; pt++)
total += *pt;

return total;
}

太简单了,不解释了!

· 12 min read
CheverJohn

C++自带一个包含函数的大型库(标准ANSI库加上多个C++类),但真正的编程乐趣在于编写自己的函数;另一方面,要提高编程效率,可以更深入地学习STL和BOOSTC++提供的功能。

7.1 复习函数的基本知识

来复习一下介绍过的有关函数的知识。要使用C++函数,必须完成以下工作:

  • 提供函数的定义
  • 提供函数原型
  • 调用函数

库函数是已经定义好和编译好的函数,同时可以使用标准库头文件提供其原型,因此只需正确地调用这种函数即可。比如说strlen()函数,可以用来确定字符串的长度。相关的标准头文件cstring包含了strlen()和其他一些与字符串相关的函数的原型。

然后还有一点要注意的是,咱们程序员在编写函数的时候,一定要注意三点——定义、提供原型、调用。

#include <iostream>

void simple();

int main(){
using namespace std;
cout << "main() will call the simple() function:\n";
simple();
cout << "main() is finished with the simple() function.\n";
cin.get();
return 0;
}



void simple(){
using namespace std;
cout << "I'm but a simple function.\n";
}

上面就是一个小例子。

7.1.1 定义函数

可以将函数分为两类:没有返回值的函数和有返回值的函数。没有返回值的函数被称为void函数,其通用格式如下:

void functionName(parameterList){
statement(s)
return ;
}

其中,paramterList指定了传递给函数的参数类型和数量,本章后面将更详细地介绍该列表。可选的返回语句标记了函数的结尾;否则,函数将在右花括号处结束。

对于返回值,需要注意的是,

  • 如果原函数的数据类型是double但是返回的是int,返回值将会被强制转化为double
  • C++对返回值的类型有一定的限制:不能是数组,但可以是其他任何类型——整数、浮点数、指针、甚至可以是结构和对象(不过我们可以将数组作为结构或对象组成部分来返回)
  • 若函数包含多条返回语句(例如,它们位于不同的ifelse选项中)则函数在执行遇到的第一条返回语句后结束。

干货分享:虽然作为一名程序员不需要知道函数是怎么返回值的,但是对这个问题有所了解有助于澄清概念。

通常函数通过将返回值复制到指定的CPU寄存器或内存单元中来将其返回。

随后,调用程序将查看该存储单元。

返回函数和调用函数必须就该内存单元中存储的数据的类型达成一致。

函数原型将返回值类型告知调用程序,而函数定义命令被调用函数应返回什么类型的数据。

在原型中提供与定义中相同的信息似乎有些多余,但这样做确实有道理。要让信差从办公室的办公桌上取走一些物品,则向信差和办公室中的同事交代自己的意图,将提高信差顺利完成这项工作的概率。

7.1.2 函数原型和函数调用

咱们对函数调用这个知识点是很熟悉的,但是对函数原型并不清楚,函数原型通常隐藏在include文件中。

 函数声明由函数返回类型、函数名和形参列表组成。形参列表必须包括形参类型,但是不必对形参命名。这三个元素被称为函数原型,函数原型描述了函数的接口。

1.为什么需要原型

原型描述了函数到编译器的接口,也就是说,它将函数返回值的类型(如果有的话)以及参数的类型和数量告诉编译器。

double volume = cube(side);

首先,原型告诉编译器,cube()有一个double 参数。如果程序没有提供这样的参数,原型将让编译器能够捕获这种错误。其次,cube()函数完成计算后,将把返回值放置在指定的位置——可能是CPU寄存器,也可能是内存中。然后调用函数(这里为main())将从这个位置取得返回值。由于原型指出了cube()的类型为double,因此编译器知道应检索多少个字节以及如何解释它们。如果没有这些信息,编译器将只能进行猜测,而编译器是不会这样做的。

编译器需要原型,因为这样将提高效率,让编译器在广大的文件里找有用的文件,是一件大海捞针的事儿,且编译器在搜索文件的时候将必须停止 对main()的编译。一个更严重的问题是,函数甚至可能并不在项目的文件中(C++允许将一个程序放在多个文件中,单独编译他们,然后再将它们组合起来),在这种情况下,可能导致编译器在编译main()时无权访问函数代码。如果函数位于库中,情况也将如此。

综上,避免使用函数原型的唯一办法就是,在首次使用函数之前定义它,但这并不总是可行的。

2.原型的语法

函数原型是一条语句,因此必须以分号结束。获取原型的方法就是,复制函数定义中的函数头,并添加分号即可。

3.原型的功能

可以极大地降低程序出错的几率。

原型应确保以下几点:

  • 编译器正确处理函数返回值
  • 编译器检查使用的参数数目是否正确
  • 编译器检查使用的参数类型是否正确。如果不正确,则转换为正确的类型(如果可能的话)。

讲一讲参数数目不对时将发生的情况

例如,假设进行了如下调用

double z = cube();

如果没有函数原型,编译器将允许它通过。当函数被调用时,它将找到cube()调用存放值的位置,并使用这里的值。这个正式C语言从C++借鉴原型之前的工作方式。对于ANSI C(美国国家标准规定的C语言)来讲,原型是可选的,因此有些C语言程序正是这样工作的。但在C++中,原型是不可选。因此保证了不会发生这种错误。

接下来,我们假设提供了一个参数,但是它的类型不正确。在C语言中,这将造成奇怪的错误,例如,如果函数需要一个int值(假设占16位),而程序员传递了一个double值(假设占64位),则函数将只检查64位中的前16位,并试图将它们解释为一个int值。但是C++自动将传递的值转换为原型中指定的类型,条件是两者都是算数类型。

这个时候我来举个例子吧

#include <iostream>
void cheers(int);
double cube(double x);
int main(){
using namespace std;
cheers(5);
cout << "Give me a number: ";
double side;
cin >> side;
double volume = cube(side);
cout << "A" << side << "-foot cube has a volume of ";
cout << volume << " cubic feet.\n";
cheers(cube(2));
return 0;
}

void cheers(int n){
using namespace std;
for(int i = 0; i < n; i++){
cout << "Cheers! ";
cout << endl;
}
}

double cube(double x){
return x * x * x;
}

这个是C++的代码

这个代码就能够应付下述语句中两次出现的类型不匹配的情况:

cheers(cube(2));

代码讲解:首先程序将int的值2传递给cube(),而后者期望的是double类型。编译器注意到,cube()原型指定了一个double类型参数,因此将2转化为2.0(一个double值)。接下来cube()返回一个double值(8.0),这个值被用作cheer()的参数。编译器将再一次检查原型,并发现cheer()要求一个int参数,因此它将返回值转换为整数8.通常原型自动将被传递的参数强制转换为期望的类型

​ 自动类型转换并不能避免所有可能的错误。例如,如果将8.33E27传递给期望一个int值的函数,则这样大的值将不能正确转换为int值。当较大的类型被自动转换为较小的类型时,有些编译器将发生警告,指出这可能丢失数据。

​ 仅当有意义的时候,原型才会导致类型转换。正如原型不会将整数转换为结构或指针。

在编译阶段进行的原型化被称为静态类型检查(static type checking)。可以看出,静态类型检查可捕获许多在运行阶段非常难以捕获的错误。

· 4 min read
CheverJohn

7.2 函数参数和按值传参

C++中经常性的一种操作就是,案值传递参数,这意味着将数值传递给一个函数,然后后者还会返回一个值赋给一个新的变量。

在函数中被声明的变量是函数私有的,这是他自己凭本事声明的变量。在函数被调用时,计算机会为他们(指变量)申请内存。在函数结束的时候,计算机会释放掉内存,这样的变量我们称之为局部变量

7.2.1 多个参数

函数可以包含多个参数,例如写成这种形式:

function('R', 25);

上述函数调用将两个参数传递给函数function( ),

同样,在定义函数时,也在函数头中使用由逗号分割的参数声明列表

void function(char c, int n)

之前写的function中R传递给了c,25传递给了n

7.2.2 另外一个接受两个参数的函数

注意了注意了,这边要讲一个比较有趣的东西了

Problem:美国许多州都采用某种纸牌游戏的形式来发行彩票,让参与者从卡片中选择一定数目的选项,例如从51个数字中选取6个,随后彩票管理者将随机抽取6个数,如果参与者选择的数字与这6个完全相同,将赢得大约几百万美元的奖金,我们的函数将计算中奖的几率

首先,需要一个公式。假设必须要从51个数中选取6个数,而获奖的几率是1/R,则R的计算公式如下 $$ R =\frac {51×50×49×48×47×46}{6×5×4×3×2×1} $$ 选择6个数时,分母为前6个整数的乘积或6的阶乘分子也是6个连续整数的乘积,从51开始依次递减一,推而广之,如果从numbers个数中选取picks个数,而分母是picks的阶乘,分子为numbers开始向前的picks个整数的乘积。可以用for循环进行计算:

long double result = 1.0
for (int n = numbers, p = picks; p > 0; n--, p--)
result = result * n / p;

循环不是首先将所有的分子项相乘,而是首先将1.0与第1个分子项相乘,然后除以第1个分母项,然后下一轮循环,乘以第2个分子项并除以第2个分母项,这样得到的乘积和先进行乘法运算得到的一样,例如对于(10*9)/(2×1)和(10÷2)×(9÷1),前者将计算90÷2得到45,或者将计算5×9得到49,这两种方法得到的结果相同,但前者的中间值90大于后者,因子越多,中间值的差别就越大,当数字非常大时,这种交替进行乘除运算的策略,可以防止中间结果超过最大的浮点数。

7.3 函数和数组

· 7 min read
CheverJohn

正如标题哈,本篇博文讲的就是结构体和函数。不是新手向的教程哈,更多的这是一篇记录经验的文章。话不多说,开始咯!

我们将注意力从之前的数组转到结构上面。为结构写函数可比为数组写函数要简单得多了。虽然结构变量和数组一样,都可以存储多个数据项。但是在涉及到函数的时候,结构变量的行为更接近与基本的单值变量。这个名词可能太专业了哈。我做一下解释,还是拿数组来做比较,数组中都是一个元素为单位存储的,在结构中,相应的便是将数据组合成一个实体,实体就是数据,数据就是实体。实体(结构)==元素(数组)

这边实现传值的思想主要是利用了一个结构可以赋给另外一个结构这样子的常识知识。就像普通变量一样。我需要额外补充的是,函数将使用原始结构的副本。

函数也可以返回结构 。这边与使用数组名就是代表了数组第一个元素的地址这样的观点不同的是,结构名只是结构的名称,要获得结构的地址,必须使用地址运算符&

咱们传递结构体的基本方法就是按值传递(圈起来,要考!)但是C++作为一门精细的语言,细节到每一个内存都要深挖,不能忍受这种方法的一个缺点:如果结构非常大的话,复制结构将增加内存要求,降低系统运行的速度。出于这种原因,我们更愿意的是采取按地址传递(没错,又是“该死”的指针,所以说C++指针一定要学好!)。然后使用指针来访问结构的内容。

首先介绍“按值传递”

当结构比较小时,按值传递最舒服了用起来。接下来举的例子来源于《C++ Primer Plus(第六版)》

例子:

从a到b城需要3小时50分钟,而从b到g城需要1小时25分钟,对于这种时间,可以使用结构来表示——一个成员表示小时值,另一个成员表示分钟值,将两个时间加起来需要一些技巧,因为可能需要将分钟值转换为小时。例如前面列出的两个时间的总和为4小时75分钟,应将它转化为5小时15分钟,下面开发用于表示时间值的结构,然后再开发一个函数,它接受两个这样的结构为参数并返回表示参数的和的结构。

突出介绍一下结构函数的写法

struct travel_time{
int hours;
int mins;
};

//在已经定义好时间的结构的前提下,开始声明结构函数

travel_time sum(travel_time t1, travel_time t2);

完整代码如下:

#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};
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){
using namespace std;
cout << t.hours << " hours, " << t.mins << " minutes\n";
}

代码解析

travel_time 就像是一个标准的类型名,可以用来声明变量、函数的返回类型和函数的参数类型。

然后试travel_time结构的变量total 和t1,使用成员运算符进行数据的操作。代码很简单,可以看得懂。

然后介绍“按地址传递”

这一次我换了个题目,题目的内容我简要地说一下哈:通常我们表示一件物品的位置的时候,都是采取选择参照系利用直角坐标系或者极坐标系进行精确表示的,这边写了一个程序用于两种坐标系之间的转换。就是这样。

由于和上面的代码极其类似,所以我认为看看例子就能理解欧,就不做解析了。over

这个代码有一个小东西讲讲熬,就是while里边的cin>> 的用法,cin的特性是可以输入int整型数,但是一旦发现你输入的不是数字的时候,他就会不满足条件,变为0,然后while就会跳出循环,蛮好用的小技巧,推荐学习!

代码奉上:

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