CPP 8. 函数探幽 2020-11-03 浏览量 729 暂无评论 # 第8章 函数探幽 ## 8.1 C++内联函数 🐸小知识: 1. 内联函数是C++为提高程序运行速度所做的一项改进,具体是怎么提高运行速度的呢? **调用常规函数:** 存储调用指令的地址-->复制函数参数至堆栈-->跳到标记函数起点的内存单元并执行函数代码(也许还需将返回值放入到寄存器中)-->调回地址被保存的调用指令处。 **使用内联函数:** 编译器使用相应的函数代码替换函数调用,无需跳来跳去,以占用更多内存为代价。 2. 内联函数不能递归; 3. 使用规则:要么在函数声明前加上关键字inline,要么在函数定义前加上关键字inline,通 常省略原型,将整个定义放在本应提供原型的地方; 4. 大函数不适合用内联,因为本身执行时间就很长; 5. C++内联函数可以按值传递,但C语言的宏定义`#define SQUARE(X) ((X)*(X))`不可以按值传递; **示例:** ```cpp #include 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 << endl; cout << "c = " << c; cout << ", c square = " << square(c++) << endl; } ``` ## 8.2 引用变量 引用是一种复合类型,它是已定义的变量的别名,与原变量指向相同的值和地址; 🐸小知识: 1. 通过将引用变量用作参数,函数将使用原始数据,而不是其副本; 2. 引用必须在声明时将其初始化,更接近const指针; 3. 在用作函数参数时,尽可能使用const; 4. 返回引用时直接把返回值复制给接收变量,而不用创建临时变量(应避免返回临时变量的引 用或指针); 5. 返回引用的函数实际上式被引用的变量的别名; **创建引用变量** ```cpp int rats; int & rodents = rats; // 让rodents成为rats的别名 ``` **将引用用作函数参数** ```cpp #include void swap(int & a, int & b); int main() { using namespace std; int wallet1 = 300; int wallet2 = 350; swap(wallet1, wallet2); } void swap(int & a, int & b) { int temp; temp = a; a = b; b = temp; } ``` **临时变量、引用和const** C++将在两种情况下生成临时变量(仅当参数为const时,C++才允许这样): 1. 实参的类型正确,但不是左值; 2. 实参的类型不正确,但可以转换为正确的类型。 临时变量并不会影响原变量。 ```cpp double refcube(const double &ra) { return ra * ra * ra; } double side = 3.0; double * pd = &side; double & rd = side; long edge = 5L; double lens[4] = {2.0, 5.0, 10.0, 12.0}; double c1 = refcube(side); double c2 = refcube(lens[2]); double c3 = refcube(rd); double c4 = refcube(*pd); double c5 = refcube(edge) // ra is temporary variable, case 2 double c6 = refcube(7.0); // ra is temporary variable, case 1 double c7 = refcube(side + 10.0); // ra is temporary variable, case 1 ``` **右值引用** 这种引用可指向右值,使用&&声明。 ```cpp double && rref = std::sqrt(36.00); double j = 15.0; double && jref = 2.0 * j * 18.5; std::out << rred << endl; std::out << jred << endl; ``` **将引用作为函数的返回值** ```cpp // 返回string const string v1(const string & s1, const string & s2) { string temp; string = s2 + s1 +s2; return temp; } // 返回string的引用 const string & v2(string & s1, const string & s2) { s1 = s2 + s1 + s2; return s1; } ``` **对象、继承和引用** 1. 派生类继承基类的方法; 2. 基类引用可以指向派生类对象。 注意下面的程序:参数os(类型为ostream &)可以指向ostream对象(如cout),也可以指向ostream的派生类ofstream对象(如fout)。 ```cpp #include #include #include using namespace std; void file_it(ostream & os, double fo, const double fe[], int n); const int LIMIT = 5; int main() { 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 << "Eyepiece #" << i + 1 << ": "; cin >> eps[i]; } file_it(fout, objective, eps, LIMIT); file_it(cout, objective, eps, LIMIT); cout << "Done\n"; cin.get(); cin.get(); 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.1. 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); } ``` **何时使用引用参数:** 1. 修改调用函数中的数据对象; 2. 提高程序的运行速度; ## 8.3 默认参数 🐸小知识: 1. 通过函数原型设置默认值`char * left(const char * str, int n = 1);`,必须从右向左添加默认值; 2. ***在设计类时通过使用默认参数,可以减少要定义的析构函数、方法以及方法重载的数量;*** ## 8.4 函数重载(函数多态) C++在C语言的基础上新增的功能。 特征标:函数的参数列表,包括参数数目、类型、排列顺序。 🐸小知识: 1. 默认参数允许使用不同数目的参数调用同一个函数,而函数多态允许使用多个同名的函数(**那C++怎么知道是在用同一个函数的默认参数还是多态的同名的函数呢?函数重载的条件是它们的特征标不同**); 2. 从编译器的角度考虑特征标; 3. 没有匹配的原型并不会自动停止使用其中的某个函数,因为C++将尝试使用标准类型转换强制进行匹配,但如果有多个函数满足强制匹配,则C++将其视为错误; 4. 匹配函数时,并不区分const和非const变量; ```cpp void dribble(char * bits); void dribble(const char *cbits); void dabble(char * bits); void drivel(const chat * bits); const char p1[20] = "How's the weather?"; char p2[20] = "How are you?"; dribble(p1); // dribble(const char *) dribble(p2); // dribble(char *) dabble(p1); // no match dabble(p2); // dabble(char *) drivel(p1); // drivel(const char *) drivel(p2); // drivel(const char *) ``` 5. 重载引用参数时,将调用最匹配的版本(参数是左值、const还是右值); 6. C++怎么分得清到底调用哪个 **何时使用函数重载** 仅当函数基本上执行相同的任务,但使用不同类型的参数时,才应使用函数重载,如果只是参数数量不同,可以考虑默认参数。 ## 8.5 函数模板 函数模板就是使用泛型来定义函数,其中的泛型可以用具体的类型来替换,编译器在编译的时候会将泛型替换为具体的类型。 🐸小知识: 1. 并非所有的模板参数都必须是模板参数类型; 2. 在原型声明时需要加上`template `; 3. 函数模板虽然试图使用泛型来简化代码,但是它仍然不能处理所有数据类型,比如常规变量 的`a=b`不能用于数组,`a*b`不能用于指针参数等,所以有必要使用不同的原型,那怎么分清使用哪个原型呢?这时候就要涉及到原型优先级:非模板函数>具体化>常规模板(见下面示例); 4. 既然可以隐式实例化,显式实例化有什么用呢?加快运行速度? ```cpp template // template void Swap(AnyType &a, AnyType &b) { AnyType temp; temp = a; a = b; b = temp; } ``` **具体化** ```cpp // non template function prototype, job is a struct void Swap(job &, job &); // template prototype template void Swap(T &, T &); // explicit specialization for the job type template <> void Swap(job &, job &); template <> void Swap(job &, job &); ``` **原型优先级** ```cpp #include using namespace std; void Swap(int &a, int &b); // 隐式实例化(explicit instantiation) template void Swap(T &a, T &b); // 显式实例化(explicit instantiation) template void Swap(int, int); // 显示具体化(explicit specialization),不能和显示实例化同时使用 template <> void Swap(job &j1, job &j2); void Show(job &j); struct job { char name[40]; double salary; int floor; }; int main() { cout.precision(2); cout.setf(ios::fixed, ios::floatfield); int i = 10, j = 20; cout << "i, j = " << i << ", " << j << ".\n"; cout << "Using compiler-generated int swapper:\n"; Swap(i, j); cout << "Now i, j = " << i << ", " << j << ".\n"; job sue = {"jhan", 4235454.76, 7}; job sidney = {"xyhan", 32424.56, 9}; cout << "Before job swapping:\n"; Show(sue); Show(sidney); Swap(sue, sidney); cout << "After job swapping:\n"; Show(sue); Show(sidney); cin.get(); return 0; } void Swap(int &a, int &b) { cout << "Enter regular Swap.\n"; int temp; temp = a; a = b; b = temp; } template void Swap(T &a, T &b) { cout << "Enter template Swap.\n"; T temp; temp = a; a = b; b = temp; } template <> void Swap(job &j1, job &j2) { cout << "Enter specification Swap.\n"; 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; } 输出: i, j = 10, 20. Using compiler-generated int swapper: Enter regular Swap. Now i, j = 20, 10. Before job swapping: jhan : $4235454.76 on floor 7 xyhan : $32424.56 on floor 9 Enter specification Swap. After job swapping: jhan : $32424.56 on floor 9 xyhan : $4235454.76 on floor 7 ``` **🌈原型的匹配及完全匹配(难点):** 在函数重载、函数模板、函数模板重载时,C++按以下过程决定使用哪个函数: 1. 创建候选函数列表。这里放名称一样的函数和模板函数; 2. 使用候选函数列表创建可行函数列表。这里是参数数目正确的函数; 3. 确定是否有最佳的可行函数。如果有,就使用它,否则调用出错。 从最佳到最差的顺序如下: 1. 完全匹配,但常规函数优先于模板函数; 2. 提升转换(如char和shorts自动转换为int,float自动转换为double); 3. 标准转换(如int转换为char,long转换为double); 4. 用户定义的转换,如类声明中定义的转换。 原型的完全匹配允许一些无关紧要的转换,如下表: 从实参|到形参 ---|--- Type|Type & Type &|Type Type[]|* Type Type (argument-list)|Type (*) (argument-list) Type|const Type Type|volatile Type Type *|const Type Type *|volatile Type * ```cpp #include template T lesser(T a, T b) { return a < b ? a : b; } int lesser (int a, int b) { a = a < 0 ? -a : a; b = b < 0 ? -b : b; return a < b ? a : b; } int main() { using namespace std; int m = 20; int n = -30; double x = 15.5; double y = 25.9; cout << lesser(m, n) << endl; // use #2 cout << lesser(x, y) << endl; // use #1 with double // 指出应使用模板函数 cout << lesser<>(m, n) << endl; // use #1 with int // 进行显示实例化 cout << lesser(x, y) << endl; // use #1 with int return 0; } ``` **关键字decltype和后置返回类型(C++11)** 在C++98中,有时无法确定编写的模板函数中用到的中间变量类型,这时候decltype就派上了用场(像numpy中的astype)。 ```cpp int x; decltype(x) y; decltype(x + y) xpy = x + y; double x = 5.5; decltype ((xx)) r = xx; // r2 is double & decltype (xx) w = xx; // w is double auto h(int x, float y) -> double; template auto gt(T1 x, T2 y) -> decltype(x + y) { ... return x + y; } ``` 赞赏 微信支付 支付宝支付