2020 我的C++的学习之路 第八章函数

以C++ Primer Plus为参考书籍,自身归纳知识点,加深记忆。

1 内联函数

常规的函数调用使程序跳到一个地址并在结束时返回,对于内联函数而言,程序无需跳转即可执行,因此内联函数运行速度比常规函数稍快,但代价是占用更多内存。倘若程序在10个不同的地方调用同一个内联函数,该程序则包含该函数代码的10个副本。

要使用内联函数,必须采取下述措施之一:
① 函数声明前加关键字inline
②函数定义前加关键字inline
通常的做法是省略原型,将函数头以及函数代码放在本应提供原型的地方。

#include<iostream>

inline double square(double x)
	{return x*x;}

int main()
{
	using namespace std;
	double a,b;
	double c=2.0;
	a = square(5.0);
	b = square(4.5+1.5);
	cout<<"a= "<<a<<endl;//25.0
	cout<<"b= "<<b<<endl;//36.0
	cout<<"csquare= "<<square(c++)<<endl;//4.0
	cout<<"c= "<<c<<endl;//3
	return 0;
}

内联函数与宏
C语言使用预处理器语句#define提供宏
#define SQUARE(X) X*X
本身并不是通过传递参数实现,而是通过文本替换实现
a = SQUARE(5.0); 其实就是a=5.0 * 5.0;
b=SQUARE(4.5+1.5)::::其实就是b=4.5+1.5 * 4.5+1.5
SQUARE(c++):::::(c++) * (c++)不同编译器给出的结果还不一样

内联函数通过传递参数实现,因此在square(c++)中,传递c给square()函数然后再c++,在编译器上没有冲突存在。

2 引用变量

引用变量是C++新增的复合类型,引用是已定义变量的别名,通过将引用变量做参数,函数使用原始数据,而不是副本。

2.1 创建引用变量

int count;
int &cnt = count;//int&指的是指向int的引用,count与cnt指向相同的值和内存单元

上述例子中,倘若对cnt做递增运算,那么count也会做相应的变化。

int count;
int &cnt;
cnt = count;//引用不同于指针,必须在声明时就用初始化该引用。

2.2 引用与函数参数

#include<iostream>
void swapr(int &a,int &b);
void swapp(int *a,int *b);
void swapv(int a,int b);

int main()
{
	using namespace std;
	int wallet1 = 300;
	int wallet2 = 350;
	
	swapr(wallet1,wallet2);
	cout<<wallet1<<"  "<<wallet2<<endl;  // 350   300
	swapp(&wallet1,&wallet2);
	cout<<wallet1<<"  "<<wallet2<<endl; // 300   350
	swapv(wallet1,wallet2);
	cout<<wallet1<<"  "<<wallet2<<endl;//  300  350
	return 0;
}
void swapr(int &a,int &b)
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}
void swapp(int *a,int *b)
{
	int p;
	p = *a;
	*a = *b;
	*b = p;
	
}
void swapv(int a,int b)
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}

在swapr()中变量a,b是wallet1,wallet2的别名,所以两者交换也将wallet1,2的值进行了交换,但swapv()中的a和b是复制了wallet1,2的新变量,将它俩交换并不影响main()中的变量的值,因此需要利用指针指出wallet1,2内存所在的地址方可进行交换。

2.3 引用与结构

引用非常适合用于结构和类,假设有如下结构定义:

struct free
{
	string name;
	int made;
	int attempts;
	float percent;
}

则可以这样编写函数原型,在函数中将指向该结构的引用作为参数:

void set_pc(free& ft);
void display(const free& ft);//倘若不想修改传入的结构,关键词const
#include <iostream>
#include <string>
struct free_throws
{
    std::string name;
    int made;
    int attempts;
    float percent;
};

void display(const free_throws & ft);
void set_pc(free_throws & ft);
free_throws & accumulate(free_throws &target, const free_throws &source);

int main()
{
    free_throws one = {"Ifelsa Branch", 13, 14};
    free_throws two = {"Andor Knott", 10, 16};
    free_throws three = {"Minnie Max", 7, 9};
    free_throws four = {"Whily Looper", 5, 9};
    free_throws five = {"Long Long", 6, 14};
    free_throws team = {"Throwgoods", 0, 0};
    free_throws dup;
    set_pc(one);
    display(one);
    accumulate(team, one);
    display(team);
// use return value as argument
    display(accumulate(team, two));
    accumulate(accumulate(team, three), four);
    display(team);
// use return value in assignment
    dup = accumulate(team,five);
    std::cout << "Displaying team:\n";
    display(team);
    std::cout << "Displaying dup after assignment:\n";
    display(dup);
    set_pc(four);
// ill-advised assignment
    accumulate(dup,five) = four;//five的数据给到dup中,再用four覆盖dup
    std::cout << "Displaying dup after ill-advised assignment:\n";
    display(dup);
    // std::cin.get();
    return 0;
}

void display(const free_throws & ft)
{
    using std::cout;
    cout << "Name: " << ft.name << '\n';
    cout << "  Made: " << ft.made << '\t';
    cout << "Attempts: " << ft.attempts << '\t';
    cout << "Percent: " << ft.percent << '\n';
}
void set_pc(free_throws & ft)
{
    if (ft.attempts != 0)
        ft.percent = 100.0f *float(ft.made)/float(ft.attempts);
    else
        ft.percent = 0;
}

free_throws & accumulate(free_throws & target, const free_throws & source)
{
    target.attempts += source.attempts;
    target.made += source.made;
    set_pc(target);
    return target;
}

何时使用引用参数

对于使用传递的值不做修改的函数:
①数据对象很小,如内置数据类型或小型结构,则按值传递;
②数据对象是数组,则使用指针,并声明为const
③数据对象是大型结构,则使用const指针或者const引用,节省复制结构所需的时间和空间
④数据对象是类对象,使用const引用

对于修改调用函数中数据的函数
①数据对象是内置数据类型,则使用指针
②数据对象是数组,则只能使用指针
③数据对象是结构,则使用引用或指针
④数据对象是类对象,使用引用

默认参数

默认参数指的是当函数调用中省略了实参时自动使用的一个值。
设置默认值必须通过函数原型:

char* left(const char* str, int n =1 );

对于带参数列表的函数,必须从右向左添加默认值

 int harp(int n, int m=2, int j=3);//可用
 int hard(int n,int m =3, int j);//不可用
 int saiji(int n =5, int m =1, int j =9);//可用

实参从左到右的顺序依次赋给相应的形参,而不能跳过任何参数。只有在函数原型中指明了默认值,函数定义与没有默认参数时完全相同。

函数重载

默认参数能够使用不同数目的参数调用同一个函数,而函数多态(函数重载)能够使用多个同名的函数,多态是指有多种形式,重载是指有多个重名的函数,两个术语是一回事,但通常使用函数重载。
函数重载的关键是函数的参数列表,也成为函数特征标,如果两个函数的参数数目,排列顺序都相同,则它们的特征标相同,变量名无关紧要。函数重载的条件就是特征标不同。

void print(const char *str,int n);
void print(long l,int n);
void print(double d,int n);
void print(const char *str);

上述函数的特征标均不相同,并且返回类型也可以不同,因此可认为是函数重载

double cube(double x);
double cube(double &x);

编译器在检查函数特征标时,把类型的引用与类型本身视为同一个特征标。

函数模板

函数模板是通用的函数描述,使用泛型来定义函数,其中的泛型可用具体的类型(int,double等)替换,函数模板允许以任意类型的方式定义函数,如以下模板所示:

template<class T>//第一行template必需,class与typename可替换,必须使用尖括号
void swap(T& a,T& b)//类型名可以任意选择,但命名规则要遵守
{
	T temp;
	temp = a;
	a = b;
	b = temp;
}
// funtemp.cpp -- using a function template
#include <iostream>
// function template prototype
template <typename T>  // or class T
void Swap(T &a, T &b);

int main()
{
    using namespace std;
    int i = 10;
    int j = 20;
    cout << "i, j = " << i << ", " << j << ".\n";
    cout << "Using compiler-generated int swapper:\n";
    Swap(i,j);  // generates void Swap(int &, int &)
    cout << "Now i, j = " << i << ", " << j << ".\n";

    double x = 24.5;
    double y = 81.7;
    cout << "x, y = " << x << ", " << y << ".\n";
    cout << "Using compiler-generated double swapper:\n";
    Swap(x,y);  // generates void Swap(double &, double &)
    cout << "Now x, y = " << x << ", " << y << ".\n";
    // cin.get();
    return 0;
}

// function template definition
template <typename T>  // or class T
void Swap(T &a, T &b)
{
    T temp;   // temp a variable of type T
    temp = a;
    a = b;
    b = temp; 
}

显式具体化

C++98标准:
①对于给定的函数名,可以有非模板函数、模板函数和显式具体化函数及其重载版本
②显示具体化的原型和定义应以template<>打头,并通过原型指出类型
③具体化优先于常规模板,而非模板函数优于具体化和常规模板

//非模板函数
void swap(job&,job&);
//模板函数
template<typename T>
void swap(T& ,T&);
//显式具体化
template<>void swap<job>(job&,job&)//<job>是可选的,job只是一个具体化

实例化与具体化

代码中包含函数模板本身并不会生成函数定义, 只是一个用于生成函数定义的方案,当编译器使用模板为特定类型生成函数定义时,得到的便是函数实例,例如上述swap(i,j)便是模板函数swap()的一个实例,这种实例化的方式成为隐式实例化
显式实例化的语法是声明所需的种类,用<>表示类型,并在声明前加上关键字template:

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

显式具体化,显式实例化,隐式实例化统称为具体化,相同之处在于表示的都是使用具体类型的函数定义,而不是通用描述。前缀template与前缀template<>分别代表显示实例化和显式具体化

编译器选择哪个函数版本

完全匹配和最佳匹配

①指向非const数据的指针和引用优先与非const指针和引用参数匹配
②const和非const之间的区别只适用于指针和引用指向的数据
③完全匹配优于另一个的情况还有一个是非模板函数,而另一个不是,此时非模板函数优于模板函数(包括显式具体化)
④如果两者都是模板函数,那么较具体的模板函数优先。

部分排序规则

重载解析寻找最匹配的函数:
如果只存在一个这样的函数,则选择它;
如果存在多个这样的函数,但其中只有一个是非模板函数,则选择非模板函数;
如果存在多个合适的函数,并且都为模板函数,但只有一个比其他更具体,则选择该函数;
如果有多个同样合适的非模板或模板函数,但没有一个更具体的,则会报错;
如果不存在匹配的,也会报错

关键词decltype(C++11)

decltype (x+y)xpy;//xpy的类型为x+y的类型
xpy = x+y;//等价于decltype(x+y)xpy=(x+y)

decltype (expression) var
decltype的确定类型:
①如果expression是一个没有用括号括起的标识符,则var与该标识符的类型相同,包括const等限定符;

double x=2.0;
double y=7.9;
double& rx = x;
const double *ptr;
decltype(x)w;//w是double类型
decltype(rx) u =y;//u是double的引用
decltype(pd)v;//v是一个const double *

②如果expression是一个函数调用,则var与函数返回类型相同;

long indeed(int);
decltype(indeed(3))m;//m是long类型,并且函数没有实际调用

③如果expression是一个左值,则var指向其类型的引用,一种显而易见的情况就是expression是用括号括起的标识符;

double xx = 4.4;
decltype((xx))r2 = xx;//r2是一个double的引用
decltype(xx)W = xx;//w是一个double

④如果前面条件都不满足,那么var与expression类型相同

int j=3;
int &k =j;
int &n =j;
decltype(j+6)x;//x为int
decltype(100L)y;//y为long
decltype(k+n)z;//z为int,k,n为引用,但两者相加得到的是int

后置返回类型(C++11)

对于下面的原型:

double h(int,float);

使用新增的语法可编写成:

auto h(int,float) -> double;//auto是一个占位符,表示后置返回类型提供的类型
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值