首页 C++ 函数详解
文章
X

C++ 函数详解

在博客《 C++ 基本知识》已经谈到了函数,但 C++ 还提供了许多新的函数特性,使之有别于 C 语言。新特性包括内联函数、 按引用传递参数、默认的参数值、函数重载(多态)以及模板哈数

内联函数

内联函数的编译代码与其他程序代码“内联”起来。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需 跳到函数定义处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。如果程序在 10 个不同 的地方调用同一个内联函数,则该程序将包含该函数代码的 10 个副本。

内联函数

应有选择地使用内联函数。如果执行函数代码时间比处理函数调用机制时间长,则节省的时间将占整个过程的很小一部分。如果代码时间 执行时间很短,虽然节省时间的比例较大,但节省的时间绝对值并不大,除非该函数经常被调用。

1
要使用内联函数,必须采取下述措施之一:
  • 在函数声明前加上关键字 inline;
  • 在函数定义前加上关键字 inline;

宏不能按值传递,如果使用 C 语言的宏执行了类似函数的功能,应考虑将它们转换为 C++ 内联函数。

内联函数的链接性是内部的,这意味着函数定义必须在使用函数的文件中。一般在使用函数的文件中包含(声明和定义了内联函数的)头文件可确保将定义 放在正确的地方。也可以将定义放在实现文件中,但必须删除关键字 inline,这样函数的链接性将是外部的。

函数重载

函数多态(函数重载)让你能够使用多个同名的函数。术语“多态”指的是多种形式,因此函数多态允许函数可以有多种形式。 类似地,术语函数重载指的是可以有多个同名的函数,因为对名称进行了重载。这两个术语是同一回事。可以通过函数重载来 设计一系列函数—-它们完成相同的工作,但使用不同的参数列表。

1
函数重载条件:
  • 函数名相同
  • 参数(不是参数名称)列表不同

    • 参数个数不同
    • (或者)参数个数相同但对应位存在类型不同的情况
    • 引用类型和类型本身,非指针和非引用类型带 const 与否,均视为同一个特征标(即视为参数相同)
    • 对于指针或引用类型带 const 与否认为参数不同(主要是因为,将非 const 值赋给 const 变量是合法的,但反之是非法的)
  • 返回值类型不是特征标

虽然函数重载很吸引人,但也不要滥用。仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载(实质上是 为了重载函数名,而好的函数名是显示其功能)。事实上,C++ 通过名称修饰(如加后缀)来区分重载函数中不同的函数。

函数模板

前面提到的“函数重载”,重用了函数名和函数逻辑,但仍然要针对不同的数据类型写不同的函数(函数体)。那能不能针对不同的 数据类型但参数个数相同的“函数重载”只用一个函数来表示呢?这就是函数模板出现时机。

函数模板是通用的函数描述(使用泛型来定义函数)。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时也称为参数化 类型。

1
不但普通函数可以声明为函数模板,类的成员函数也可以声明为函数模板。

需要注意的是,函数模板不能缩短可执行程序,有几个模板实例就有几个独立的函数定义,就像以手工方式定义了这些函数一样。 最终代码不包含任何模板,而只包含了为程序生成的实际函数。可见,函数模板将代表着不同类型的一组函数,它们都使用相同的代码。 通常模板放在头文件中。

1
使用模板的好处是,它使生成多个函数定义更简单、更可靠。

模板函数

  • 函数模板是模板函数的一个样板,它可以生成多个重载的模板函数,这些模板函数重用函数体代码。
  • 模板函数是函数模板的一个实例。是函数模板的实例化(instantiation)

函数模板如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template <typename T1,typename T2>
void add(T1 a[],T2 b[],int size){
    for(int i=0;i<size;i++) b[i]+=a[i];
}

//另外一例
//typename 或 class 均可(也可混合使用)

template<class stype>
void bubble(stype *item, int count)
{
	stype bubb;
	for(int i=0;i<=count-1;i++)
		for(int j=i+1;j<count;j++)
			if(item[i]>item[j])
			{
				bubb=item[i];
				item[i]=item[j];
				item[j]=bubb;
			}
}
//模板函数
template<typename T>
T add(T a,T b){return a+b;}

cout<<add<int>(3,5);
//编译系统将生成如下的模板函数
int add(int a,int b){return a+b;}

函数模板重载

函数模板只能简化(大部分) 参数个数相同的函数重载,但对于参数个数不同的函数重载没有办法。所以此时需要对函数模板进行 重载,和常规重载一样,被重载的模板的函数特征标必须不同。

模板的局限性

编写的模板函数很可能无法处理某些类型(如含 > 的模板对指针不管用),因为某些运算符不适合特定类型,甚至这样做是没有意义的。 如果有意义的话,你可以:

  • 通过重载运算符来解决此类问题
  • (或者)为特定类型提供具体化的模板定义

普通函数和函数模板可以同名!匹配顺序是:先同名普通函数->同名函数模板

1
同名普通函数(参数个数等也相同)可以看做函数模板的具体化。

使用模板警告

重载解析

上述提到了函数重载、函数模板和函数模板重载,这三者的函数名都可以相同,那到底选哪一个呢?

1
重载解析步骤如下:
  • 创建候选函数列表(包括三者)
  • 使用候选函数列表创建可行函数列表(函数模板实例化)
  • 完全匹配顺序:普通函数->模板函数,指向非 const 数据的指针和引用 -> 非 const 指针和引用
  • 最佳(含类型转换)匹配顺序:普通函数->模板函数,转换最少优先
  • 报错

函数模板的发展

前面提到的函数模板形式化了参数,但返回值可能是不同于形式化参数的类型(如两个 int 相除,出现 float 等)。C++ 给出了方案如下:

  • 新增关键字 decltype 用于推断函数中运算得到的新类型
    1
    2
    3
    4
    5
    
    template <typename T1, class T2>
    {
      decltype(x+y) xpy = x + y;
      //xpy 的类型由 decltype 智能确定(类似 auto )
    }
    
  • 后置返回类型 ```cpp template <typename T1, typename T2> ?type? sum(T1 x, T2 y) { return x+y; } //此时还没有声明参数 x 和 y //不能将返回类型 decltype(x + y) //但可以用后置返回类型

template <typename T1, typename T2> auto sum(T1 x, T2 y) -> decltype(x + y) { return x+y; } //decltype 在参数声明后面 //因此 x 和 y 位于作用域,可以使用它们 ```

函数重写(也称为覆盖)

之前的函数重载通常指同一个类中行为(也有非类成员函数)。而函数重写体现的是父类和子类之间的多态性、子类重新定义父类 中有相同名称及参数列表的虚函数(虚函数只是一个声明)。

1
重写函数必须具有相同的函数头(只是重写函数体及其访问修饰符而已)
  • 被重写的函数不能是 static 的,必须是 virtual 的
  • 重写函数必须有相同的类型、名称和参数列表(即相同的函数原型
  • 重写函数的访问修饰符可以不同,尽管 virtual 是 private 的,派生类可以重写为 public等

函数重定义(也称为隐藏)

子类重新定义父类中有相同名称的非虚函数(参数列表可以不同,貌似是重载和重写思想的杂交)。此时的同名函数匹配次序参照重载函数 的匹配顺序,不过此时子类中函数的优先级要大于父类。

父类和子类完全匹配(参照函数重载)时,直接调用子类中的函数,相当于父类的函数被隐藏

1
函数重定义只是名字相同而已,分别位于派生类与基类

我要评论

本文由作者按照 CC BY 4.0 进行授权