C++学习——第8章 使用函数编程

来源:互联网 发布:香港4g网络频段2017 编辑:程序博客网 时间:2024/05/10 16:41

1. 函数

1.1 定义函数

double power (double x, int n)

其中包含三部分:返回值的数据类型(double),函数名power和在圆括号中的函数的参数列表。注意:在函数头的最后不需要加上分号。

一般形式:

返回类型 函数名(参数列表)

如果函数没有返回值,返回类型就由void关键字指定。

函数也可以没有任何参数:

void MyFunction()

或者:

void MyFunction(void)

注意参数的类型不能是void

注意:由于用void指定返回类型的函数没有返回值,因此这种函数不能再调用程序的表达式中使用。此函数不等于任何值,测试其值或者给它赋值是没有意义的。以这种方式使用这类函数,编译器会产生错误消息。

 

1.2 函数体

代码:使用函数

#include <iostream>#include <iomanip>using namespace std;double power(double x, int n){double result = 1.0;if (n >= 0)for (int i = 0; i < n; i++)result *= x;elsefor (int i = 0; i < -n; i++)result /= x;return result;}int main(){cout << endl;for (int i = -3; i <= 3; i++)cout << setw(10) << power(8.0, i);cout << endl;return 0;}

power()函数调用了7次,每次调用时,第一个参数都是8.0,而第二个参数i的值,从-3到+3。输出为7个值,分别是8-3,、8-2、8-1、80、81、82和83

 

 

1.3 参数和变元

在调用函数值,要通过指定的变元把信息传送给函数。在调用时,变元放在函数名后面的圆括号中。如:

cout << std::setw(10) << power(8.0, i);

在调用函数式,指定的变元将代替在函数定义中使用的参数。函数中的代码在执行时,就使用变元值来初始化对应的参数。

 

1.4 返回值

一般情况下,在调用返回类型不是void的函数时,该函数必须返回一个值,其类型已在函数头中指定。因此在调用函数power()的表达式中,power()实际上是一个double类型的值。

return语句

一般形式:

return expression;

其中,expreesion必须等于在函数头中为返回值指定的类型的值。该表达式还可以是任何表达式,只要其值是指定的类型即可。它可以包含函数调用,甚至可以包含同一函数的调用。

如果返回类型指定为void,return语句中不应有表达式:

return;

 

2. 函数的声明

可以再使用之前声明函数,或者通过函数原型来定义函数。

函数原型

可以把power()函数的函数原型写为:

double power(double x, int n);

注意有分号

 

3. 给函数传送参数

3.1 按值传送机制

代码:给函数传送变元——编写一个函数,它试图修改它的一个变元,但是不会成功

#include <iostream>#include <iomanip>using namespace std;double change_it(double it);int main(){double it = 5.0;double result = change_it(it);cout << "After function execution, it = " << it << endl << "Result returned is " << result << endl;return 0;}double change_it(double it){it += 10.0;cout << endl << "Within function, it = " << it << endl;return it;}

从输出中,可以看出,在函数change_it()中给变量it加上10,对main()中的变量it没有任何影响。

函数change_it()中的变量it时该函数的局部变量,在调用该函数时,会引用所传送的变元值的副本

当然,在返回change_it()的局部变量it的值时,会制作其当前值的副本,把该副本返回给调用程序。

 

给函数传送指针

代码:传送指针——如上修改变元的值,这次成功了

#include <iostream>#include <iomanip>using namespace std;double change_it(double* pointer_to_it);int main(){double it = 5.0;double result = change_it(&it);cout << "After function execution, it = " << it << endl << "Result returned is " << result << endl;return 0;}double change_it(double* pit){*pit += 10.0;cout << endl << "Within function, it = " << *pit << endl;return *pit;}


double change_it(double* pointer_to_it);

其中在函数原型中的参数名可以与函数定义中的参数名不相同

在main()中,声明并初始化变量it后,就再调用change_it()函数时,给它传送变量it的地址:

double result = change_it(&it);

不需要创建一个指针变量来存储it的地址。只需要给函数传送的地址,可以再函数调用使用地址运算符。

 

给函数传送数组

代码:把数组作为函数的参数传送

#include <iostream>#include <iomanip>using namespace std;double average(double array[], int count);int main(){double values[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};cout << endl << " Average = " << average(values, (sizeof values)/(sizeof values[0])) << endl;return 0;}double average(double array[], int count){double sum = 0.0;for (int i = 0; i < count; i++)sum += array[i];return sum/count;}


double average(double array[], int count);

从函数原型中可以看出,它接受两个参数,数组和数组中元素的个数:

第一个参数指定为double类型的数组,不能在方括号中指定数组的大小,即使指定也没有效果。这是因为数组第一维的大小不是其类型的一部分(在考虑多维数组的指针时,也会有类似的问题)。

 

代码:在传送数组时使用指针表示法

#include <iostream>#include <iomanip>using namespace std;double average(double* array, int count);int main(){double values[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};cout << endl << " Average = " << average(values, (sizeof values)/(sizeof values[0])) << endl;return 0;}double average(double* array, int count){double sum = 0.0;for (int i = 0; i < count; i++)sum += *array++;return sum/count;}


这里的for循环,很有意思:注意修改的地方,函数原型和函数定义的参数,不是带有方括号的数组名,而是*array。

sum += *array++;

按值传送机制复制了原数组地址,并把副本传送给函数。我们修改的是副本,原数组地址并没有修改。一般情况下,只要给函数传送一维数组,就可以把所传送的值作为指针,以任何方式修改地址。(这一段不太懂)

 

代码:传送多维数组


#include <iostream>#include <iomanip>using namespace std;double yield(double array[][4], int n);int main(){double beans[3][4] = {{1.0, 2.0, 3.0, 4.0}, {5.0, 6.0, 7.0, 8.0},{9.0, 10.0, 11.0, 12.0}};cout << endl << "Yield = " << yield(beans, sizeof beans/sizeof beans[0]) << endl;return 0;}double yield(double array[][4], int count ){double sum = 0.0;for (int i=0; i<count; i++)for (int j=0; j<4; j++)sum += array[i][j];return sum;}

 函数yield()的第一个参数定义为一个数组,它有任意多行,但是每行有4个元素。

3.2 按引用传送机制

要指定引用类型,只需在类型名的后面加上&。

 

引用只是另一个变量的别名,引用类型的变量存储对其他变量的引用。在把函数参数指定为引用类型时,函数就要使用按引用传送机制来传送参数。在调用函数时,对应于引用参数的变元不会复制,因为参数名就是调用程序中该变元值的别名。只要在函数体中使用参数名,它就会直接访问调用函数值中的变元值。

 

引用有风险:引用很高效,但是也有负面影响(安全性等)。

代码:引用参数


#include <iostream>#include <iomanip>using namespace std;int larger(int& m, int& n);int main(){int value1=10;int value2=20;cout<<endl<<larger(value1, value2)<<endl;return 0;}int larger(int& m, int& n){return m>n ? m : n;}

使用常量引用 

int larger(const int& m, const int& n);

现在函数可以处理变量和常量了。只要不打算修改传送过来的参数,就总是可以把参数定义为const。

使用const引用参数,就可以获得引用参数的高性能和高效率,以及按值传送方法的安全性。

 

引用和指针

在大多数情况下,使用引用参数比使用指针更好。只要可能就应该把引用参数声明为const,因为这会为调用程序提供参数的安全性。

二者区别:指针和引用的一个重要区别是,指针可以为空,而引用总是要引用某个数据项——当然,只要它不是空指针的一个别名。如果允许参数为空,惟一的选择就是指针参数。指针参数可以为空,所以必须在解除对指针的引用之前测试它。如果试图解除对空指针的引用,程序就会崩溃。

 

声明引用

引用不会显示为函数的参数。引用可以以另一个变量的别名独立存在。假定有一个变量声明为:

long number = 0;

long& rnumber = number;

类型long后面的宏&表示声明一个引用,等号后面的初始化值指定为变量名number。因此,rnumber是“引用long”类型。

同声明指针一样,&也可以放到变量名前面:

long &rnumber = number;

 

一些约束:

1.      声明引用时,引用必须总是初始化为变量的一个别名。

2.      在声明引用时,决不能不对它进行初始化。(必须初始化)

3.      引用时固定的,在声明之后就不能修改,它总是同一个变量的别名。

 

使用指针和使用引用有一个显著区别:指针需要解除引用,才能获得或操作它指向的变量值,而引用不需要这一步。在某些方面,引用类似于已解除引用的指针,但引用不能修改为引用另一个变量。

引用是与引用的变量完全等价的。

 

3.3   main()的参数

函数main()可以有参数:

int main(int argc, char* argv[]){

//Code for main()

}

 

4.       默认的参数值

在许多情况下,给一个或多个函数参数赋予默认值是非常有用的。这意味着,仅需要在希望参数值不同于默认值时,为参数指定值。

例如:有一个函数要用于输出标准的错误消息。大多数情况下,使用默认的消息就足够了,但有时候需要指定另一个消息。为此,可在函数原型中为参数指定默认值。输出消息的函数的定义如下:

void show_error(const char* message)

{

  cout<<endl<<message<<endl;

}

要指定默认消息,可以再这个函数的原型中编写一个字符串,用作默认的参数值,如下:

void show_error(const char* message=”Program Error”);

如果要使用该函数输出默认的消息,在调用它可以不指定参数,如下:

show_error();

该函数会显示结果:

Program Error

如果要提供某个消息,就可以指定函数的参数:

show_error(“Nothing works!”);

 

下面使用string类型的参数定义show_error()函数,而不是C样式的字符串。

void show_error(const string message=”Program Error”);

 

代码:使用多个默认参数值


#include <iostream>#include <iomanip>#include <string>using namespace std;void show_data(const int data[], int count=1, const string& title = "Data Values", int width = 10, int perLine = 5);int main(){int samples[]={1,2,3,4,5,6,7,8,9,10,11,12};int dataItem=99;show_data(&dataItem);dataItem=13;show_data(&dataItem, 1, "Unlucky for some!");show_data(samples, sizeof samples/sizeof samples[0]);show_data(samples, sizeof samples/sizeof samples[0], "samples");show_data(samples, sizeof samples/sizeof samples[0], "samples", 14);show_data(samples, sizeof samples/sizeof samples[0], "samples", 14, 4);return 0;}void show_data(const int data[], int count, const string& title, int width, int perLine){cout<<endl<<title;for(int i=0; i<count; i++){if(i % perLine==0)cout<<endl;cout<<setw(width)<<data[i];}cout<<endl;}

5.       从函数中返回值 

5.1 返回一个指针

在从函数中返回一个指针时,必须确保它指向的地址是0,或者在调用函数仍旧有效的内存地址。换言之,在指针返回到调用函数中后,指针所指向的变量必须仍在其作用域中。其中的黄金规则不要从函数中返回自动局部变量的地址。

例如:

int* larger(int* a, int* b)

{

  if(*a>*b)

return a;

  else

    return b;

}

可以用以下代码调试该函数:

*larger(&value1, &value2)=100;

 

5.2 返回一个引用

引用的黄金规则不要从函数中返回自动局部变量的引用。

例如:

int& larger(int& m, int& n)

{

  return m>n?m:n;

}

返回类型是对int的引用,参数是非常量的引用。我们要返回某个引用参数,就不能把参数声明为const。

用以下语句来使用该函数修改两个参数中的较大值:

larger(value1,value2)=50;

 

5.3   从函数中返回新变量

在函数中,可以再自由存储区中创建新变量,并通过指针返回值将它返回给调用程序。使用new运算符就可以为新变量分配内存空间,并返回其地址。

危险大,内存泄露的可能性非常高。每次调用这样的函数时,都要在自由存储区中分配更多的内存。调用函数应负责使用delete运算符来释放这些内存。

 

6.       内联函数

如果函数非常短,编译器为处理传送过来的参数以及返回结果的代码的系统开销,与进行实际计算的代码相比就非常多。这两类代码的执行时间非常相关。

在极端情况下,调用函数的代码占用的内存会比函数体中的代码还多。在这种情况下,编译器就应使用函数体中的实际代码替代函数的调用,并做适当的调整,已处理局部名称。这会使得程序更短更快。

为了让编译器完成此任务,可以在函数的定义中使用inline关键字。如:

inline int larger(int m, int n)

{

  return m>n?m:n;

}

通过这个定义,编译器就会用内联代码替代调用。

但是这只是一个建议,它取决于编译器是否采纳这个建议。把函数声明为inline,函数的定义就必须可以再调用函数的每个源文件中使用。因此,内联函数的定义通常放在头文件中,而不是源文件中,该头文件包含在使用该函数的每个源文件中。

(不同的编译器采用不同的规则来确定定义为inline的函数是否用内联代码代替其调用。

 

有时编译器选择不按照请求把函数看做内联函数,这有个缺点:在这个情况下,函数调用会按照正常的函数调用来编译,但编译器一般还是会把该函数看做源文件的本地函数,所以每个使用它的源文件都要拥有该函数的已编译副本。结果是,如果函数在几个不同的源文件中使用,函数的代码就要进行不必要的重复。

 

7.       静态变量

前面编写的所有函数中,函数体在每次执行之后都不会保留任何信息。假定要计算某个函数的调用次数,怎么办?

一种方法:在文件作用域定义一个变量,然后再在函数中递增它。但是这个方法不好控制。

另一种方法:在函数体重把该变量声明为static,静态变量。

静态变量在定义它的语句中创建,在此之后,它一直存在,知道程序结束为止。

下面的函数示例声明了一个静态变量:

void nexInterger()

{

  static int count=1;

  cout<<endl<<count++;

}

只要程序在执行,以后对函数的调用都会使用count的当前值。

代码:在函数中使用静态变量

#include <iostream>#include <iomanip>#include <string>using namespace std;long next_Fibonacci();int main(){cout<<endl<<"The Fibonacci Series"<<endl;for(int i=0; i<30; i++){if(i%5==0)cout<<endl;cout<<setw(12)<<next_Fibonacci();}cout<<endl;return 0;}long next_Fibonacci(){static long last=0;static long last_but_one=1;long next = last + last_but_one;last_but_one = last;last = next;return last;}


只要程序存在,静态变量就存在,但静态变量只能在声明它们的块中访问,所以变量last和last_but_one只能在next_Fibonacci()函数体中访问。

原创粉丝点击