【捡起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;
}
将返回类型移到了参数声明后面。