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

1.内联函数

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


Code Snippet
//Demo1 内联函数
#include
inline double square(double x)
{
    return x*x;
}
int main()
{
    double a,b;
    double c = 13.0;
    a = square(5.0);
    b = square(4.5+7.5);
  
    return 0;
}

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

Code Snippet
//Demo2 初始化引用变量
#include
int main()
{
    using namespace std;
    int rats = 101;
    int &rodents = rats;
    cout<<"rats = "<<rats;
    cout<<",rodents = "<<rodents<<endl;
  
    cout<<"rats address = "<<&rats;
    cout<<",rodents address = "<<&rodents<<endl;
  
    int bunnies = 50;
    rodents = bunnies;//可以通过初始化来设置引用,但不能够通过赋值来改变引用,这里虽然rats,rodents的值改变了,但rodents的引用没改变。
  
    cout<<"bunnies = "<<bunnies;
    cout<<",rats = "<<rats;
    cout<<",rodents = "<<rodents<<endl;
  
    cout<<"bunnies address = "<<&bunnies;
    cout<<",rodents address = "<<&rodents<<endl;
    return 0;
}
3.在函数调用时,如果实参与引用参数不匹配,C++将生成临时变量,仅当参数为const引用时,C++才允许这样做。

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

Code Snippet
//Demo4 结构引用
#include
using namespace std;
typedef struct sysop
{
    char name[26];
    char quote[64];
    int used;
};
const sysop & use(sysop & sysopref);
int main()
{
    sysop looper =
    {
        "Rick \"Fortran\" Lopper",
        "I'm a goto kind of guy.",
        0
    };
  
    use(looper);
    cout<<"Lopper:"<<looper.used<<" use(s)\n";
    sysop copycat;
    copycat = use(looper);
    cout<<"Lopper:"<<looper.used<<" use(s)\n";
    cout<<"Copycat:"<<copycat.used<<" use(s)\n";
    cout<<"use(lopper):"<<use(looper).used<<" use(s)\n";
    return 0;
}
const sysop & use(sysop & sysopref)
{
    cout<<sysopref.name<<" says:\n";
    cout<<sysopref.quote<<endl;
    sysopref.used ++;
    return sysopref;
}
5.应当避免函数使用返回引用临时变量的引用,以及返回指向临时变量的指针。否则会返回一个空引用或空指针。

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

能够改掉函数中的数据对象
通过传递引用而不是整个数据对象,可以提高程序的运行速度
7.函数参数什么时候使用引用?什么时候使用指针?什么时候用值传递?

如果数据对象很小,如内置数据类型或小型结构,则按值传递。
如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。
如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省赋值结构所需的时间和空间。
如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递。
如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码(其中xint型),则很明显,函数将修改x
如果数据对象是数组,则只能使用指针。
如果数据对象是结构,则使用引用或指针。
如果数据对象是类对象,则使用引用。
Code Snippet
//Demo3 引用传递、指针专递和值传递
#include
void swapr(int &a,int &b);
void swapp(int *p,int *q);
void swapv(int a,int b);
int main()
{
    using namespace std;
    int wallet1 = 300;
    int wallet2 = 350;
  
    cout<<"wallet1 = $"<<wallet1;
    cout<<",wallet2 = $"<<wallet2<<endl;
  
    cout<<"Using references to swap contents:\n";
    swapr(wallet1,wallet2);
    cout<<"wallet1 = $"<<wallet1;
    cout<<",wallet2 = $"<<wallet2<<endl;
  
    cout<<"Using pointers to swap contents:\n";
    swapp(&wallet1,&wallet2);
    cout<<"wallet1 = $"<<wallet1;
    cout<<",wallet2 = $"<<wallet2<<endl;
    return 0;
}
  
void swapr(int &a,int &b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}
void swapp(int *p,int *q)
{
    int temp;
    temp = *p;
    *p = *q;
    *q = temp;
}
void swapv(int a,int b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}
8.函数的默认参数
    char* left(const char * str,int n = 1);
    使用默认参数可以减少定义的析构函数、方法以及方法重载的数量。可以定义多个函数默认参数,上面的函数声明中,在调用函数时,如果不传入参数left()调用,则在函数内部会采用n=1来实现。

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

函数模板原型声明:
template
void swap(T &a,T &b);
关键字templateclass是必须的,可以用typename代替class,模板函数也支持重载

Code Snippet
//Demo5 C++函数模板
#include
template<class T>
void Swap(T &a,T &b);
int main()
{
    using namespace std;
    int i = 10;
    int j = 20;
    Swap(i,j);
    cout<<"i,j = "<<i<<","<<j<<".\n";
    return 0;
}
template<class T>
void Swap(T &a,T &b)
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}
显示具体化(explicit specialization),当编译器找到与函数条用匹配的具体化定义时,将使用该定义,而不再寻找模板。
具体化标准
    1.对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及它们的重载
    2.显示具体化的原型和定义应以template<>开头,并通过名称来指出类型
    3.具体化将覆盖常规模板,而非模板函数将覆盖具体化和常规模板。

Code Snippet
//Demo6 函数模板之显式具体化
#include
template <class T>
void Swap(T &a,T &b);
struct job
{
    char name[40];
    double salary;
    int floor;
};
  
template <> void Swap<job>(job &j1,job &j2);//这里也可以写成template<>void Swap(job &j1,job &j2);
void show(job &j);
  
int main()
{
    using namespace std;
    cout.precision(2);
    cout.setf(ios::fixed,ios::floatfield);
    int i = 10;
    int j = 20;
    cout<<"i,j = "<<i<<","<<j<<endl;
    cout<<"Using compiler-generated in swapper:\n";
    Swap(i,j);
    cout<<"Now i,j = "<<i<<","<<j<<endl;
  
    job sue ={"Susan Yaffee",7300.60,7};
    job sidney = {"Sidney Taffee",78060.72,9};
    cout<<"Before job swapping:\n";
    show(sue);
    show(sidney);
    //声明了两个模板函数,但是编译器会按照非模板函数->显式具体化模板函数->非显式具体化模板函数的顺序去查找函数定义
    Swap(sue,sidney);
    cout<<"After job swapping:\n";
    show(sue);
    show(sidney);
  
  
    return 0;
}
//普通函数模板
template <class T>
void Swap(T &a,T &b)
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}
//函数模板显式具体化
template <> void Swap<job>(job &j1,job &j2)
{
    double t1;
    int t2;
    t1 = j1.salary;
    j1.salary = j2.salary;
    j2.salary = t1;
    t2 = j1.floor;
    j1.floor = j2.floor;
    j2.floor = t2;
}
void show(job &j)
{
    using namespace std;
    cout<<j.name<<":$"<<j.salary
        <<" on floor "<<j.floor<<endl;
}



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


评论