【C++ Primer Plus】学习笔记--第8章 函数探幽


8.1 C++内联函数

P253

常规函数调用和内联函数的执行过程

编译过程的最终产品是可执行程序——由一组机器语言组成。运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令。有时(如有循环或分支语句时),将跳过一些指令,向前或者向后跳到指定地址常规函数调用也使程序跳到另一个地址(函数的地址),并执行结束时返回。下面更详细地介绍这一过程的典型实现。

执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数的内存单元,执行函数代码(也许还需将返回值放入到寄存器中),然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定的开销

C++内联函数提供了另一种选择。内联函数的编译代码与其他程序代码“内联”起来了。也就是说,编译器将使用相应的函数代码替换函调用。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。如果程序在10个不同的地方调用同一个内联函数,则该程序将包含代码的10个副本。如果代码执行时间很短,则内联调用就可以节约非内联调用使用的大部分时间。

内联函数常规函数调用示意图
在这里插入图片描述

内联与宏

内联函数和常规函数一样,按值传递参数的。宏函数只是简单通过文本替换来实现。

技巧:用C++内联函数代替宏定义

代码如下:

// a inline function definition
inline double square(double x){
    
    return x*x;}

#define SQUARE(X) X*X

a = square(4.5 + 7.5); // is replaced by (4.5+7.5)^2
b = SQUARE(4.5 + 7.5); // is replaced by 4.5 + 7.5*4.5 + 7.5

8.2 引用变量(重点)

P255

引用变量的主要用途是用作函数的形参
必须在声明引用变量时进行初始化。引用更接近于const指针
c++ 引用相比于指针能够实现运算符重载

int &rodents = rats;
// 伪装表示如下,const变量必须初始化
int* const pr = &rats; 
//引用 rodent 等价于 *pr

按值传递的函数,可使用多种类型的实参;传递引用限制更严格非常量引用的实参应是左值

左值

  • 左值(可以通过地址访问)参数是可被引用的数据对象。例如:变量、数组元素、结构成员、引用和解引用的指针都是左值。
  • 非左值(无确定的地址)包括字面常量(用引号括起来的字符串除外)和包含多项的表达式。

const引用

如果实参与引用参数不匹配,仅当参数为const引用时,C++将生成临时匿名变量

相比值传递,const引用可以减少将实参拷贝给形参的开销。

如果引用参数作为实参的目的是修改为参数传递的变量,则禁止创建临时变量。相反,目的只是使用传递的值,而不修改它们,应将声明为const引用
代码如下:

double cube(double a);
double refcube1(double &ra);
double refcube2(const double &ra){
    
    return ra*ra*ra};
//...
double z = cube(x + 2.0); //evaluate x+2.0, pass value
double z = refcube1(x + 2.0); //no, should not compile
double z = refcube2(x + 2.0); //yes, ra is temporary variable

技巧尽可能使用const引用

  • const引用可以避免无意中修改数据的编程错误。
  • const引用可以使函数能够处理const和非const实参。
  • const引用使函数能够正确生成并使用临时变量。

返回引用

int& reftarget(int& rt) {
    
    
rt +=rt;
return rt;
}
//...
int target1 = 10;
int target2 = reftarget(target1);

如果ref返回int,而不是指向引用,则需要将 rt 复制到一个临时变量,再将这个拷贝复制给target2。如果ref返回int引用,则直接将rt复制到target2效率更高

注意】:不能返回一个指向临时变量的引用,函数运行完毕后它将被销毁

// 返回const引用,creftarget变为不可修改的左值
const int& creftarget(int& rt);
creftarget = 4; //not allowed for const reference return

技巧当需要返回引用,但又不希望作为左值。可以返回const引用,可以避免返回值作为左值,避免模糊特性

何时使用引用参数

对于使用值而不修改值的函数

  • 内置数据类型,按值传递
  • 数组,则只能使用指针
  • 较大的结构,则使用指针或者引用
  • 类对象,使用引用

对于内置数据类型,数据的字节数小(32位指针4个字节),而且按值传递为直接寻址,效率较高。
对于数组作为实参时,只需要将数组的首地址传递给函数,函数将使用原来的函数,只能使用指针传递
对于类对象,类设计的语义常常要求使用引用;同时引用传递具有参数检查,比指针传递安全;传递类对象参数的标准方式是按引用传递

8.3 默认参数

P274

通过函数原型设置默认参数,形参必须从右向左添加默认值。

void harpo(int n, int m = 4, int j = 5); //valid
void chico(int n, int m = 6, int j); //invalid

实参按从左到右的顺序依次被赋给相应的形参,不能跳过任何参数。

harpo(2); // same as harpo(2, 4, 5)
harpo(1, 8); // same as harpo(1, 8, 5)

在设计类时,通过使用默认参数,可以减少要定义的析构函数、方法以及方法重载的数量。

默认参数代码如下:

//left.cpp -- string function with a default argument
#include<iostream>
const int ArSize{
    
     80 };
char* left(char* str, int n = 1); //function prototype

int main()
{
    
    
	using namespace std;
	char sample[ArSize]{
    
    };
	
	cout << "Enter a string: ";
	cin.get(sample, ArSize);
	char* pr = left(sample, 10);
	cout << pr << endl;
	delete[] pr;
	pr = left(sample);
	cout << pr << endl;
	delete[] pr;

	system("pause");
	return 0;
}

//consisting of the first n characters in the str string
char* left(char* str, int n)
{
    
    
	if (n < 0)
		n = 0;
	char* p = new char[n + 1]{
    
    };
	int i{
    
    };
	for (; i < n && str[i]; i++)
		p[i] = str[i];
	while (i<=n)
	{
    
    
		p[i++] = '\0';
	}
	return p;
}

8.4 函数重载

P296

函数重载的关键是阐述特征标不同,即为参数数目或参数类型不同

引用重载

编译器会把类型引用类型本身视为同一个特征标,他们实参类型相同,因此不会发生重载。值得注意:常引用会发生函数重载

double cube(double x); // not overloaded
double cube(double& x); // not oberloaded
//... 
//const reference
double cube(double& x); // overloaded
double cube(const double& x); // oberloaded

const重载

下面的两个函数是重定义,不是函数重载,两个函数都不会改变a的值,本质上是一样的,所以会报错,提示函数重定义。

void func(int len);  //not overloaded
void func(const int len); //not overloaded
//...
func(a);

而对于下面的两个函数会发生重载,第一个函数用于常规指针,另一个函数用于const指针,编译器会根据实参是否为const来决定使用哪个原型。

void func(char* str);  //overloaded
void func(const char* str); //overloaded
//...
func(p1);

返回类型不同

【注意】:是特征标不同,而不是函数类型不同。

long gronk(int n); // same signatures,
double gronk(int n); // not overloaded

因此, 返回类型可以不同,但必须特征标不同才可以发生函数重载。

long gronk(int n); // overloaded
double gronk(double n); // overloaded

8.5 函数模板

如果需要多个将同一种算法用于不同类型的函数,请使用函数模板

//function template prototype
template <typename/class T>  
void Swap(T& a, T& b);
//标准C++98,添加关键字typename

注意:函数模板不能缩短可执行程序。最终的代码不包含任何模板,而只包含了为程序生成的实际函数。

函数模板的好处是:它使生成多个函数定义更简单、更可靠。

显式具体化

模板函数很可能无法处理某些数据类型(例如结构体)。因此一种解决办法为运算符重载,另一种为特定类型提供具体化的模板函数定义

显式具体化如下:

template<> void Swap<job>(job &, job &);
template<> void Swap(job &, job &); // <job>可以省略

实例化和具体化

模板并非函数定义,但使用int的模板实例是函数定义-----隐式实例化

显式实例化如下:

template void Swap<job>(job &, job &);

同一个文件中使用同一种类型的显示实例和显式具体化将出错

显示实例化,无论是否程序有用,编译器都会生成一个实例函数。

调用优先级:非模板函数>显式具体化>显式实例化>模版函数

关键字decltype(C++11)

作用:选择并返回操作数的数据类型

int x;
decltype(x) y; //make y the same type as x

C++后置返回类型

template <typename T1, typename T2>
auto gt(T1 x, T2 y) ->decltype(x + y) // 后置返回类型
{
    
    
	//...
	return x + y;
}

猜你喜欢

转载自blog.csdn.net/ZR_YHY/article/details/111637904