C++(13)--函数的进阶:内联、传递引用、参数默认值、重载、函数模板

模块化编程--函数的进阶

  • 1.内联函数
    • 1.1 inline基本情况
    • 1.2 inline 的前世今生-带参的宏替换
  • 2.传递引用(重点)
    • 2.1引用、理由、注意事项
    • 2.3 交换两个变量的数值
  • 3.返回引用
    • 3.1不要返回局部变量的引用
    • 3.2函数可以不返回值,默认返回传入的引用对象本身
    • 3.3返回引用时,要求函数的参数中必须包含被返回的引用对象
  • 4.参数的默认值
  • 5.随堂练习--使用函数实现游戏中的私聊
  • 6.函数重载(重点)
  • 7.函数模版(难点)

《老九学堂C++课程》《C++ primer》学习笔记。《老九学堂C++课程》详情请到B站搜索《老九零基础学编程C++入门》
-------------简单的事情重复做,重复的事情用心做,用心的事情坚持做(老九君)---------------

1.内联函数

1.1 inline基本情况

在大多数的机器上,调用函数都要做很多工作;调用前要先保存寄存器,并在返回时恢复;复制实参;程序还必须转向一个新位置执行。在想利用函数的好处(代码复用,修改效率高)但又想避免函数调用的时间资源消耗,就可以使用内联函数。

内联(inline)函数:(内部联结)是C++为了提高程序运行速度所做的一项改进(C中没有),与常规函数的主要区别在于被调用时的运行机制不同(编写方式是一致的,在函数头部多一个incline关键字)

常规函数运行机制:依据函数名找到函数具体实现处,执行函数
内敛函数运行机制:编译器使用函数代码替换函数调用

内联函数:在函数声明处/函数定义时,加关键字;二者取其一即可。

inline void func1(){cout << 1;cout << 2;}
int main(){func1();
}

在编译将展开函数func1():

int main(){cout << 1;cout << 2;
}

如果 执行函数代码的时间 比 处理函数调用的时间 长,则节约的时间只占整个过程的很小一部分。

建议函数较短的时候使用内联,可以节省函数调用的时间。如果函数较长,将会耗费较多的内存空间(存代码呀,后续之后可能会用在类中用到)

如果函数体太长的话,编译器可能会选择忽略inline关键字,转而做普通函数调用

1.2 inline 的前世今生-带参的宏替换

C时代,如何实现内联呢?内敛等价于替换,C中常用的替换操作是宏定义

#include <iostream>
#define N 5 // 以后在所有使用到N的场合都会被替换成5
#define S(num) num*num  // 宏定义了一个S函数,参数是num, 以后在所有使用到S(num)的地方,都会被自动的体化成 num * num  还不需要考虑参数的数据类型,整型和浮点型都能传,但是,直接替换会处问题
using namespace std;int main(){int result1 = S(5);float result2 = S(5.5);int result3 = S(5 + 10);cout << result1 << '\t' << result2 << '\t' << result3 << endl;return 0;
}

C++ 中为了升级了带参数宏定义直接替换的bug,但是也失去了内连函数的能传任意类型数据的优势

2.传递引用(重点)

2.1引用、理由、注意事项

引用:为对象起了另一名字,实际底层是对对象地址的封装。引用并非对象,只是对象的一个别名。

引用更接近于const 指针,一旦于某个变量关联起来,一般不会变换它效忠的对象;而是为这个对象做一些事情。

int value = 1024;
int &refValue = value;  // 引用必须初始化,使用refValue和value操作的是同一个对象
// 不可以直接引用常量(字面量)
// double &d = 12.3; ❌
const double &d = 12.3; // ☑️ 因为之后对d的修改编译器会报错

使用引用的理由:

  1. 可以更加简便的书写代码,省掉指针传参时的取地址符号
  2. 可以直接传递某个对象,而不是把对象复制一份

将引用变量作为参数时,函数对原始变量进行操作,而非原始变量的副本。
但数据所占的内存比较大时,建议使用引用参数。

注意:传递数组只能用指针,并使用const 关键字,

2.3 交换两个变量的数值

demo 1: 交换两个变量的值

// 三个版本的交换两个数字
void Swaps1(int, int);
void Swaps2(int *, int *);
void Swaps3(int &, int &);int main(){int num1 = 10, num2 = 5;Swaps1(num1, num2);cout << num1 << "\t" << num2 << endl;Swaps2(&num1, &num2);cout << num1 << "\t" << num2 << endl;Swaps3(num1, num2);      cout << num1 << "\t" << num2 << endl;return 0;}void Swaps1(int num1, int num2){// 书写简单,功能不达int	tmp = num1;num1 = num2;num2 = tmp;
}
void Swaps2(int *p1, int *p2){// 书写繁杂,功能实现// 交换目标地址的值// 取p1目标值,给指针变量tmpint tmp = *p1;// 将p1目标值改为p2的目标值*p1 = *p2;// 将p2的目标值改为tmp中存的值*p2 = tmp;
}
void Swaps3(int &ref1, int &ref2){// 写法和方式1一致,但是能起到交换的结果// 结合1和2 的优点int tmp = ref1;ref1 = ref2;ref2 = tmp;
}

使用引用的注意点:在不想修改变量的地方,需要使用cons限制

demo2: 只想现实一下变量的值,而不想改造他们

void show(const int &, const int &);
int main(){int num1 = 10, num2 = 5;show(num1, num2);return 0;
}
void show(const int &ref1,  const int &ref2){// ref1 = 998;   赋值编译不过cout << ref1 << "\t" << ref2 << endl;}

3.返回引用

函数的返回值可以是引用类型

3.1不要返回局部变量的引用

(函数调用完,局部变量就被销毁了,返回其引用是一个无意义的东西)

void test();
int & Sum();
int & Sum(){// rnum是在Sum()函数中定义的局部变量,其生存期只在Sum()函数中int num = 10;int & rnum = num;return rnum;// 编译: warning: reference to stack memory associated with local variable 'num' returned [-Wreturn-stack-address]// 内存回收:并不是把内存保存数值清零,而是没有变量去操作他了,那一块空间还是在}
void test(){int x = 1;
}

输出是对,是10

int main(){// result 引用了一个局部变量int & result = Sum();cout << result;   // 正常输出return 0;
}

多加一个test输出就不是10,相当于那一块地址的值被1 占了,所以原来的result 引用就是后来的1了。
(另一个函数将覆盖先前函数栈上的数据)

int main(){// result 引用了一个局部变量int & result = Sum();test();          // 多加一个测试函数就会变成1;cout << result;return 0;
}

3.2函数可以不返回值,默认返回传入的引用对象本身

编译可以过,但是无法运行:
zsh: illegal hardware instruction "/Users/chenyingying/CppProject/Helloworld/"main
老师返回的是16,最后一个修改的参数

int & Sum2(int &, int &);int main(){int num1 = 10;int num2 = 15;int &result = Sum2(num1, num2);         // 编译可以过,但是无法运行:// zsh: illegal hardware instruction  "/Users/chenyingying/CppProject/Helloworld/"main// 老师返回的是16,最后一个修改的参数cout << "result = " << result << endl;return 0;
}
int & Sum2(int & num1, int & num2){num1++;num2++;// 编译warming : control reaches end of non-void function [-Wreturn-type]
}

3.3返回引用时,要求函数的参数中必须包含被返回的引用对象

(不能返回带运算的引用return num1 + num2 是不对的)

int & Sum2(int & num1, int & num2){num1++;num2++;// ❌,提示:非常量引用的初始值必须为左值C/C++(461return num1 + num2;
}

在返回引用类型时,为了避免在设计过程中存在模糊的情况,在函数定义时增加const限定,规避由模糊引起的犯错。

4.参数的默认值

参数可以有默认值,在声明时可以传递默认值

void sample(int = 10);
int main(){sample();sample(123);
}
void sample(int num){cout << num << endl;
}

注意点:
1.默认值可以在函数原型或者定义中给出,不能再两个位置同时出现
实验时在定义中给出,会报错:main.cpp:16:6: note: candidate function not viable: requires 1 argument, but 0 were provided,我还是在声明的时候给默认值吧
2.对于带参数列表的函数,必须从右向左添加默认值(有默认值得得放在参数列表的后端)

void test1(int a, int b = 5, int c = 10); // ✔️对的,调用时 test1(1); test1(1,2);test3(1,2,3);都对
void test2(int a = 2, int b = 5, int c);  // ❎错,默认参数后的参数也必须有默认值
void test3(int a = 1, int b = 5, int c = 10); // ✔️对的,调用时 test3(1); test3(1,2);test3(1,2,3);都对

5.随堂练习–使用函数实现游戏中的私聊


// 模拟现实游戏中的私聊函数
string chatTo(const string &toName, const string &content);
string chatFrom(const string & = "系统", const string & = "快睡觉");
/*** 跟某人聊天--负责字符串的拼接(聊天格式)* Param tonName	聊天对象的名称* Param content	聊天的内容* return	按某格式拼接聊天信息后的字符串
*/
string chatTo(const string &toName, const string &content){string msg = "❄️ 你悄悄的对[" + toName + "]说:" + content;return msg; 
}
string chatFrom(const string &fromName, const string &content){string msg = "❄️[" + fromName + "]悄悄对你说:" + content;return msg;
}int main(){cout << "请输入对方的名称:";string toName,content,chatMsg;getline(cin, toName);cout << "请输入发送对方的聊天信息:";getline(cin, content);chatMsg = chatTo(toName, content);    // 后台应该是需要做处理的cout << endl << chatMsg << endl;cout << "请输入来自对方的信息:";                 // 应该是从网络上接收对的,但是这里只是模拟getline(cin, content);string fromName = toName;chatMsg = chatFrom(fromName, content);                       // 不能通过等号的方式进行传参么cout << endl << chatMsg << endl;return 0;
}

6.函数重载(重点)

overloading: 可以有多个同名函数, 但是参数列表不同(特征标不同)

重载决议:编译器在编译时,会依据参数列表对函数进行重命名,来实现重载函数的唯一性

(返回值不同并产生不同的函数重载)

void Swap(int a, int b) 编译器处理为 Swap_int_int()
void Swap(float a, float b) 编译器处理为 Swap_float_float()

deno1: 类型的引用和类型本身所表达的参数列表是一致的(不是函数重载)

void eating(string food){cout << "吃" << food << "1" << endl;
}
void eating(string &food){cout << "吃" << food << "2" << endl;
}

从编译器的角度来看,eating(sting) 和eating(&string)的特征标(参数列表)是一致的

还需要注意:函数重载时不区分const和非const变量。

demo2: 使用重载实现不同数据类型数组的排序

void sort(int[], int);
void sort(float[], int);
void sort(double[], int);
void Show(int[], int);
void Show(float[], int);
void Show(double[], int);void sort(int nums[], int len){int tmp;// cout << "sizeof(nums):" << sizeof(nums) << endl;for(int i = 0; i < len; i++){for(int j = 0; j < len - i -1; j++){if(nums[j] > nums[j + 1]){tmp = nums[j];nums[j] = nums[j+1];nums[j+1] = tmp;}}}}
void sort(float nums[], int len){float tmp;for(int i = 0; i < len; i++){for(int j = 0; j < len - i -1; j++){if(nums[j] > nums[j + 1]){tmp = nums[j];nums[j] = nums[j+1];nums[j+1] = tmp;}}}}void sort(double nums[], int len){double tmp;for(int i = 0; i < len; i++){for(int j = 0; j < len - i -1; j++){if(nums[j] > nums[j + 1]){tmp = nums[j];nums[j] = nums[j+1];nums[j+1] = tmp;}}}}void Show(int nums[], int len){for(int i=0; i < len; i++){cout << nums[i] << "\t" ;}cout << endl;
}
void Show(float nums[], int len){for(int i=0; i < len; i++){cout << nums[i] << "\t" ;}cout << endl;
}
void Show(double nums[], int len){for(int i=0; i < len; i++){cout << nums[i] << "\t" ;}cout << endl;
}int main(){int iNums[] = {1, 3, 2, 4, 6, 5};float fNums[] = {1.2, 3.4, 7.8, 5.5, 6.9};double dNums[] = {1.2, 3.4, 7.8, 5.5, 6.9};cout << "排序前:";Show(iNums, sizeof(iNums)/sizeof(iNums[0]));sort(iNums, sizeof(iNums)/sizeof(int));cout << "排序后:";Show(iNums, sizeof(iNums)/sizeof(iNums[0]));cout << "排序前:";Show(fNums, sizeof(fNums)/sizeof(fNums[0]));sort(fNums, sizeof(fNums)/sizeof(float));cout << "排序后:";Show(fNums, sizeof(fNums)/sizeof(float));cout << "排序前:";Show(dNums, sizeof(dNums)/sizeof(dNums[0]));sort(dNums, sizeof(dNums)/sizeof(double));cout << "排序后:";Show(dNums, sizeof(dNums)/sizeof(double));return 0;
}

输出

排序前:1       3       2       4       6       5
排序后:1       2       3       4       5       6
排序前:1.2     3.4     7.8     5.5     6.9
排序后:1.2     3.4     5.5     6.9     7.8
排序前:1.2     3.4     7.8     5.5     6.9
排序后:1.2     3.4     5.5     6.9     7.8

7.函数模版(难点)

模板函数实际上就是建立一个通用函数,在该函数的定义时不指定具体的数据类型(使用虚拟类型代替)。模板函数被调用时,编译器会根据实参反推数据类型(类型参数化)

// 模板头与函数声明定义需要写在一起
template <typename 类型参数1typename 类型参数2>
返回值类型 函数名(形参列表){// 在函数体重可以使用类型参数
}template<typename T> void Swap(T &, T &);
// T 可以为任意类型, 可以看成类型参数化, typename(新推荐)/class(旧习惯)
template<typename T> // 模版头部
void Swap(T &a, T &b){T temp = a;a = b;b = temp;
}

demo1: 模版函数实现-函数重载(part 6 中demo2)


template<typename T> void Sort(T tArray[], int len);
template<typename T> void Show(T tArray[], int len);template<typename T> 
void Sort(T tArray[], int len){// 冒泡排序法T temp;for(int i = 0; i < len-1; i++){for(int j =0; j <len - i ; j++){if(tArray[j+1] < tArray[j]){temp = tArray[j];tArray[j] = tArray[i];tArray[i] = temp;}}}
}template<typename T>
void Show(T tArray[], int len){for(int i = 0; i < len; i++){cout << tArray[i] << "\t";}cout << endl;
}
int main(){//模版函数demoint iNums[] = {1, 3, 2, 4, 6, 5};float fNums[] = {1.2, 3.4, 7.8, 5.5, 6.9};double dNums[] = {1.2, 3.4, 7.8, 5.5, 6.9};cout << "排序前:";Show(iNums, sizeof(iNums)/sizeof(iNums[0]));Sort(iNums, sizeof(iNums)/sizeof(int));cout << "排序后:";Show(iNums, sizeof(iNums)/sizeof(iNums[0]));cout << "排序前:";Show(fNums, sizeof(fNums)/sizeof(fNums[0]));Sort(fNums, sizeof(fNums)/sizeof(float));cout << "排序后:";Show(fNums, sizeof(fNums)/sizeof(float));cout << "排序前:";Show(dNums, sizeof(dNums)/sizeof(dNums[0]));Sort(dNums, sizeof(dNums)/sizeof(double));cout << "排序后:";Show(dNums, sizeof(dNums)/sizeof(double));return 0;
}

输出:

排序前:1       3       2       4       6       5
排序后:1       2       4       6       3       5
排序前:1.2     3.4     7.8     5.5     6.9
排序后:5.5     1.2     6.9     3.4     7.8
排序前:1.2     3.4     7.8     5.5     6.9
排序后:5.5     1.2     6.9     3.4     7.8

函数重载的优秀之处:相同的函数名可以拥有不同的行为(每个人的吃饭的方式都是存在差异的)
函数模版的优秀之处:重载函数实现的逻辑都一致时,可以直接使用函数模版进行精简。(中等级别的代码用函数模版的还是比较少,在书写框架的时候,函数模版就十分有用)

两者并非替代关系,当重载函数中的实现逻辑是一致时,才能用函数模版替代精简代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/444812.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

终于,我读懂了所有Java集合——set篇

HashSet &#xff08;底层是HashMap&#xff09; Set不允许元素重复。 基于HashMap实现&#xff0c;无容量限制。 是非线程安全的。 成员变量 private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private s…

加速scp传输速度

当需要在机器之间传输400GB文件的时候&#xff0c;你就会非常在意传输的速度了。默认情况下(约125MB带宽&#xff0c;网络延迟17ms&#xff0c;Intel E5-2430&#xff0c;本文后续讨论默认是指该环境)&#xff0c;scp的速度约为40MB&#xff0c;传输400GB则需要170分钟&#xf…

tcpcopy使用方法

1、下载tcpcopy http://code.google.com/p/tcpcopy/downloads/list 2、配置、编译、安装 依此使用如下命令&#xff1a; 配置&#xff1a; ./configure 编译&#xff1a; make 安装&#xff1a; make install 3、使用方法 下面以mosquitto为例&#xff0c;说明tcpcopy的用法&a…

C++(14)--面向对象

面向对象1.面向对象编程(难点)2.类和对象demo1:地主类的实现版本1demo2:地主类的实现版本23.访问修饰符demo3:外部修改成员变量不安全(版本3)demo4: 使用封装防止直接修改成员变量&#xff08;版本3&#xff09;demo5:进一步封装&#xff1a;设置/获取名字&#xff0c;修改积分…

终于,我读懂了所有Java集合——map篇(多线程)

多线程环境下的问题 1.8中hashmap的确不会因为多线程put导致死循环&#xff08;1.7代码中会这样子&#xff09;&#xff0c;但是依然有其他的弊端&#xff0c;比如数据丢失等等。因此多线程情况下还是建议使用ConcurrentHashMap。 数据丢失&#xff1a;当多线程put的时候&…

system函数的返回值和执行脚本的返回值

1、先统一两个说法&#xff1a;&#xff08;1&#xff09;system返回值&#xff1a;指调用system函数后的返回值&#xff0c;比如上例中status为system返回值&#xff08;2&#xff09;shell返回值&#xff1a;指system所调用的shell命令的返回值&#xff0c;比如上例中&#x…

OJ汇总

国内&#xff1a;&#xff08;一下排名不分先后&#xff09; 浙江大学&#xff08;ZJU&#xff09;&#xff1a;http://acm.zju.edu.cn/ 北京大学&#xff08;PKU&#xff09;&#xff1a;http://acm.pku.edu.cn/JudgeOnline/ 同济大学&#xff08;TJU&#xff09;&#xff1a;…

C++(15)--面向对象编程实践-欢乐斗地主(vector的花式输出)

面向对象编程实践-欢乐斗地主《老九学堂C课程》《C primer》学习笔记。《老九学堂C课程》详情请到B站搜索《老九零基础学编程C入门》-------------简单的事情重复做&#xff0c;重复的事情用心做&#xff0c;用心的事情坚持做(老九君)---------------要求&#xff1a;实现录入及…

Google Protobuf 使用介绍

直接在 www.google.com.hk 上搜索google protobuf 后下载官方版本。 官方版本支持C\Java\Python三门语言。 还有很多非官方的语言版本支持&#xff0c;如C\NET(C#/Vb.net)\Flex(AS3)等. 要通信&#xff0c;必须有协议&#xff0c;否则双方无法理解对方的码流。在protobuf中&…

epoll的再次认识

使用mmap加速内核与用户空间的消息传递。 这 点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很 重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关…

leetcode82. 删除排序链表中的重复元素 II

给定一个排序链表&#xff0c;删除所有含有重复数字的节点&#xff0c;只保留原始链表中 没有重复出现 的数字。 示例 1: 输入: 1->2->3->3->4->4->5 输出: 1->2->5 示例 2: 输入: 1->1->1->2->3 输出: 2->3 思路&#xff1a;判断n…

C++(16)--运算符重载(自定义Integer类)

运算符重载1.运算符重载--重点2.友元函数--难点(流运算符重载)《老九学堂C课程》《C primer》学习笔记。《老九学堂C课程》详情请到B站搜索《老九零基础学编程C入门》 -------------简单的事情重复做&#xff0c;重复的事情用心做&#xff0c;用心的事情坚持做(老九君)--------…

反应器组件 ACE_Reactor

6.1 反应器组件 ACE_Reactor反应器的基本原理是: 针对关心的某个事件写一个事件处理器(event_handler). 将该事件处理器登记到反应器中(同时指明关心的事件). 然后反应器会自动检测事件的发生. 并调用预先登记的事件处理器中的回调函数. 所以ACE Reactor 框架的责任&#x…

C++(17)--详解const

详解const《老九学堂C课程》《C primer》学习笔记。《老九学堂C课程》详情请到B站搜索《老九零基础学编程C入门》-------------简单的事情重复做&#xff0c;重复的事情用心做&#xff0c;用心的事情坚持做(老九君)---------------1.const修饰成员变量 2.const修饰函数参数 3.c…

cppcheck的安装和使用

首先从这里下载linux版本的:http://sourceforge.net/projects/cppcheck/files/cppcheck/ 然后下载对应的版本,解压,之后安装: 编译: g++ -o cppcheck -Ilib cli/*.cpp lib/*.cpp 安装: make install

leetcode24 两两交换链表中的节点

给定一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后的链表。 你不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 示例: 给定 1->2->3->4, 你应该返回 2->1->4->3. 思路&#xff1a;这一看就是个递归定义&…

再议指针和引用的一些事情吧

关于指针和引用一直是学习C++的同学们争论的焦点,什么时候用指针,什么时候用引用,还有怎么引用数组,这么用指针访问数组,以及初始化的问题。 不过有一些文章我在很早就已经写过,但是由于当时时间不充分,自己也都是随性写的,可以参看以前我的一个文章:http://blog.csd…

C++(18)--复制构造函数

复制构造函数《老九学堂C课程》《C primer》学习笔记。《老九学堂C课程》详情请到B站搜索《老九零基础学编程C入门》-------------简单的事情重复做&#xff0c;重复的事情用心做&#xff0c;用心的事情坚持做(老九君)---------------包装基本类&#xff0c;封装一些算法。 需求…

lua与C++粘合层框架

一. lua调用C 在lua中是以函数指针的形式调用函数, 并且所有的函数指针都必须满足如下此种类型: typedef int (*lua_CFunction) (lua_State *L);   也就是说, 偶们在C中定义函数时必须以lua_State为参数, 以int为返回值才能被Lua所调用. 但是不要忘记了, 偶们的lua_State是…

leetcode147 对链表进行插入排序

丢人&#xff0c;我就是按插入排序老老实实写的啊。。。。 别人肯定map了hhh。 对链表进行插入排序。 插入排序的动画演示如上。从第一个元素开始&#xff0c;该链表可以被认为已经部分排序&#xff08;用黑色表示&#xff09;。 每次迭代时&#xff0c;从输入数据中移除一个…