• c++ primer 6th 函数


    一、函数基础
    1. 即使两个形参的类型一样,也必须把两个类型都写出来。

    2. 局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。

      #include<iostream>
      #include<algorithm>
      #include<cstring>
      #include<map>
      #include<bits/stdc++.h>
      using namespace std;
      size_t count_calls()
      {
          static size_t ctr = 0;
          return ++ctr;
      }
      int main()
      {
          for(size_t i = 0;i != 10; ++i)
          {
              cout << count_calls() << endl;
          }
          return 0;
      }
      

      如果局部静态变量没有显式的初始值,它将执行值初始化,内置类型的局部静态变量初始化为0。

    二、参数传递
    1. 使用引用避免拷贝

      如果函数无需改变引用形参的值,最好将其声明为常量引用。

    2. const形参和实参

      当用实参初始化形参时会忽略掉顶层const。

      void fcn(const int i);
      void fcn(int i);//错误重复定义了fcn(int)
      
    3. 数组引用实参

      #include<stdio.h>
      #include<iostream>
      #include<string.h>
      #include<iostream>
      using namespace std;
      void print(int (&arr)[10])
      {
              for(auto elem : arr)
              {
                      cout << elem << endl;
              }
      }
      int main()
      {
              int a[10] = {1,2,3,4,5};
          	int i = 0;
          	int j[2] = {0,1};
          	print(&i);//不是10个整数的数组
          	print(j);//不是10个整数的数组
              print(a);    
      }
      
      &arr两端的括号必不可少
      f(int &arr[10])//将arr声明成了引用的数组
      f(int (&arr)[10])//arr是具有10个整数的整数数组的引用
      指针同理
      
    4. 含有可变形参的函数

      #include<stdio.h>
      #include<iostream>
      #include<string.h>
      #include<iostream>
      using namespace std;
      void error_msg(initializer_list<string> il)
      {
              for(auto beg = il.begin();beg != il.end();++beg)
              {
                      cout << *beg << " ";
              }
              cout << endl;
      }
      int main()
      {
              int a[10] = {1,2,3,4,5};
              error_msg({"hello","hello","hello"});       
      }
      
      initializer_list lst; 默认初始化:T类型元素的空列表
      initializer_list lst{a,b,c...}; lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const
      lst2(lst) 拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素
      lst.size() 列表中的元素数量
      lst.begin() 返回指向lst中首元素的指针
      lst.end() 返回指向lst中首元素的指针
      lst2 = lst 原始列表和副本共享元素
    三、返回类型和return语句
    1. 值是如何被返回的

      string make_plural(size_t ctr,const string &word,const string &ending)
      {
      	return (ctr > 1)?word + ending : word;
      }
      

      该函数的返回类型时string,意味着返回值将被拷贝到调用点。因此,该函数将返回word的副本或者一个未命名的临时string对象,该对象的内容是word和ending的和。

      const string &shorterString(const string &s1,const string &s2)
      {
      	return s1.size() <= s2.size() ? s1 : s2;
      }
      

      其中形参和返回类型都是const string的引用,不管是调用函数还有返回结果都不会真正拷贝string对象

    2. 引用返回左值

      #include<stdio.h>
      #include<iostream>
      #include<string.h>
      #include<iostream>
      using namespace std;
      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;
              //shorterString("hi","bye") = "X"; //错误:返回值是个常量
      }
      
    3. 主函数main的返回值

      如果没有return 0;编译器将隐式地插入一条返回0的return语句

    4. 不要返回局部对象的引用或指针

      #include<stdio.h>
      #include<iostream>
      #include<string.h>
      #include<iostream>
      #include<cstdlib>
      using namespace std;
      const string &manip()
      {
              return "hello";
      }
      int main()
      {
              string a = manip();
              cout << a << endl;//无法运行
      }
      

      当函数结束时临时对象占用的空间也就随之释放掉了,所以两条return语句都指向了不再可用的内存空间。

    5. 使用尾置返回类型

      //func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
      auto func(int i) -> int(*) [10]
      {        
      }
      
      
    6. 使用decltype

      int odd[] = {1,3,5,7,9};
      int even[] = {0,2,4,6,8};
      decltype(odd) *arrPtr(int i)
      {
      	return (i % 2)? &odd : & even
      }
      
      

      decltype并不负责把数组类型转换成对应的指针,所以decltype的结果是个数组,要想表示arrPtr返回指针还必须在函数声明时加一个*符号。

    四、函数重载
    1. 判断两个形参类型是否相异

      Record lookup(const Account &acct);
      Record lookup(const Account&); //省略了形参的名字
      typedef Phone Telno;
      Record lookup(const Phone&);
      Record lookup(cosnt Telno&);//Telno和Phone的类型相同
      
      

      main函数不能重载

    2. 重载和const形参

      顶层const不影响传入函数的对象。

      Record lookup(Phone);
      Record lookup(const Phone); //重复声明
      
      Record lookup(Phone*)
      Record lookup(Phone* const);//重复声明 
      

      非常量可以转换成const

      如果形参时是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的:

      Record lookup(Account&);
      Record lookup(const Account&); //新函数,作用与常量引用
      
      Record lookup(Account*)
      Record lookup(const Account*);//新函数,作用域指向常量的指针
      

      但是当我们传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。

    3. const_cast和重载

      const string &shorterString(const string &s1,const string &s2)
      {
      	return s1.size() <= s2.size()?s1:s2;
      }
      string &shorterString(string &s1,string &s2)
      {
      	auto &r = shorterString(const_cast<const string&>(s1),const_cast<const string&>(s2));
      	return const_cast<string&>(r);
      }
      

    五、特殊用途语言特性

    1. 默认实参

      typedef string::size_type sz;
      string screen(sz ht = 24,sz wid = 80,char backgrnd = ' ');
      string window;
      window = screen(, , '?');//错误:只能省略尾部的实参
      window = screen('?') //调用screen('?',80,' ')
      
    2. 默认实参声明

      string screen(sz,sz,char = ' ');
      //我们不能修改一个已经存在的默认值
      string screen(sz,sz,char = '*'); //错误:重复声明
      string screen(sz = 24,sz = 80,char); //正确
      
      
    3. 默认实参初始值

      sz wd = 80;
      char def = ' ';
      sz ht();
      string screen(sz = ht(),sz = wd,char = def);
      string window = screen();//调用screen(ht(),80,' ')
      
      //调用时:
      void f2()
      {
      	def = "*";//改变默认实参的值
      	sz wd = 100;//隐藏了
      	window = screen();//调用screen(ht(),80,'*');
      }
      
    4. 内联函数

      内联函数可避免函数调用的开销

      内联机制用于优化规模较小、流程直接、频繁调用的函数。

    5. constexpr函数

      能用于常量表达式的函数,函数的返回类型及所有形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句。

      #include <stdio.h>
      #include <iostream>
      #include <string.h>
      #include <vector>
      #include <algorithm>
      using namespace std;
      constexpr int add()
      {
              return 5;
      }
      constexpr size_t scale(size_t cnt)
      {
              return add() * cnt;
      }
      int main()
      {
              cout << scale(2) << endl;
      }
      

      把内联函数和constexpr函数放在头文件内

    6. 调试帮助

      assert是一种预处理宏,定义在cassert头文件中,所以使用时无需std::,也不需要提供using声明。

      assert(expr);
      

      NDEBUG预处理变量

      assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NODEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。

    五、函数匹配

    候选函数:函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数成为候选函数。候选函数具备两个特征:一是与被调用的函数同名,二是其声明在调用点可见。

    可行函数:考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数。可行函数也有两个特征:一是其形参数量与本次调用提供的实参数量相等,而是每个实参的类型与对应的 形参类型相同,或者能转换成形参的类型。

    含有多个形参的函数匹配

    f(int,int);
    f(double,double);
    

    f(42,3.5);会出现二义性。

    六、函数指针
    1. 使用函数指针

      #include <stdio.h>
      #include <iostream>
      #include <string.h>
      #include <vector>
      #include <algorithm>
      #include <cassert>
      using namespace std;
      bool lengthCompare(const string &s1, const string &s2)
      {
              return s1.size() > s2.size();
      }
      bool (*pf)(const string &, const string &);
      
      int main()
      {
              pf = lengthCompare;
              cout << pf("hello", "goodbye") << endl;
              cout << (*pf)("hello", "goodbye") << endl;
              cout << lengthCompare("hello", "goodbye") << endl;
              pf = 0; // pf不指向任何函数
              string :: size_type sumLength(const string&,const string &); //返回类型不匹配
              pf = sumLength;
      }
      
    2. 重载函数的指针

      void ff(int*);
      void ff(unsigned int);
      void (*pf)(unsigned int) = ff;//pf1指向ff(unsigned)
      void (*pf2)(int) = ff;//没有和一个ff与该形参列表匹配
      double (*pf3)(int *) = ff;//ff和pf3的返回类型不匹配
      
    3. 函数指针形参

      //第三个形参是函数类型,它会自动地转换成指向函数的指针
      void useBigger(const string &s1,const string &s2,bool pf(const string &,const string&));
      //等价的声明:显示地将形参定义成指向函数的指针
      void useBigger(const string &s1,const string &s2,bool (*pf)(const string &,const string &));
      //自动将函数lengthCompare转换成指向该函数的指针
      useBigger(s1,s2,lengthCompare);
      
      //Func和Func2是函数类型
      typedef bool Func(const  string &,const string&);
      typedef decltype(lengthCompare) Func2;
      //FuncP和FuncP2是指向函数的指针
      typedef bool(*FuncP)(const  string &,const string&);
      typedef decltype(lengthCompare) *Func2;
      
      void useBigger(const string&, const string& ,Func);//自动转换成指针
      void useBigger(const string&,const string&,FuncP2);
      
    4. 返回指向函数的指针

      using F = int(int*,int);//函数类型不是指针
      using PF = int(*)(int*,int);//指针类型
      
      PF f1(int);
      F f1(int);//F是函数类型,f1不能返回一个函数
      F *f1(int);
      
      int (*f1(int))(int *,int);
      auto f1(int)-> int (*)(int*,int);
      
    5. 将auto和decltype用于函数指针类型

      decltype(sumLength) *getFcn(const string &);//切记decltype返回函数类型了不返回指针类型
      
  • 相关阅读:
    hdu多校4
    hdu多校第三场
    牛客多校4
    bzoj 1477 扩展欧几里德
    bzoj 1485 卡特兰数 + 分解因子
    hdu多校 2
    牛客网暑期多校2
    bzoj 1040 基向内环树dp
    hdu 多校第一场
    SPOJ
  • 原文地址:https://www.cnblogs.com/Jawen/p/11284197.html
Copyright © 2020-2023  润新知