C++复习 07 函数



声明,所有的朋友,如果要转我的帖子,务必注明"作者:黑啤 来源:优快云博客"和具体的网络地址http://blog.youkuaiyun.com/nx500/archive/2007/10/22/1837354.aspx,并且我的所有博客内容,禁止任何形式的 商业用途,请大家尊重我的劳动.谢谢!

目 录

七.函数.
  001 函数由函数名及一组操作数类型表示.函数的操作数,也就是形参,在一对圆括号中声明,形参之间用逗号分隔.
  002 C++语言使用调用操作符(即一对圆括号)实现函数的调用.调用操作符的的操作数是函数名和一组逗号分隔的实参.
  003 函数的调用做了两件事情:用对应的实参初始化函数的形参,并将控制权转移给被调用的函数.
  004 函数体就是一个块,函数体内部定义的变量只能在函数体内访问,称为局部变量.
  005 函数执行到return语句时,函数调用结束.被调用的函数完成时,将产生一个在return语句中指定的结果值.
      被挂起的主调用函数在调用处恢复执行,并将函数的返回值用作求解调用操作符的结果,继续处理剩余的工作.
  006 类似于局部变量,函数的形参为函数提供了已命名的局部存储空间.
      他们之间的差别在于形参是在函数的形参表中定义的,并由调用函数时传递给函数的实参初始化.
  007 函数的返回类型可以是内置类型,类类型或复合类型,还可以是void类型.
      函数不能返回另一个函数或者内置数组类型,但是函数可以返回指向函数的指针,或指向数组元素的指针.
        bool is_present(int *, int);
        int count(const string &, char);
        Date &calender(const char*);
        void process();
        int *foo_bar();
      定义函数时,没有显式指定返回类型是不合法的.早期c++对于没有显式指定返回类型会默认为返回int型,现在不可以了.
  008 函数的参数表可以为空(这里和C99是有所不同,应该有个void),表示没有参数,但不能省略.
      形参表由一系列用逗号分隔的参数类型和参数名组成.如果两个参数具有相同的类型,则其类型必须重复声明.
      参数表中不可以出现相同的参数名.
  009 形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值;如果形参为引用类型,则它只是实参的别名.
      对于指针形参变量,请注意修改形参变量和修改形参变量指向元素两个不同的操作.
      不论那个操作,都不改变实参所指向的元素,但修改形参变量指向的元素也就是改变了实参指向元素的值.拗口啊,真拗口!
         //  07009.cpp
        #include  < iostream >
        
using  std::cout;
        
using  std::endl;

        
void  resetPtr( int   * ip) {
          ip 
= 0;  // ip被重置为空指针.
        }


        
void  resetVal( int   * ip) {
          
*ip = 0;
        }


        
int  main() {
        
int i = 42;
        
int *= &i;
          cout 
<< "i: " << *<<endl;  // 输出i: 42
          resetPtr(p);
          cout 
<< "i: "<< *<< endl;  // 输出i: 42
          resetVal(p);
          cout 
<< "i: "<< *<< endl;  // 输出i: 0
          return 0;
        }

        // 编译及运行结果.
        Nan@linux-g4n6:~/Documents> g++ -o o 07009.cpp
        Nan@linux-g4n6:~/Documents> ./o
        i: 42
        i: 42
        i: 0
        Nan@linux-g4n6:~/Documents>
  010 对于非引用的const形参,编译器会将其声明视为非const形参,这是为了兼容c语言,因为在c语言中非引用形参没有const和非const的区别.
  011 引用形参直接关联到其所绑定的对象,而并非这些对象的副本.c语言习惯用指针传递来实现对实参的访问,c++推荐引用方式,更安全更自然.
         //  07011.cpp
        #include  < iostream >
        
using  std::cout;
        
using  std::endl;

        
void  swap( int   & v1,  int   & v2)... {
          v1
=v1^v2;
          v2
=v1^v2;
          v1
=v1^v2;
        }


        
int  main() {
          
int i = 100;
          
int j = 99;
          cout 
<< "before swap(): i = "<< i <<" j = "<< j << endl;
          swap( i, j);
          cout 
<< "after  swap(): i = "<< i <<" j = "<< j << endl;
          
return 0;
        }

        // 编译及运行结果.
        Nan@linux-g4n6:~/Documents> g++ -o o 07011.cpp
        Nan@linux-g4n6:~/Documents> ./o
        before swap(): i = 100 j = 99
        after  swap(): i = 99 j = 100
        Nan@linux-g4n6:~/Documents>
      引用形参的另一个作用是向主调用函数返回额外的结果.
      利用引用避免复制大量的数据.如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为const引用
        bool isShorter(const string &s1, const string &s2){
          return s1.size()<s2.size();
        }
      如果函数具有非const引用形参,则不能通过const实参对象进行调用,同时也不能传递一个右值或者具有需要转换的类型对象.
      非const引用形参只能与完全同类型的非const对象关联,这样的形参在使用时不太灵活,所以应该将不需要修改的引用形参定义为const类型.
        int incr(int &val){
          return ++val;
        }
        int main(){
        short v1 = 0;
        const int v2 = 0;
          int v3 = incrc(v1); // error, v1不是int类型,需要做类型转换
          v3 = incrc(v2);     // error, v2是const类型
          v3 = incr(0);       // error, 0是个常数
          v3 = incr(v1+v2);   // error, v1+v2是个右值
          int v4 = incr(v3);  // ok
          return 0;
        }
      传递指向指针的引用.
         //  07011a.cpp
        #include  < iostream >
        
using  std::cout;
        
using  std::endl;

        
void  ptrswap( int   *& v1,  int   *& v2) {
          
*v1 = *v1 ^ *v2;
          
*v2 = *v1 ^ *v2;
          
*v1 = *v1 ^ *v2;
        }

        
int  main() {
        
int i = 10;
        
int j = 20;
        
int *ip = &i;
        
int *jp = &j;
          cout
<<"Before ptrswap(): *ip = "<< *ip <<" *jp = "<< *jp << endl;
          ptrswap(ip, jp);
          cout
<<"After  ptrswap(): *ip = "<< *ip <<" *jp = "<< *jp << endl;
          
return 0;
        }

  012 通常,函数不应该由vector或其他标准库容器类型的形参.
      调用含有普通的非引用vector形参的函数将会复制vector的每个元素, 可以将形参定义为引用类型.
      更好的方法是通过传递指向容器中需要处理元素的迭代器来传递容器.
        void print(vector<int>::const_iterator beg, vector<int>::const_iterator end){
          while(beg != end){
            cout<<*beg++;
            if(beg != end)
              cout << " ";
          }
          cout <<endl;
        }
  013 由于数组不能复制,所以不能定义数组类型形参的函数.
      因为数组名会被自动转化为指针,所以处理数组的函数,通常通过操纵指向数组中的元素的指针来处理数组.
      传递数组时,实参是指向数组第一个元素的指针,形参复制了这个指针.
        void printValues(int *a){...}
        void printValues(int a[]){...}   // 看到这个例子,你一定已经无语了,一定认为我在或说八道了,
        void printValues(int a[10]){...} // 其实后边这两个例子中的形参并没有真正的声明一个数组,其含义都是 int *a
                                         // 最后一个例子中的那个10根本没有任何意义,还容易引起编程错误,所以推荐使用int *a
      如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身.
      这种情况下,数组大小成为形参和实参类型一部分.编译器会检查数组实参的大小与形参的大小似否匹配.
        // 注意,&arr两边的括号是必须的,因为下标操作具有更高的结合优先级,没有引用数组的定义.
        void printValues(int (&arr)[10]){
          for(size_t i = 0; i != 10; ++i){
            cout<<arr[i]<<endl;
          }
        }
        int main(){
          int i = 0, j[2] = {0, 1};
          int k[10] = {0,1,2,3,4,5,6,7,8,9,10};
          printValues(&i);  // error, i不是长度为10的数组.
          printValues(j);   // error, j不是长度为10的数组.
          printValues(k);   // ok.
          return 0;
        }
  014 指向多维数组的形参有个很有意思的定义方式.
        标准的: void printValues(int (*matrix)[10]){...}
        特别的: void printValues(int matrix[][10]){...}
        对于一些程序员,可能后一种相对好理解一些,比如我,就算是一个,但是注意第一维,不要填数字,因为它是没有意义的.
  015 有三种常见的编程技巧确保函数的操作不超出数组实参的边界.
        1. 在数组本身防置一个标记来检测数组的结束.典型代表是字符串,用NULL表示串的结束.
        2. 传递数组第一个元素和最后一个元素的下一个元素的指针,类似于标准库的begin()和end().
        3. 显示传递数组的大小.
  016 main函数的参数表.
        int main(int argc, char **argv)[...}
      命令prog -d -o ofile data0
        对应main的参数argc == 5,argv[0]:"prog",argv[1]:"-d",argv[2]:"-o",argv[3]:"ofile",argv[4]:"data0"
  017 含有可变参数的函数.c++支持可变参数形参是为了支持使用了varargs的c语言程序,具体参见C语言复习相关内容.
  018 return语句用于结束当前正在执行的函数,并将控制权返回给调用此函数的函数.
      对于没有返回值的函数(void),使用return语句是为了强制结束该函数.
      对于有返回值的函数,要注意,在含有return语句的循环后如果没有提供return语句的话,编译能通过但可能引发运行上的严重问题.
        // 比较两个string类型值是否一致,或者短string是否是长string类型的起始部分
        bool str_subrange(const strng &str1, const string &str2){
          if(str1.size() == str2.size())
            return str1 == str2;
          string::size_type size = (str1.size() < str2.size()) ? str1.size() : str2.size();
          string::size_type i = 0;
          while(i != size){
            if (str1[i] != str2[i])
              return false;
          }
        } // 函数结束前应该还有个return ture;更进一步,函数应该定义一个result,通过统一的出口返回这个result.
  019 千万不能返回局部对象的引用,同样也不能返回指向局部对象的指针.
        const string& manip(const string& s){
          string ret = s;
          ...
          return ret; // ret就是一个局部对象的引用,其他的例子比如数组,vector等.
        }
  020 返回引用类型的函数返回一个左值,因此,这样的函数可以用在任何要求使用左值的地方.
         //  07020.cpp
        #include  < iostream >
        #include 
< string >
        
using  std::cout;
        
using  std::endl;
        
using  std:: string ;

        
char   & get_val( string   & str,  string ::size_type ix) {
          
return str[ix];
        }


        
int  main() {
        
string s("a value");
          cout 
<< s << endl;
          get_val(s,
0= 'A';
          cout 
<< s << endl;
          
return 0;
        }

      如果不希望引用返回值被修改,返回值应该声明为const,此时返回的是一个右值.
        const char& get_val...
  021 函数原型的三个要素: 函数返回类型,函数名和形参列表. 函数原型加上函数体就组成了一个标准的函数.
      变量可在头文件中声明,而在源文件中定义.同理,函数也应当在头文件中声明,并在源文件中定义.
      定义函数的源文件应该包含声明该函数的头文件.
  022 默认实参是通过给形参表中的形参提供明确的初始值来指定的.
      如果一个形参具有默认实参,那么它后边的所有形参必须都有默认实参.
      所以,默认实参只能用来替换函数调用缺少的"尾部实参".
      通常,应该在函数的声明部分指定默认实参,并将该声明放在合适的头文件中.
  023 作用域,生命周期,自动对象,局部变量, static局部对象.
  024 短小函数的优点:容易理解,修改方便,确保统一的行为,函数重用.
      短小函数的缺点,函数调用需要耗费资源,比直接调用几条语句慢的多,所以出现内联函数,由编译器直接在调用的地方展开.
      也就是说,内联函数形式上是一个函数,实际使用上是一个块.
      内联函数应该在头文件中定义,这一点不同于其他函数.
  025 类的所有成员都必须在类定义的花括号里面声明,而成员函数即可以在类内部定义,也可以在类外部定义.
      编译器隐式地将在类内部定义的成员函数当作内联函数.
  026 this指针,每个成员函数都有一个额外的,隐含的形参this.在调用成员函数时,形参this初始化为调用函数的对象的地址.
        total.same_isbn(trans); // 这个调用就如同下边语句的调用方式
        Sales_item::same_isbn(&total,trans);
  027 在类的成员函数形参表后跟一个const声明,将隐含的this指针声明成了const类型的指针.
        bool Sales_item::same_isbn(const Sales_item &rhs) const
        {
          return isbn == rhs.isbn;
        }
        // 上边的定义等同于下边的定义
        bool Sales_item::same_isbn(const Sales_item *const this, const Sales_item &rhs){
          return this->isbn == rhs.isbn;
        }
  028 在成员函数中,不必显式的使用this指针来访问被调用函数所属对象的成员.对类成员没有前缀的引用,都被假定为this引用.
  029 在类外定义成员函数,必须通过"::"来显示的指明他们是类的成员.
        double Sales_item::avg_price() const ...
  030 一个类可以有许多构造函数,不同的构造函数必须有不同数目或者类型的形参.默认构造函数没有形参.
      如果一个类没有显示的定义任何构造函数,编译器会自动的为这个类生成一个默认构造函数.
      对于含有内置类型或者复合类型成员的类,应该定义他们自己默认构造函数,因为系统自动创建的构造函数不会初始化这些类型的成员.
  031 类代码文件的组织:
        1.类定义应置于与名为"type.h"的头文件中,type指在该文件中定义的类的名字.
        2.成员函数的定义一般存储在与类同名的源文件"type.cpp"中.
  032 函数重载:出现在相同作用域的两个(或两个以上)函数,如果具有相同的名字而形参表不同,则称为重载函数.
      根据操作数的类型来区分不同的操作,并应用适当的操作,确定使用哪个函数是编译器的责任.
      函数重载省去为函数起名并记住函数名字的麻烦,函数重载简化了程序的实现,使程序更容易理解.
      main函数不能重载.
  033 如果两个函数声明的返回类型和形参表完全匹配,则第二个函数声明视为第一个的重复声明.
      如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的.
      还要注意,有些看起来不同的形参,如果本质上是相同的,也是错误的.比如同类型参数不同名,或参数有无默认实参值.
      对于形参与const形参,如果是非引用的,则视为等价声明.但是const引用和非const引用则视为不同的形参,指针同理.
  034 局部地声明一个函数或变量,将屏蔽而不是重载在外层作用域中声明的同名函数.
        void print(const string &);
        void print(double);
        void fooBar(int ival){
          void print(int);
          print("Value");  // error, print(int)屏蔽了print(const string &).
          print(ival);     // ok,调用print(int).
          print(3.14);     // ok,调用print(int),屏蔽了print(double).
        }
      因此,所有版本的重载函数,都应在"同一个作用域中"声明.
  035 实参与重载函数形参的匹配的原则是实参类型与形参类型越接近则匹配越佳.
        1.精确匹配.
        2.通过类型提升实现的匹配.
        3.通过标准转换实现的匹配.
        4.通过类类型转换实现的匹配.
        注意:整型对象,即使具有枚举元素相同的值,也不能用于调用枚举类型形参的函数.
  036 函数指针是指:指向函数的指针,函数指针的具体类型由函数返回类型和形参表确定.
        // pf是一个指向"返回bool类型,并含有(const string &, const string &)形参列表"类型函数的指针.
        bool (*pf)(const string &, const string &);
        如果去掉了*pf两边的括号,就变成了定义一个名为pf返回bool指针的一个函数.
      可以使用typedef简化函数指针的定义.
        typedef bool (*cmpFcn)(constr string &, const string &);
        cmpFcn pf;
      在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针.
      函数指针只能通过同类型的函数或函数指针或NULL值常量表达式进行初始化.
        bool lengthCompare(const string& s1, const string &s2);
        cmpFcn pf1 = 0;             // ok,定义pf1,初始化为空地址,表示该指针不指向任何函数.
        cmpFcn pf2 = lengthCompare; // ok,定义pf2,并初始化为函数lengthCompare.
        pf1 = lengthCompare;        // ok,将pf1指向lengthCompare函数.
        pf2 = pf1;                  // ok,两个指针具有相同的类型.
      当函数指针已经指向某个具体函数,就可以安全地用来调用函数.
        lengthCompare("hi","heipi");
        pf2("hello", "heipi");    // 隐式的调用.
        pf1 = pf2;
        (*pf1)("heihei", "pipi"); // 显式的调用.
      函数指针可以作为函数声明形参表中的形参,下边两种格式都可以,作用相同.
        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);
      指向重载函数的指针,指针的类型必须与重载函数的一个版本精确匹配.
       
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值