C/C++编程笔记:C++入门知识丨函数和函数模板
本篇要学习的内容和知识结构概览

函数的参数及其传递方式
1. 函数参数传递方式
传值:
传变量值: 将实参内存中的内容拷贝一份给形参, 两者是不同的两块内存
传地址值: 将实参所对应的内存空间的地址值给形参, 形参是一个指针, 指向实参所对应的内存空间
传引用:
形参是对实参的引用, 形参和实参是同一块内存空间
2. 对象作为函数参数, 也就是传变量值
将实参对象的值传递给形参对象, 形参是实参的备份, 当在函数中改变形参的值时, 改变的是这个备份中的值, 不影响原来的值
像这样:
void fakeSwapAB(int x , int y) {
int temp = x;
x = y;
y = temp;
} int a = ;
int b = ;
cout << "交换前: " << a << ", " << b << endl; // 传变量值
fakeSwapAB(a, b); cout << "交换后: " << a << ", " << b << endl;
3. 对象指针作为函数参数, 也就是传地址值
形参是对象指针, 实参是对象的地址值, 虽然参数传递方式仍然是传值方式, 因为形参和实参的地址值一样, 所以它们都指向同一块内存, 我们通过指针更改所指向的内存中的内容, 所以当在函数中通过形参改变内存中的值时, 改变的就是原来实参的值
像这样:
void realSwapAB(int * p, int * q) {
int temp = *p;
*p = *q;
*q = temp;
} int a = ;
int b = ;
cout << "交换前: " << a << ", " << b << endl; // 传地址值
realSwapAB(&a, &b); cout << "交换后: " << a << ", " << b << endl;
对于数组, 因数组名就是代表的数组首地址, 所以数组也能用传数组地址值的方式
void swapArrFirstAndSecond(int a[]) {
int temp = a[];
a[] = a[];
a[] = temp;
} int main(int argc, const char * argv[]) { int a[] = {, };
cout << "交换前: " << a[] << ", " << a[] << endl;
swapArrFirstAndSecond(a);
cout << "交换后: " << a[] << ", " << a[] << endl;
return ;
}
4. 引用作为函数参数, 也就是传地址(注意: 这里不是地址值)
在函数调用时, 实参对象名传给形参对象名, 形参对象名就成为实参对象名的别名. 实参对象和形参对象代表同一个对象, 所以改变形参对象的值就是改变实参对象的值
像这样:
void citeSwapAB(int & x, int & y) {
int temp = x;
x = y;
y = temp;
} int a = ;
int b = ;
cout << "交换前: " << a << ", " << b << endl; // 传引用
citeSwapAB(a, b); cout << "交换后: " << a << ", " << b << endl;
优点: 引用对象不是一个独立的对象,不单独占内存单元, 而对象指针要另外开辟内存单元(内存中放实参传过来的地址),所以传引用比传指针更好用。
5. 默认参数
不要求程序在调用时必须设定该参数, 而由编译器在需要时给该参数赋默认值.
规则1:当程序需要传递特定值时需要显式的指明. 默认参数必须在函数原型中说明.
如果函数在main函数后面定义, 而在声明中设置默认参数, 在定义中不需要设置默认参数
像这样:
// 在main函数前声明函数, 并设置默认参数
void PrintValue(int a, int b = , int c = ); int main(int argc, const char * argv[]) { // 调用函数
PrintValue(); return ;
} // 在main函数后定义函数, 不需要设置默认参数
void PrintValue(int a, int b, int c) {
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
如果函数在main函数前面定义, 则在定义中设置默认参数
像这样:
// 在main前定义函数, 需要设置默认参数
void PrintValue(int a, int b = , int c = ) {
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
} int main(int argc, const char * argv[]) { // 调用函数
PrintValue(); return ;
}
规则2:默认参数可以多于一个,但必须放在参数序列的后部。
像这样:
可以有一个默认参数:void PrintValue(int a, int b, int c = 0);
可以是有多个默认参数:void PrintValue(int a, int b = 0, int c = 0);
不可以在中间设置默认参数:void PrintValue(int a, int b = 0, int c);
规则3:如果一个默认参数需要指定一个特定值时,则在此之前的所有参数都必须赋值
// 调用函数 第一种: 三个参数全部有特定值
PrintValue(, , ); // 调用函数 第二种: 我们给第二个参数设特定值, 它前面所有参数必须赋值, 所以可以
PrintValue(, ); /*
调用函数 第三种: 当一个默认参数有特定值时, 它前面所有的参数都必须赋值,
我们给第三个默认参数设特定值 也就是说第一, 二个参数也必须赋值 所以不可以
*/
// PrintValue(5, , 9);
6. 使用const保护数据
用const修饰要传递的参数, 该函数只能使用参数, 而无权修改参数, 以提高系统的自身安全.
像这样:
// 拼接字符串的函数
void catStr(const string str) {
string str2 = str + " Ray!"; // 函数内部不能修改const修饰的形参, 所以不能这么使用
// str = "Hi";
cout << str2 << endl;
} int main(int argc, const char * argv[]) { // 实例化一个字符串
string str = "Hello"; // 调用函数
catStr(str); return ;
}
函数返回值
C++函数返回值类型可以是除数组和函数以外的任何类型
当返回值是指针或引用对象时, 需要注意函数返回值所指的对象必须存在, 因此不能将函数内部的局部对象作为函数返回值, 因为函数内, 局部变量或者对象在函数运行完毕后内存就释放啦
1. 返回引用的函数
函数可以返回一个引用, 目的是为了让该函数位于赋值运算符的左边
格式: 数据类型 & 函数名(参数列表);
像这样:
// 全局数组
int arr[] = {, , , }; // 获得数组下标元素
int & getValueAtIndex(int i) {
return arr[i];
} int main(int argc, const char * argv[]) { cout << "更改前: " << arr[] << endl; // 调用函数, 并且用于计算或者重新赋值
getValueAtIndex() = ;
cout << "更改后: " << arr[] << endl; return ;
}
2. 返回指针的函数
返回值是存储某种数据类型数据的内存地址, 这种函数称为指针函数
格式: 数据类型 * 函数名(参数列表);
像这样:
// 返回指针的函数
int * getData(int n) { // 根据形参, 申请内存空间
int * p = new int[n]; // 给申请下来的内存空间赋值
for (int i = ; i < n; i++) {
p[i] = i + ;
} // 返回这段内存空间的首地址
return p;
} int main(int argc, const char * argv[]) { // 调用函数, 并接收返回值, 不要忘记释放函数中分配的内存
int * p = getData(); // 打印指针所指向的内存中的内容
for (int i = ; i < ; i++) {
cout << p[i] << endl;
} return ;
}
3. 返回对象的函数
格式: 数据类型 函数名(参数列表);
像这样:
// 返回对象的函数
string sayHello(string s) {
// 我们拼接好一个字符串, 给str
string str = "Hello " + s; // 并把str这个对象返回
return str;
} int main(int argc, const char * argv[]) { // 调用函数, 接收函数返回的对象
string str = sayHello("Ray");
cout << str << endl; return ;
}
4. 函数返回值作为函数参数
如果函数返回值作为另一个函数的参数, 那么这个返回值必须与另一个函数的参数类型一致
像这样:
// 求最大值的函数
int getMax(int x, int y) {
return x > y ? x : y;
} int main(int argc, const char * argv[]) { // 先求8, 9返回最大值; 返回值再跟5比较, 返回最大值
int maxValue = getMax(, getMax(, ));
cout << maxValue << endl; return ;
}
内联函数
1. 内联函数的概念
使用关键字inline声明的函数称为内联函数, 内联函数必须在程序中第一次调用此函数的语句出现之前定义, 这样编译器才知道内联函数的函数休, 然后进行替换
像这样:
// 判断输入的字符是否为数字
inline bool isNumber(char c) {
if (c >= '' && c <= '') {
return true;
} else {
return false;
}
} int main(int argc, const char * argv[]) { // 声明字符c
char c; // 从键盘输入字符
cin >> c; // 进行判断, 这里的isNumber(c), 在程序编程期间就会被isNumber()函数体所替换, 跟宏一样一样的
// 如果函数体特别大, 替换的地方特别多, 就增加了代码量
if (isNumber(c)) {
cout << "输入了一个数字" << endl;
} else {
cout << "输入的不是一个数字" << endl;
} return ;
}
2. 注意
在C++中, 除具有循环语句, switch语句的函数不能说明为内联函数外, 其它函数都可以说明为内联函数.
3. 作用
使用内联函数可以提高程序执行速度, 但如果函数体语句多, 则会增加程序代码量.
函数重载和默认参数
1. 函数重载
一个函数名具有多种功能, 具有多种形态, 称这种我为多态性, 一个名字, 多个函数
函数重载要满足的条件:
参数类型不同或者参数个数不同
像这样:
// 求和的函数 2两个整型参数
int sumWithValue(int x, int y) {
return x + y;
} // 求和的函数 3两个整型参数
int sumWithValue(int x, int y, int z) {
return x + y + z;
} // 求和的函数 2个浮点型参数
double sumWithValue(double x, double y) {
return x + y;
} // 求和的函数 3个浮点型参数
double sumWithValue(double x, double y, double z) {
return x + y + z;
} int main(int argc, const char * argv[]) { // 两个整型变量求和
int sumValue1 = sumWithValue(, ); // 三个整型变量求和
int sumValue2 = sumWithValue(, , ); // 两个浮点型变量求和
double sumValue3 = sumWithValue(1.2, 2.3); // 三个浮点型变量求和
double sumValue4 = sumWithValue(1.2, 2.3, 3.4); cout << sumValue1 << endl;
cout << sumValue2 << endl;
cout << sumValue3 << endl;
cout << sumValue4 << endl; return ;
}
2. 函数重载与默认参数
当函数重载与默认参数相结合时, 能够有效减少函数个数及形态, 缩减代码规模.
这样我们每种数据类型只保留一个函数即可完成我们的功能, 直接少了两个函数.
像这样:
// 整型参数求和
int sumWithValue(int x = , int y = , int z = ) {
return x + y + z;
} // 浮点型参数求和
double sumWithValue(double x = , double y = , double z = ) {
return x + y + z;
} int main(int argc, const char * argv[]) { // 两个整型变量求和
int sumValue1 = sumWithValue(, ); // 三个整型变量求和
int sumValue2 = sumWithValue(, , ); // 两个浮点型变量求和
double sumValue3 = sumWithValue(1.2, 2.3); // 三个浮点型变量求和
double sumValue4 = sumWithValue(1.2, 2.3, 3.4); cout << sumValue1 << endl;
cout << sumValue2 << endl;
cout << sumValue3 << endl;
cout << sumValue4 << endl; return ;
}
如果使用默认参数, 就不能对参数个数少于默认个数的函数形态进行重载, 只能对于多于默认参数个数的函数形态进行重载.
像这样:
// 求和的参数, 并且使用默认参数, 最多三个整型参数求和
int sumWithValue(int x = , int y = , int z = ) {
return x + y + z;
} // 像这样是不行的, 不能对参数个数少于默认个数的函数形态进行重载
//int sumWithValue(int x, int y) {
// return x + y;
//} // 像这样是可以的, 当调用时传入4个整型参数时就会调用该参数
int sumWithValue(int x, int y, int z, int t) {
return x + y + z + t;
} int main(int argc, const char * argv[]) { // 求和, 只给两个特定值
int sumValue1 = sumWithValue(, ); // 求和, 给三个特定值
int sumValue2 = sumWithValue(, , ); // 求和, 有4个整型参数
int sumValue3 = sumWithValue(, , , ); cout << sumValue1 << endl;
cout << sumValue2 << endl;
cout << sumValue3 << endl; return ;
}
函数模板
从而上面可以看出, 它们是逻辑功能完全一样的函数, 所提供的函数体也一样, 区别仅仅是数据类型不同, 为了统一的处理它们, 引入了函数模板.
现在我们的函数从4个缩减成一个, 但是我们的功能没有减少, 反而增加了. 比如我们可以计算char, float类型
1. 什么是函数模板
在程序设计时没有使用实际存在的类型, 而是使用虚拟的参数参数, 故其灵活性得到加强.
当用实际的类型来实例化这种函数时, 就好像按照模板来制造新的函数一样, 所以称为函数模板
格式: 一般用T来标识类型参数, 也可以用其它的
Template <class T>
像这样:
// 定义模板
template <class T> // 定义函数模板
T sumWithValue(T x, T y) {
return x + y;
} int main(int argc, const char * argv[]) { // 调用模板函数
int sumValue1 = sumWithValue(, ); // 调用模板函数
double sumValue2 = sumWithValue(3.2, 5.1);
cout << sumValue1 << endl;
cout << sumValue2 << endl;
return ;
}
当用用函数模板与具体的数据类型连用时, 就产生了模板函数, 又称为函数模板实例化
2. 函数模板的参数
函数模板名<模板参数>(参数列表);
我们可以将参数列表的数据强制转换为指定的数据类型
像这样:int sumValue2 = sumWithValue<int>(3.2, 5.1);
我们将参数列表里的数据强制转换为int类型, 再参与计算
也可以样:double sumValue2 = sumWithValue(3.2, (double)5);
我们也可以将参数列表里的单个参数进行强制类型转换, 再参与计算
不过我们一般不会加上模板参数.
3. 使用关键字typename
用途就是代替template参数列表中的关键字class
像这样
template <typename T>
只是将class替换为typename, 其它一样使用.
强烈建议大家使用typename, 因为它就是为模板服务的, 而class是在typename出现之前使用的, 它还有定义类的作用, 不直观, 也会在一些其它地方编译时报错.
总结:
可能对于初学者来说, 函数有点不是很好理解, 包括我当初也是, 不要想得过于复杂, 其实它就是一段有特定功能的代码, 只不过我们给这段代码起了个名字而已, 这样就会提高代码的可读性和易维护性。
自学C/C++编程难度很大,不妨和一些志同道合的小伙伴一起学习成长!
C语言C++编程学习交流圈子,【点击进入】微信公众号:C语言编程学习基地
有一些源码和资料分享,欢迎转行也学习编程的伙伴,和大家一起交流成长会比自己琢磨更快哦!
C/C++编程笔记:C++入门知识丨函数和函数模板的更多相关文章
- C/C++编程笔记:C++入门知识丨多态性和虚函数
本篇要学习的内容和知识结构概览 多态性 编译时的多态性称为静态联编. 当调用重载函数时, 在编译期就确定下来调用哪个函数. 运行时的多态性称为动态联编. 在运行时才能确定调用哪个函数, 由虚函数来支持 ...
- C/C++编程笔记:C++入门知识丨类和对象
本篇要学习的内容和知识结构概览 类及其实例化 类的定义 将一组对象的共同特征抽象出来, 从而形成类的概念. 类包括数据成员和成员函数, 不能在类的声明中对数据成员进行初始化 声明类 形式为: clas ...
- C/C++编程笔记:C++入门知识丨从结构到类的演变
先来看看本节知识的结构图吧! 接下来我们就逐步来看一下所有的知识点: 结构的演化 C++中的类是从结构演变而来的, 所以我们可以称C++为”带类的C”. 结构发生质的演变 C++结构中可以定义函数, ...
- C/C++编程笔记:C++入门知识丨运算符重载
本篇要学习的内容和知识结构概览 运算符重载使用场景 常规赋值操作 我们现在有一个类 想要实现这种赋值操作 具体实现如下: 所以说呢,我们在使用运算符进行运算的时候, 实际上也是通过函数来实现运算的. ...
- C/C++编程笔记:C++入门知识丨继承和派生
本篇要学习的内容和知识结构概览 继承和派生的概念 派生 通过特殊化已有的类来建立新类的过程, 叫做”类的派生”, 原有的类叫做”基类”, 新建立的类叫做”派生类”. 从类的成员角度看, 派生类自动地将 ...
- C/C++编程笔记:C++入门知识丨认识C++面向过程编程的特点
一. 本篇要学习的内容和知识结构概览 二. 知识点逐条分析 1. 使用函数重载 C++允许为同一个函数定义几个版本, 从而使一个函数名具有多种功能, 这称之为函数重载. 像这样: 虽然函数名一样, 但 ...
- C/C++编程笔记:C++入门知识丨认识C++的函数和对象
一. 本篇要学习的内容和知识结构概览 二. 知识点逐条分析 1. 混合型语言 C++源文件的文件扩展名为.cpp, 也就是c plus plus的简写, 在该文件里有且只能有一个名为main的主函数, ...
- UnityShader学习笔记1 — — 入门知识整理
注:资料整理自<Unity Shader入门精要>一书 一.渲染流程概念阶段: 应用阶段:(1)准备好场景数据:(如摄像机位置,物体以及光源等) (2)粗粒度剔除(Culling): ...
- AngularJs学习笔记1——入门知识
1.什么是AngularJs AngularJs 诞生于2009年,由Misko Hevery 等人创建,后被Google收购,是一个优秀的Js框架,用于SPA(single pag ...
随机推荐
- html中doctype有几种类型 以及doctype的作用
htm中doctype标签是一种标准通用标记语言的文档类型声明,它的目的是要告诉标准通用标记语言解析器,它应该使用什么样的文档类型定义(DTD)来解析文档. dotype 的特点: <!doct ...
- css实现内容渐变隐藏效果,手机网页版知乎内容隐藏效果的实现
看到一个需求,如下图,知乎手机网页版的一个视觉效果,对内容很长的部分有一个渐变的隐藏的效果,个人觉得这个设计还是很好的,符合手机大小的应用场景,没有一下子显示完全,可以很快的滑倒页面底部,一定程度上减 ...
- JS数组与对象赋值问题
在W3C的在线编程中经过测试发现以下问题: 当一个数组内部元素为对象时,给数组赋值应该先给对象赋值,然后把该对象push到数组中. 如下所示: 在控制台打印之后的数据格式为下图所示: 如果在给数组赋值 ...
- redis源码学习之工作流程初探
目录 背景 环境准备 下载redis源码 下载Visual Studio Visual Studio打开redis源码 启动过程分析 调用关系图 事件循环分析 工作模型 代码分析 动画演示 网络模块 ...
- java 面向对象(九):类的结构:构造器(一)简介;属性赋值顺序;JavaBean的概念
1.构造器(或构造方法):Constructor构造器的作用: * 1.创建对象 * 2.初始化对象的信息2.使用说明: * 1.如果没显式的定义类的构造器的话,则系统默认提供一个空参的构造器 * 2 ...
- CMDB04 /流程梳理、cmdb总结
CMDB04 /流程梳理.cmdb总结 目录 CMDB04 /流程梳理.cmdb总结 1. 流程梳理 1.1 环境 1.2 远程连接服务器 1.3 向服务器上传文件 1.4 运维管理服务器 2. cm ...
- Django框架01 / http协议、web框架本质
Django框架01 / http协议.web框架本质 目录 Django框架01 / http协议.web框架本质 1.http协议 1.1 http协议简介 1.2 什么是http协议 1.3 H ...
- .NET Core微服务开发网关篇-ocelot
通过上篇我们知道,网关是外部访问的统一入口,本文采用Ocelot作为Api网关. 环境要求: vs2019 .NetCore3.1 Ocelot16.0.1 创建一个产品服务Api站点(AAStore ...
- python也能玩视频剪辑!moviepy操作记录总结
前几篇文章咱们介绍了一下图片的处理方式,今天咱们说说视频的处理.python能够支持视频的处理么?当然是肯定的,人生苦读,我用python.万物皆可python. moviepy库安装 今天咱们需要使 ...
- 同一台机器oralce11g和12c公用一个监听器监听多个端口
启动数据库服务 (这里还没有启动监听器) 如上图OracleServiceORCL为11g的服务,服务名为orcl,OracleServiceWX为12c的服务名,服务名为wx,两个服务均已正常启动, ...