首页 > 编程语言 >C++学习笔记——函数探幽

C++学习笔记——函数探幽

时间:2023-12-01 19:57:27浏览次数:41  
标签:const 函数 int double 笔记 C++ 类型 探幽 模板

C++内联函数

  内联函数是一种用空间换时间的技术,是C++提高程序运行速度做的改进。运行程序时操作系统将指令载入计算机内存中,并逐条执行这些指令,遇到循环或分支时向前或向后跳转到特定的地址(每条指令都有特定的内存地址)。常规函数也是如此,在调用常规函数时立即存储该指令的地址,并跳转到函数的地址,在函数执行结束后返回到跳转之前的地址继续往下执行。来回的跳跃并记录跳跃位置需要花费一定的开销。

  与常规函数不同,如果使用内联函数,编译器会使用相应的函数代码替换函数调用,此时程序无需跳到另一个位置执行代码,再跳回来。因此内联函数运行速度比常规函数快。但相应的需要占用更多的内存,所以内联函数适合代码量较小的函数,且不能是递归函数。

 

1 inline double square(double x) { return x * x; }

 

  inlien是C++新增的特性,C使用预处理语句#difine来提供宏,以此来达到内联函数的功能。但是宏是单纯的文本替换,不能按值传递。

 

引用变量

  引用变量是C++新增的一种复合类型。引用变量的主要作用是作为函数的形参,此时函数将使用原始数据,而不是副本。提高函数处理大型数据结构的效率(常用于结构、类)。引用和指针很像,表达式 b 和 *p 都可以与 a 互换, &b 和 p 可以和 &a 互换。但在声明引用时必须初始化,指针可以先声明,后赋值。

1 int a = 10;
2 int& b = a;   //声明int类型引用,并将b作为a的别名, int* const b = &a;
3 int* p = &a;

  使用引用作为函数参数时,函数参数中的变量名成为调用程序中的变量别名。按引用传递可以让被调用函数直接访问调用函数中的原始数据,而不是其副本。函数调用使用实参初始化形参。如果想使用引用参数而又不想对原始数据造成影响,则使用常量引用。

1 double refcube(const double& x);

  如果传递的实参与形参类型不匹配,但是可以转换为正确的类型,或者实参的类型正确,但不是左值。C++将生成临时变量(前提形参为const引用),将实参的值传递给临时变量,并将const引用指向临时变量。且临时变量只在该函数调用期间存在。因为如果是使用参数const引用的函数,表明该函数不对原始数据进行修改,而是使用,此时生成临时变量不会造成不利的影响。

  传统的返回机制与按值传递参数类似,计算 return 后面表达式的值,概念上来说,这个值被复制到一个临时位置,然后调用程序使用这个值(编译器可能会优化)。返回引用时直接复制,效率更高。同时要避免返回临时变量的引用,简单的做法是返回作为参数传递给函数的引用。或者使用 new 来分配新的存储空间。

  将返回类型声明为 const 引用可以避免将函数调用作为左值。

  当引用的对象是类,且该类的派生类采用公有继承时,基类的引用可以指向基类或派生类对象(指针同理)。如果数据对象时数组,则使用指针是唯一的选择

 

默认参数

  通过函数原型设置函数参数默认值,同时必须从右向左提供默认值,中间不能跳过任何参数。

1 char* left(const char* str, int n = 1);

 

函数重载

  默认参数使得可以使用不同数目的参数调用同一个函数,函数重载允许使用多个同名的函数——完成相同工作,但使用不同的参数列表。通过上下文确定要使用的函数重载版本。要定义多个同名的函数,它们的特征标(参数数目、参数类型)必须不同,变量名无关紧要。

  如果传递的参数列表不匹配任何函数原型,C++将尝试使用标准类型转换强制进行匹配,此时若有多个函数原型可以转换匹配,C++将拒绝这中函数调用,并将其视为错误。同时,C++将类型类型引用视为同一个特征标。匹配函数时,不区分const和非const变量。

1 double cube(double x);
2 double cube(double& x);
3 
4 count << cube(x); //同时匹配 double x 原型和 double& x 原型

   C++如何跟踪每个重载函数呢?C++对函数的编码方式不同,C++编译器把函数原型中的形参对函数名进行修饰,以此来区别函数名相同的函数。修饰时使用的约定根据编译器而异。

 

函数模板

  使用泛型来定义函数,泛型可用具体的类型替换,将类型作为参数传递给模板,编译器根据模板和传递的参数生成相关的函数定义。注意函数模板不是函数。

  template告诉编译器要定义一个模板,后面跟着一对尖括号代表模板参数列表,typename(或class)指出 T 是一个接收类型的类型参数,这里的 T 起着占位符的作用,如果传给 T 的类型为 int, 则所有 T 替换为 int。模板不创建任何函数,只是告诉编译器如何定义函数。

  在第 11 行代码中,编译器会检查使用的参数类型,并生成相应的函数,此处生成 int 版本的Swap函数。注意函数模板不能缩短可执行程序,而且模板的定义放在头文件中。

 1 template <typename T>
 2 void Swap(T& a, T& b) {
 3     T temp;
 4     temp = a;
 5     a = b;
 6     b = temp;
 7 }
 8 
 9 
10 int i = 10, j = 20;
11 Swap(i, j);

  当然模板也支持重载,模板参数列表中也并非必须为类型参数,如下面第四个模板声明。

 1 template <class T>
 2 void Swap(T& a, T& b);
 3 
 4 template <class T>
 5 void Swap(T& a, T& b, T& c);
 6 
 7 template <typename T>
 8 void Swap(T* a, T* b);
 9 
10 template <typename T>
11 void Swap(T a[], T b[], int n);

   模板也有一些局限性,可能无法处理某些类型。如果 T 为数组,则下面模板中的赋值、if条件语句、T c = a*b语句都不成立。解决的一种办法是为特定类型提供具体化的模板定义。

1 template<typename T>
2 void func(T a, T b) {
3     a = b;
4     if (a > b) {
5         ...
6     }
7 
8     T c = a * b;
9 }

  提供具体化模板函数的定义称为显示具体化。对于给定的函数名,可以有非模板函数、模板函数和具体化模板函数以及函数的重载版本。显示具体化模板函数的原型和定义以 template<> 开头,通过名称指出类型。下面分别为非模板函数、模板函数、具体化模板函数,编译器在选择原型时,优先级为:非模板函数 > 显示具体化模板函数 > 模板函数

 1 struct job {
 2     char name[40];
 3     double salary;
 4     int floor;
 5 };
 6 
 7 void Swap(job a, job b);
 8 
 9 template<typename T>
10 void Swap(T&, T&);
11 
12 template<> void Swap<job>(job&, job&); //Swap<job>(job&, job&)中的<job> 可选,因为函数的参数类型已经表明这是 job 的一个具体化

   代码中包含模板本身不会生成函数定义,编译器使用模板为特定类型生成函数定义时,得到模板实例。模板不是函数定义,但是使用 int的模板实例是函数定义(隐式实例化)。显式具体化和显式实例化的区别在于,显式具体化声明在template后包含<>, 而显式实例化没有。可以在文件中使用函数来创建显式实例化,如 std::cout << Swap<double>(a, b) << std::endl; 。在同一个文件中,同一类型显式实例化和显示具体化不能同时存在。

  对于函数重载、函数模板和函数模板重载,C++通过重载解析来确定为函数调用哪个定义。首先,编译器创建候选函数列表,从候选函数列表创建可行的函数列表,再从可行的函数列表中选择最佳的可行函数。最佳到最差的顺序:完全匹配 > 提升转化 > 标准转化 > 用户自定义转化。如果有多个匹配的原型,则编译器可能无法完成重载解析的过程。但有时候,即使两个函数都完全匹配,仍然可以完成重载解析,比如 const 和 非const 之间的区别(只适用于指针和引用指向的数据,如下所示)。

 

1 void func(int);
2 void func(const int);  //编译器报错,出现二义性错误
3 
4 void func(int&);
5 void func(const int&); //const 和 非const的区别只适用于指针和引用

 

  术语“最具体”并不一定意味着显示具体化,而是指编译器推断使用哪种类型时,执行的转换最少。

1 template<class T> void recycle(T t);  #1
2 template<class T> void recycle(T* t); #2
3 
4 recycle(&link); //recycle(&link)调用会和 #2 模板匹配,因为在生成过程中,它需要进行的转换更少

  在使用模板时,可能会出现变量的类型不确定的情况。C++11新增关键字decltype可以解决这种情况。

 1 template<class T1, class T2>
 2 void ft(T1 x, T2 y) {
 3     ...
 4     ?xpy? = x + y; //变量xpy的类型是? 解决办法是使用C++11新增的关键字decltype
 5     //decltype(x + y) xpy = x + y;
 6     ...
 7 }

  decltype的处理实际上更复杂一些。

 

 1     /*
 2         decltype(expression) var;
 3     */
 4     
 5     //如果exression是一个没有用括号括起的标识符。
 6     double x = 5.5;
 7     double y = 7.9;
 8     double& rx = x;
 9     const double* pd;
10       
11     decltype(x) w;              // w的类型为double
12     decltype(rx) u = y;         // u的类型为double&
13     decltype(pd) v;             // v的类型为const double*
14 
15     //如果expression是一个函数调用
16     long indeed(int);           //声明indeed函数
17     decltype(indeed(3)) m;      //m的类型为indeed函数的返回类型(long),实际上编译器不会调用函数,只是会查看函数的原型
18 
19     //如果expression是用括号括起的标识符且是一个左值,则var为指向其类型的引用
20     double xx = 4.4;
21     decltype ((xx)) r2 = xx;   //r2的类型为double&
22     decltype (xx) w = xx;      //w的类型为double
23 
24     //如果前面的条件都不满足,var的类型与expression的类型相同
25     int j = 3;
26     int& k = j;
27     int& n = j;
28     decltype(j + 6) i1;            //i1的类型为int
29     decltype(100L)  i2;            //i2的类型为long
30     decltype(k + n) i3;            //i3的类型为int, k和n虽然为引用,但是表达式 k+n 是两个int类型的和

  解决了函数模板中函数体的变量类型问题后,还有函数模板返回类型的问题。如下所示,无法直接使用 decltype(x + y),因为此时还未声明参数x和y,必须在声明参数x,y后才能使用decltype。

1 template<class T1, class T2>
2 ?type? func(T1 x, T2 y) {   //函数的返回类型是?
3     return x + y;
4 }

  解决方法是使用后置返回类型。此时decltyep在参数声明后面,x和y位于作用域内,可以使用它们。

1 double h(int x, int y);
2 auto h(int x, int y) -> double; //后置返回类型,auto是一个占位符
3 
4 template<class T>
5 auto func(T x, T y) -> decltype(x + y) {
6     return x + y;
7 }

 

 

 

 

 

  

 

标签:const,函数,int,double,笔记,C++,类型,探幽,模板
From: https://www.cnblogs.com/owmt/p/17861175.html

相关文章

  • 《深度学习入门——自制框架》读书笔记
    1.自动微分step2创建变量的函数#箱子类,存放一个变量数据classVariable: def__init__(self,data): self.data=data#函数类的基类classFunction:#__call__方法是一个特殊的Python方法。#定义了这个方法后,当f=Function()时,就可以通过编写f(...)来......
  • 计算机网络笔记第一章
    计算机网络一、计网体系结构计算机网络概述计算机网络:是一个将分散的、具有独立功能的计算机系统,通过通信设备与线路连接起来,由功能完善的软件实现资源共享和信息传递的系统。计算机网络是互连的,自治的计算机集合。互连:通过通信链路互联互通。自治:无主从关系计算机网络功能......
  • 2023年11月30日阅读笔记
    《白帽子讲web安全》为何要了解Web安全不遵守整洁代码之道和安全系统之道的系统就像一颗定时炸弹,你不知道它什么时候就会爆炸又或者是虚晃一枪,又让我想起整洁代码之道一书的封面这张图是M104:草帽星系,其核心是一个质量超大的黑洞,有100万个太阳那么重,环绕着M104的光环就......
  • error: Microsoft Visual C++ 14.0 or greater is required
    1、错误背景python在安装aiohttp库时,出现MicrosoftVisualC++14.0orgreaterisrequired的提示:2、解决方案按照错误提示,访问https://visualstudio.microsoft.com/visual-cpp-build-tools/,下载生成工具:执行下载的exe执行文件:选择使用C++桌面开发,选......
  • .NET Core|--调用C++库|--docker环境下让web api应用程序调用C++类库
    前言#前提安装docker环境~启动docker~#多说一句,为什么我要搞这个一个镜像,既包含gcc开发环境,又包含.NET开发环境我的api应用程序是基于.NET写的,但是我的这个api程序,又要调用c++的一些东西,特别是涉及一些画图之类的,所以就需要gcc的开发环境,最终搞了这么一......
  • LLM 入门笔记-Tokenizer
    以下笔记参考huggingface官方tutorial:https://huggingface.co/learn/nlp-course/chapter6下图展示了完整的tokenization流程,接下来会对每个步骤做进一步的介绍。1.Normalizationnormalize其实就是根据不同的需要对文本数据做一下清洗工作,以英文文本为例可以包括删除......
  • 硬件笔记之MacOS打印不清楚或者打印字体发虚
    参考帖子: 求助!!!有人遇到过macOS下打印不清晰吗 此处做个测试说明,系统为Monterey和Ventura,打印机为惠普黑白打印机,即使设置为普通PCL打印机,效果还是发虚有毛边;以下为移除打印机,再次添加打印机后的操作。按照如下设置,让mac自动选择打印机信息后,会自动设置为普通PostScript打印......
  • Unity学习笔记--数据持久化XML文件(2)
    IXmlSerializable接口:使用该接口可以帮助处理不能被序列化和反序列化的特殊类得到处理,使特殊类继承IXmlSerializable接口,实现其中的读写方法,检测到读写方法被重写之后,便会按照自定义实现的接口来实现方法。usingSystem;usingSystem.IO;usingSystem.Runtime.InteropServi......
  • 编译C++程序调用dll的方法
    在拥有.cpp源文件的情况下,调用其它dll并生成exe的方法第一步:新建C++空项目。 第二步:将源文件放到项目根目录路径下,并在项目的源文件下添加现有项,将源文件添加进项目。第三步:在项目根目录下创建include文件夹,将需要被调用的dll的.h头文件放入该文件夹。第四步:在项目根目......
  • 圆锥曲线做题笔记
    大致只会写思路而非详细过程。新高考I卷2022点\(A(2,1)\)在双曲线\(C:\frac{x^2}{a^2}-\frac{x^2}{a^2-1}=1\(a>1)\)上,直线\(l\)交\(C\)于\(P,Q\)两点,\(k_{AP}+k_{AQ}=0\)。求\(l\)的斜率;若\(\tan\anglePAQ=2\sqrt{2}\),求\(\triangleAPQ\)的面积。......