IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 第 8 章 函数探幽 -> 正文阅读

[C++知识库]第 8 章 函数探幽

本章内容包括:

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

8.1 C++内联函数

内联函数是C++为提高程序运行速度所做的一项改进。常规函数和内联函数之间的主要区别不在于编写方式,而在于C++编译器如何将它们组合到程序中。要了解内联函数与常规函数之间的区别,必须深入到程序内部。

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

执行到函数调用指令时,程序将在函数调用后立即执行该指令的内存地址,并将函数复制到堆栈,跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定的开销。

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

应有选择的使用内联函数,如果执行代码的时间比处理函数调用机制的时间长,则节省的时间将只占整个过程很小一部分。如果代码执行的时间很短,则内联调用就可以节省非内联调用使用的大部分时间。另一方面,由于这个过程相当快,因此尽管节省了该过程的大部分时间,但节省的时间绝对值并不大,除非该函数经常被调用。

内联函数的特性,

  • 在函数声明前加上inline;
  • 在函数定义前加上关键字inline。

通常的做法是省略原型,将整个定义(即函数头和所有函数代码)放在本应提供原型的地方。

程序员请求将函数作为内联函数时,编译器并不一定会满足这种请求。它可能认为该函数过大或注意到函数调用了自己(内联函数不能递归),因此不将其作为内联函数;而有些编译器没有启用或实现这种特性。

#include <iostream>

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

int main()
{
  using namespace std;
  double a,b;
  double c =13.0;

  a=square(5.0);
  b=square(4.5+7.5); //can pass expressions
  cout <<"a= "<<a<<",b="<<b<<endl;
  cout <<"c= "<< c <<",c squared = "<< square(c++)<<endl;
  cout <<"Now c= "<<c<<endl;

  return 0;
}

程序输出如下,输出表明,内联函数和常规函数一样,也是按值来传递参数的如果参数为表达式,如4.5+7.5,则函数将传递表达式的值。

内联与宏

?inline工具是C++新增的特性。C语言使用预处理器语句#define来提供宏——内联函数的原始实现。

如下,并不是通过传递参数实现,而是通过文本替换来实现的,X 是“参数”的符号标记。只有第一个a能正常工作。

8.2 引用变量

8.2.1 创建引用变量

前面讲过,C和C++使用&符号来指示变量的地址。C++给&符号赋予了另一个含义,将其用来声明引用。例如将rodents作为rats变量的别名,你可以这样做:

int rats;
int & rodents =rats;

其中,&不是地址运算符,而是类型标识符的一部分。就像声明中的char* 指的是指向char的指针一样, int &指的是指向int的引用。

引用有些类似于指针,但是还是不同于指针,除了表示法不同外,还有其他的差别,例如必须在声明引用时将其初始化,而不能像指针那样,先声明,再赋值:

int rats =101;
int & rodents =rats; //rodents a reference
int * prats =&rats; //prats a pointer

int rat;
int & rodent;
rodent = rat;  //No, you can't do this

引用更接近const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就将一直效忠于它。

8.2.2 将引用用作函数参数

引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。按引用传递允许被调用的函数能够访问调用函数中的变量。C++新增的这项特性是对C语言的超越,C语言只能按值传递。按值传递导致被调用函数使用调用程序的值的拷贝。当然,C语言也允许避开按值传递的限制,采用按指针传递的方式。

#include <iostream>

using namespace std;
void swapr(int &a, int &b);  // a,b are aliases for ints
void swapp(int *p, int *q);  // p,q are addresses of ints
void swapv(int a, int b);   // a,b are new variables

void swapr(int &a, int &b)
{
	int temp;
	temp=a;
	a=b;
	b=temp;
}
void swapp(int *p, int *q)
{
	int temp;
	temp=*p;
	*p=*q;
	*q=temp;
}
void swapv(int a, int b)
{
	int temp;
	temp=a;
	a=b;
	b=temp;
}

int main()
{
	int wallet1 =300;
	int wallet2 =350;
	cout<<"wallet1 = $"<<wallet1<<endl;
	cout<<"wallet2 = $"<<wallet2<<endl;
	
	cout<<"using references to swap contents:"<<endl;
	swapr(wallet1,wallet2);
	cout<<"wallet1 = $"<<wallet1<<endl;
	cout<<"wallet2 = $"<<wallet2<<endl;
	
	cout<<"using pointers to swap contents:"<<endl;
	swapp(&wallet1,&wallet2);
	cout<<"wallet1 = $"<<wallet1<<endl;
	cout<<"wallet2 = $"<<wallet2<<endl;
	
	cout<<"Trying to use passing by value:"<<endl;
	swapv(wallet1,wallet2);
	cout<<"wallet1 = $"<<wallet1<<endl;
	cout<<"wallet2 = $"<<wallet2<<endl;
	
	return 0;
	
}

 

如上是按引用,指针,值作为参数传递的方式,引用和指针都成功地交换了两个钱夹的内容,而按值传递的方法没能完成这项任务。

8.2.3 引用的属性和特别之处

引用传递时候,不允许引用函数内改变引用地址的值。

#include<iostream>
using namespace std;

double cube(double a);
double refcube(double &ra);

double cube(double a)
{
	a *=a*a;
	return a;
}
double refcube(double &ra)
{
	ra *=ra *ra;
	return ra;
}


int main()
{
	double x =3.0;
	cout<<cube(x)<<" = cube of "<<x<<endl;
	
	cout<<refcube(x)<<" = cube of "<<x<<endl;
	
	return 0;
}

临时变量、引用参数和const

如果实参与引用参数不匹配,C++将生成临时变量。当前,仅当参数为const引用时,C++才允许这样做。

尽可能的使用const

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

8.2.4 将引用用于结构

引用非常适合用于结构和类。使用结构引用参数的方式与使用基本变量引用相同,只需要在声明结构参数时使用引用运算符&即可。

struct free_throws
{
	std::string name;
	int made;
	int attempts;
	float percent;
 } 

void set_pc(free_throws &ft); //use a reference to a structure

返回引用时,要避免返回函数终止时不再存在的内存单元引用。

8.2.5 将引用用于类对象

将类对象传递给函数时,C++通常的做法时使用引用。

8.2.6 对象、继承和引用

ostream和ofstream类凸显了一个有趣属性,ofstream对象可以使用ostream类的方法,这使得文件输入/输出的格式与控制台输入/输出相同。使得能够将特性从一个类传递给另一个类的语言特性称为继承。ostream是基类,ofstream是派生类。派生类继承了基类的方法。

继承的另一个特征是, 基类引用可以指向派生类对象,而无需进行强制类型转换。这种特征的一个实际结果是,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。

8.2.7 何时引用参数

使用引用参数的主要原因有两个。

  • 程序员能够修改调用函数中的数据对象
  • 通过传递引用而不是整个数据对象,可以提高程序的运行速度

当数据对象较大时(如结构和类对象),第二个原因最重要。这些也是使用指针参数的原因。因为引用参数实际上是基于指针的代码的另一个接口。

对于使用传递的值而不作修改的函数。

  • 如果数据对象很小,如内置数据类型或小型结构,则按值传递
  • 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。
  • 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间。
  • 如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象的标准方式是按引用传递。

对于修改调用函数中数据的函数:

  • 如果数据对象是内置数据类型,则使用指针。
  • 如果数据对象是数组,则只能使用指针。
  • 如果数据对象是结构,则使用引用或指针。
  • 如果数据对象是类对象,则使用引用。

8.3 默认参数

对于带参数列表的函数,必须从右向左添加默认值。也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值。

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

8.4 函数重载

函数多态是C++在C语言的基础上新增的功能。默认参数能让您使用不同数目的参数调用同一个函数,而函数多态(函数重载)让您能够使用多个同名的函数。

函数重载的关键是函数的参数列表——也称为函数特征标。如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名是无关紧要的。C++允许定义名称相同的函数,条件是它们的特征标不同。如果参数数目或参数类型不同,则特征标也不同。

void print(const char *str, int width); // #1
void print(double d, int width);        // #2
void print(long l, int width);          // #3
void print(int i, int width);           // #4
void print(const char *str);            // #5

print("Pancakes",15); // #1
print("Syrup");       // #2
print(1999.0,10);     // #3
print(1999, 12);      // #4
print(1999L,15);      // #5

是特征标,而不是函数类型使得可以对函数进行重载。例如,下面的两个声明是互斥的:

long gronk(int n, float m);
double gronk(int n, float m);  //hence not allowed


long gronk(int n, float m); 
double gronk(float n, float m);  //hence allowed

8.4.1?何时使用函数重载

虽然函数重载很吸引人,但也不要滥用。仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载。另外您可能还想知道,是否可以通过使用默认参数来实现同样的目的。例如,可以用两个重载函数来代替面向字符串的left()函数:

char * left(const char *str, unsigned n);
char * left(const char *str);

使用一个带默认参数的函数要简单些。只需编写一个函数(而不是两个函数),程序也只需为一个函数(而不是两个)请求内存;需要修改函数时,只需要修改一个。然而,如果需使用不同类型的参数,则默认参数不管用了,在这种情况下,应该使用函数重载。

8.5 函数模板

现在的C++编译器实现了C++新增的一项特性——函数模板。函数模板是通用的函数描述,也就是说它们使用泛型来定义函数,其中的泛型可用具体的类型(如int,double)替换。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型。

之前定义了一个交换两个int值得函数,假设要交换两个double值,则一种方法是复制原来的代码,并用double替换所有int。但是这种方法容易出错,如用全局查找和替换,将double替换int,可能将:

int x;
short interval;

转换为:

double x;
short doubleerval; //unintended change of variable name

C ++的函数模板功能能自动完成这一过程,可以节省时间,且更可靠。

函数模板允许以任意类型的方式来定义函数。例如,可以建立一个交换模板:

template <typename AnyType>
void Swap(AnyType &a, AnyType &b) 
{
	AnyType temp;
	temp =a;
	a=b;
	b=temp;
}

第一行指出,要建立一个模板,并将类型命名为AnyType。关键字template和typename是必需的,除非可以使用关键字class代替typename。另外,必须使用尖括号。类型名可以任意选择(这里为AnyType)。只要遵守C++命名规则即可,许多程序员都使用简单的名称,如T。余下的代码描述了交换两个AnyType值得算法。模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换int得函数时,编译器将按模板模式创建这样得函数,并用double代替AnyType。

在标准C++98添加关键字typename之前,C++使用关键字class来创建模板。也即是说,可以这样编写模板定义,typename关键字使得参数AnyType表示类型这一点更为明显。

template <class AnyType>
void Swap(AnyType &a, AnyType &b) 
{
	AnyType temp;
	temp =a;
	a=b;
	b=temp;
}

8.5.1 重载的模板

需要多个对不同类型使用同一算法的函数时,可使用模板。然而,并非所有的类型都使用相同的算法。为满足这种需求,可以像重载常规函数定义那样重载模板定义。和常规重载一样,被重载的模板的函数特征标必须不同。并非所有的模板参数都必须时模板参数类型。

#include <iostream> 
using namespace std;
template <typename T>
void Swap(T &a, T &b);

template <typename T>
void Swap(T *a, T *b,int n);

void Show(int a[]);
const int Lim=8;

int main()
{	
	int i=10, j=20;
	cout<<"i,j = "<<i<<", "<<j<<endl;
	cout <<"Using compiler-generated int swapper:"<<endl;
	Swap(i,j);
	cout<<"Now i,j="<<i<<", "<<j<<endl;
	
	int d1[Lim]={0,7,0,4,1,7,7,6};
	int d2[Lim]={0,7,2,0,1,9,6,9};
	cout<<"Original arrays:"<<endl;
	Show(d1);
	Show(d2);
	Swap(d1,d2,Lim);
	cout<<"Swapped arrays:"<<endl;
	Show(d1);
	Show(d2);
	return 0;
}


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[])
{
	cout<<a[0]<<a[1]<<"/";
	cout<<a[2]<<a[3]<<"/";
	for(int i=4;i<Lim;i++)
	{
		cout<<a[i];
		
	}
	cout<<endl;
 } 

8.5.2 模板的局限性

8.5.3 显式具体化

8.5.4 实例化和具体化

8.5.5 编译器选择使用哪个函数版本

8.5.6 模板函数的发展

8.6 总结

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-30 11:49:20  更:2021-08-30 11:49:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:46:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码