C++PrimerPlus第八章学习笔记——函数探幽

2024-04-09 14:08

本文主要是介绍C++PrimerPlus第八章学习笔记——函数探幽,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

此文为本人学习所做一些记录,仅做个人学习之用,加入了我的理解,如发现错误欢迎指正,邮箱:lujialun99 A T gmail.com

内联函数

概念

内联函数,关键字inline,是C++为提高程序运行速度所做的一项改进。对于使用了内联函数的位置,程序无需跳到另一个位置执行函数代码,而是直接将函数代码副本包含进程序内。
内联函数比常规函数的运行速度稍快,但代价是需要更多的内存。

inline指令就象register,它只是对编译器的一种提示,而不是命令。也就是说,只要编译器愿意,它就可以随意地忽略掉你的指令,事实上编译器常常会这么做。例如,大多数编译器拒绝内联”复杂”的函数(例如,包含循环和递归的函数);还有,即使是最简单的虚函数调用,编译器的内联处理程序对它也爱莫能助。(这一点也不奇怪。virtual的意思是”等到运行时再决定调用哪个函数”,inline的意思是”在编译期间将调用之处用被调函数来代替”,如果编译器甚至还不知道哪个函数将被调用,当然就不能责怪它拒绝生成内联调用了)。

内联函数与宏

C语言使用预处理语句来提供宏,这其实是内联代码的原始实现。但宏定义并不是通过传递参数来实现的而是直接的文本替换。
如:

    #define SQUARE(X) X*Xa=SQUARE(4.5+7.5);//a=4.5+7.5*4.5+7.5

宏不能按值传递,如果宏要执行类似函数的功能,应考虑转化为C++内联函数。

使用内联函数的注意事项

  • 不要过度使用内联函数,尤其是大函数
  • 内联函数中不要含有循环、判断、选择、递归。
  • 内联成员函数可以访问成员变量(public private protected),除了比普通成员函数执行快之外,不要刻意区分内联成员函数和普通成员函数。

 //8.1 inline.cpp -- 内联函数的使用#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 expressionscout << "a = " << a << ", b = " << b << "\n";cout << "c = " << c;cout << ", c squared = " << square(c++) << "\n";cout << "Now c = " << c << "\n";// cin.get();return 0;  }

引用变量

引用是已定义变量的别名,主要用途是用作函数的形参。通过引用变量,函数将使用原始数据,而不是临时数据,对处理大型结构和设计类来说是必不可少的。

创建引用变量

注意:在创建引用变量的必须同时进行初始化。

 int rat;int &rodent;rodent = rat;//这样写是错误的

引用更接近于const指针,对于

 int &rodents=rats;

实际上是下述代码的伪装表示

    int *const pr =&rats;

将引用变量用作函数参数

这种传递参数的方法称为按引用传递,允许函数能够访问和调用函数中的变量。C语言只能按值传递,函数只能使用变量的副本,当然如果使用指针的话就能避开按值传递的限制。
下图显示了按值传递和按引用传递的区别。
Markdown

引用的属性和特别之处

 // 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;// cin.get();return 0;}double cube(double a){a *= a * a;return a;}double refcube(double &ra){ra *= ra * ra;return ra; }

该程序输出为:

27 = cube of 3
27 = cube of 27

该程序体现了按值传递与按引用传递的区别,如果我们的意图是想让函数使用传递给它的信息而不对这些信息进行修改,同时想使用引用,则应使用常量引用。如下

     double refcube(const double &ra)

传递引用比按值传递的限制更加严格,如下的代码就是不合理的

    double z=refcube(x+3.0)

函数的实参应当为变量,而x+3.0并不是一个变量,在现代C++中这是错误的。

应尽可能使用const

理由有三:

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

有关临时变量、引用参数和const详见书262页

将引用用于结构

 //strc_ref.cpp -- using structure references#include <iostream>#include <string>struct free_throws{std::string name;int made;int attempts;float percent;};void display(const free_throws & ft);void set_pc(free_throws & ft);free_throws & accumulate(free_throws &target, const free_throws &source);int main(){free_throws one = {"Ifelsa Branch", 13, 14};free_throws two = {"Andor Knott", 10, 16};free_throws three = {"Minnie Max", 7, 9};free_throws four = {"Whily Looper", 5, 9};free_throws five = {"Long Long", 6, 14};free_throws team = {"Throwgoods", 0, 0};free_throws dup;set_pc(one);display(one);accumulate(team, one);display(team);// use return value as argumentdisplay(accumulate(team, two));accumulate(accumulate(team, three), four);display(team);// use return value in assignmentdup = accumulate(team,five);std::cout << "Displaying team:\n";display(team);std::cout << "Displaying dup after assignment:\n";display(dup);set_pc(four);// ill-advised assignmentaccumulate(dup,five) = four;std::cout << "Displaying dup after ill-advised assignment:\n";display(dup);// std::cin.get();return 0;}void display(const free_throws & ft){using std::cout;cout << "Name: " << ft.name << '\n';cout << "  Made: " << ft.made << '\t';cout << "Attempts: " << ft.attempts << '\t';cout << "Percent: " << ft.percent << '\n';}void set_pc(free_throws & ft){if (ft.attempts != 0)ft.percent = 100.0f *float(ft.made)/float(ft.attempts);elseft.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;}

为何要返回引用?

传统返回机制将返回值复制到一个临时位置,而调用程序将使用这个位置的值。返回引用将直接返回被引用的变量,其效率更高。

返回引用时应注意的问题

避免返回一个指向临时变量的引用,也就是向下面这样的。

    const free_throws & clone2(free_throws & ft){free_throws newguy;newguy=ft;return newguy;}

为何将const用于返回类型

在返回类型中省略const可以编写更简短代码,但其含义也更模糊,为避免犯错,应使用const。

将引用用于类对象

将类对象传递给函数时,C++通常做法是使用引用。下面三个函数是个例子,函数的作用为将指定的字符串加入另一个字符串的前面和后面。

  /* function a */string version1(const string & s1, const string & s2){string temp;temp = s2 + s1 + s2;return temp;}/* function b */const string & version2(string & s1, const string & s2)   // has side effect{s1 = s2 + s1 + s2;// safe to return reference passed to functionreturn s1; }/* function c */const string & version3(string & s1, const string & s2)   // bad design{string temp; temp = s2 + s1 + s2;// unsafe to return reference to local variablereturn temp;}
function编译运行解释
a通过正确使用引用不需要创建新的string对象,提高了效率,使用const限定确保不被修改
b通过正确s1未加const限定将直接修改s1,这就是此函数的副作用
cwarning: reference to local variable ‘temp’ returned输入字符串程序崩溃引用指向了一个局部变量,而此局部变量函数结束后即被释放

对象、继承和引用

没怎么看懂,— —,略过。

何时使用引用参数

默认参数

请看下面代码:

    // left.cpp 截取某一字符串前n个字符#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];  // copy characterswhile (i <= n)p[i++] = '\0';  // set rest of string to '\0'return p; }

函数 char * left(const char * str, int n = 1);的作用是截取一个字符串前n个字符,该函数为参数n设置默认参数1。left(hello);将调用默认参数。

在函数原型中指定默认值即可,函数定义不用指定。

对于一个含有多个默认参数的函数来说,必须从右边第一个参数开始添加向左添加,中间不能包含非默认参数。而且在给默认参数赋值时必须从左边第一个默认参数开始赋值,然后依次向右,中间不能跳过任何参数。 下面这个写法就是错误的:

 void f(int a,int b=1;int c=2);f(1, , 3);

函数重载

函数重载的关键就是函数的参数列表——也成为函数特征标(function signature)。请看下面一组原型:

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

这是一个函数重载的例子,如果有下列语句:

  unsigned int year=222;print(year6);

在五个重载的函数原型中,没有一个与之匹配,所以C++会尝试使用强制转换,将该函数与最相近的函数进行匹配进行匹配。 值得注意的是,编译器在检查函数特征标的时候将把类型引用和类型本身视为同一个特征标。

该书中277页有关于const和非const变量重载的描述似乎有些错误,而且描述十分模糊,简单的来说,只有指针和引用才能重载。

 //不重载void f(char );void f(const char );//重载void f(char * );void f(const char* );

何时使用函数重载

能使用默认参数就使用默认参数,否则使用函数重载。

什么是名称修饰

C++为了跟踪每一个重载函数,会对函数进行名称修饰(name decoration)或称名称矫正(name mangling),它根据函数原型中指定的形参类型对每个函数名进行加密。 例如:

 long MyFunctionFoo(int,float);

修饰为:

MyFunctionFoo@@YAXH

编译器将对函数进行编码,编码根据函数参数数目和类型不同而不同。

函数模板

函数模板是通用的函数描述,它使用泛型来定义函数,其中的泛型可用具体的类型来替换。通过将类型作为参数传递给模板,可使编译器自动生成该类型的函数。因此模板有时也被称为通用编程

由于类型是用参数来表示的,因此模板特性有时也被称为参数化类型(parameterized types)

下面是一个例子:

 template <typename T>  // or class Tvoid Swap(T &a, T &b){T temp;   // temp a variable of type Ttemp = a;a = b;b = temp; }

template为模板关键字,template之后必须使用尖括号。在平时的使用中class比typename似乎使用的更多,因为在之前的C++标准中并未包含typename这个关键字。为了避免此处使用class带来的混淆,所以引入了typename,但这里的typename并不完全等价于class,typename另外一个作用为:使用嵌套依赖类型(nested depended name)。C++ primer plus这本书对此也没有过多解释,所以我们就暂时了解到这里。

模板并不创建任何函数,只是告诉编译器如何定义函数。模板也并不能缩短可执行程序,最终的代码不包含任何模板,而只包含为程序而生成的实际函数。使用模板的好处是在需要生成多个函数时函数的定义更简单、更可靠。

重载的模板

模板也可以重载。

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

从第二个模板函数可以看出,并非所有的模板参数都必须是模板参数类型。

显示具体化(explicit specialization)

C++98给出的具体化方法:

  • 对于给定的函数名,可以有非模板函数、模板函数和显示具体化函数以及他们的重载版本。
  • 显示具体化的原型和定义应以template<>打头,并通过名称来指出类型。
  • 具体化优于常规模板,而非模板函数优于具体化和常规模板。
    下面是用于交换结构体job的非模板函数、模板函数和具体化的原型。
    void Swap(job &, job &);//非模板template <class T>//模板void Swap(T &, T &);template <> void Swap<job>(job &, job &);//具体化

显示具体化中Swap中的是可选的,因为函数的参数类型表明,这是job的一个具体化。因此函数原型也可以这样写:

     template <> void Swap(job &, job &);

实例化和具体化

编译器使用模板为特定类型生成函数定义时得到的是模板实例

下面这种实例化的方式称为隐式实例化。

  template <typename T>void Swap(T &a, T &b) {T temp;temp = a;a = b;b = temp;}int i=10,j=20;Swap(i,j);

以前C++只允许隐式实例化,现在还允许显示实例化,即直接命令编译器创建特定的实例。如:

    template void Swap<int>(int &,int &);//使用Swap()模板生int类型的函数定义(不用再额外定义)

而显示具体化是像下面这样的:

     template <> void Swap<int>(int &, int &);template <> void Swap(int &, int &);

声明上面的函数以后,编译器就不会使用常规模板来为int生成定义,而是专门的函数定义。

注意:试图在同一个文件(或转换单元)中使用同一种类型的显示实例和显示具体化将出错。

隐式实例化、显示实例化和显示具体化统称为具体化。

编译器会怎么样选择函数版本

对于函数重载、函数模板、函数模板重载的调用策略的过程称为重载解析,大致过程如下:
Markdown

编译器首先确定名称相同、参数数目相同和参数能够被强制转换的可行函数,然后根据以下所需要进行的转化从最佳到最差来匹配:

  1. 完全匹配,但常规函数优于模板
  2. 提升转换(例:char和short自动转化为int,float自动转换为double)
  3. 标准转换(例:int转换为char,long转换为double)
  4. 用户定义的转换,如类声明中定义的转换

完全匹配和最佳匹配

进行完全匹配时C++允许某些“无关紧要的转换”。
Markdown

具体有关理解参看C++ primer plus 291页。

关键字decltype

C++11新标准,后面再讨论。

PS:第一次写这么长的博客,花了很多时间也挺累的但是收获很大。

这篇关于C++PrimerPlus第八章学习笔记——函数探幽的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/888357

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名