C++学习(九)函数深入

来源:互联网 发布:with an orchid 知乎 编辑:程序博客网 时间:2024/06/05 06:36

1.内联函数

内联函数是C++为提高程序运行速度所做的一项改进。常规函数和内敛函数之间的主要却别在于编写方式,而在于编译器如何将他们组合到程序中。要了解内联函数与常规函数之间的区别,必须深入到程序的内部。
    执行到函数调用指令时,程序将在函数调用后立即存储指令的内存地址,不能够将函数参数复制到堆栈(为此保留的对战块),跳到标记函数起点的内存单元,执行函数代码(也许还需要将返回值放入到),然后跳回到地址被保存的指令处
    内联函数的编译代码与其他程序代码“内联”起来了。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,函数无需跳到另一个位置处执行代码,然后再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多的内存。

Code Snippet
  1. //Demo1 内联函数
  2. #include
  3. inline double square(double x)
  4. {
  5.     return x*x;
  6. }
  7. int main()
  8. {
  9.     double a,b;
  10.     double c = 13.0;
  11.     a = square(5.0);
  12.     b = square(4.5+7.5);
  13.  
  14.     return 0;
  15. }

2.引用操作符(&)、地址操作符(&)和指针操作符(*)的区别
int rats = 101;
int &rodents = rats;//引用变量
int *prats = &rats;//指针变量
rodents和*prats都可以同rats互换,&rodents和prats都可以同&rats互换。
引用必须在声明时将其初始化
引用更接近const指针,必须在创建时进行初始化。
比如:int &rodents = rats;实际上是int * const r = &rats;的伪装表示,rodents扮演的角色与表达式*pr相同。

Code Snippet
  1. //Demo2 初始化引用变量
  2. #include
  3. int main()
  4. {
  5.     using namespace std;
  6.     int rats = 101;
  7.     int &rodents = rats;
  8.     cout<<"rats = "<<rats;
  9.     cout<<",rodents = "<<rodents<<endl;
  10.  
  11.     cout<<"rats address = "<<&rats;
  12.     cout<<",rodents address = "<<&rodents<<endl;
  13.  
  14.     int bunnies = 50;
  15.     rodents = bunnies;//可以通过初始化来设置引用,但不能够通过赋值来改变引用,这里虽然rats,rodents的值改变了,但rodents的引用没改变。
  16.  
  17.     cout<<"bunnies = "<<bunnies;
  18.     cout<<",rats = "<<rats;
  19.     cout<<",rodents = "<<rodents<<endl;
  20.  
  21.     cout<<"bunnies address = "<<&bunnies;
  22.     cout<<",rodents address = "<<&rodents<<endl;
  23.     return 0;
  24. }

3.在函数调用时,如果实参与引用参数不匹配,C++将生成临时变量,仅当参数为const引用时,C++才允许这样做。

4.引用非常适合用于结构和类,引入引用主要是为了用于这些类型的,而不是基本的内置类型。

Code Snippet
  1. //Demo4 结构引用
  2. #include
  3. using namespace std;
  4. typedef struct sysop
  5. {
  6.     char name[26];
  7.     char quote[64];
  8.     int used;
  9. };
  10. const sysop & use(sysop & sysopref);
  11. int main()
  12. {
  13.     sysop looper =
  14.     {
  15.         "Rick /"Fortran/" Lopper",
  16.         "I'm a goto kind of guy.",
  17.         0
  18.     };
  19.  
  20.     use(looper);
  21.     cout<<"Lopper:"<<looper.used<<" use(s)/n";
  22.     sysop copycat;
  23.     copycat = use(looper);
  24.     cout<<"Lopper:"<<looper.used<<" use(s)/n";
  25.     cout<<"Copycat:"<<copycat.used<<" use(s)/n";
  26.     cout<<"use(lopper):"<<use(looper).used<<" use(s)/n";
  27.     return 0;
  28. }
  29. const sysop & use(sysop & sysopref)
  30. {
  31.     cout<<sysopref.name<<" says:/n";
  32.     cout<<sysopref.quote<<endl;
  33.     sysopref.used ++;
  34.     return sysopref;
  35. }

5.应当避免函数使用返回引用临时变量的引用,以及返回指向临时变量的指针。否则会返回一个空引用或空指针。

6.何时使用引用参数能够修改调用函数中的数据对象

  • 能够改掉函数中的数据对象
  • 通过传递引用而不是整个数据对象,可以提高程序的运行速度

7.函数参数什么时候使用引用?什么时候使用指针?什么时候用值传递?

  • 如果数据对象很小,如内置数据类型或小型结构,则按值传递。
  • 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。
  • 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省赋值结构所需的时间和空间。
  • 如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递。
  • 如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码(其中x是int型),则很明显,函数将修改x。
  • 如果数据对象是数组,则只能使用指针。
  • 如果数据对象是结构,则使用引用或指针。
  • 如果数据对象是类对象,则使用引用。
Code Snippet
  1. //Demo3 引用传递、指针专递和值传递
  2. #include
  3. void swapr(int &a,int &b);
  4. void swapp(int *p,int *q);
  5. void swapv(int a,int b);
  6. int main()
  7. {
  8.     using namespace std;
  9.     int wallet1 = 300;
  10.     int wallet2 = 350;
  11.  
  12.     cout<<"wallet1 = $"<<wallet1;
  13.     cout<<",wallet2 = $"<<wallet2<<endl;
  14.  
  15.     cout<<"Using references to swap contents:/n";
  16.     swapr(wallet1,wallet2);
  17.     cout<<"wallet1 = $"<<wallet1;
  18.     cout<<",wallet2 = $"<<wallet2<<endl;
  19.  
  20.     cout<<"Using pointers to swap contents:/n";
  21.     swapp(&wallet1,&wallet2);
  22.     cout<<"wallet1 = $"<<wallet1;
  23.     cout<<",wallet2 = $"<<wallet2<<endl;
  24.     return 0;
  25. }
  26.  
  27. void swapr(int &a,int &b)
  28. {
  29.     int temp;
  30.     temp = a;
  31.     a = b;
  32.     b = temp;
  33. }
  34. void swapp(int *p,int *q)
  35. {
  36.     int temp;
  37.     temp = *p;
  38.     *p = *q;
  39.     *q = temp;
  40. }
  41. void swapv(int a,int b)
  42. {
  43.     int temp;
  44.     temp = a;
  45.     a = b;
  46.     b = temp;
  47. }

8.函数的默认参数
    char* left(const char * str,int n = 1);
    使用默认参数可以减少定义的析构函数、方法以及方法重载的数量。可以定义多个函数默认参数,上面的函数声明中,在调用函数时,如果不传入参数left()调用,则在函数内部会采用n=1来实现。

9.函数的模板
    函数模板时通用的函数描述,可以使用通用类型来定义函数,通用类型又可以用具体类型来替换。通过将类型作为参数传递给模板,可使编译器生成该类的函数。Java的泛型很像C++的模板函数,不过Java的泛型只支持对象类型,不支持基本数据类型的泛型。函数的模板可以自动帮我们完成重载的过程。

  • 函数模板原型声明:
    template
    void swap(T &a,T &b);
    关键字template和class是必须的,可以用typename代替class,模板函数也支持重载
  • Code Snippet
    1. //Demo5 C++函数模板
    2. #include
    3. template<class T>
    4. void Swap(T &a,T &b);
    5. int main()
    6. {
    7.     using namespace std;
    8.     int i = 10;
    9.     int j = 20;
    10.     Swap(i,j);
    11.     cout<<"i,j = "<<i<<","<<j<<"./n";
    12.     return 0;
    13. }
    14. template<class T>
    15. void Swap(T &a,T &b)
    16. {
    17.     T temp;
    18.     temp = a;
    19.     a = b;
    20.     b = temp;
    21. }
  • 显示具体化(explicit specialization),当编译器找到与函数条用匹配的具体化定义时,将使用该定义,而不再寻找模板。
    具体化标准
        1.对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及它们的重载
        2.显示具体化的原型和定义应以template<>开头,并通过名称来指出类型
        3.具体化将覆盖常规模板,而非模板函数将覆盖具体化和常规模板。
  • Code Snippet
    1. //Demo6 函数模板之显式具体化
    2. #include
    3. template <class T>
    4. void Swap(T &a,T &b);
    5. struct job
    6. {
    7.     char name[40];
    8.     double salary;
    9.     int floor;
    10. };
    11.  
    12. template <> void Swap<job>(job &j1,job &j2);//这里也可以写成template<>void Swap(job &j1,job &j2);
    13. void show(job &j);
    14.  
    15. int main()
    16. {
    17.     using namespace std;
    18.     cout.precision(2);
    19.     cout.setf(ios::fixed,ios::floatfield);
    20.     int i = 10;
    21.     int j = 20;
    22.     cout<<"i,j = "<<i<<","<<j<<endl;
    23.     cout<<"Using compiler-generated in swapper:/n";
    24.     Swap(i,j);
    25.     cout<<"Now i,j = "<<i<<","<<j<<endl;
    26.  
    27.     job sue ={"Susan Yaffee",7300.60,7};
    28.     job sidney = {"Sidney Taffee",78060.72,9};
    29.     cout<<"Before job swapping:/n";
    30.     show(sue);
    31.     show(sidney);
    32.     //声明了两个模板函数,但是编译器会按照非模板函数->显式具体化模板函数->非显式具体化模板函数的顺序去查找函数定义
    33.     Swap(sue,sidney);
    34.     cout<<"After job swapping:/n";
    35.     show(sue);
    36.     show(sidney);
    37.  
    38.  
    39.     return 0;
    40. }
    41. //普通函数模板
    42. template <class T>
    43. void Swap(T &a,T &b)
    44. {
    45.     T temp;
    46.     temp = a;
    47.     a = b;
    48.     b = temp;
    49. }
    50. //函数模板显式具体化
    51. template <> void Swap<job>(job &j1,job &j2)
    52. {
    53.     double t1;
    54.     int t2;
    55.     t1 = j1.salary;
    56.     j1.salary = j2.salary;
    57.     j2.salary = t1;
    58.     t2 = j1.floor;
    59.     j1.floor = j2.floor;
    60.     j2.floor = t2;
    61. }
    62. void show(job &j)
    63. {
    64.     using namespace std;
    65.     cout<<j.name<<":$"<<j.salary
    66.         <<" on floor "<<j.floor<<endl;
    67. }


  • 隐式实例化和显示实例化
    在调用普通模板Swap(5,6)时,编译器会生成一个Swap()的实例,该实例为int型,同样Swap(2.56,3.87)会生成一个double型的Swap()实例,这种实例化的方式是编译器去做的,也叫隐式实例化。C++现在允许显式实例化,显式实例化的函数声明和显式具体化的声明很像,不过有一点点区别,显式实例化比显式具体化在关键字template后少了一个<>。
    template void Swap(int,int);//显式实例化
    template<>void Swap(int,int);//显式具体化
原创粉丝点击