2026/1/1 15:07:08
网站建设
项目流程
找人做公司网站,上海互联网营销策划公司,宁波网站建设设计公司排名,数据开发网站模板函数和数组
到目前为止#xff0c;本书的函数示例都很简单#xff0c;参数和返回值的类型都是基本类型。但是#xff0c;函数是处理更复
杂的类型#xff08;如数组和结构#xff09;的关键。下面来如何将数组和函数结合在一起。
假设使用一个数组来记录家庭野餐中每人吃了…函数和数组到目前为止本书的函数示例都很简单参数和返回值的类型都是基本类型。但是函数是处理更复杂的类型如数组和结构的关键。下面来如何将数组和函数结合在一起。假设使用一个数组来记录家庭野餐中每人吃了多少个甜饼每个数组索引都对应一个人元素值对应于这个人所吃的甜饼数量。现在想知道总数。这很容易只需使用循环将所有数组元素累积起来即可。将数组元素累加是一项非常常见的任务因此设计一个完成这项工作的函数很有意义。这样就不必在每次计算数组总和时都编写新的循环了。考虑函数接口所涉及的内容。由于函数计算总数因此应返回答案。如果不分吃甜饼则可以让函数的返回类型为int。另外函数需要知道要对哪个数组进行累计因此需要将数组名作为参数传递给它。为使函数通用而不限于特定长度的数组还需要传递数组长度。这里唯一的新内容是需要将一个形参声明为数组名。下面来看一看函数头及其其他部分int sum_arr(int arr[],int n)这看起来似乎合理。方括号指出arr 是一个数组而方括号为空则表明可以将任何长度的数组传递给该函数。但实际情况并非如此arr 实际上并不是数组而是一个指针好消息是在编写函数的其余部分时可以将arr 看作是数组。首先通过一个示例验证这种方法可行然后看看它为什么可行。程序清单7.5 演示如同使用数组名那样使用指针的情况。程序将数组初始化为某些值并使用sum_arr( )函数计算总数。注意到sum_arr( )函数使用arr 时就像是使用数组名一样。#include iostream const int ArSize 8; int sum_arr(int arr[], int n); int main() { using namespace std; int cookies[ArSize] { 1,2,4,8,16,32,64,128 }; int sum sum_arr(cookies, ArSize); cout Total cookies eaten: sum \n; return 0; } int sum_arr(int arr[], int n) { int total 0; for (int i 0; i n; i) total total arr[i]; return total; }函数如何使用指针来处理数组在大多数情况下C和C 语言一样也将数组名视为指针。第4 章介绍过C将数组名解释为其第一个元素的地址cookiescookies[0];该规则有一些例外。首先数组声明使用数组名来标记存储位置其次对数组名使用sizeof 将得到整个数组的长度以字节为单位第三正如第4 章指出的将地址运算符用于数组名时将返回整个数组的地址例如cookies 将返回一个32 字节内存块的地址如果int 长4 字节。程序清单7.5 执行下面的函数调用int sumsum_arr(cookies,ArSize);其中cookies 是数组名而根据C规则cookies 是其第一个元素的地址因此函数传递的是地址。由于数组的元素的类型为int因此cookies 的类型必须是int 指针即int *。这表明正确的函数头应该是这样的int sum_arr(int *arr,int n)其中用int * arr 替换了int arr [ ]。这证明这两个函数头都是正确的因为在C中当且仅当用于函数头或函数原型中int *arr 和int arr [ ]的含义才是相同的。它们都意味着arr 是一个int 指针。然而数组表示法int arr[ ]提醒用户arr 不仅指向int还指向int 数组的第一个int。当指针指向数组的第一个元素时本书使用数组表示法而当指针指向一个独立的值时使用指针表示法。别忘了在其他的上下文中int * arr 和int arr [ ]的含义并不相同。例如不能在函数体中使用int tip[ ]来声明指针。鉴于变量arr 实际上就是一个指针函数的其余部分是合理的。第4 章在介绍动态数组时指出过同数组名或指针一样也可以用方括号数组表示法来访问数组元素。无论arr 是指针还是数组名表达式arr [3]都指的是数组的第4 个元素。就目前而言提请读者记住下面两个恒等式将不会有任何坏处arr[i]*(ari) arr[i]ari记住将指针包括数组名加1实际上是加上了一个与指针指向的类型的长度以字节为单位相等的值。对于遍历数组而言使用指针加法和数组下标时等效的。将数组作为参数意味着什么我们来看一看程序清单7.5 暗示了什么。函数调用sum_arr(coolies, ArSize)将cookies 数组第一个元素的地址和数组中的元素数目传递给sum_arr( )函数。sum_arr( )函数将cookies 的地址赋给指针变量arr将ArSize 赋给int 变量n。这意味着程序清单7.5 实际上并没有将数组内容传递给函数而是将数组的位置地址、包含的元素种类类型以及元素数目n 变量提交给函数参见图7.4。有了这些信息后函数便可以使用原来的数组。传递常规变量时函数将使用该变量的拷贝但传递数组时函数将使用原来的数组。实际上这种区别并不违反C按值传递的方法sum_arr( )函数仍传递了一个值这个值被赋给一个新变量但这个值是一个地址而不是数组的内容。数组名与指针对应是好事吗确实是一件好事。将数组地址作为参数可以节省复制整个数组所需的时间和内存。如果数组很大则使用拷贝的系统开销将非常大程序不仅需要更多的计算机内存还需要花费时间来复制大块的数据。另一方面使用原始数据增加了破坏数据的风险。在经典的C 语言中这确实是一个问题但ANSI C 和C中的const 限定符提供了解决这种问题的办法。稍后将介绍一个这样的示例但先来修改程序清单7.5以演示数组函数是如何运作的。程序清单7.6 表明cookies 和arr 的值相同。它还演示了指针概念如何使sum_arr 函数比以前更通用。该程序使用限定符std::而不是编译指令using 来提供对cout 和endl 的访问权。#include iostream const int ArSize 8; int sum_arr(int arr[], int n); int main() { int cookies[ArSize] {1,2,4,8,16,32,64,128}; std::cout cookies array address, ; std::cout sizeof cookies sizeof cookies\n; int sum sum_arr(cookies, ArSize); std::cout Total cookies eaten: sum std::endl; sumsum_arr(cookies,3); sumsum_arr(cookies4,4); std::cout Last four eaters ate sum cookies.\n; return 0; } int sum_arr(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 total arr[i]; return total; }运行效果000000EDA4B4FB28 array address, 32 sizeof cookies 000000EDA4B4FB28 arr, 8 sizeof arr Total cookies eaten: 255 000000EDA4B4FB28 arr, 8 sizeof arr 000000EDA4B4FB38 arr, 8 sizeof arr Last four eaters ate240cookies.注意地址值和数组的长度随系统而异。另外有些C实现以十进制而不是十六进制格式显示地址还有些编译器以十六进制显示地址时会加上前缀0x。程序说明程序清单7.6 说明了数组函数的一些有趣的地方。首先cookies 和arr 指向同一个地址。但sizeof cookies的值为32而sizeof arr 为4。这是由于sizeof cookies 是整个数组的长度而sizeof arr 只是指针变量的长度上述程序运行结果是从一个使用4 字节地址的系统中获得的。顺便说一句这也是必须显式传递数组长度而不能在sum_arr( )中使用sizeof arr 的原因指针本身并没有指出数组的长度。由于sum_arr( )只能通过第二个参数获知数组中的元素数量因此可以对函数“说谎”。例如程序第二次使用该函数时这样调用它sumsum_arr(cookies,3);通过告诉该函数cookies 有3 个元素可以让它计算前3 个元素的总和。为什么在这里停下了呢还可以提供假的数组起始位置sumsum_arr(cookies4,4);由于cookies 是第一个元素的地址因此cookies 4 是第5 个元素的地址。这条语句将计算数组第5、6、7、8 个元素的总和。请注意输出中第三次函数调用选择将不同于前两个调用的地址赋给arr 的。是的可以将cookies[4]而不是cookies 4 作为参数它们的含义是相同的。注意为将数组类型和元素数量告诉数组处理函数请通过两个不同的参数来传递它们void fillArray(int arr[],int size);而不要试图使用方括号表示法来传递数组长度void fillArray(int arr[size]);更多数组函数示例选择使用数组来表示数据时实际上是在进行一次设计方面的决策。但设计决策不仅仅是确定数据的存储方式还涉及到如何使用数据。程序员常会发现编写特定的函数来处理特定的数据操作是有好处的这里讲的好处指的是程序的可靠性更高、修改和调试更为方便。另外构思程序时将存储属性与操作结合起来便是朝OOP 思想迈进了重要的一步以后将证明这是很有好处的。来看一个简单的案例。假设要使用一个数组来记录房地产的价值假设拥有房地产。在这种情况下程序员必须确定要使用哪种类型。当然double 的取值范围比int 和long 大并且提供了足够多的有效位数来精确地表示这些值。接下来必须决定数组元素的数目。对于使用new 创建的动态数组来说可以稍后再决定但我们希望使事情简单一点。如果房地产数目不超过5 个则可以使用一个包含5 个元素的double 数组。现在考虑要对房地产数组执行的操作。两个基本的操作分别是将值读入到数组中和显示数组内容。我们再添加另一个操作重新评估每种房地产的值。为简单起见假设所有房地产都以相同的比率增加或者减少。别忘了这是一本关于C的书而不是关于房地产管理的书。接下来为每项操作编写一个函数然后编写相应的代码。下面首先介绍这些步骤然后将其用于一个完整的示例中。1填充数组由于接受数组名参数的函数访问的是原始数组而不是其副本因此可以通过调用该函数将值赋给数组元素。该函数的一个参数是要填充的数组的名称。通常程序可以管理多个人的投资因此需要多个数组因此不能在函数中设置数组长度而要将数组长度作为第二个参数传递就像前一个示例那样。另外用户也可能希望在数组被填满之前停止读取数据因此需要在函数中建立这种特性。由于用户输入的元素数目可能少于数组的长度因此函数应返回实际输入的元素数目。因此该函数的原型如下int fill_array(double ar[],int limit);该函数接受两个参数一个是数组名另一个指定了要读取的最大元素数该函数返回实际读取的元素数。例如如果使用该函数来处理一个包含5 个元素的数组则将5 作为第二个参数。如果只输入3 个值则该函数将返回3。可以使用循环连续地将值读入到数组中但如何提早结束循环呢一种方法是使用一个特殊值来指出输入结束。由于所有的属性都不为负因此可以使用负数来指出输入结束。另外该函数应对错误输入作出反应如停止输入等。这样该函数的代码如下所示int fill_array(double ar[],int limit) { using namespace std; double temp; int i; for(i0;ilimit;i) { coutEnter value #(i1):; cintemp; if(!cin) { cin.clear(); while(cin.get()!\n) continue; coutBad input;input process terminated.\n; break; } else if(temp0) break; ar[i]temp; } return i; }注意代码中包含了对用户的提示。如果用户输入的是非负值则这个值将被赋给数组否则循环结束。如果用户输入的都是有效值则循环将在读取最大数目的值后结束。循环完成的最后一项工作是将i 加1因此循环结束后i 将比最后一个数组索引大1即等于填充的元素数目。然后函数返回这个值。2显示数组及用const 保护数组创建显示数组内容的函数很简单。只需将数组名和填充的元素数目传递给函数然后该函数使用循环来显示每个元素。然而还有另一个问题—确保显示函数不修改原始数组。除非函数的目的就是修改传递给它的数据否则应避免发生这种情况。使用普通参数时这种保护将自动实现这是由于C按值传递数据而且函数使用数据的副本。然而接受数组名的函数将使用原始数据这正是fill_array( )函数能够完成其工作的原因。为防止函数无意中修改数组的内容可在声明形参时使用关键字const 参见第3 章void show_array(const double ar[],int n);该声明表明指针ar 指向的是常量数据。这意味着不能使用ar 修改该数据也就是说可以使用像ar[0]这样的值但不能修改。注意这并不是意味着原始数组必须是常量而只是意味着不能在show_array( )函数中使用ar 来修改这些数据。因此show_array( )将数组视为只读数据。假设无意间在show_array( )函数中执行了下面的操作从而违反了这种限制ar[0]10;编译器将禁止这样做。例如Borland C将给出一条错误消息如下所示稍作了编辑Cannot modify a const object in function show_array(const double *,int)其他编译器可能用其他措词表示其不满。这条消息提醒用户C将声明const double ar [ ]解释为const double *ar。因此该声明实际上是说ar 指向的是一个常量值。结束这个例子后我们将详细讨论这个问题。下面是show_array( )函数的代码void show_array(const double ar[],int n) { using namespace std; for(int i0;in;i) { coutProperty #(i1):$; coutar[i]endl; } }3修改数组在这个例子中对数组进行的第三项操作是将每个元素与同一个重新评估因子相乘。需要给函数传递3 个参数因子、数组和元素数目。该函数不需要返回值因此其代码如下void revalue(double r,double ar[],int n) { for(int i0;in;i) ar[i]*r; }由于这个函数将修改数组的值因此在声明ar 时不能使用const。4将上述代码组合起来至此您根据数据的存储方式数组和使用方式3 个函数定义了数据的类型因此可以将它们组合成一个程序。由于已经建立了所有的数组处理工具因此main( )的编程工作非常简单。该程序检查用户输入的是否是数字如果不是则要求用户这样做。余下的大部分编程工作只是让main( )调用前面开发的函数。程序清单7.7 列出了最终的代码它将编译指令using 放在那些需要iostream 工具的函数中。#include iostream const int Max 5; int fill_array(double ar[], int limit); void show_array(const double ar[], int n); void revalue(double r, double ar[], 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 ar[], 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; } ar[i] temp; } return i; } void show_array(const double ar[], int n) { using namespace std; for (int i 0; i n; i) { cout Property # (i 1) : $; cout ar[i] endl; } } void revalue(double r, double ar[], int n) { for (int i 0; i n; i) { ar[i] * r; } }运行结果Enter value #4:240000 Enter value #5:118000 Property #1: $100000 Property #2: $80000 Property #3: $222000 Property #4: $240000 Property #5: $118000 Enter revaluation factor:0.8 Property #1: $80000 Property #2: $64000 Property #3: $177600 Property #4: $192000 Property #5: $94400 Done.函数fill_array( )指出当用户输入5 项房地产值或负值后将结束输入。第一次运行演示了输入5 项房地产值的情况第二次运行演示了输入负值的情况。5程序说明前面已经讨论了与该示例相关的重要编程细节因此这里回顾一下整个过程。我们首先考虑的是通过数据类型和设计适当的函数来处理数据然后将这些函数组合成一个程序。有时也称为自下而上的程序设计bottom-up programming因为设计过程从组件到整体进行。这种方法非常适合于OOP—它首先强调的是数据表示和操纵。而传统的过程性编程倾向于从上而下的程序设计top-down programming首先指定模块化设计方案然后再研究细节。这两种方法都很有用最终的产品都是模块化程序。6数组处理函数的常用编写方式假设要编写一个处理double 数组的函数。如果该函数要修改数组其原型可能类似于下面这样void f_modify(double ar[],int n);如果函数不修改数组其原型可能类似于下面这样void f_no_change(const double ar[],int n);当然在函数原型中可以省略变量名也可将返回类型指定为类型。这里的要点是ar 实际上是一个指针指向传入的数组的第一个元素另外由于通过参数传递了元素数这两个函数都可使用任何长度的数组只要数组的类型为doubledouble rewards[1000]; double faults[50]; ... f_modify(rewards,1000); f_modify(faults,50);这种做法是通过传递两个数字数组地址和元素数实现的。正如您看到的函数缺少一些有关原始数组的知识例如它不能使用sizeof 来获悉原始数组的长度而必须依赖于程序员传入正确的元素数。使用数组区间的函数正如您看到的对于处理数组的C函数必须将数组中的数据种类、数组的起始位置和数组中元素数量提交给它传统的C/C方法是将指向数组起始处的指针作为一个参数将数组长度作为第二个参数指针指出数组的位置和数据类型这样便给函数提供了找到所有数据所需的信息。还有另一种给函数提供所需信息的方法即指定元素区间range这可以通过传递两个指针来完成一个指针标识数组的开头另一个指针标识数组的尾部。例如C标准模板库STL将在第16 章介绍将区间方法广义化了。STL 方法使用“超尾”概念来指定区间。也就是说对于数组而言标识数组结尾的参数将是指向最后一个元素后面的指针。例如假设有这样的声明double elbuod[20];则指针elboud 和elboud 20 定义了区间。首先数组名elboub 指向第一个元素。表达式elboud 19指向最后一个元素即elboud[19]因此elboud 20 指向数组结尾后面的一个位置。将区间传递给函数将告诉函数应处理哪些元素。程序清单7.8 对程序清单7.6 做了修改使用两个指针来指定区间。#include iostream const int ArSize 8; int sum_arr(const int *begin,const int *end); int main() { using namespace std; int cookies[ArSize] {1,2,4,8,16,32,64,128}; int sumsum_arr(cookies, cookiesArSize); cout Total cookies eaten: sum endl; sum sum_arr(cookies, cookies3); cout First three eaters ate: sum cookies.\n; sum sum_arr(cookies4, cookies8); cout Last four eaters ate: sum cookies.\n; return 0; } int sum_arr(const int *begin, const int *end) { const int* pt; int total 0; for (pt begin; pt ! end; pt) total total *pt; return total; }运行结果Total cookies eaten:255 First three eaters ate:7 cookies. Last four eaters ate:240 cookies.程序说明请注意程序清单7.8 中sum_array( )函数中的for 循环for(ptbegin;pt!end;pt) totaltotal*pt;它将pt 设置为指向要处理的第一个元素begin 指向的元素的指针并将*pt元素的值加入到total中。然后循环通过递增操作来更新pt使之指向下一个元素。只要pt 不等于end这一过程就将继续下去。当pt 等于end 时它将指向区间中最后一个元素后面的一个位置此时循环将结束。其次请注意不同的函数调用是如何指定数组中不同的区间的int sumsum_arr(cookies,cookiesArSize); ... sumsum_arr(cookies,cookies3) ... sumsum_arr(cookies4,cookies8);指针cookies ArSize 指向最后一个元素后面的一个位置数组有ArSize 个元素因此cookies[ArSize − 1]是最后一个元素其地址为cookies ArSize – 1。因此区间[cookiescookies ArSize]指定的是整个数组。同样cookiescookies 3 指定了前3 个元素依此类推。请注意根据指针减法规则在sum_arr( )中表达式end – begin 是一个整数值等于数组的元素数目。另外必须按正确的顺序传递指针因为这里的代码假定begin 在前面end 在后面。指针和const将const 用于指针有一些很微妙的地方指针看起来总是很微妙我们来详细探讨一下。可以用两种不同的方式将const 关键字用于指针。第一种方法是让指针指向一个常量对象这样可以防止使用该指针来修改所指向的值第二种方法是将指针本身声明为常量这样可以防止改变指针指向的位置。下面来看细节。首先声明一个指向常量的指针ptint age39; const int *ptage;该声明指出pt 指向一个const int这里为39因此不能使用pt 来修改这个值。换句话来说*pt 的值为const不能被修改*pt1 cin*pt;现在来看一个微妙的问题。pt 的声明并不意味着它指向的值实际上就是一个常量而只是意味着对pt而言这个值是常量。例如pt 指向age而age 不是const。可以直接通过age 变量来修改age 的值但不能使用pt 指针来修改它*pt20; age20;以前我们将常规变量的地址赋给常规指针而这里将常规变量的地址赋给指向const 的指针。因此还有两种可能将const 变量的地址赋给指向const 的指针、将const 的地址赋给常规指针。这两种操作都可行吗第一种可行但第二种不可行const float g_earth9.80; const float *peg_earth; const float g_moon1.63; float pmg_moon;对于第一种情况来说既不能使用g_earth 来修改值9.80也不能使用pe 来修改。C禁止第二种情况的原因很简单—如果将g_moon 的地址赋给pm则可以使用pm 来修改g_moon 的值这使得g_moon的const 状态很荒谬因此C禁止将const 的地址赋给非const 指针。如果读者非要这样做可以使用强制类型转换来突破这种限制详情请参阅第15 章中对运算符const_cast 的讨论。如果将指针指向指针则情况将更复杂。前面讲过假如涉及的是一级间接关系则将非const 指针赋给const 指针是可以的int age39; int *pdage; const int *ptpd;然而进入两级间接关系时与一级间接关系一样将const 和非const 混合的指针赋值方式将不再安全。如果允许这样做则可以编写这样的代码const int **pp2; int *p1; const int n13; pp2p1; *pp2n; *p110;上述代码将非const 地址pl赋给了const 指针pp2因此可以使用pl 来修改const 数据。因此仅当只有一层间接关系如指针指向基本数据类型时才可以将非const 地址或指针赋给const 指针。注意如果数据类型本身并不是指针则可以将const 数据或非const 数据的地址赋给指向const 的指针但只能将非const 数据的地址赋给非const 指针。假设有一个由const 数据组成的数组const int months[12]{31,28,31,30,31,30,31,31,30,31,30,31}则禁止将常量数组的地址赋给非常量指针将意味着不能将数组名作为参数传递给使用非常量形参的函数int age39; const int *ptage;第二个声明中的const 只能防止修改pt 指向的值这里为39而不能防止修改pt 的值。也就是说可以将一个新地址赋给ptint age80; ptsage;但仍然不能使用pt 来修改它指向的值现在为80。第二种使用const 的方式使得无法修改指针的值int sloth3; const int *pssloth; int *const fingersloth;在最后一个声明中关键字const 的位置与以前不同。这种声明格式使得finger 只能指向sloth但允许使用finger 来修改sloth 的值。中间的声明不允许使用ps 来修改sloth 的值但允许将ps 指向另一个位置。简而言之finger 和ps 都是const而finger 和ps 不是参见图7.5。如果愿意还可以声明指向const 对象的const 指针double trouble2.0E30; const double *const sticktrouble;其中stick 只能指向 trouble而 stick 不能用来修改 trouble 的值。简而言之stick 和*stick 都是const。通常将指针作为函数参数来传递时可以使用指向const 的指针来保护数据。例如程序清单7.5 中的show_array( )的原型void show_array(const double ar[],int n);在该声明中使用const 意味着show_array( )不能修改传递给它的数组中的值。只要只有一层间接关系就可以使用这种技术。例如这里的数组元素是基本类型但如果它们是指针或指向指针的指针则不能使用const。