【捡起C++】函数探幽

【捡起C++】函数探幽
C++内联函数

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

​ C++内联函数提供了另一种选择。 内联函数的编译代码与其他程序代码”内联“起来了。也就是说,编译器将使用相应的函数代码替换函数调用。内联函数的运行速度比常规函数稍快,但代价是需要占用更多的内存。

​ 应当有选择地使用内联函数, 如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间将只占整个过程的很小一部分。如果代码执行时间很短,则内联调用就可以节省非内联调用使用的大部分时间。

//inline.cpp -- using an inline function

#include <iostream>

//an inline funciton 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 = " << a << ", b = " << b << "\n";
	cout << "c = " << c;
	cout << ", c squared = " << square(c++) << endl;
	cout << "Now c = " << c << "\n";
	return 0;
}
引用变量

​ 将rodents作为rats变量的别名,可以这样做:

int rats;
int & rodents = rats;

​ 他们只想相同的值和内存单元。

//firstref.cpp -- defining and using a reference
#include <iostream>
int main() {
	using namespace std;
	int rats = 101;
	int& rodents = rats;
	cout << "rats = " << rats;
	cout << " ,rodents = " << rodents << endl;
	rodents++;
	cout << "rats = " << rats;
	cout << ", rodents = " << rodents << endl;


	cout << "rats address = " << &rats;
	cout << ", rodents address = " << &rodents << endl;
	return 0;
}

注意:必须在声明引用变量时进行初始化。

//cubes.cpp -- regular and reference arguments
#include <iostream>
double cube(double a);
double refcube(double &ra);
int main() {
	using namespace std;
	double x = 3.0;
	cout << cube(x);
	cout << " = cube of " << x << endl;
	cout << refcube(x);
	cout << " = cube of " << x << endl;
	return 0;
}

double cube(double a) {
	a *= a * a;
	return a;
}

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

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

​ 返回引用时最重要的一点是,应避免返回函数终止时不再存在的内存单元引用。应该避免下述代码:

const free_throws & clone2(free_throws & ft){
    free_throws newguy; // first step to big error
    newguy = ft;        // copy info
    return newguy;      // return reference to copy
}

​ 该函数返回一个指向临时变量的引用,函数运行完毕后它将不再存在。



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;
}
accumulate(dup, five) = four;  //valid

​ 首先将five的数据添加到dup中,再使用four的内容覆盖dup的内容。在赋值表达式中,左边的子表达式必须标识一个可修改的内存块。在这里,函数返回指向dup的引用,他确实标识了这样的内存块,因此这条语句是合法的。


将引用用于类对象

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

//strquote.cpp  --- different designs
#include <iostream>
#include <string>
using namespace std;
string version1(const string& s1, const string& s2);
const string & version2(string& s1, const string& s2);  // has side effect
const string & version3(string& s1, const string& s2);  // bad design

int main() {
	string input, copy, result;
	cout << "Enter a string:";
	getline(cin, input);
	copy = input;
	cout << "Your string as entered: " << input << endl;
	result = version1(input, "***");
	cout << "Your string enhanced: " << result << endl;
	cout << "Your original string: " << input << endl;

	result = version2(input, "###");
	cout << "Your string enhanced: " << result << endl;
	cout << "Your original string: " << input << endl;

	cout << "Resetting original string.\n";
	input = copy;
	result = version3(input, "@@@");
	cout << "Your string enhanced: " << result << endl;
	cout << "Your original string: " << input << endl;
	return 0;
}

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

const string& version2(string& s1, const string& s2) { // has side effect
	s1 = s2 + s1 + s2;
	//safe to return reference passed to function
	return s1;
}

const string& version3(string& s1, const string& s2) {  // bug
	string temp;
	temp = s2 + s1 + s2;
	//unsafe to return reference to local variable
	return temp;
}

该程序在运行的时候会崩溃

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

   result = version1(input, "***");
实参类型与引用参数类型不匹配
实参类型与引用参数类型不匹配,但可被转换为引用类型,程序将创建一个正确类型的临时变量,使用转换后的实参值来初始化它,然后传递一个指向该临时变量的引用。
对象、继承和引用

​ ostream是基类, 而ofstream是派生类。

基类引用可以指向派生类对象,而无需进行强制类型转换。例如,参数类型为 ostream &的函数 可以接受 ostream对象(如 cout)或声明的ofstream对象作为参数。

//filefunc.cpp -- function with ostream & parameter
#include<iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
void file_it(ostream& os, double fo, const double fe[], int n);
const int LIMIT = 5;
int main(void) {
	ofstream fout;
	const char* fn = "ep-data.txt";
	fout.open(fn);
	if (!fout.is_open()) {
		cout << "can't open " << fn << ".bye.\n";
		exit(EXIT_FAILURE);
	}
	double objective;
	cout << "enter the focal length of your "
		"telescope objective in mm";
	cin >> objective;
	double eps[LIMIT];
	cout << "enter the focal lengths, in mm, of " << LIMIT << " eyepieces.\n";
	for (int i = 0; i < LIMIT; i++) {
		cout << "eyepice #" << i + 1 << ":";
		cin >> eps[i];
	}
	file_it(fout, objective, eps, LIMIT);
	file_it(cout, objective, eps, LIMIT);
	cout << "done\n";
	system("pause");
	return 0;
}
void file_it(ostream& os, double fo, const double fe[], int n) {
	ios_base::fmtflags initial;
	initial = os.setf(ios_base::fixed);
	os.precision(0);
	os << "focal length of objective:" << fo << " mm\n";
	os.setf(ios::showpoint);
	os.precision(1);
	os.width(12);
	os << "f eyepiece";
	os.width(15);
	os << "magnification" << endl;
	for (int i = 0; i < n; i++) {
		os.width(12);
		os << fe[i];
		os.width(15);
		os << int(fo / fe[i] + 0.5) << endl;
	}
	os.setf(initial);
}

​ 该程序最重要的一点是,参数os(其类型为ostream &)可以指向ostream对象(如 cout),也可以指向ofstream对象(如fout)。该程序还演示了如何使用ostream类中的格式化方法。

​ 方法 setf()用来设置各种格式化状态

​ 例如,方法调用**setf(ios_base::fixed)**将对象置于使用定点表示法的模式;

​ **setf(ios_base::showpoint)将对象置于显示小数点的模式,即使小数部分为零。方法precision()**指定显示多少位小数(假定对象处于定点模式下)。**width()**设置下一次输出操作使用的字段宽度,这种设置是一次性的(不是永久性的)。默认的字段宽度为0,意味着刚好容纳下要显示的内容。

    ios_base::fmtflags initial;
	initial = os.setf(ios_base::fixed);
     ...
	os.setf(initial);

​ 方法 **setf()**返回调用它之前有效的所有格式化设置。 ios_base::fmtflags 是存储这种信息所需的数据类型名称。因此,将返回值赋给initial将存储调用file_it()之前的格式化设置,然后便可以使用变量initial作为参数来调用setf()。

函数重载

​ 函数重载的关键是 函数的参数列表

​ 重载引用参数

void sink(double & r1);   //matches modifiable lvalue
void sank(const double & r2);   //matches modifiable or const lvalue, rvalue
void sink(double & r1);   //matches modifiable lvalue
double x = 55.5;
const double y = 32.0
stove(x);                       //calls stove(double &)
stove(y);                       //calls stove(const double &)
stove(x + y);                   //calls stove(double &&)
//如果没有定义stove(double &&), stove(x + y)将调用stove(const double &)
函数模板
//funtemp.cpp -- using a function template
#include<iostream>
#include <fstream>
#include <cstdlib>
using namespace std;

template<typename T>
void Swap(T& a, T& b);

int main(void) {
	int i = 1, j = 2;
	cout << "i, j = " << i << ", " << j << ".\n"; 
	Swap(i, j);
	cout << "after swap\ni, j = " << i << ", " << j << ".\n";

}

template<typename T>
void Swap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}
//funtemp.cpp -- using a function template
#include<iostream>
#include <fstream>
#include <cstdlib>
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(void) {
	using std::cout;
	int i = 1, j = 2;
	cout << "i, j = " << i << ", " << j << ".\n";
	Swap(i, j);
	cout << "after swap\ni, j = " << i << ", " << j << ".\n";

	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:\n";
	Show(d1);
	Show(d2);
	Swap(d1, d2, Lim);
	cout << "Swapped arrays:\n";
	Show(d1);
	Show(d2);
	return 0;

}

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

template<typename T>
void Swap(T* a, T* b, int n) {
	for (int i = 0; i < n; i++) {
		T temp = *(a + i);
		*(a + i) = *(b + i);
		*(b + i) = temp;
	}
}

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

}

实例化和具体化

显式实例化

template void Swap<int>(int, int);  // explicit instantiation	

显式具体化

template<> void Swap<int>(int&, int&); //explicit specialization
template<> void Swap(int &, int &);    //explicit specialization

不要使用swap()模版来生成函数定义,而应使用专门为int类型显示地定义的函数定义

显式具体化声明 在关键字template后包含<>,而显示实例化没有。

还可通过在程序中使用函数来创建显式实例化。例如下述代码:

template <class T>
T Add(T a, T b){
 	return a + b;   
}

...
    
int m = 6;
double x = 10.2;
cout << Add<double>(x, m) << endl; //explicit instantiation

这里的模板与函数调用Add(x, m)不匹配,因为该模板要求两个函数参数的类型相同。但通过使用Add<double>(x, m),可强制为double类型实例化,并将参数m强制转换为double类型。

//tempover.cpp -- template overloading
#include<iostream>

template <typename T>
void ShowArray(T arr[], int n);


template <typename T>
void ShowArray(T* arr[], int n);

struct debts
{
	char name[50];
	double amount;
};
int main() {
	using namespace std;
	int things[6] = { 13, 31, 103, 301, 310, 130 };
	debts mr_E[3] = {
		{"aaa" , 2400.0 },
		{"bbb", 1300.0},
		{"ccc", 1800.0}
	};
	double* pd[3];

	for (int i = 0; i < 3; i++) {
		pd[i] = &mr_E[i].amount;
	}
	cout << "Listing Mr.E's counts of things:\n";
	//
	ShowArray(things, 6);
	cout << "Listing Mr.E's debts:\n";

	ShowArray(pd, 3);
	return 0;
}

template <typename T>
void ShowArray(T arr[], int n) {
	using namespace std;
	cout << "template A\n";
	for (int i = 0; i < n; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;

}


template <typename T>
void ShowArray(T* arr[], int n) {
	using namespace std;
	cout << "template B\n";
	for (int i = 0; i < n; i++) {
		cout << *arr[i] << " ";
	}
	cout << endl;

}
关键字decltype(c++11)

C++11新增关键字

int x;
decltype(x) y; //make y the same type as x
// 给decltype提供的参数可以是表达式
decltype(x + y) xpy;
xpy = x + y;
//也可以写成
decltype(x + y) xpy = x + y;
double xx = 4.4;
decltype ((xx)) r2 = xx;   //r2 is double&
decltype (xx) w = xx;  // w is double 

后置返回类型

template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x + y){
	...
	return x + y;
}

将返回类型移到了参数声明后面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值