首页 > 代码库 > 第七章 函数

第七章 函数

第七章  函数

7.1  函数的基础知识

    要使用函数,必须完成如下工作:

    Ø  提供函数定义

    Ø  提供函数原型

    Ø  调用函数

    7.1.1  函数的定义

    函数总体来说可以分为两类,一类是没有返回值的,另一类是具有返回值的,两类的函数定义的格式如下

void functionName(parameterList){    statement(s)    return;            //可以有也可以没有}typeName functionName(parameterList){    statement(s)    return value;    // value 的类型是typeName或者可以自动转换为typeName}

    C++对于返回类型有限制,不能是数组,但可以是整型、浮点型、指针、结构和对象。函数内在遇到第一个返回语句将结束。     有返回值的函数,必须使用返回语句,返回值可以是常量、变量或者表达式。其结果必须可以转化为typeName类型。

    7.1.2  函数原型和函数调用

    函数原型描述了函数到编译器的接口,把函数的返回类型、参数列表(数量,类型和顺序)告知编译器。

    函数原型是一条语句,必须以分号结束,可以通过复制函数头并添加分号来快速构建函数原型。

    函数原型不要求提供变量名,但是拥有变量名的函数原型可以提高程序的可读性。

    函数原型的作用有3个:

    Ø  编译器正确处理函数返回值;

    Ø  编译器检查使用的参数数目是否正确;

    Ø  编译器检查使用的参数类型是否正确,如果不正确,则转化为正确的类型(如果可能)。

    7.1.3  ANSI C和C++ 的函数原型的区别

    在ANSI C中函数原型是可选的,而在C++中函数原型是必须的。

    在C++中,圆括号中参数列表为空与在圆括号中使用void是等效的,意味着没有参数。在ANSI C中圆括号为空意味着不指出参数,将在后面定义参数列表。C++不指定参数列表应该使用省略号(…),例如:

void say_bye(...);        //不指出参数,将在后面的定义中指出参数列表

    C++中,通常仅当与可变参数C函数交互时才这样做。

7.2  函数参数与按值传递

    按值传递参数时,将数值参数传递给函数,而后将其赋值给新变量。如:

double cube(double x);double a = 1.0;double volume = cube(a);

    函数cube被调用时,将创建一个名为x的新变量,并初始化为a的值1.0。这样在函数内部对x的修改,将不会改变a的值。

    形参:接收传递值的变量。

    实参:传递给函数的值。

    C++使用参数(argument)来表示实参,参量(parameter)来表示形参。

    函数中声明的变量是函数私有的,函数被调用是,为这些变量分配内存,在函数结束时,释放这些内存。这样的变量成为局部变量。存储类型为自动存储。

7.3  函数和数组

    看如下原型:

int sum_arr(int arr[], int n)    //arr是数组,n是数组元素数量

    参数arr是一个数组,但实际上在函数内部,arr并不是数组,而是一个指针。

    在C++中,当且仅当用于函数头或函数原型时,int arr[]和int * arr等价。但是int arr[]可以具有提醒的作用,表示传递的指针为数组第一个元素的地址。

    当把数组第一个元素的地址和数组元素的数目传递给函数时,并没有将函数的内容传递给函数,而是将数组第一个元素的地址,数组元素的类型和数组元素的数目传递给了函数。当传递常规变量时,函数将使用该变量的拷贝,淡传递数组时,函数将直接使用该数组,这一位置对数组内容的改变会改变原来的数组。

    实际上传递数组时,所传递的地址也可以看做为普通变量,函数将使用指针的拷贝,把拷贝的指针指向其他地址,将不会修改原来的指针值。

    传递数组地址可以节省复制整个数组内容所需的时间和内存,但是另一方面会对原数据带来风险。ANSI C和C++使用const限定符来解决这个问题。如下:

void show_arr(const double arr[], int n);        //参数为指向const的指针void show_arr(const double *arr, int n);        //与上一个完全等价

    这两个函数将不能在函数内部修改数组的内容,任何赋值操作将会被编译器报告错误。

    可以用2种方式将关键字const用于指针。

    1.        使指针指向常量;

    2.        将指针本身声明为常量。

    如下代码:

int n = 10;const int * p1 = &n;    //可以修改p1的值,但是不可以修改*p1的值int * const p2 = &n;    //不可以修改p2的值,但是可以修改*p2的值

    可以将const变量或者非const变量的地址赋值给指向const的指针,但是只可以将非const变量的地址赋值给常规指针。

    当在函数内部不修改指针或者数组的内容时,应当使用指向const的指针。有2个理由:

    Ø  可以避免无意的修改而导致的错误

    Ø  使用const作为形参,可以接受const和非const实参,否则只能接受非const实参。

7.4  函数和二维数组

    看如下原型:

int sum(int m[][4], int size);    //size指定行数,4为列数int sum(int(*m)[4], int size);    //与上一个等价

    上面的int (*m)[4]的圆括号必不可少。带括号表示m是一个数组,数组内元素的类型是指向含有4个元素数组的指针;不带括号表示m是一个指针的数组,作为形参就是int **类型。原因是[]运算符的优先级高于*,不加括号时,[]先作用于m,表示m是一个具有4个元素的数组,而元素的类型是int *;加括号时,*先作用于m,表示m是一个指针,所指类型为一个具有4个int型元素的数组。

7.5  函数和C-风格字符串

    表示字符串的方式有3种:

    Ø  Char数组

    Ø  用引号括起的字符串常量

    Ø  被设置为字符串地址的char指针

    C-风格字符串内置有’\0’,表示字符串的结尾,因此作为参数可以不用将元素的数量传递给函数。

7.6  函数和结构

    函数处理结构和处理基本类型的变量类似。

    需要注意的是,当结构很大时,应该选用指向const的指针来代替直接传递结构,这样做可以提高效率。

7.7  函数与array对象

    Array为C++11的模板类。将array对象作为参数传递给函数时,将拷贝array的内容。

7.8  递归

    C++函数可以自己调用自己,与C语言不同的是C++不允许main函数调用自己。这种功能成为递归。一般的格式如下:

void recurs(argumentList){    statements1    if (test)    {        recurs(arguments)    }    statements2}

   这里如果test值为false时,调用链将结束。

7.9  函数指针

    函数指针的好处是可以将函数名作为参数传递给函数,这样可以给函数传递策略。当修改策略时,不用修改函数本身,只需要传递不同的函数指针实参,这就是委托协议。

    使用函数指针必须完成以下工作:

    Ø  获取函数地址

    Ø  声明一个函数指针

    Ø  使用函数指针调用函数

    1) 获取函数地址

      获取函数地址就是把函数名作为参数传递给其他函数,如:

 

process(think);        //把think函数地址传递给processthought(think());    //把think函数的返回值传递给thought


     
2) 声明函数指针

      声明方法如下:

double f(int);            //f是函数double(*pf1) (int);        //pf1是函数指针pf1 = f;                //为函数指针赋值double(*pf2) (int) = f;    //声明时初始化

     通常,为了声明特定类型的函数指针,可以首先编写函数原型,再用(*pf)替换函数名,这样pf就是这种类型的函数的指针。

    3) 使用函数指针调用函数

    使用函数指针有两种方式调用函数。一种是使用(*pf),另一种是直接使用pf。如下:

double x = f(4);               //通过函数名调用函数double y = (*pf1)(5);         //通过函数指针调用函数double z = pf2(6);           //函数指针名直接调用函数

     实际上函数名就是函数的起始地址,C++折衷考虑使用两种方式都可以调用函数。

7.10  深入探讨函数指针

    先看下面几个等价的函数:

    const double * f1(const double arr[], int n);    const double * f2(const double[], int);    const double * f3(const double *, int);

     声明函数指针并赋予初值:

const double * (*p1) (const double*, int) = f1;

     可以使用C++11的自动类型推断:

auto p2 = f2;

     函数指针数组声明如下:

const double * (*pa[3]) (const double*, int) = { f1, f2, f3 };

     注意这里[3]的位置。这里不用为*pa加括号是因为[]运算符的优先级高于*运算符。[]首先作用于pa,表示pa是一个具有3个元素的数组,元素的类型是函数指针。这里也不能用auto自动类型推断。因为自动类型推断只能用于单值初始化,而不能用于初始化列表。但是可以声明同样类型的数组:

auto pb = pa;

     Pa和pb都是指向函数指针的指针,可以这样调用:

double av[3] = { 1.0, 2.0, 3.0 };const double * px = pa[0](av, 3);const double * py = (*pb[0])(av, 3);double x = *pa[1](av, 3);double y = *(*pb[1])(av, 3);

     可以使用自动类型推断简单声明指向整个数组的指针:

auto pc = &pa;

    Pc的类型为

const double * (*(*pd)[3]) (const double*, int) = &pa;

    使用typedef简化声明:

typedef double real;    //real是double的别名typedef const double * (*p_fun) (const double*, int);    //p_fun是函数指针类型p_fun p1 = f1;p_fun pa[3] = { f1, f2, f3 };p_fun(*pd)[3] = &pa;

    这里再说一下使用auto的利弊:

    利:使用auto可以简化声明;

    弊:可能提供错误的初值,使声明的类型不正确。

第七章 函数