本篇要学习的内容和知识结构概览

函数的参数及其传递方式

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++入门知识丨函数和函数模板的更多相关文章

  1. C/C++编程笔记:C++入门知识丨多态性和虚函数

    本篇要学习的内容和知识结构概览 多态性 编译时的多态性称为静态联编. 当调用重载函数时, 在编译期就确定下来调用哪个函数. 运行时的多态性称为动态联编. 在运行时才能确定调用哪个函数, 由虚函数来支持 ...

  2. C/C++编程笔记:C++入门知识丨类和对象

    本篇要学习的内容和知识结构概览 类及其实例化 类的定义 将一组对象的共同特征抽象出来, 从而形成类的概念. 类包括数据成员和成员函数, 不能在类的声明中对数据成员进行初始化 声明类 形式为: clas ...

  3. C/C++编程笔记:C++入门知识丨从结构到类的演变

    先来看看本节知识的结构图吧! 接下来我们就逐步来看一下所有的知识点: 结构的演化 C++中的类是从结构演变而来的, 所以我们可以称C++为”带类的C”. 结构发生质的演变 C++结构中可以定义函数, ...

  4. C/C++编程笔记:C++入门知识丨运算符重载

    本篇要学习的内容和知识结构概览 运算符重载使用场景 常规赋值操作 我们现在有一个类 想要实现这种赋值操作 具体实现如下: 所以说呢,我们在使用运算符进行运算的时候, 实际上也是通过函数来实现运算的. ...

  5. C/C++编程笔记:C++入门知识丨继承和派生

    本篇要学习的内容和知识结构概览 继承和派生的概念 派生 通过特殊化已有的类来建立新类的过程, 叫做”类的派生”, 原有的类叫做”基类”, 新建立的类叫做”派生类”. 从类的成员角度看, 派生类自动地将 ...

  6. C/C++编程笔记:C++入门知识丨认识C++面向过程编程的特点

    一. 本篇要学习的内容和知识结构概览 二. 知识点逐条分析 1. 使用函数重载 C++允许为同一个函数定义几个版本, 从而使一个函数名具有多种功能, 这称之为函数重载. 像这样: 虽然函数名一样, 但 ...

  7. C/C++编程笔记:C++入门知识丨认识C++的函数和对象

    一. 本篇要学习的内容和知识结构概览 二. 知识点逐条分析 1. 混合型语言 C++源文件的文件扩展名为.cpp, 也就是c plus plus的简写, 在该文件里有且只能有一个名为main的主函数, ...

  8. UnityShader学习笔记1 — — 入门知识整理

    注:资料整理自<Unity Shader入门精要>一书 一.渲染流程概念阶段:  应用阶段:(1)准备好场景数据:(如摄像机位置,物体以及光源等)   (2)粗粒度剔除(Culling): ...

  9. AngularJs学习笔记1——入门知识

    1.什么是AngularJs          AngularJs 诞生于2009年,由Misko Hevery 等人创建,后被Google收购,是一个优秀的Js框架,用于SPA(single pag ...

随机推荐

  1. 本地连接虚拟机db2V10.5遇到的问题

    在连接虚拟机数据库时发现自己不知道db2的端口号是多少,百度上说50000,60000的都有,所以还是决定自己试一下,并记录下这个过程 # 首先切换到db2inst1的用户 su - db2inst1 ...

  2. 前段人员必藏的7 个 CSS 好用的属性绝对干货

    学习CSS是构建好看网页的一种方式. 但是,在学习过程中,我们倾向于(大部分时间)限制自己,一遍又一遍地使用相同的属性. 毕竟,我们是一种习惯性的动物,我们会使用自己习惯且熟悉的东西. 因此,在这篇文 ...

  3. 仅需5步,轻松升级K3s集群!

    Rancher 2.4是Rancher目前最新的版本,在这一版本中你可以通过Rancher UI对K3s集群进行升级管理. K3s是一个轻量级Kubernetes发行版,借助它你可以几分钟之内设置你的 ...

  4. [JAVA]解决不同浏览器下载附件的中文名乱码问题

    附件下载时,遇到中文附件名的兼容性问题,firefox.chrome.ie三个派系不兼容,通过分析整理,总结出处理该问题的办法,记录如下: 1.文件名编码 服务器默认使用的是ISO8859-1,而我们 ...

  5. ArrayList源码分析-jdk11 (18.9)

    目录 1.概述 2.源码分析 2.1参数 2.2 构造方法 2.2.1 无参构造方法 2.2.2 构造空的具有特定初始容量值方法 2.2.3构造一个包含指定集合元素的列表,按照集合的迭代器返回它们的顺 ...

  6. 02 flask源码剖析之flask快速使用

    02 flask快速使用 目录 02 flask快速使用 1.flask与django的区别 2. 安装 3. 依赖wsgi Werkzeug 4. 快速使用flask 5. 用户登录&用户管 ...

  7. Nginx to start, restart, shutdown and upgrade

    1.start cd usr/local/nginx/sbin ./nginx 2.restart kill -HUP PID #主进程号或进程号文件路径 #或者使用 cd /usr/local/ng ...

  8. Unity-Editor

    Undo.RecordObject [MenuItem("Example/Random Rotate")] static void RandomRotate() { var tra ...

  9. PHP 反序列化漏洞入门学习笔记

    参考文章: PHP反序列化漏洞入门 easy_serialize_php wp 实战经验丨PHP反序列化漏洞总结 PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患 利用 pha ...

  10. python也能玩视频剪辑!moviepy操作记录总结

    前几篇文章咱们介绍了一下图片的处理方式,今天咱们说说视频的处理.python能够支持视频的处理么?当然是肯定的,人生苦读,我用python.万物皆可python. moviepy库安装 今天咱们需要使 ...