概述
C++函数具有一些C语言不具有的特性,内联函数、引用参数、const参数、const与引用返回值、默认参数、函数重载、函数模板等特性。承接上一篇C++语法入门【2】函数,这里介绍C++的函数特性。
C++内联函数
使用关键字inline
定义,添加在函数声明或者定义前即可将函数声明为内联函数。
内联函数与常规函数的区别在于,常规函数在调用时程序会跳转到另一个地址(即函数的地址)执行指令,此过程中涉及到参数的传递、堆栈的使用、指令指针的跳转、寄存器状态的保存与恢复等。
而内联函数调用时编译器会用函数的代码替换函数调用。中间不会执行堆栈的压入与弹出等操作。提高了效率。
使用inline
关键字使函数称为内联函数,但编译器不一定会满足这种要求。如果编译器认为函数过大或者函数调用了自身(内联函数不能递归调用),则不会将其作为内联函数。这因编译器而异。
内联定义:
inline double square(double x){ return x *x;}
在类中定义的成员函数将会自动成为内联函数。
引用类型
C++增加了一种复合类型——引用变量。引用(reference)是已定义的变量的一个别名。顾名思义,别名只是换了一个名称而已,但数据是同一份数据。例如Hebe是田馥甄的别名一样,如果定义了Hebe是田馥甄的别名,那么这两个变量仍然表示同一个人。
引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用实参的原始数据,而不是其副本(拷贝)。
创建引用变量
引用变量采用运算符 &
来定义。
例如:
int b = 2;
int & a = b;
则称a是b的引用(或者 a 是指向 b 的引用),a和b具有相同的值,相同的地址。改变a,b也将发生改变。称 a 的类型为int &
,即指向int
变量的引用。
这里的&
不是以往的取地址运算符,这是一种运算符重载(以后介绍)。
C++语法规定引用变量在定义时必须被初始化。只定义不赋值的行为将会被编译器严厉驳回。
可以理解为给一个人取一个小名必须要有这个人,且在取这个小名的时候必须要声明是给谁取的,不能先取好了名字再叫一个人来说我给你取了一个小名,现在我要用你的小名叫你,这显然也是不会被同意的,That’s not how it works!这显然是很容易理解的。
所以下列代码是错误的:
int b;
int & a; //invalid
a = b; //invalid
int & c = 2; //also invalid
引用的内部实现
引用的实现是采用指针实现的,例如:
int b = 1;
int *a = b;
则可以知道变量 b 和变量 *a 具有相同的值,相同的地址(&b 和 a 相同),改变无论哪一个另一个都会随之改变。这里就可以说 *a 是 b 的别名。
C++中引用是编译器通过指针实现的,但这个实现在语言层面对程序员做了透明化处理。
C++11中将引用分为左值引用和右值引用,貌似还分顶层引用和底层引用,暂时不是很理解,以后探讨。
再谈const
前面有谈到过const
变量,只能在定义时初始化,后面便不可再更改。这经常用在函数参数传递上,在按值传递中,形参并不影响实参。但在指针传递和引用传递中,如果我们不希望函数修改参数的值,则可以使用const参数,保证参数不被更改。而const与指针和引用会有一些微妙的关系。
const与指针
const
与指针(pointer)可以有两种关系。一种是使指针指向常量数据,这样便不能通过指针来修改指向数据的值。
如:
int age = 20;
const int *p = &age; //const修饰*p,p指向常量,不能通过*p来改变age的值
*p = 19; //invalid,将会报错
age = 21; //但可以通过age来改变age的值
C++中,const
变量的地址只能赋给指向const的指针。因为如果非const指针可以指向const变量,而又无意中通过该指针修改了const变量的值,无疑会将const
置于无比尴尬的地位。
const int age = 19;
int *p = &age; //invalid,不能将const变量的地址赋给指向非const的指针
另一种是将指针本身声明为常量,即指针变量的值不可改变,即不能改变指针的指向。
int age = 22;
int * const p = & age; //const修饰p,p的指向不可变
int age2 = 30;
p = &age2; //invalid
const与引用
引用变量必须在定义时被初始化,且不可更改指向。所以如果将引用声明为const
,则将不能通过该引用改变引用指向的变量的值。与指针类似,同样不能将const
赋给非const
引用。
引用最主要的作用是用作函数的形参。
函数的const与引用参数
const参数
在不需要修改形参的函数中,使用const
形参将使函数不能在内部修改该形参的值。这可以防止无意间修改了参数值的编程错误。
在需要修改形参值的函数中,不能使用const
。
引用参数
前面提到,按值传递参数将会把实参复制给形参。形参的改变并不会影响到实参。
而按引用传递则不同,在引用传递中,形参将成为实参的引用,通过更改形参便可以更改实参的值。
例如最经典的引用参数示例(交换两个变量的值):
void swap(int &a,int &b)
{
int temp = a;
a = b;
b = temp;
}
使用引用传参的好处:
1. 通过形参可以修改实参的值
2. 形参和实参使用同一份数据,而不是实参的拷贝,提高了效率,节省了时间(这尽管在内置类型上体现不明显,但对于比较大的对象则可以显著提高效率)
const引用参数
使用引用传递时更改形参的值会改变实参。如果我们不希望这样做,则可以采用 const &
参数,既可以提高效率,又不会使我们忽视无意间更改了参数的错误。
在对象参数的传递中,经常使用const &
参数。
例如(这里是结构):
struct name
{
string first_name;
string last_name;
};
void show_name(const name & n)
{
cout << n.first_name << " " << n.last_name << endl;
}
在show_name()
函数中使用 const &
参数使得函数使用的是形参的数据,而又不能更改它。且提高了效率。
传参的一些指导性规则
如果传递不做修改的参数:
1. 如果参数很小,为内置类型或者小型结构,使用按值传递
2. 传递数组,通过数组名,即是指针传递,并将指针声明为指向const的指针
3. 传递大型结构,使用const指针或者const引用,以提高效率
4. 传递类对象,通常要求使用const &
对于传递需要更改带参数:
1. 内置类型,使用指针或者引用
2. 传递数组,只能使用指针
3. 传递结构,使用指针或者引用
4. 传递类对象,使用引用
这只是一般原则,亦可做出其他选择,根据需要传递合适的参数即可。
const与引用返回值
与参数类似,返回值也可以由const修饰,也可以返回引用。
返回引用
在C++中可以返回引用,但只能返回传递给函数的引用参数的引用。不能返回函数中的临时变量与局部变量的引用。因为局部变量在函数执行完后将会被释放,而指向已经释放的内存的引用将会产生错误。而传递给函数的引用参数不会被释放,所以只能返回传递给函数的引用参数。
例如:
int & add(int a,int b,int & sum)
{
sum = a+b;
return sum;
}
使用引用返回值有两个好处:
其一,与使用引用参数的目的相同,返回引用效率高于按值返回,这常用于类对象。但并不是返回对向就必须返回引用,在某些情况下也只能按值返回。这将在后面介绍。
其二,返回非const
引用,将可以让我们对函数表达式执行操作。
例如:
int a = 1;
int b = 2;
int sum = 0;
add(a,b,sum) = 5; //等同于下面的两条语句,因为返回传入的引用参数sum的非`const`引用
//add(a,b,sum);
//sum = 5;
cout << sum << endl; //结果为5
返回引用在类的运算符重载中很常用。
例如下面类中重载<<运算符的函数:
class name
{
friend ostream & operator<<(ostream & out,const name & _name)
{
out << _name.first_name << " " << _name.last_name ;
return out;
}
public:
name(const string & fn,const string & ln): first_name(fn) ,last_name(ln){ } //类内实现,内联定义
~name(){ }
private:
string first_name;
string last_name;
};
上面友元函数返回 ostream &
使得可以进行下面的操作。
name Kim("Kim","Possible");
cout << Kim << endl;
代码中cout << Kim
调用了ostream & operator<<(ostream & out,const name & _name)
函数,返回了cout
对象,所以可以在后面接上<< endl
继续调用 ostream
类中的重载运算符<<
的函数。
返回const引用
当希望返回引用而传入的参数为const &
时,则必须返回const &
,因为不能将常量(const修饰的变量)赋给非const
引用。这时调用时将不能对函数表达式进行改变其值的操作。
某些情况下只希望提高效率,而禁止对传入的对象进行操作时(例如只打印出传入对象的成员而又希望返回对象使其能够一条语句调用多个打印函数),应该使用const &
参数与const &
返回值。
例如:
class name
{
public:
name(const string & fn,const string & ln): first_name(fn) ,last_name(ln){ } //类内实现,内联定义
~name(){ }
const name & show_first_name() const
{
cout << first_name << " ";
return *this;
}
const name & show_last_name() const
{
cout << last_name << endl;
return *this;
}
private:
string first_name;
string last_name;
};
上面的成员函数const name & show_first_name() const
中返回了const name &
,末尾的const表示这是一个常成员函数,修饰*this
。所以函数返回了传入了const &
。
这里仅仅是一个示例,现实中我们可能并不会像上面这样使用。
则我们可以向下面这样调用:
name Kim("Kim","Possible");
Kim.show_first_name().show_last_name();
将会打印出结果Kim Possible
。
按值返回const
并没有意义。与返回非const并没有区别。
默认参数
C++函数允许函数有默认参数(default parameters)。即参数具有默认值。调用时就可以省略传入改参数,而使用默认参数。在函数原型中给参数赋值即可实现。
例如:
void fun(int a=0) //此处函数定义在main()函数前,不需要原型,则直接将默认参数写在定义中
{
cout << a << endl;
}
则可以不传入参数直接调用fun()
,结果将打印出 0 。
也可以传入参数如调用fun(2)
,则将打印出结果 2 。传入的参数将覆盖默认参数。
默认参数必须从右向左添加。即是,如果某个参数有默认值,那么它右边的所有参数都必须有默认值。因为实参是按照从左往右的顺序依次赋给形参的。
void fun(double a,int b = 2,bool c = true);
则下列调用都是合法的:
fun(2.1);
fun(2.0,1);
fun(1.0,2,false);
默认参数在类中定义构造函数与析构函数时经常使用。某些时候,通过使用默认参数,可以减少定义构造函数、析构函数与重载方法的数量。
函数重载
上面的默认参数使得我们可以使用不同数目的参数调用同一个函数。而函数重载也可以做到这一点,只不过函数重载后调用的不是同一个函数,而是不同的同名函数。
什么是函数重载
C++还允许我们对函数进行重载(function overloading)。函数重载指的是我们能够定义多个同名的函数,对函数名称进行了重载。但是他们的参数列表不同。
这样我们可以设计一系列函数,他们的函数名相同,但是可以传入不同的参数。这样可以简化程序的设计,不同的参数、不同的函数,只要重载好了相应的函数,便可以使用同一个函数名调用。
函数重载的关键时函数的参数列表——也称函数特征标。C++通过函数参数列表来区分调用哪一个函数。
例如:
void print(int c); //#1
void print(char ch); //#2
void print(double d); //#3
void print(int a,int b); //#4
void print(const char * str); //#5
上述五个函数原型的特征表各不相同(必须参数类型、顺序和个数完全相同,特征标才相同)。使用函数print()
时,函数将通过实参类型判断将要执行哪一个函数。
print(1,2); //use #4
print('q'); //use #2
print(2.4); //use #3
print(5); //use $1
print("hello,world!"); //use #5
函数匹配
上面的调用都找到了参数与之匹配的函数原型,所以可以顺利执行。但如果所有函数原型都不与调用参数匹配,则编译器会尝试对参数进行标准强制类型转换。如果有多个函数都可以在转换后与之匹配,则将会调用最匹配的版本,如果编译器不能判断哪一个最匹配,则将会报错。
例如:
print(1.2,3.0); //use #4
上面的调用并没有一个void print(double,double)
原型与之匹配,但C++会将其进行强制类型转换,比如将double
转换为int
,char
转换为int
,int
转换为double
等。
一些看起来不同的参数类型会被认为是一样的。比如,编译器在检查函数特征表时,会将类型引用和类型本身视为同一特征标。
例如有如下两个函数原型:
double square(double x);
double square(double & x);
则调用时会发生错误,将不能通过编译:
square(2.1); //编译器将不能分辨调用哪个函数
何时使用函数重载
函数重载看起来非常好用,但也不要滥用,应当仅仅在函数执行相同任务,但使用不同类型的数据时才使用函数重载。
在类的设计中将经常使用运算符重载(一种特殊的函数重载)。
名称修饰
我们不禁要问,C++如何区分这都些同名的函数呢?
答案是,在编译时,C++编译器将会对这些同名但是特征标不同的函数进行一些操作——名称修饰或名称矫正,它会根据函数原型中的形参列表对每个函数名进行加密/编码。这样在编译得到的程序内部将会得到不一样的函数名,这样便可以区分这些函数了。编码方法因编译器而异。
函数模板
有空再写。
参考资料:C++ Primer Plus
最后
以上就是坦率汉堡为你收集整理的C++语法入门【3】C++函数特性C++内联函数引用类型再谈const函数的const与引用参数const与引用返回值默认参数函数重载函数模板的全部内容,希望文章能够帮你解决C++语法入门【3】C++函数特性C++内联函数引用类型再谈const函数的const与引用参数const与引用返回值默认参数函数重载函数模板所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复