虽然我上面开了两篇博客准备一锅把函数端掉,但是当我想到数组哈,我觉得还是有必要对它好好搞一番。为什么呢?因为当我们在形参中放一个数组时,要开始注意了昂,这个数组是按照数组基本的操作传递值的,它是以指针的方式运转的!!!一提到指针哈,就得好好琢磨琢磨了,毕竟我江某人如今的观点是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;
}
接下来我来详细讲一讲哈,其实我们当初在学习数组的时候就知道,数组名是可以当做指针来用的,数组名指向该数组的第一个元素的地址。但是呢,这边在函数中讨论数组和指针的话。我得把需要注意的几个点额外说一哈
- 数组声明使用数组名来表示存储位置
- 对数组使用sizeof得到的是整个数组的长度,举个例子哈,int aaa[8],int是4个字节,对这个数组使用sizeof的话,我们得到的长度为32位字节
- 如果我们使用取地址符&的话,我们得到的也会是一个长度为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;
}
太简单了,不解释了!