`

(三)函数

阅读更多

函数

1、关于返回值

函数的返回类型可以是内置类型(intdouble)、复合类型(int&string*)、还可以是void。不能返回数组或者函数,但可以返回指向数组的指针或指向函数的指针。

当函数返回非引用类型时,将函数的返回值复制给临时对象。这时,返回值可以是局部对象,也可以是求解表达式的结果。

当函数返回引用类型时,它返回的是对象本身而不是复制返回值。所以千万不要返回指向局部的变量的引用。

以下是正确返回引用类型

    const string &shortString(const string &s1,const string &s2){

        retrn s1.size()<s2.size()?s1:s2;

    }

    以下是错误的返回引用类型,因为ret是一个局部变量,函数运行完以后ret被回收。

    const string &manip(const string &s){

        string ret=s;

        retrn ret;

}

事实上,返回引用的函数返回的是一个左值。因此,这样的函数可以用于任何需要使用左值的地方。如果不希望返回值被修改,应该应该将返回值声明为const。参见下例:

    char &getValue(string &str,string::size_type ix){

        return str[ix];

    }

    int main(){

         string s("a value");

        getValue(s,0)='A'; //function gets s[0],and then changed

        cout<<s<<endl;   //the result is "A value"

        return 0;

}

注意:不要返回指向局部的指针,函数结束后局部对象会被释放,返回的指针变成了指向不再存在的对象的悬垂指针。

 

<!--[if !supportLists]-->2、<!--[endif]-->关于参数传递

C++是一种静态强类型语言,对每一次的函数调用,编译时都会检查实参的类型。

    int gcd(int v1,int v2){    //求最大公约数

        while(v2){

            int temp=v2;

            v2=v1%v2;

            v1=temp;

        }

        return v1;

}

调用gcd(“hello”,”world”)是编译出错,因为实参不是int而是const char*

调用gcd(3.14,6.29)时编译器发出警告,但是仍然会进行隐式参数转换,调用gcd(3,6)

参数有两种类型:非引用型参数和引用型参数。

非引用型参数的传递是通过复制赋值完成参数的初始化的,即将实参复制给一个副本,副本参与函数的具体操作。前面gcd改变了v1v2的值,但这只是改变了副本的值,它并没有改变实参的值,如果调用gcd(a,b)a,b的值并不会改变。

    void reset(int *p){

        *p=0;

        p=0;

    }

调用reset(a)后,a的值(指针)给副本_a_a参与函数操作,所以a所指向的地址单元存储的值变成了0a本身的地址没有改变。

可以用const来防止指针改变所指的内容。例:void use(const int *ip),不能用ip改变指针所指的内容。

use的实参可以是int*型和const int*型,而reset只能是int *型,不能是const int *型。

对于内置类型,由于函数使用的是副本,所以以下正确:

        void fun1(int m);  void fun2(const int m);

        const int a=5; int b=6;

        fun1(a);fun1(b);fun2(a);fun2(b);

   fun1(a)是用const对象初始化了副本,fun2(b)表明b的副本在函数运行过程中其值不会改变。

  

   引用形参数的传递会直接与实参绑定,形参只是实参的别名。下面的整数交换会成功:

       void swap(int &m,int &n)

           {int temp=m; n=m; m=n; }

引用型参数可以返回额外的结果。下例中得到了元素出现的次数。

    vector<int>::const_iterator findValue(vector<int>::const_iterator begin,

        vector<int>::const_iterator end,int value,vector<int>::size_type &times){

             times=0;

             vector<int>::const_iterator firstLocation=end;

            for( ;begin!=end;begin++){

              if(*begin==value){

                if(firstLocation==end)

                    firstLocation=begin;

                times++;

              }

            }

           return firstLocation;

      }

  int main(){

    vector<int> v;

    v.push_back(1);v.push_back(2);v.push_back(1);v.push_back(3);v.push_back(5);

    vector<int>::size_type t;

    vector<int>::const_iterator iter=findValue(v.begin(),v.end(),1,t);

    cout<<*iter<<" "<<t<<endl;

    return 0;

}

    大型对象在复制过程中效率比较低,用const型引用型参数可以省略复制过程,且保证实参不做改变。

    bool isShort(const string &s1,const string &s2){

        retrn s1.size()<s2.size();

    }

如果函数有非const型引用型参数,那么不能传递const实参。因为函数可能修改了实参的内容。如果函数不会改变实参的值,那么应该将非const修改为const,扩大函数的使用范围。

        void increaseValue(int &value){

            value++;

         }

         const int v=1;

        increaseValue(v); //error

        increaseValue(0); //error

指针的引用与其他的数据的引用一样,只是它传递的是一个地址。下面的整数交换会成功。

        void swap(int * &m,int * &n)

           {int *temp=m; n=m; m=n; }

 

3、数组及vector的参数传递

     为了避免复制vector,应该将vector参数声明为引用型。然而,更多人倾向于传递指向容器的迭代器。

    void print(vector<int>::const_iterator begin,vector<int>::const_iterator end){

        while(begin!=end)

            cout<<*begin++;

        cout<<endl;

    }

数组不能通过复制赋值,所以无法编写数组类型的参数的函数;又由于使用数组名时,数组名会被转化为指向数组第一个元素的指针,所以函数用指向数组元素的指针来处理数组。有三种方式指定数组参数:

    void print(int *);   //用指向数组元素的指针来处理数组

    void print(int[]);   //等价于第一种形式

    void print(int[10]); //等价于第一种形式,这种情况下的10没有意义,只会引起误解

数组形参也可以定义为引用型和非引用型。非引用型将第一个元素的地址复制给副本,函数操纵的是指针副本,这不会改变原来的数组的地址。不需要改变数组元素时应该将指针定义为指向const对象的指针,即:void f(const int*)

对于引用型的数组形参,编译器不会将数组转化为指针,而是传递数组的本身。这时的数组大小为参数的一部分,编译器会严格的检查数组实参的大小与形参的大小是否匹配。

        void print(int (&arr)[10]){

            for(size_t i=0;i!=10;i++)

                cout<<arr[i]<<" ";

            cout<<endl;

        }

        int main(){

            int i=0;

            int j[2]={0,1};

             int k[10]={0,1,2,3,4,5,6,7,8,9};

             print(&i);   //error,not an array of 10 ints

             print(j);    //error,not an array of 10 ins

             print(k);    //ok

            return 0;

         }

使用泛型可以解决引用型数组参数受数组的长度固定的限制。调用printArray(j)

      template<class T ,size_t N> void printArray(T (&arr)[N]){

          for(size_t i=0;i!=N;i++)

              cout<<arr[i]<<" ";

          cout<<endl;

      }

C++中没有多维数组,所谓的多维数组是指数组的数组,数组名也是指向第一个元素的指针。多维数组的元素本身就是数组,所以将其作为参数时,除第一维以外其它所有维的长度都是元素类型的一部分,必须指明。

    void print(int (*arr)[10],int rowSize)  //arr是指向含有10int的数组的指针

    void print(int arr[][10],int rowSize)   //与上面等价

注意:int *matrix[10]是一个长为10的数组,数组的每个元素都是int*指针

      int (*matrix)[10]是一个指针,指向一个数组,这个数组由10int组成

传递数组时为了使操作不越界,常有三种方法。第一种是数组本身放一个结束的标记,C风格字符串就是用null作为标记符号;第二种是传递指向数组第一个和最后一个元素的指针;第三种是传入数组的大小。

        void print(const int* begin,int *end){

            while(begin!=end)

                cout<<*begin++<<" ";

            cout<<endl;

        }

        void print(const int* array,size_t size){

            for(size_t i=0;i!=size;i++)

                cout<<array[i]<<" ";

            cout<<endl;

        }

        int main(){

            int k[10]={0,1,2,3,4,5,6,7,8,9};

            print(k,k+10);

            print(k,10);

            return 0;

        }

 

4、声明、默认参数、局部静态对象及内联函数

变量可以在头文件中声明,源文件中定义。同样,函数也应该在头文件中声明,源文件中定义。放在头文件中可以保证指定函数所有的声明一致,如果函数的接口发生改变,只许修改唯一的声明。注意,定义函数的源文件应该包含声明函数的头文件。

默认实参是形参列表中形参提供的初始值来指定的。函数调用的实参按照位置解析,默认实参只能替代函数缺少的尾部实参。例如,声明的函数为:string initScreen(string::size_type height=24,string::size_typewidth=80,char background=''),则

         string screen;

        screen=initScreen();         //equivalent to initScreen(24,80,'')

        screen=initScreen(66);       //equivalent to initScreen(66,80,'')

        screen=initScreen(66,36);    //equivalent to initScreen(66,36,'')

        screen=initScreen(66,36,'#');

        screen=initScreen( , ,'?');  //error

        screen=initScreen('?');      //error,calls initScreen('?',80,'')

也可以用其它的表达式来来确定默认实参。

    string::size_type getHeight(int h);

    string initScreen(string::size_type

              height=getHeight(12),string::size_typewidth=80,char background='')

一般适合把默认参数放在头文件中。

static局部对象一旦被创建,直到程序结束前都不会被撤销,函数结束后它的值还在,以后调用函数时,都不会执行初始化语句。

        size_t count(){

            static size_t c=0;

            return ++c;

        }

       int main(){

          for(size_t i=0;i!=10;++i)

              cout<<count()<<endl;

       }

    函数调用是一个消耗较大的工作:调用前先保存寄存器,并返回时恢复、实参复制、程序必须指向一个新的位置。使用内联函数能够避免这些开销。

    inline const string &shortString(const string &s1,const string &s2){

        retrn s1.size()<s2.size()?s1:s2;

    }

那么调用cout<<shortString(str1,str2)<<endl,在编译时将展为

    cout<<str1.size()<str2.size()?sstr11:str2<<endl

一般说来,内联机制适合优化小的、只有几行的而且经常调用的函数。它不支持递归。内联函数应该定义在头文件中,它对于编译器必须是可见的,这一点不同于其它函数。

 

<!--[if !supportLists]-->5、<!--[endif]-->类的成员函数

成员函数的声明必须在类定义的换括号里面(class SalesItem {...},省略号处)。它的定义可以放在括号里面也可以放在括号外面。下面的成员函数中sameISBN声明和定义在花括号内,averagePrice的声明在花括号里面,定义在花括号外面。在类外定义的函数需要用作用域操作符指明函数的作用范围。

        class SalesItem{

        public:

            double averagePrice() const;

            bool sameISBN(const SalesItem &other)const

                {return isbn==other.isbn;}  //等价于return this->isbn==other.isbn

            SalesItem():sold(0),revenue(0.0){ }

        private:

            string isbn;

            unsigned sold;

            double revenue;

        }

        double SalesItem::averagePrice() const{

             if(sold!=0)

                return revenue/sold;

             return 0;

        }

当调用s.sameISBN(t)的时候,编译器会重写函数为SalesItem::sameISBN(&s,t),这里传入了隐含的指针this,它指向了调用对象的地址。函数后面的const就是用来修饰this所指的对象,这样的函数成为const成员函数,这时,函数不能修改对象的状态(成员变量)。类外定义的const成员函数不能把const省略掉。

SalesItem():sold(0),revenue(0.0){ }定义的是默认构造函数。

一般将类的声明放在头文件中,大多数情况下类外定义的函数成员函数放在源文件中,按照习惯,SalesItem的声明放在SalesItem.h中,成员函数的定义放在SalesItem.cc中,它应该包含SalesItem.h

 

<!--[if !supportLists]-->6、<!--[endif]-->函数重载

作用域相同相同的两个函数,如果它们名字相同而形参表不同,则称它们重载。有些看起来不同的形参表的本质是相同的。例如

Record lookup(const Account &acc)Record lookup(const Account &)相同;

typedef Phone Telno;Record lookup(const Phone &)Record lookup(const Telno&)相同;

Record lookup(const Name&)Record lookup(const Name&=””)相同;

Record lookup(const Phone)Record lookup(Phone)相同;形参列表被认为一样,当然,前者中副本的值一旦确定后就不能更改。这里值得注意的一点是,const的引用形或指针形参的参数表与非const不一样。

Record lookup(const Phone&)Record lookup(Phone&)不同。

Record lookup(const Phone*)Record lookup(Phone*)不同。

在局部声明的名子会屏蔽全局声明的名字。例如:

        string init(){return "";}

        int main(){

            int init=1;      //局部变量init屏蔽了全局函数init

            string s=init(); //error

        }

同样,局部声明的函数会屏蔽掉重载的函数。

        void print(string s){cout<<s<<endl;}

        void fa(){

             void print(int i){cout<<i<<endl;}

             print("hello");//error,print(string) is hidden

        }

匹配时函数调用时从重载函数集合中选取函数关联的过程。这个过程分三步:1、确定重载函数集合;2、选取可行函数,可行函数要求参数个数与实参相同且类型匹配或者隐形转换后匹配;3、寻找最佳匹配,要求实参与形参越接近越好,参数类型匹配的好坏依次为:精确匹配、类型提升、标准转换、类类型转换。如char提升为int优于转换为shortdouble转换为long或者float均是标准转换,这时产生二义性。

对于引用和指针,编译器会通过形参和实参是否为const来确定哪个最匹配。

    Record lookup(const Phone&);

    Record lookup(Phone&);

    const Phone a(“123456”);

    Phone b;

    lookup(a);    //调用lookup(const Phone&)

    lookup(b);    //调用lookup(Phone&)

指针也一样,当然这里说的const指针是指const*,而不是fun(int *const a)fun(int *const a)fun(int *a)是重复定义。

 

<!--[if !supportLists]-->7、<!--[endif]-->指向函数的指针

语句bool (*pf)(const string&,const string&);pf声明为一个指向函数的指针,所指向的函数的参数为两个const string&类型,返回类型为bool

函数指针类型相当冗长,可以用typedef来简化函数指针的定义。

   typedef bool (*pf)(const string&,const string&);

这样就可以使用pf类型了。这样的指针只能赋值完全对应类型的函数地址(函数名、函数名取地址)。

如果有函数bool compare(const string &,const string &),那么compare理解为如下形式的指针bool(*)(const string &,const string &)。注:compare&compare等价。

        pf m=0;//表示不指向任何函数

        pf n=compare;

        m=compare;

        compare(“hi”,”hello”);

        m(“hi”,”hello”);

        (*m)(“hi”,”hello”);

还可以将函数指针作为函数的参数或者返回类型。

      void useBigger(const string& ,const string&,bool(const string&,const string&));//最后个参数被认为是函数指针,也可以写成:

      void useBigger(const string& ,const string&,bool *(const string&,const string&));

名称为ff、返回为int (*)(int*,int)、参数为int的函数为:int (*ff(int))(int*,int)。写起来极其麻烦,如果typedef int (*PF)(int*,int),那么只需写成PF ff(int)

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics