c++拾遗-----函数探幽

来源:互联网 发布:nestopia mac 手柄 编辑:程序博客网 时间:2024/06/12 22:12

1、内联函数

内联函数是c++为提高程序运行速度而做出的改进。常规函数域内联函数的区别在于:常规函数的调用使得程序跳到该函数的地址,并在函数结束时返回。而内联函数被编译器使用相应的代码替换函数的调用,程序无需跳到一个内存地址,在跳回来,速度稍快,但牺牲的是内存空间。同样

要使用这项特性,方法如下:

  • 在函数声明前加上关键字inline;
  • 在函数定义前加上关键字inline;

但实际上内联函数还要看编译器是否开放这种特性,在vs2010上可以将内联函数进行递归调用。而一般来说是不允许的。内联函数实际上是通过宏来实现的。

#include<iostream> inline int func(int n) {     if (n == 0)         return 1;     else         return n*func(n-1); }int main(){    using namespace std;    cout<<func(5);    system("pause");    return 0;}

2、引用变量

引用变量是以创建变量的别名。与以创建变量公用同一内存空间。

创建引用变量

int a=5;int &b=a;

通过指针可以更改指向内存的内容,但指针不必在声明时初始化,而且可以更改指向的地址。但引用变量必须在声明时初始化,且一旦与某个变量关联,就只能一直效忠它。所以引用更像指针常量。

函数参数传递

c++中参数传递方式有三种:

按值传递,这种方式会生成一个参数副本,函数内的操作并不会改变参数本身,当然数组除外,因为它传递的是数组的地址。

地址传递,如数组和指针,可以改变指针指向空间的内容。但其实同样传递后产生了一个指针副本,所以无法改变指针的指向的地址。

引用传递,传递的是变量的别名,与变量公用同一内存空间,所以可以直接改变参数的值。

临时变量,引用参数和const

当参数是const引用时,以下两种情况会生成临时变量:

  • 实参类型正确,但不是左值。
  • 实参类型不正确,但可以转换成正确的类型
#include<iostream>double refcube(const double &ra){    return ra*ra*ra;}int main(){    using namespace std;    cout << refcube(7);    system("pause");    return 0;}

传入值7,回生成临时变量,ra指向它,该变量只存在于函数调用期间。而把参参数列表中的const去除,则报错。

如果不是const引用,也生成临时变量,那么函数最终改变的对象就是临时变量,而不是希望的引用。所以其他情况无法生成临时变量

函数返回局部变量

函数返回一般局部变量是可以的,但如果返回的是局部变量的地址或引用时,调用会出错,因为指针指向的的内存空间在函数运行结束时被释放掉了。当然如果数据存储在静态存储区,那么该空间在程序结束时才会被释放。

#include<iostream>char * fun();char *fun1();int main(){    using namespace std;    char* p = fun();    //数据当作常量存储在静态存储区,无法修改    //p[0] = 'c';    cout << p << endl;    char *q = fun1();    cout << q << " ";    q[0] = 'c';    cout << q << endl;     system("pause");    return 0;}char * fun(){    char *p= "abcd";    return p;}char *fun1(){    static char p[5] = { "abcd" };    return p;}

何时使用引用参数

使用引用参数的原因主要有:

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

对于使用传递的值而不做修改的函数

  • 如果数据对象很小,则使用按值传递
  • 如果数据对象是数组,使用指针,并声明为常量指针
  • 如果数据对象是较大的结构,则使用const指针或const引用。
  • 如果数据对象是类对象,则使用const引用

对于修改调用函数中数据的函数

  • 如果是内置对象,则使用指针
  • 如果是数组,使用指针
  • 如果是结构,指针或引用
  • 如果是对象,则使用饮用

3、默认参数

默认参数指函数调用省略实参时自动使用默认值。

注意事项:

  • 对于带参数列表的函数,必须从右往左添加,也就是说如果为某个参数设置默认值,则必须为他右边的所有参数设置默认值。
  • 实参按从左到右依次付给相应的形参,不能跳过任何参数。

4、函数重载

函数重载主要针对于完成相同的工作,而使用不同的参数列表。c++允许定义定义名称相同的函数,前提是参数列表不同。返回值可以相同,但参数列表必须不同

double cube(double x);double cube(double &x);

上面两个函数不能共存,因为调用时无法确定使用哪个原型。

double cube(double x);double cube(const double x);

同上,这两个函数也无法共存。因为非const变量既可赋值给const变量,也可以赋值给非const变量。所以编译器无法确定使用哪个函数。

但当参数为引用或指针,则可以使用const进行重载,这是因为按值传递时,对用户而言,这是透明的,用户不知道函数对形参做了什么手脚,在这种情况下进行重载是没有意义的,所以规定不能重载!当指针或
引用被引入时,用户就会对函数的操作有了一定的了解,不再是透明的了,这时重载是有意义的,所以规定可以重载。

double cube(double &x);double cube(const double &x);double cube(double *x);double cube(const double *x);

如果重载的函数都是引用或指针,const 变量 只能调用带有const 参数的方法,非const 变量既能调用带const 参数的方法,也能调用不带cosnt 参数的方法,优先调用不带const 参数的方法.

5、函数模板

函数模板使用范型来定义函数,其中范型可以使用具体的类型来替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。

#include<iostream>template <typename T>void swap(T &a, T&b);int main(){    int a = 3, b = 5;    std::cout << "a=" << a << ",b=" << b << std::endl;    swap(a, b);    std::cout << "a=" << a << ",b=" << b << std::endl;    double c = 4.5, d = 5.5;    std::cout << "c=" << c << ",d=" << d << std::endl;    swap(c, d);    std::cout << "c=" << c << ",d=" <<d << std::endl;    system("pause");    return 0;}template <typename T>void swap(T &a, T &b){    a = a + b;    b = a - b;    a = a - b;}

要建立一个模板,关键字template和typename是必须的,其中typename可以用class替换。

重载的模板

需要对不同类型使用相同算法时,可以使用模板,但并不是所有类型都这样,所以可以想重载常规函数一样重载函数模板,记住,并非所有模板参数都必须是模板参数类型。

#include<iostream>template <typename T>void swap(T &a, T &b){    T temp;    temp = a;    a = b;    b = temp;}template <typename T>void swap(T *a, T *b, int n){    T temp;    for (int i = 0; i < n; i++)    {        temp = a[i];        a[i] = b[i];        b[i] = temp;    }}int main(){    int a = 5,b=3;    double c[5] = {1.2,2.3,3.4,4.5,5.6};    double d[5] = {6.7,7.8,8.9,9.1,10.2};    swap(a,b);    std::cout << a <<" "<< b << std::endl;    swap(c,d,5);    for (int i = 0; i < 5; i++)        std::cout << c[i] << " " << d[i] <<std::endl;    system("pause");    return 0;}

显示具体化

因为对于某些特殊类型,可能不适合模板实现,需要重新定义实现,此时就是使用显示具体化的场景.

template <typename T>void swap(T &a,T&b);//template <>void swap(int &a,int &b);template <> void swap<int>(int &a,int &b);

上面两种写法皆可,显示具体化一定要有一个相同的模板函数,将其换成具体类型,所以需要一开始的模板函数。

显式实例化

代码中包含的模板并不会生成相应的定义,他们只是一个生成定义的方案,而显式实例化意味着无论程序是否是否使用,都会生成相应的定义。

template void swap<int>(int,int);

隐式实例化

后面有程序用了,编译器才会根据模板生成一个实例函数.

编译器选择使用哪个函数

1、进行完全匹配时,c++允许一些无关紧要的转换

这里写图片描述

2、重载解析过程将寻找最匹配的函数。如果只存在一个这样的函数,则选择该函数;如果存在多个这样的函数,但其中只有一个是非模板函数,则选择该函数;如果存在多个模板函数,但其中一个函数比其他的都具体,则选择该函数。当存在多个匹配的非模板函数或模板函数,或者不存在匹配的函数,都将出现错误。

3、通过代码选择

#include<iostream>template <typename T>T lesser(T t1, T t2){    return t1 > t2 ? t2 : t1;}int lesser(int a, int b){    a = a < 0 ? -a : a;    b = b < 0 ? -b : b;    return a < b ? a : b;}int main(){    using namespace std;    int m = 20;    int n = -30;    double x = 15.5;    double y = 19.5;    cout << lesser(m, n)<<endl;    cout << lesser(x, y) << endl;    cout << lesser<>(m, n) << endl;    cout << lesser<int>(x, y) << endl;    system("pause");    return 0;}

第一个函数调用匹配非模板函数和模板函数,所以调用非模板函数。

第二个与非模板函数匹配

第三个<>指出需要使用模板函数

第四个指出要使用显示实例化函数,所以返回的是15而不是15.5

所以同时匹配模板函数和非模板函数时,默认使用非模板函数,但可以通过<>来强制使用模板函数

4、多个参数的函数

当函数有多个参数时,最匹配的函数一定是所有参数匹配程度都不比其他函数差

c++11新增关键字decltype

当在模板函数中无法知道要声明变量的类型时,可以使用关键字decltype来自动声明(有点像加强版的auto)。decltype确定变量类型的步骤如下:

假设有声明如下

 decltype(expression) var;

1、如果expression是一个没有用括号扩起来的标识符,则var的类型与标识符的一致,包括const等限定符。

 double x=5.5; double y=7.9; double &rx=x; const double *pd; decltype (x) w;   //w is type double decltype (rx) u=y; //rx is type doubel & decltype (pd) v;  //pd is const double *

2、如果expression是函数调用,则var的类型为函数返回类型。

3、如果expression是用括号扩起来的,则var指向其类型为引用

int y=5decltype((y)) z=y; //z type int &decltype(y) w=y;  //w type int

4、如果上述条件都不满足,则var类型与expression一致

int j=3int &k=j;int &r=j;decltype (k+r) z;//z type int

decltype后置返回类型

在模板函数内不确定要声明变量类型时可以用decltype,但函数返回值无法直接使用decltype

template <typename T1,typename T2>auto swap(T1 t1,T2 t2) ->decltype(t1+t2){   return t1+t2;}



0 0
原创粉丝点击