8. 函数探幽

第8章 函数探幽

  • 内联函数
  • 引用函数
  • 如何按引用传递函数参数
  • 默认参数
  • 函数重载
  • 函数模板
  • 函数模板具体化

8.1 内联函数

  • 作用: 为提高程序运行的速度。

  • 与常规函数的区别: 编译原理不同。
    在这里插入图片描述

  • 适应场景: 函数执行时间耗时短且使用频繁。

  • 实现: 将整个函数定义放在原本提供原型的地方,并在定义前加上关键字inline.

# include <iostream>
inline double square(double x){ return x*x;}
int main()
{
	using namespace std;
	double a,b;
	a = square(5.0);
	b = square(4.0+3.0);
	cout<< a << ", " << b;
	return 0;
}
25, 49
  • 如果使用C语言的宏执行了类似函数的功能,应该考虑将它们转换为内联函数。但是,宏与内联有区别,宏类似于文本替代,而内联是函数。

8.2 引用变量

  • 何为引用: 引用是已定义变量的别名,是原始数据的另一个名称,指向相同的值和地址不是创建的副本。
  • 用来干啥: 主要用来作函数的形参。

8.2.1 创建引用变量

  • 下面介绍引用变量是如何工作的
int a = 1;
int & b = a; // &不是地址运算符,而是作为类型标识符(引用)
cout<<a<<b;
11
//using a reference
#include<iostream>
using namespace std;
int main()
{
	int a = 101;
	int & b = a;
	cout<< " a = " << a<< ", " <<" b = "<<b << endl;
	a ++ ; // a++ , b也++
	cout<< " a = " << a<< ", " <<" b = "<<b << endl;
	cout<< " a address = " << &a << ", " << " b address = " << &b;
	return 0;
}
 a = 101,  b = 101
 a = 102,  b = 102
 a address = 0x7fc2e04fd050,  b address = 0x7fc2e04fd050
  • 引用不仅值相同,地址也相同。对其中一个变量操作相当于对2个变量都进行了操作。
  • 引用区别于指针:引用必须在声明时就必须初始化。更像const指针,必须在创建时就初始化,并一直效忠于它。 int & b = a 等价于 int* cont b = &a
//引用变量必须声明就初始化
#include <iostream>
using namespace std;
int main()
{
	int a =1;
	int &b =  a;
	cout<< "a = " << a <<", b = " << b<<endl; 
	int c = 2;
	b = c;
	cout<< " a = " << a << ", b= "<< b <<", c = " << c;
	return 0;
}
a = 1, b = 1
a = 2, b= 2, c = 2
  • 对引用变量赋值会同时改变引用变量对应的变量。

8.2.2 将引用用作函数参数

  • 作用:使得被调用的函数可以访问调用函数中的变量,而不是其副本。这种传递方法也称为——引用传递.
#include <iostream>

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

void swapp( int *a ,int *b)
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

void swapv( int a ,int b)//不改变传递进来的参数
{
    int temp;
    temp =a;
    a=b;
    b=temp;
}

using namespace std;
int main()
{
 	int x1 = 1;
    int x2 = 2;
    swapr(x1,x2);//x1,x2交换
    cout<< " 引用传递后 " << x1 << x2 <<endl;
    swapp(&x1, &x2);//x1,x2交换
    cout<< " 指针传递后 " << x1 << x2 << endl;
    swapv( x1, x2);//x1,x2本身没有交换
    cout<< "按值传递后 " << x1 << x2<< endl;
    return 0;
}
 引用传递后 21
 指针传递后 12
 按值传递后 12
  • 按值传递不能交换传入函数的2个参数,因为传递来的是2个副本,交换副本后,原始变量没有交换。而引用传递参数是变量的别名,形参被改变,实参实际上也被改变。

8.2.3 引用的属性和特别之处

  • 引用传参必须传入函数声明时的同类型变量,同类型参量都不行。
  • const 引用比引用更兼容,类似按值传递。当传入的类型不对时,会生成一个临时变量传入函数。
  • && 右值引用可以引用右值,比如表达式
double cube (double a)//按值传递的函数实现立方
{
    a *= a*a;
    return a;
}
double cude_reference(double &a)//引用传递的函数实现立方
{
    a *= a*a;
    return a;
}
double x =3.0;
cout<<cube(x);
27.000000
cout<<x;
3.0000000
cout<<cude_reference(x);
27.000000
cout<<x;
27.000000
  • cude_reference() 是引用传递参数,所以实际上也改变了x。cube()是按值传递,所以不改变传入的参数。
  • 如果只是想使用引用传递,而不修改参数,可以使用常量引用,这样在修改参数时会报错。
double cude_ref( const double & a)
{
    a *= a*a;//这里修改了a 的值
    return a;
}
[1minput_line_36:3:7: [0m[0;1;31merror: [0m[1mcannot assign to variable 'a' with const-qualified type 'const double &'[0m
    a *= a*a;
[0;1;32m    ~ ^
[0m[1minput_line_36:1:33: [0m[0;1;30mnote: [0mvariable 'a' declared const here[0m
double cude_ref( const double & a)
[0;1;32m                 ~~~~~~~~~~~~~~~^
[0m


Interpreter Error: 
  • 另外,按值传递因为传递是变量的副本,所以可以使用多种类型的实参,包括表达式,定值常数。下面这些都是合法的。
double z = cube(x+2.0);
z = cube(8.0);
int y = 3;
z = cube(y); // int 类型转换为 double
double yy[3] = {1.0, 2.0, 3.0};
z = cube(yy[2]);
cout<<z;
27.000000
  • 引用传递参数时更加严格。实参只能是double变量,而不能是表达式,常量或是其他类型的变量。
double cude_reference(double &a)
{
    return a*a*a;
}

double z = cude_reference(x+2.0);
[1minput_line_91:2:13: [0m[0;1;31merror: [0m[1mno matching function for call to 'cude_reference'[0m
 double z = cude_reference(x+2.0);
[0;1;32m            ^~~~~~~~~~~~~~
[0m[1minput_line_90:1:8: [0m[0;1;30mnote: [0mcandidate function not viable: expects an l-value for 1st argument[0m
double cude_reference(double &a)
[0;1;32m       ^
[0m


Interpreter Error: 
double z = cude_reference(2.0);
[1minput_line_92:2:13: [0m[0;1;31merror: [0m[1mno matching function for call to 'cude_reference'[0m
 double z = cude_reference(2.0);
[0;1;32m            ^~~~~~~~~~~~~~
[0m[1minput_line_90:1:8: [0m[0;1;30mnote: [0mcandidate function not viable: expects an l-value for 1st argument[0m
double cude_reference(double &a)
[0;1;32m       ^
[0m


Interpreter Error: 
long y = 5L;
double z = cude_reference(y);
[1minput_line_93:3:12: [0m[0;1;31merror: [0m[1mno matching function for call to 'cude_reference'[0m
double z = cude_reference(y);
[0;1;32m           ^~~~~~~~~~~~~~
[0m[1minput_line_90:1:8: [0m[0;1;30mnote: [0mcandidate function not viable: no known conversion from 'long' to 'double &' for 1st argument[0m
double cude_reference(double &a)
[0;1;32m       ^
[0m


Interpreter Error: 
  • 反而,const引用允许一些实参与引用参数不匹配的情况,因为c++这时将会生成临时变量
double cube_const_ref( const double & a){
    return a*a*a;// a的值没有被修改
}
double a = 3.0;
cout<<cube_const_ref(a);
27.000000
cout<<a;
3.0000000
double *b = &a;
cout<<cube_const_ref(*b);//传入指针
27.000000
cout<<*b;
3.0000000
long c = 3L;
cout<<cube_const_ref(c);//传入long类型
27.000000
cout<<c;
3
cout<<cube_const_ref(3.0); //传入常量
27.000000
cout<<cube_const_ref(1.0+2.0);//传入表达式
27.000000
  • 注意: 对于const 引用传参如果函数调用的参数不是左值或参数类型不匹配,则c++将创建类型正确的匿名临时变量,将函数调用的参数的值传递给该临时变量,并让参数引用该临时变量。这就类似于按值传递
    如果使用引用应尽可能使用const引用
  • 补充: 右值引用&&
double a =1.0;
double &&b = 2.0*a + 1.0;//&&右值引用可以引用表达书这类右值
cout<<b;
3.0000000

8.2.4 将引用用于结构

#include <iostream>
#include <string>

struct free_throws//定义一个结构体
{
    std::string name;
    int made;
    int attempts;
    float percent;
}

void display( const free_throws &ft)//打印结构体的成员
{
    using namespace std;
    cout<< "name: "<< ft.name;
    cout<< "   Made: "<< ft.made;
    cout<< "   attempts: "<< ft.attempts;
    cout<< "   percent: "<< ft.percent << '\n';
}

void set_pc(free_throws & ft)//计算percent
{
    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)//把结构体2加到结构体1上
{
    target.attempts += source.attempts;
    target.made += source.made;
    set_pc(target);
    return target;
}

int main(){

	using namespace std;

    free_throws one = {"aaa", 13,14};
    free_throws two = {"bbb", 10,16};
    free_throws three = {"ccc", 7, 9};
    free_throws four = {"ddd", 5,9};
    free_throws five = {"eee", 6, 14};
    free_throws acc = {"fff" ,0, 0};
    free_throws dup;
    
    set_pc(one);
    display(one);
    accumulate(acc, one);
    display(acc);
    
    display(accumulate( acc, two));
    display(accumulate(accumulate(acc,three), four));
    
    dup = accumulate(acc, five);
    display(acc);
    display(dup);
    
    accumulate(dup,five) = four;
    display(dup);
    return 0;
}
name: aaa   Made: 13   attempts: 14   percent: 92.8571
name: fff   Made: 13   attempts: 14   percent: 92.8571
name: fff   Made: 23   attempts: 30   percent: 76.6667
name: fff   Made: 35   attempts: 48   percent: 72.9167
name: fff   Made: 41   attempts: 62   percent: 66.129
name: fff   Made: 41   attempts: 62   percent: 66.129
name: ddd   Made: 5   attempts: 9   percent: 0

程序说明:

  1. set_pc()的形参为引用,因此调用的是结构体本体,可以直接对成员进行修改,而若采用按值传递则无法做到。另一种做法是使用指针参数传递地址,但要复杂一些。
void set_pcp( free_throws * pt)
{
 	if(pt->attempts != 0)
	{
		pt->percent = 100.0f*float(pt->made)/float(pt->attempts);
	}
	else
	{
 		pt->percent = 0;
	}
}
set_pcp( &one);
  1. display()形参是const引用,因为只显示而不修改。这里也可以使用按值传递,但是更耗费时间和内存。
  2. accumulate()第一个形参为引用,第二个形参为const引用,因为不修改第二个实参,只修改第一个实参。返回值为引用,引用指向修改后的第一个实参。这样可以在返回时减少了复制的成本。

注意: 应避免返回函数终止时不再存在的内存单元引用。因为临时变量在函数允许完毕后不再存在。

8.2.5 将引用用于类对象

  • 将类对象传递给函数时,c++通常使用引用。
#include <iostream>
#include <string>

std::string version1 (const std::string & s1, const std::string &s2)
{
    
    std::string temp;
    
    temp = s2+ s1+ s2;
    return temp;
}

const std::string & version2( std::string  & s1, const std::string & s2)
{
    s1 = s2 + s1 + s2;
    return s1;
}

const std::string & version3 ( std::string & s1, const std::string & s2)
{
    std::string temp;
    temp = s2 + s1 +s2;
    return temp;
}

int main()
{
	using namespace std;
	string s1 = "aaa";
	string result;
	result = version1( s1, "**");//version1返回一个string临时变量
	cout<< "version1 " << result<< "\n";
	cout<< " s1: " << s1<< "\n";
	result = version2( s1, "##");//version2引用s1且直接修改s1,返回修改s1后的引用。
	cout<< " version2 " << result << "\n";
	cout<< " s1: " << s1<< "\n";
	result = version3( s1, "//");//version3返回一个临时变量的引用,这是不允许的。
	cout << " version3 " << result << "/n";
	 cout<< " s1: " << s1;
}
 version1 **aaa**
 s1: aaa
 version2 ##aaa##
 s1: ##aaa##

8.2.6 对象、继承和引用

继承的2个特性:

  • 派生类继承了基类的方法,可以使用基类的特性;
  • 基类的引用可以指向派生类对象。eg. 参数类型为ostream& 的函数可以接受ostream对象以及派生类ofstream对象作为实参。

8.2.7 何时使用引用参数

在这里插入图片描述

8.3 默认参数

  • 默认函数让你能够使用不同数目的参数调用同一个函数
  • 必须通过函数原型设置参数默认值;
  • 必须从右往左添加默认值,实参必须从左往右给出。
#include <iostream>
char * left (const char * str, int n = 1)
{
    if(n < 0)
        n =0;
    char * p = new char[n+1];
    int i ;
    for( i =0; i < n && str[i]; i++)
        p[i] = str[i];
    while ( i <= n)
        p[i++] = '\0';
    return p;
}

int main(){
	using namespace std;
	const int Arsize = 80;
	char sample[Arsize];
	cin.get(sample,Arsize);
	char * ps = left( sample, 4);
	cout << ps << "\n";
	ps = left(sample);
	cout<< ps << "\n";
	return 0;
}

ssssssss
ssss
s

8.4 函数重载(多态)

  • 多态让你使用多个同名函数,它们完成相同的工作,但使用不同的参数列表
  • 何时使用: 仅当函数基本上执行相同任务,但是使用不同数据类型时。
//我可以定义一组原型如下的print()函数
void print( const char* pr, int width);
void print ( double d, int width);
void print( long l, int width);
void print ( int i, int width);
  • 定义多个同名函数的关键是,它们的参数数目或类型不同,即它们的函数特征标不同
  • 使用被重载函数时,需要在函数调用中使用正确的参数类型。如果没有匹配的原型,c++会尝试进行强制类型转换,此时如果会有多个函数匹配,这将被视为错误。
  • 类型引用和类型本身是同一种特征标
  • 函数重载,返回类型可以不同,但是特征标必须不同
#include <iostream>

unsigned left( unsigned long num, unsigned ct)
{
    unsigned digits = 1;
    unsigned long n = num;
    if(ct == 0 || num ==0)
        return 0;
    while(n /= 10)
        digits++;
    if(digits > ct)
    {
        ct = digits -ct;
        while( ct--)
            num /= 10;
        return num;
    }
    else
    {
        return num;
    }
}

char * left ( const char* str, int n )
{
    if (n<0 )
        n = 0;
    char * p = new char[n+1];
    int i ;
    for( i =0; i < n && str[i]; i++)
        p[i] = str[i];
    while( i <= n)
        p[i++] = '\0';
    return p;
}

int main()
{
	using namespace std;
	char * trip = "Hawii!!";
	unsigned long n = 12345678;
	int i ;
	char * temp;
	for ( i = 1; i < 10; i++)
	{
	    cout << left( n, i) << "\n";
	    temp = left(trip, i);
	    cout<< temp << "\n";
	    delete [] temp;
	}
	return 0;
}
[1minput_line_47:3:15: [0m[0;1;35mwarning: [0m[1mISO C++11 does not allow conversion from string literal to 'char *' [-Wwritable-strings][0m
char * trip = "Hawii!!";
[0;1;32m              ^
[0m

1
H
12
Ha
123
Haw
1234
Hawi
12345
Hawii
123456
Hawii!
1234567
Hawii!!
12345678
Hawii!!
12345678
Hawii!!

8.5 函数模板

  • 模板允许你同一个函数使用不同的参数类型。
#include <iostream>

template <typename Anytype>//template,typename是关键字,指出要建立一个模板函数。typename也可以用class代替
void Swap(Anytype &a, Anytype &b)
{
    Anytype temp;
    temp = a;
    a = b;
    b = temp;
}

int main()
{
	using namespace std;
	int i = 10;
	int j =20;
	cout<< "i,j = " << i<< ", " << j<< ".\n";
	Swap(i,j);// Swap接收了2个int类型参数
	cout<<"i,j = " << i << ", "<<j<< ".\n";
	
	double x = 23.5;
	double y = 81.7;
	cout<< " x,y = " << x <<", " << y <<".\n";
	Swap(x,y);// Swap 接收了2个double类型参数
	cout<< " x,y = " << x <<", " << y <<".\n";
	return 0;
}
 i,j = 10, 20.
 i,j = 20, 10.
 x,y = 23.5, 81.7.
 x,y = 81.7, 23.5.

8.5.1 重载的模板

  • 可以像重载常规函数那样重载模板函数
#include <iostream>
template <typename T>
void Swap( T &a, T &b)
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}
template <typename T>
void Swap( T a[], T b[], int n )
{
    T temp;
    for(int i =0; i<n; i++)
    {
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;
    }
}

void show ( int a[])
{
    using namespace std;
    cout<< a[0] << a[1] << "/";
    cout<< a[2] << a[3]<< "/";
    for ( int i =4; i < 8; i++)
    {
        cout << a[i];
    }
    cout<< "\n";
}

int main()
{
	using namespace std;
	int i =10, j =20;
	cout<< "i,j = " << i<< ", " << j<< ".\n";
	Swap(i,j);// Swap接收了2个int类型参数
	cout<<"i,j = " << i << ", "<<j<< ".\n";
	
	int a1[8] = {1,2,3,4,5,6,7,8};
	int a2[8] = {8,7,6,5,4,3,2,1};
	show(a1);
	show(a2);
	Swap(a1,a2,8);
	cout<< " After swap: "<<".\n";
	show(a1);
	show(a2);
	return 0;
}
i,j = 10, 20.
i,j = 20, 10.
12/34/5678
87/65/4321
 After swap: .
87/65/4321
12/34/5678

8.5.2 模板的局限性

  • 模板函数是一种泛化函数,虽然对某些类型是合适的,但是对其他类型可能会引发问题。例如,int类型可以加减,但是数组和结构体加减却没有意义。
  • 一个解决方法是,重载运算符,使之能够用于特定的结构体和类;
  • 另一个是, 为特点类型提供具体化的模板定义

8.5.3 显式具体化

下面是交换job结构的非模板函数,模板函数和具体化原型:

struct job 
{
    char name[40];
    double salary;
    int floor;
}
void Swap(job &, job &);//非模板函数

template <typename T>//模板函数
void Swap(T &,T &);

template <> void Swap<job>(job &, job &);//具体化//<job>是可选的
template <typename T>
void Swap(T &a, T &b)
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}

template <> void Swap<job> ( job &j1, job &j2)
{
    double d1;
    int i1;
    d1 = j1.salary;
    j1.salary = j2.salary;
    j2.salary = d1;
    i1 = j1.floor;
    j1.floor = j2.floor;
    j2.floor = i1;
}

void show( job & j)
{
    using namespace std;
    cout<< j.name << ", "  << j.floor << ".\n";
}
int main()
{
	using namespace std;
	cout.precision(2);
	cout.setf(ios::fixed, ios::floatfield);
	int i = 10, j =20;
	cout<< "i,j = " << i << ", " << j << ".\n";
	Swap(i,j);
	cout<< "After Swap: ";
	cout<< "i,j = " << i << ", " << j << ".\n";
	
	job j1 = {"aaaaa", 7300.666, 7};
	job j2 = {"bbbb", 111.1111, 11};
	show (j1);
	show(j2);
	Swap(j1, j2);
	cout<< "After Swap: ";
	show(j1);
	show(j2);
	return 0;
}
i,j = 10, 20.
After Swap: i,j = 20, 10.
aaaaa, 7.
bbbb, 11.
After Swap: aaaaa, 11.
bbbb, 7.

8.5.4 实例化和具体化

  • 模板函数并非函数的定义,它只是一个用于生成函数的方案。具体的函数由编译器将模板具体化得到.
  • 具体化包括实例化和显示具体化,而实例化又包括隐式实例化和显示实例化。
  • 隐式实例化和显式实例化
  • 隐式具体化是指调用模板函数时,编译器根据参数类型自动生成一个具体函数的方法;
  • 显示实例化是指直接命令编译器创建特定的实例。
  • 显式实例化和显式具体化
  • 显式具体化正如上一节介绍,需要另外提供函数定义,语法上也略有区别。
  • 显式实例化可以不用原型,直接在main函数中创建。
  • 注意:同一种类型的显式实例化和显式具体化将会出错
  • 显示实例化:
template void Swap<int>(int, int);//使用Swap()模板生成int类型的函数定义

main中:Swap(x, y);//其中x,y是int型变量

  • 显示具体化:
template<> void Swap<int> (int &, int &);
template<> void Swap( int &, int & );//两种声明等价

8.5.5 模板函数的发展

  • 在编写模板函数时,有个问题是,并非总能知道应该在声明中使用哪种类型。
template<class t1, class t2>
void ft(t1 x, t2 y)
{
    ...
    ?type? xpy = x+y;//xpy的类型将是不确定的
    ...
}

怎么办呢???

8.5.5.1 关键字decltype
//可以对上面的模板函数修改为
int x ;
double y;
decltype(x+y) xpy = x+y;// xpy类型为x+y的类型包括const等限定符
8.5.5.2 另一种函数声明语法(后置放回类型)
template<class T1, class T2>
?type? gt (T1 x, T2 y)
{
    ...
    return x+y;
    ...
}

由于还不知道x+y的类型,所以decltype无法用于函数声明。

template<class T1, class T2>
auto gt (T1 x, T2 y) -> decltype(x+y)// 后置返回类型
{
    ...
    return x+y;
    ...
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值