C++ Primer Plus学习笔记(六)函数探幽

  1. 内联函数
  2. 引用变量
  3. 默认参数
  4. 函数重载
  5. 函数模板
    5.1 基本介绍
    5.2 模板重载
    5.3 显式具体化
    5.4 显式实例化
  6. 函数调用匹配

1.内联函数

1.1 基本用法

基于常规函数,内联函数要在函数声明或者函数定义前面加上inline。
注:内联函数可使得程序免去从一个位置跳去另一个位置执行代码。例如,程序再10个地方调用内联函数,则该程序将包含函数代码的10个副本。

1.2 与宏定义区别

内联函数的实现是按值传递,而宏定义仅仅是文本替换方式。
例如,

inline double square(double x){return x*x;};
double a = square(1+2);//按值传递
#define SQUARE(X) X*X
double b = SQUARE(1+2);// b equals(1+2*1+2)

2. 引用变量

2.1 基本用法

引用变量相当于目标变量的别名,其值、地址都与目标变量相同,等价于const下的*p。引用变量主要用于函数的形参,这样函数将使用变量原始数据而不是其拷贝。

int a = 10;
int & b = a;//引用变量必须在初始化时就指定目标引用

//错误用法
int & b;
b = a;

int * const ptr = &a;// b equals *ptr

典型用法是引用作为函数参数

void swap(int & a, int & b);
void swap(int & a, int & b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}

2.2 按引用传递调用函数(const下引用对应的临时变量)

引用变量在调用时与普通函数一样,只是在定义和声明时在变量前需要&符号。
调用函数时,按引用传递比按值传递更为严格
例如:

void test(int &a);
int b = 2,c[10] = {1,2,3};
test(b);test(c[2]);//OK
test(b+2);//wrong

上述调用中,(b+2)并不是变量,不能按引用传递调用。const引用会产生临时变量

当满足两个条件:(1)实参与引用参数不匹配;(2)参数为const引用时,会产生临时变量。
其中,条件(1)意味着要么实参类型正确但不是左值,要么实参类型不正确但可以转化为正确类型。

void test1(const double &a);
void test2(double &b);
double a = 11.1;
long b = 5L;
test1(a);//OK
test1(b);//temporary variable
test2(b);//wrong,必须是const引用
test1(5);//temporary variable
test1(a+2);//temporary variable

注:若函数的目的不是修改变量,尽可能使用const引用。这样的好处:(1)避免无意修改变量;(2)能同时接受const和非const实参;(3)能够生成临时变量。

2.3 函数返回引用

struct item
{
    int length; 
};
item & test(item & a,item & b)
{
    a.length = a.length + b.length;
    return a;
}

如果函数返回结构时,相当于把结构复制到临时位置再进行赋值;返回指向结构的引用时,则直接把结构本身赋值。
返回引用的函数实际上是被引用变量的别名。
函数返回引用时需要注意,应该避免函数终止时不再存在的内存的引用。一个策略是传入两个参数时,利用其中一个参数作为返回的引用。如下是正确和错误的示例。

//错误返回引用示例
const int & test(int &a)
{
    int b;
    b = a;
    return b;//wrong,函数终止时b不存在
}
//正确示例
const int & test1(int &a, int &b)
{
    a = a + b;
    return a;
}

2.4 基类引用可以指向派生类对象

3. 默认参数

带参数列表的函数,必须从右向左添加默认值,即某个参数提供默认参数后,必须为他右侧所有参数提供默认值。

4. 函数重载

函数重载(多态)意味着函数名称可以相同,但是函数特征标(参数列表)必须不同 。
注:
(1)匹配函数时,函数引用视为相同的函数特征标,例如:

void test(int a);
int test(int &a);//不能作为重载,引用与非引用特征标相同;此外,函数返回类型相同不相同无所谓,特征标必须不同

(2)匹配函数时,有且仅有一种转换目标类型方式时,对其进行转换,否则报错。

void test(int a);
void test(char a);
unsigned int a = 10;
test(a);//只有一组数字与之匹配,因此函数会进行类型转换,而不是不执行、报错

void test1(int a);
void test1(double a);
void test1(char a);
unsigned int a = 10;
test1(a);//有两组数字与之匹配,因此函数不执行、报错

(3)匹配函数时,const变量和非const变量不给予区分。
如果有完全匹配的,则进行相应的调用,如果不是完全匹配的,可以将非const变量赋给const变量,反之不行。

5. 函数模板

5.1 基本介绍

模板是为了给不同类型数据执行相同的算法而存在,使用泛型定义函数生成函数模板,例如:

template <typename,AnyType>
void swap(AnyType &a,AnyType &b)
{
    AnyType temp;
    temp = a;
    a = b;
    b = temp;
}
//typename 可以用class替换

5.2 模板重载

模板重载是为了解决有时候需要给不同类型数据执行不同的算法,
类似于函数重载,模板特征标必须不同。例如:

template <typename,Anytype>
void swap(Anytype &a,Anytype &b);
template<typename.Anytype>
void swap(Anytype *a,Anytype *b,int n);//模板重载,int n 说明并非所有的模板参数都必须是模板参数类型

5.3 显式具体化

为了满足模板的特殊需求,需要具备模板具体化功能。比如,上述的交换模板,需要具备一个功能,即将一个结构体的两个成员交换,则通用模板不具备上述功能,因此需要进行模板具体化。

struct job
{
    char name;
    int length;
    double weight;
}

void swap(job &,job &);//非模板函数

template<typename,T>
void swap(T &a,T &b);//通用模板

template<> void swap<job>(job &,job &);//模板具体化,<job>表明这是job的一个具体化,可以省略

注:调用优先级:非模板函数>模板具体化>模板

5.4 显式实例化

区别于隐式实例化,隐式实例化是通过赋给模板具体类型值实现的,比如

template<typename,T>
void test(T &a, T &b);
int a,b;
test(a,b);//隐式实例化

显式实例化是通过明确指定类型实现,语法结构为

template void swap<int>(int &,int &);//int表明生成一个使用int类型的实例

区别于显式具体化,显式具体化实现为:

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

也可以用函数实现显示实例化,通过<type>指定类型.

template <typename T>
T swap(T a, T b)
{
    return (a+b);
}
int a = 1;
double b = 2.3;
double c;
c = swap(a,b);//报错,因为a,b类型不同,不能用模板实现
c = swap<double>(a,b);//正常,<double>实现显式实例化,将类型不匹配的a强制转换为double类型
//注:若swap函数形参是引用变量,则此时会报错,因为引用double类型不能指向int变量

6. 函数调用匹配

对于函数重载、函数模板和函数模板重载,要有一个函数调用策略,称为重载解析。
编译器根据函数调用参数与可行的候选参数进行相应的转换来确定使用哪个函数(总体策略是转换越小越匹配),从最佳到最差的顺序如下:
1. 完全匹配,但常规函数优先于模板
2. 提升转换(char和shorts自动转换为int,float转换为double)
3. 标准转换(int转换为char,long转换为double)
4. 用户定义的转换

注:匹配策略中,若同时有最佳匹配,则会报错(二义性),但需要注意:
有时都是完全匹配,仍可以完成重载解析:
(1)指向非const数据的指针和引用优先与非const指针和引用匹配,前提是指针或引用

void test(int);
void test(const int);
void test(int &);
void test(const int &);
//如果只有函数1和2,调用test(a)会报错,因为二义性
//如果只有函数3和4,调用test(a)不会报错,会调用函数3

(2)越具体的越优先调用。比如,非模板函数优先于模板函数,模板函数中越具体的优先调用。具体意味着相应的转换较少。
(3)用户自定义顺序
例如:

template <class T>
T test(T a , T b);//模板函数
int test(int a, int b);//非模板函数

调用函数时,正常来说都是按照上述的函数匹配策略进行调用,但是也可以采用用户自定义指定的,进行哈数调用,比如:

int a,b;
double c,d;
test(a,b);//调用非模板函数,属于正常函数匹配策略
test<>(a,b);// <>表示调用模板函数
test<int>(c,d);//调用模板函数,并用int具体化,即将double类型的c和d转换为int(引用类型时不能用)

猜你喜欢

转载自blog.csdn.net/yanrong1095/article/details/80978734