C++ 第三周

内联函数 

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

       编译过程的最终产品是可执行程序——由一组机器语言指令组成。运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令。有时(条件语句,循环语句)将跳过这些指令,向前或向后跳到特定地址。

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

       内联函数提供了另一种更好的选择,内联函数的编译代码与其他程序代码“内联”起来了。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置执行代码,再跳回来。因此内联函数运行速度更快,但需要占用更多内存。

       如果程序在10个不同的地方调用同一个内联函数,则该程序将包含该函数代码的10个副本。

 要使用这项特性,必须采取下述措施之一:

(1)在函数声明前加上关键字 inline ;

(2)在函数定义前加上关键字 inline ;

         通常的做法是省略原型,将整个定义放在本应提供原型的地方。

// inline.cpp -- using an inline function
#include<iostream>

// an inline function definition
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);
	cout << a << "	" << b << endl;
	cout << square(c++) << endl;
	
	return 0;
}

     尽管程序没有提供独立的原型,但 C++ 原型特性仍在起作用。这是因为在函数首次使用前出现的整个函数定义充当了原型。这意味着可以给square()传递 int 或 long 值,将值传递给函数前,程序自动将这个值强制转换为double 类型。

内联和宏

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

      例如:下面是一个计算平方的宏:

#define SQUARE( X ) X * X

注意 : 这并不是通过传递参数实现的,而是通过文本替换实现的—— X 是 " 参数 " 的符号标记。

       但宏不能按值传递。

引用变量

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

        之前的 & 是用来指示变量地址的。现在还可以用其来声明引用。

例如:要将bro 作为 wuhu 变量的别名:

int wuhu;
int & bro = wuhu

// int & 指的是指向 int 的引用

若将bro 加 1 将同时影响这两个变量,更准确的说,bro++操作将一个有两个名称的变量加 1 .

       所以 现在使用 & 是 指向 某变量 的引用或者是指针呢?

例如:创建yep的引用和指针:

int yep = 1;
int & yyds = yep;       // 引用 
int * YYDS = &yep;      // 指针 

       必须在声明引用变量时进行初始化,而不能像指针那样,先声明再赋值。虽然可以通过初始化声明来设置引用,但不能通过赋值来设置。

       引用更接近 const 指针:

int & bro = wuhu;
// 这实际上是下述代码的伪装表示: 
int * const pr = &wuhu; 

将引用作为函数参数

       引用常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名,这种传递参数的方法称为按引用传递。按引用传递允许被调用的函数能够访问调用函数中的变量。

       现在通过一个常见的问题——交换两个变量的值,来比较一下引用和指针;再加上一种不可行的方法。

       示例:

#include<iostream>
void swapr(int & a,int & b)      // 使用引用 
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}

void swapp(int * px,int * py)    // 使用指针 
{
	int temp;
	temp = *px;
	*px = *py;
	*py= temp;
}

void swapt(int a,int b)          // 不可行的方法 
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}
int main()
{
	using namespace std;
	int m = 520;
	int n = 1314;
	cout << m << "	" << n << endl;
	
	cout << "使用引用后:" << endl;
	swapr(m,n);
	cout << m << "	" << n << endl;
	
	m = 520;
	n = 1314;
	cout << "使用指针后:" << endl;
	swapp(&m,&n);
	cout << m << "	" << n << endl;
	
	
	m = 520;
	n = 1314;
	cout << "使用第三种方法后:" << endl;
	swapt(m,n);
	cout << m << "	" << n << endl;
	
	return 0;
}

// 运行结果如下:
// 520     1314
// 使用引用后:
// 1314    520
// 使用指针后:
// 1314    520
// 使用第三种方法后:
// 520     1314 

引用的属性和特别之处

       

#include<iostream>
double cube(double a); 
double refcube(double &b);

int main()
{
	using namespace std;
	double x =3.0;
	
	cout << cube(x) << endl;
	cout << x << endl;  
	
	cout << refcube(x) << endl;
	cout << x << endl;
	return 0;
}
double cube(double a)
{
	a *= a * a;
	return a;
}

double refcube(double &b)
{
	b *= b * b;
	return b;
}

// 运行结果 
// 27
// 3
// 27
// 27 

        refcube() 函数修改了 main() 中 x 的值,这就是按引用传递的特殊之处。如果我们的意图是让函数使用传递给它的信息,而不对这些信息进行修改,同时又想使用引用,则应使用常量引用,可以在函数原型或函数头使用const。

const double refcube(double &b);
// 这样做,编译器发现代码修改了 b的值时会生成错误信息

         如果b是一个变量的别名,则实参应该是该变量:

double t = refcube(x + 1.0)
// 这是错的,因为表达式 x + 1.0 不是变量

临时变量 引用参数 和 const

       C++的临时变量是编译器在需要的时候自动生成的临时性变量,它们并不在代码中出现.但是它们在编译器生成的二进制编码中是存在的,

       如果实参与引用参数不匹配, C++ 将生成临时变量。首先,什么时候将创建临时变量呢? 如果引用参数是 const ,则如下两种情况将生成临时变量 :

(1)实参的类型正确,但不是左值 ;

(2)实参类型不正确,但可以转换为正确的类型。

        应尽可能的使用 const:

将引用参数声明为常量数据的引用理由如下:

(1)使用 const 可以避免无意中修改数据的编程错误;

(2)使用 const 使函数能够处理 const 和 非 const 实参,否则将只能接受 非 const 的数据;

(3)使用 const 引用使函数能够正确生成并使用临时变量。
 

现在的引用都是左值引用,后面的章节会介绍右值引用·。

引用用于结构

       引用很适合用于结构和类。

假设有如下结构定义:

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

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

void set_pc(free_throw & ft);

如果不希望函数修改传入的结构,可使用const:

void set_pc(const free_throw & ft);

为何要返回引用

      传统返回机制与按值传递函数参数相似:计算return 后面的表达式,并将结果返回给调用函数

从理论上来说,这个值被复制到一个临时的位置,而调用程序将使用这个值。

      看下面的语句:

dup = accumulate(team,five);

       如果accumulate()返回一个结构,而不是指向结构的引用,将把整个结构复制到一个临时位置,再将这个拷贝复制给dup;如果返回值为引用时,将直接把team 复制到 dup,效率更高。

       注意:返回引用的函数实际上是被引用的变量的别名。

返回引用时需要注意的问题

      返回引用时最重要的一点是,应避免返回函数终止时不再存在的内存单元引用;避免返回指向临时变量的指针。

      为了避免这种问题,最简单的方法是,返回一个作为参数传递给函数的引用·。作为参数的引用将指向调用函数使用的数据,因此返回的引用也将指向这些数据。  

      另一种方法是用 new 来分配新的存储空间。下面是使用引用来完成类似工作的方法:

const free_throw & clone(free_throw & ft)
{
	free_throw *pt;
	*pt = ft;
	return *pt;
}

       第一条语句创建了一个无名的 free_throw 结构,并让指针 pt 指向该结构,因此 *pt 就是该结构。这样似乎会返回该结构,但实际上返回的是这个结构的引用

引用用于类对象

        将类对象传递给函数时,C++ 通常的做法是使用引用。例如,可以通过使用引用,让函数将类string,ostream,istream,ofstream,ifstream类的对象作为参数。

        下面看一个示例:

#include<iostream>
#include<string>
using namespace std;
string versional(const string & s1,const string & s2)
{
	string temp;
	temp = s2 + s1 + s2;
	return temp;
}
int main()
{
	string input,result;
	cout << "Enter a string: ";
	getline(cin,input);
	result = versional(input,"***");
	cout << "Your string enhanced: " << result << endl;
	return 0;
}

       在这种情况下,s1 和 s2 为string对象。使用引用效率更高,因为函数不需要创建新的string对象,并将在原来对象中的数据复制到新对象中。const符指出,该函数将使用原来的string对象,但不会修改它。

       temp是一个新的string对象,只在函数versional()中有效,该函数执行完毕后,它将不存在,因此返回指向temp的引用不可行,因此该函数的返回类型是string。这意味着temp的内容将被复制到一个临时存储单元中,然后在main()中,该存储单元的内容被复制到一个名为 result 的string中。

        

对象,继承和引用

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

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

何时使用引用参数

       使用引用参数的原因:

(1)程序员能够修改调用函数中的数据对象;

(2)通过传递引用而不是整个数据对象,可以提供程序运行速度。

        

        引用参数实际上是基于指针的代码的另一个接口。那什么时候使用引用,什么时候使用指针,什么时候按值传递呢?

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

(1)如果数据对象很小,如内置数据类型或小型结构,则按值传递 .

(2)如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针.

(3)如果数据对象是较大的结构,则使用const 指针或const 引用,提高程序效率。这样可以节省复制结构所需要的时间和空间。

(4)如果数据对象是类对象,则使用const 引用。类设计的语义常常要求使用引用,这是C++新增这项新特性的主要原因。因此,传递类对象参数的标准方式是按引用传递。

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

(1)如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码,则很明显,该函数将修改x。

(2)如果数据对象是数组,只能使用指针。

(3)如果数据对象是结构,使用引用和指针。

(4)如果数据对象是类对象,使用引用。

默认参数

       默认参数指的是当函数调用中省略了实参时自动使用的一个值

直接看示例:

#include<iostream>
const int ArSize = 80;
char * left(const char * str,int n = 1);
int main()
{
	using namespace std;
	char sample[ArSize];
	cout << "Enter a string:\n";
	cin.get(sample,ArSize);
	char *ps = left(sample,4);
	cout << ps << endl;
	delete [] ps;
	ps = left(sample);
	cout << ps << endl;
	delete [] ps;
	return 0;
}

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;
}

 

函数重载

       函数多态是C++ 在 C 的基础上新增的功能。默认参数让您能够使用不同数目的参数调用同一个函数,而函数多态(函数重载)让你能够使用多个同名的函数。可以通过函数重载来设计一系列函数——他们完成相同的工作,但使用不同的参数列表。形象点说,重载函数就像是有多种含义的动词。

       函数重载的关键是函数的参数列表。例如,可以定义一组原型如下的 print() 函数:

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() 函数时,编译器将根据所采取的用法使用有相应特征标的原型:

print("Pancakes",15);  //  1
print("Syrup");        //  5
print(1990.0,10);      //  2
print(1099,12);        //  4
print(199L,15);        //  3

       使用被重载的函数时,需要在函数调用中使用正确的参数类型。例如:

unsigned int year = 2022;
print(year,6);

       它不与任何原型匹配,但C++ 尝试使用标准类型转换强制进行匹配。但是如果有多个将数字作为第一个参数的原型,这种情况下,C++ 拒绝这种函数调用。

 

       一些看起来彼此不同的特征标是不能共存的。例如,编译器在检查特征标时,将把类型引用和类型本身视为同一个特征标。

       另外,在匹配函数时,要区分const 和 非 const 变量。

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

long gronk (int n,float m);
double gronk (int n,float m);

       返回类型可以不同,但特征标也必须不同:

long gronk (int n,float m);
double gronk (float n,int m);

重载引用参数

      

 

下面看一个示例:

#include<iostream>
unsigned long left(unsigned long num,unsigned ct);
char * left(const char * str,int n = 1);

int main()
{
	using namespace std;
	char * trip = "Hawaii!";
	unsigned long n = 12345678;
	int i;
	char * temp;
	
	for(i = 1 ; i < 10 ; i++)
	{
		cout << left(n, i) << endl;
		temp = left(trip, i);
		cout << temp << endl;
		delete [] temp;
	}
	return 0;
} 

unsigned long 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;
}

// 运行结果
// 1
// H
// 12
// Ha
// 123
// Haw
// 1234
// Hawa
// 12345
// Hawai
// 123456
// Hawaii
// 1234567
// Hawaii!
// 12345678
// Hawaii!
// 12345678
// Hawaii!

函数模板

        函数模板是通用的函数描述,也就是说,他们使用泛型来定义函数,其中的泛型可用具体的类型(如 int 或 double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。

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

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

       第一行指出,要建立一个模板,并将类型命名为AnyType. 关键字 template 和 typename 是必需的,除非可以用关键字 class 代替 typename 。

       模板并不创建任何函数,只是告诉编译器如何定义函数。需要交换int 的函数时,编译器将按模板创建这样的函数,并用int 代替 double。

       提示:如果需要将同一种算法用于不同类型的函数,请使用模板。

重载的模板

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

        示例:

template <typename T>        // original template
void swap(T &a,T &b);

template <typename T>        // new template
void swap(T *a,T *b,int n);

模板的局限性

       假设有如下模板函数:

template <typename T>
void f(T a,T b)
{
    ...
}

       通常,代码假定可执行哪些操作。例如,下面的代码假定定义了赋值,但如果T 为数组,这种假设不成立 : a = b;

       同样,当 T 为结构 ,该假设不成立: a > b;

       解决方案是,为特定类型提供具体化的模板。

显示具体化

       

       下面看一个示例来看看显示具体化的工作方式:

#include<iostream>
template <typename T>
void swap(T &a,T &b);

struct job
{
	char name[40];
	double salary;
	int floor;
};

template <> void swap <job>(job &j1, job &j2);
void show(job &j);

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";
	cout << "Using compiler-genrated int swapper:\n";
	swap(i,j);
	cout << "Now i, j = " << i << ", " << j << ".\n";
	
	job sue = {"Susan",73000.60,7};
	job sidney = {"Sidney Paul",78544.80,9};
	cout << "Before job swapping:\n";
	show(sue);
	show(sidney);
	swap(sue,sidney);
	cout << "After job swapping:\n";
	show(sue);
	show(sidney);
	
	return 0;
}

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 t1;
	int t2;
	t1 = j1.salary;
	j1.salary = j2.salary;
	j2.salary = t1;
	t2 = j1.floor;
	j1.floor = j2.floor;
	j2.floor = t2;
}

void show(job &j)
{
	using namespace std;
	cout << j.name << ": $" << j.salary
	     << " on floor" << j.floor << endl;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DDsoup

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值