特殊用途语言特性——默认参数、内联函数和constexptr函数

1 默认实参

某些函数有这样一些参数,在函数的很多次调用中它们都被赋予一个相同的值,此时,我们把这个反复出现的值称为函数的默认实参。调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。

我们可以为一个或多个形参定义默认值,不过需要注意的是,一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。

 

使用默认实参调用函数

如果我们想使用默认实参,只要在调用函数的时候省略该实参就可以了。

函数调用调用时实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参(靠右侧位置)。

当设计含有默认实参的函数时,其中一项任务是合理设置形参的顺序,尽量让不怎么使用默认值的形参出现在前面,而让那些经常使用默认值形参出现在后面。

 

默认实参声明

对于函数的声明来说,通常的习惯是将其放在头文件中,并且一个函数只声明一次,但是多次声明同一函数也是合法的。不过有一点需要注意,在给定的作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那些没有默认值的形参添加默认实参,而且该形参右侧的所有形参必须都有默认值。假定给定

//表示高度和宽度的形参没有默认值

string screen(sz,sz,char=' ');

我们不能修改一个已经存在的默认值:

string screen(sz,sz,char='*');  //错误: 重复声明

但是可以按照如下形式添加默认实参:

string screen(sz=24,sz=80,char);  //正确:添加默认实参

通常,应该在函数声明中指定默认实参,并将该声明放在合适的头文件中。

 

默认实参初始值

局部变量不能作为默认实参除此之外,只有表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参

//wd、def和ht的声明必须出现在函数之外

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;  //隐藏了外层定义的wd,但是没有改变默认值

  window=screen();  调用screen(ht(),80,'*');

}

我们在函数f2内部改变了def的值,所有对screen的调用将会传递这个更新过的值。另一方面,虽然我们的函数还声明了一个局部变量用于隐藏外层的wd,但是该局部变量与传递给screen的默认实参没有任何关系。

 

2 内联函数和constexpr函数

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

将函数指定为内联函数,通常就是将它的每个调用点上“内联地”展开。假设我们把shorterString函数定义成内联函数,则如下调用

cout<<shorterString(s1,s2)<<endl;

将在编译过程中展开成类似于下面的形式

cout<<(s1.size()<s2.size()?s1:s2)<<endl;

从而消除shorterString函数的运行时开销。

在shorterString函数的返回类型前面加上关键字inline,这样就可以将它声明成内联函数了:

//内联版本:寻找两个string对象中较短的那个

inline const string &shorterString(const string &s1,const string &s2)

{

  return s1.size()<=s2.size()?s1:s2;

}

 

3 constexpr函数

constexpr函数是指能用于常量表达式的函数。定义constexpr函数的方法与其他函数类似,不过要遵循几项规定:函数的返回类型及所有形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句

constexpr int new_sz() {return 42;}

constexpr int foo=new_sz();  //正确:foo是一个常量表达式

我们把new_sz定义成无参数的constexpr函数。因为编译器能在程序编译时验证new_sz函数的返回的是常量表达式,所有可以用new_sz函数初始化constexpr类型的变量foo。

执行该初始化任务时,编译器把对constexpr函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr函数被隐式的定义为内联函数。

constexpr函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。例如,constexpr函数中可以有空语句、类型别名以及using声明。

我们允许constexpr函数的返回值并非一个常量:

//如果arg是常量表达式,则scale(arg)也是常量表达式

constexpr size_t scale(size_t cnt) {return new_sz()*cnt;}

当scale的实参是常量表达式时,它的返回值也是常量表达式:反之则不然:

int arr[scale(2)];  //正确:scale(2)是常量表达式

int i=2;  //i不是常量表达式

int a2[scale(i)];   //错误:scale(i)不是常量表达式

如上例所示,当我们给scale函数传入一个形如字面值2的常量表达式时,它的返回类型也是常量表达式。此时,编译器用相应的结果值替换对scale函数的调用。

如果我们用一个非常量表达式调用scale函数,比如int类型的对象i,则返回值是一个非常量表达式。当把scale函数用在需要常量表达式的上下文中时,由编译器负责检查函数的结果是否符合要求。如果结果恰好不是常量表达式,编译器将发出错误信息。

 

constexpr函数不一定返回常量表达式

 

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

和其他函数不一样,内联函数和constexpr函数可以再程序中多次定义。毕竟,编译器要想展开函数仅有函数声明是不够的,还需要函数的定义。不过,对于某个给定的内联函数或者constexpr函数来说,它的多个定义必须定义一致。基于这个原因,内联函数和constexpr函数通常定义在头文件中。

 

函数问题

一)inline: 内联函数,就是相当于把被调用的函数硬嵌入调用它的函数中去,不用保存栈,所以速度快。

1. 要使inline起作用,必须是inline函数定义(不但是声明)在其调用函数范围内

2. 不同头文件包含相同inline函数,不是重定义,但是两个或多个inline函数必须一模一样。

二)constexpr:如果参数是常量,就可以了在编译时计算的函数。

定义一个constexpr函数是为了可以使得函数用于常量表达式中,例如:

constexpr int fac(int n)
{
return (n>1) ? n∗fac(n−1) : 1;
}
constexpr int f9 = fac(9); // must be evaluated at compile time
void f(int n)
{
int f5 = fac(5); // may be evaluated at compile time
int fn = fac(n); // evaluated at run time (n is a var iable)
constexpr int f6 = fac(6); // must be evaluated at compile time
constexpr int fnn = fac(n); // error : can’t guarantee compile-time evaluation (n is a var iable)
char a[fac(4)]; // OK: array bounds must be constants and fac() is constexpr
char a2[fac(n)]; // error : array bounds must be constants and n is a var iable
// ...
}

constexpr函数有很多限制,constexpr机制也只是用于相对简单的功能的,如下:

int glob;
constexpr void bad1(int a) // error : constexpr function cannot be void
{
glob = a; // error : side effect in constexpr function
}
constexpr int bad2(int a)
{
if (a>=0) return a; else return −a; // error : if-statement in constexpr function
}
constexpr int bad3(int a)
{
sum = 0; // error : local var iable in constexpr function
for (int i=0; i<a; +=i) sum +=fac(i); // error : loop in constexpr function
return sum;
}

也如内联函数一样,在不同.h文件中,定义必须相同。可以认为constexpr函数是一个受限制的内联函数。

Constexpr函数不能写操作非局部函数。但是可以读操作非局部函数,可以使用作引用参数,但是也不能作写操作。如:

constexpr int ftbl[] { 1, 2, 3, 5, 8, 13 };
constexpr int fib(int n)
{
return (n<sizeof(ftbl)/siz eof(∗ftbl)) ? ftbl[n] : fib(n);
}

有条件选择句调用constexpr函数是:没有被选择的分支函数计算是在run-time时发生的。

 

函数返回值问题:
string to_string(int a); //前置返回值 prefix return type
auto to_string(int a) −> string; //后置返回值suffix return type

两者是一样效果的。

后者主要使用在模板中,如下:
template<class T, class U>
auto product(const vector<T>& x, const vector<U>& y) −> decltype(x∗y);

 

注意不要返回临时变量指针或引用。

时间: 2024-10-07 11:21:06

特殊用途语言特性——默认参数、内联函数和constexptr函数的相关文章

实例讲解在C++的函数中变量参数及默认参数的使用_C 语言

包含变量参数列表的函数如果函数声明中最后一个成员是省略号 (...),则函数声明可采用数量可变的参数.在这些情况下,C++ 只为显式声明的参数提供类型检查.即使参数的数量和类型是可变的,在需要使函数泛化时也可使用变量参数列表.函数的系列是一个使用变量参数列表的函数的示例.printfargument-declaration-list 包含变量参数的函数 若要访问声明后的参数,请使用包含在标准包含文件 STDARG.H 中的宏(如下所述). 采用数量可变的参数的函数声明至少需要一个占位符参数(即使

C++中函数的默认参数详细解析_C 语言

使用方法:(1)在函数声明或定义时,直接对参数赋值,该参数就是默认参数.(2)在函数调用时,省略部分或全部参数,这时就会使用默认参数进行代替. 注意事项:(1)一般在声明函数是设置默认参数. 如果在函数声明和定义函数时都设置了默认参数,则以函数声明的默认参数为准. 复制代码 代码如下: #include<iostream>using namespace std;int main(){ double add(double a=3.2,double b=9.6);//在函数声明时设置默认参数 co

从汇编看c++函数的默认参数的使用说明_C 语言

在c++中,可以为函数提供默认参数,这样,在调用函数的时候,如果不提供参数,编译器将为函数提供参数的默认值.下面从汇编看其原理. 下面是c++源码: 复制代码 代码如下: int add(int a = 1, int b = 2) {//参数a b有默认值    return a + b;}int main() {   int c= add();//不提供参数 } 下面是mian函数里面的汇编码: 复制代码 代码如下: ; 4    : int main() {     push    ebp 

如何调试C/C++内联函数及过程

这篇文章描述了一种持续的权衡方法,可以在调试程序和使之运行 更快之间取得正确的平衡.这篇文章同时还描述了如何调试内联函数及过程. 编写一个运行得快的程序并不容易.编译器可以帮助将一个程序转换成运行得更加快,但权衡是转换之后的程序与初始的程序会有所不同.在某些作了积极优化转换(aggressive optimization transform)的情形里,从人类的角度看转换后的程序与初始程序相比较已经几乎没有可读的一致性了. 因此,调试有问题的已优化程序会更加困难.这是一个问题,因为为了获得最大化的

C++默认参数与函数重载 注意事项

一.默认参数在C++中,可以为参数指定默认值.在函数调用时没有指定与形参相对应的实参时, 就自动使用默认参数. 默认参数的语法与使用:(1)在函数声明或定义时,直接对参数赋值.这就是默认参数:(2)在函数调用时,省略部分或全部参数.这时可以用默认参数来代替. 注意:(1)默认参数只可在函数声明中设定一次.只有在没有函数声明时,才可以在函数定义中设定.(#add ,此句意为存在函数声明和定义两部分的时候.验证表明有这个限制,可以随便,但出于规范,在声明中指定)(2)如果一个参数设定了缺省值时,其右

C++ 语言特性的性能分析

大多数开发人员通常都有这个观点,即汇编语言和 C 语言适合用来编写对性能要求非常高的程序.而 C++ 语言的主要应用范围是编写复杂度非常高的程序,但是对性能要求不是那么严格的程序.但是事实往往并非如此,很多时候,一个程序的速度在框架设计完成时大致已经确定了,而并非是因为采用了C++语言才使其速度没有达到预期的目标.因此当一个程序的性能需要提高时,首先需要做的是用性能检测工具对其运行的时间分布进行一个准确的测量,找出关键路径和真正的瓶颈所在,然后针对瓶颈进行分析和优化,而不是一味盲目地将性能低劣归

《高阶Perl》——3.2 内联缓存

3.2 内联缓存 给一个函数添加缓存的最直接的方式就是给函数一个私有的散列.在这个例子里,可以使用一个数组代替散列,因为fib()的参数总是一个非负整数.但是一般需要使用一个散列,那么将会看到: ### Code Library: fib-cached # Compute the number of pairs of rabbits alive in month n { my %cache; sub fib { my ($month) = @_; unless (exists $cache{$m

最牛X的 GCC 内联汇编

正如大家知道的,在C语言中插入汇编语言,其是Linux中使用的基本汇编程序语法.本文将讲解 GCC 提供的内联汇编特性的用途和用法.对于阅读这篇文章,这里只有两个前提要求,很明显,就是 x86 汇编语言和 C 语言的基本认识. 1. 简介 1.1 版权许可 Copyright (C) 2003 Sandeep S. 本文档自由共享;你可以重新发布它,并且/或者在遵循自由软件基金会发布的 GNU 通用公共许可证下修改它;也可以是该许可证的版本 2 或者(按照你的需求)更晚的版本. 发布这篇文档是希

GCC 内联汇编 HOWTO

v0.1, 01 March 2003. 本 HOWTO 文档将讲解 GCC 提供的内联汇编特性的用途和用法.对于阅读这篇文章,这里只有两个前提要求,很明显,就是 x86 汇编语言和 C 语言的基本认识. 1. 简介 1.1 版权许可 Copyright (C) 2003 Sandeep S. 本文档自由共享:你可以重新发布它,并且/或者在遵循自由软件基金会发布的 GNU 通用公共许可证下修改它:也可以是该许可证的版本 2 或者(按照你的需求)更晚的版本. 发布这篇文档是希望它能够帮助别人,但是