C++入门(二):函数重载、引用、const引用和 inline 内联函数 C入门二函数重载、引用、const引用和 inline 内联函数 星恒随风个人主页❄️ 个人专栏《指针合集》《C语言基础》《数据结构》《机器学习导论》《前端基础》《python基础》✨ 数据即知识压缩即智能目录C入门二函数重载、引用、const引用和 inline 内联函数一、函数重载是什么二、函数重载的三种常见形式1. 参数类型不同2. 参数个数不同3. 参数类型顺序不同三、返回值不同不能构成重载四、缺省参数和函数重载可能产生歧义五、函数重载的本质理解六、引用是什么七、引用和取地址符都用 怎么区分八、引用的三个基本特性1. 引用定义时必须初始化2. 一个变量可以有多个引用3. 引用一旦绑定不能再引用其他对象九、引用传参比指针传参更直观十、引用传参还能减少拷贝十一、引用返回返回的不是值而是对象本身十二、引用返回的坑不能返回局部变量引用十三、const 引用是什么十四、const 引用可以绑定临时对象十五、指针和引用的关系十六、inline 内联函数是什么十七、inline 只是建议不是命令十八、inline 和宏函数有什么关系十九、inline 函数一般放在哪里MathUtil.h二十、总结一、函数重载是什么C 语言中同一个作用域中不能定义两个同名函数。但是 C 支持函数重载。函数重载指的是在同一作用域中可以存在多个同名函数但它们的参数列表必须不同。参数列表不同主要包括参数类型不同参数个数不同参数类型顺序不同示例#includeiostreamusingnamespacestd;// 参数类型不同intAdd(intleft,intright){coutint Add(int, int)\n;returnleftright;}doubleAdd(doubleleft,doubleright){coutdouble Add(double, double)\n;returnleftright;}intmain(){coutAdd(10,20)\n;coutAdd(1.1,2.2)\n;return0;}输出int Add(int, int) 30 double Add(double, double) 3.3虽然两个函数都叫Add但参数类型不同所以构成函数重载。二、函数重载的三种常见形式1. 参数类型不同intAdd(intx,inty){returnxy;}doubleAdd(doublex,doubley){returnxy;}调用时Add(1,2);// 调用 int 版本Add(1.1,2.2);// 调用 double 版本编译器会根据实参类型选择最匹配的函数。2. 参数个数不同voidPrint(){coutPrint()\n;}voidPrint(intx){coutPrint(int)\n;}调用Print();// 调用无参版本Print(10);// 调用一个 int 参数版本3. 参数类型顺序不同voidFunc(inta,charb){coutFunc(int, char)\n;}voidFunc(charb,inta){coutFunc(char, int)\n;}调用Func(10,a);// 调用 Func(int, char)Func(a,10);// 调用 Func(char, int)虽然参数个数一样类型也一样但是类型顺序不同也可以构成重载。三、返回值不同不能构成重载下面这种写法是不允许的voidFunc(){}intFunc(){return0;}原因是函数调用时编译器无法仅根据返回值判断你到底想调用哪个函数。例如Func();这句代码没有接收返回值编译器不知道应该调用void Func()还是int Func()。所以函数重载只看函数名和参数列表不靠返回值区分。四、缺省参数和函数重载可能产生歧义看下面代码voidf(){coutf()\n;}voidf(inta10){coutf(int)\n;}intmain(){f();// 这里会产生歧义return0;}为什么因为f()既可以匹配无参版本voidf()也可以匹配带缺省参数的版本voidf(inta10)编译器无法判断你想调用哪个于是报错。所以函数重载和缺省参数一起用时要格外小心。建议如果缺省参数已经能解决问题就不要再写容易冲突的重载版本。五、函数重载的本质理解函数重载不是运行时才决定的。它是在编译阶段由编译器根据参数类型、参数个数等信息决定调用哪个函数。所以它属于编译时多态。你可以简单理解为同一个函数名可以根据参数不同表现出不同形态。这让函数接口更自然。比如Add(1,2);Add(1.1,2.2);我们都想表达“加法”没必要写成AddInt(1,2);AddDouble(1.1,2.2);C 用函数重载让代码更符合人的表达习惯。六、引用是什么引用是 C 非常重要的语法。引用不是新定义一个变量而是给已经存在的变量取一个别名。语法类型引用名被引用对象;示例#includeiostreamusingnamespacestd;intmain(){inta10;intba;intca;couta\n;coutb\n;coutc\n;b20;couta\n;coutb\n;coutc\n;return0;}输出10 10 10 20 20 20这里b和c都是a的别名。修改b其实就是修改a。可以把它理解成同一个人有多个名字。比如一个人本名叫“李逵”外号叫“黑旋风”别人也叫他“铁牛”。名字不同但指向的是同一个人。七、引用和取地址符都用怎么区分C 中引用使用intba;C 语言中取地址也使用a这两个不是同一个语义。区分方式看上下文intba;// 这里的 是引用声明couta;// 这里的 是取地址在类型后面出现intb表示引用类型。在表达式前面出现a表示取地址。八、引用的三个基本特性引用有三个重要特性1. 引用定义时必须初始化错误写法intra;// 错误引用必须初始化正确写法inta10;intraa;引用不是“空别名”。既然是别名就必须一开始说明它是谁的别名。2. 一个变量可以有多个引用inta10;intba;intca;intdb;这里b、c、d本质上都是a的别名。3. 引用一旦绑定不能再引用其他对象看下面代码inta10;intb20;intraa;rab;很多我们容易误以为ra 改成引用 b 了。其实不是。这句rab;表示把b的值赋给ra也就是赋给a。最终结果是a20b20但ra仍然是a的引用没有改绑到b。九、引用传参比指针传参更直观C 语言中如果想在函数中交换两个变量的值通常使用指针voidSwap(int*px,int*py){inttmp*px;*px*py;*pytmp;}调用时Swap(x,y);C 可以使用引用传参voidSwap(intrx,intry){inttmprx;rxry;rytmp;}调用时Swap(x,y);完整示例#includeiostreamusingnamespacestd;voidSwap(intrx,intry){inttmprx;rxry;rytmp;}intmain(){intx0;inty1;coutx y\n;Swap(x,y);coutx y\n;return0;}输出0 1 1 0引用传参的效果和指针传参类似都能在函数内部修改外部变量。但引用写起来更自然。十、引用传参还能减少拷贝如果函数参数是一个大对象按值传参会产生拷贝。例如voidPrint(vectorintv){// 会拷贝整个 vector}更好的写法是voidPrint(constvectorintv){// 不拷贝同时不允许修改 v}这个写法在 C 中非常常见。它的优点是使用引用避免拷贝使用const防止函数内部误修改接口语义更清楚。虽然现在还没正式学习vector不过目前你可以把这个当成一个数组但可以先记住这个常见写法const类型参数名它通常表示只读引用传参。十一、引用返回返回的不是值而是对象本身引用不仅可以做参数也可以做返回值。比如栈顶函数intTop(int*a,inttop){returna[top-1];}调用intarr[5]{1,2,3,4,5};inttop5;Top(arr,top)10;coutarr[4]\n;输出15为什么可以这样因为Top返回的是arr[top - 1]的引用也就是这个元素本身的别名。所以Top(arr,top)10;等价于arr[4]10;十二、引用返回的坑不能返回局部变量引用错误示例intBad(){intx10;returnx;}为什么错误因为x是函数内部的局部变量。函数结束后x的生命周期就结束了。你返回它的引用相当于返回了一个已经失效对象的别名。这会造成悬空引用。正确原则只有当返回对象在函数结束后仍然存在时才可以返回引用。可以返回全局变量的引用静态变量的引用参数对象中的成员引用容器内部仍然有效元素的引用。不要返回局部变量的引用临时对象的普通引用。十三、const 引用是什么有时候我们不希望通过引用修改对象。这时可以使用 const 引用。constintraa;示例inta10;constintraa;// ra 20; // 错误不能通过 const 引用修改对象如果被引用对象本身是const必须用 const 引用constinta10;// int ra a; // 错误权限放大constintraa;// 正确为什么普通引用不行因为intraa;意味着可以通过ra修改a。但a是 const 对象本来就不允许修改。所以这是权限放大编译器不允许。十四、const 引用可以绑定临时对象看下面代码inta10;// int rb a * 3; // 错误constintrba*3;// 正确a * 3的结果不是一个有名字的变量而是一个临时对象。普通引用不能绑定这种临时对象。但是 const 引用可以绑定临时对象。类似constintr30;这也是合法的。可以简单理解为临时对象不能被普通引用修改但可以被 const 引用只读访问。十五、指针和引用的关系指针和引用很像但不是一回事。示例inta10;intb20;int*pa;pb;// 指针可以改变指向intra;rb;// 不是改变引用对象而是把 b 的值赋给 a十六、inline 内联函数是什么inline用来修饰函数。被inline修饰的函数叫内联函数。示例inlineintAdd(intx,inty){returnxy;}它的思想是编译器可以在函数调用处直接展开函数体从而减少函数调用开销。普通函数调用大致要经历建立栈帧参数传递跳转到函数地址执行函数返回调用位置对于非常短小、频繁调用的函数这些调用开销可能比函数本身还大。所以 C 提供了inline。十七、inline 只是建议不是命令inline对编译器来说更像一个建议你可以把这个当成一种更安全的宏定义。编译器可以选择展开也可以选择不展开。一般适合 inline 的函数函数体很短调用频率高逻辑简单。不适合 inline 的函数函数体很长有复杂循环递归函数代码膨胀风险大。例如inlineintMax(intx,inty){returnxy?x:y;}这种比较适合写成inlin函数。十八、inline 和宏函数有什么关系C 语言中为了减少函数调用开销常用宏函数#defineSQUARE(x)((x)*(x))但是宏有很多问题不做类型检查容易因为括号写错导致优先级问题参数可能被多次求值不方便调试。例如#defineSQUARE(x)((x)*(x))inta5;coutSQUARE(a)\n;宏展开后a可能被执行两次结果不一定符合预期。C 的inline更安全inlineintSquare(intx){returnx*x;}所以可以粗略理解为inline 是 C 用来替代一部分宏函数的更安全方案。十九、inline 函数一般放在哪里如果一个函数要作为 inline 函数使用通常把定义放在头文件中。例如MathUtil.h#pragmaonceinlineintAdd(intx,inty){returnxy;}为什么因为内联展开要求编译器在调用点看到函数定义。如果只在头文件中放声明inlineintAdd(intx,inty);然后把定义放到.cpp中其他源文件包含头文件时可能看不到函数体导致无法正确内联甚至产生链接问题。二十、总结这一篇主要讲了 C 入门中非常重要的几个增强语法。函数重载同一作用域中允许同名函数要求参数列表不同返回值不同不能构成重载重载解析发生在编译阶段。引用引用是已有变量的别名引用定义时必须初始化引用一旦绑定不能再绑定其他对象引用传参可以修改外部变量也能减少拷贝引用返回可以返回对象本身但不能返回局部变量引用。const 引用可以引用 const 对象可以引用普通对象但不能通过引用修改可以绑定临时对象常用于只读传参避免拷贝。指针和引用指针保存地址可以改变指向引用是别名不能改变绑定对象指针更灵活引用更安全直观。inline适合短小、频繁调用的函数只是对编译器的建议不保证一定展开比宏函数更安全通常定义在头文件中。