本篇要学习的内容和知识结构概览
函数的参数及其传递方式
1. 函数参数传递方式
传值:
传变量值: 将实参内存中的内容拷贝一份给形参, 两者是不同的两块内存
传地址值: 将实参所对应的内存空间的地址值给形参, 形参是一个指针, 指向实参所对应的内存空间
传引用:
形参是对实参的引用, 形参和实参是同一块内存空间
2. 对象作为函数参数, 也就是传变量值
将实参对象的值传递给形参对象, 形参是实参的备份, 当在函数中改变形参的值时, 改变的是这个备份中的值, 不影响原来的值
像这样:
- void fakeSwapAB(int x , int y) {
- int temp = x;
- x = y;
- y = temp;
- }
- int a = 5;
- int b = 8;
- 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 = 5;
- int b = 8;
- cout << "交换前:" << a << "," << b << endl;
- // 传地址值
- realSwapAB(&a, &b);
- cout << "交换后:" << a << "," << b << endl;
对于数组, 因数组名就是代表的数组首地址, 所以数组也能用传数组地址值的方式
- void swapArrFirstAndSecond(int a[]) {
- int temp = a[0];
- a[0] = a[1];
- a[1] = temp;
- }
- int main(int argc, const char * argv[]) {
- int a[] = {2, 3};
- cout << "交换前:" << a[0] << "," << a[1] << endl;
- swapArrFirstAndSecond(a);
- cout << "交换后:" << a[0] << "," << a[1] << endl;
- return 0;
- }
4. 引用作为函数参数, 也就是传地址 (注意: 这里不是地址值)
在函数调用时, 实参对象名传给形参对象名, 形参对象名就成为实参对象名的别名. 实参对象和形参对象代表同一个对象, 所以改变形参对象的值就是改变实参对象的值
像这样:
- void citeSwapAB(int & x, int & y) {
- int temp = x;
- x = y;
- y = temp;
- }
- int a = 5;
- int b = 8;
- cout << "交换前:" << a << "," << b << endl;
- // 传引用
- citeSwapAB(a, b);
- cout << "交换后:" << a << "," << b << endl;
优点: 引用对象不是一个独立的对象, 不单独占内存单元, 而对象指针要另外开辟内存单元 (内存中放实参传过来的地址), 所以传引用比传指针更好用.
5. 默认参数
不要求程序在调用时必须设定该参数, 而由编译器在需要时给该参数赋默认值.
规则 1. 当程序需要传递特定值时需要显式的指明. 默认参数必须在函数原型中说明.
如果函数在 main 函数后面定义, 而在声明中设置默认参数, 在定义中不需要设置默认参数
像这样:
- // 在 main 函数前声明函数, 并设置默认参数
- void PrintValue(int a, int b = 0, int c = 0);
- int main(int argc, const char * argv[]) {
- // 调用函数
- PrintValue(5);
- return 0;
- }
- // 在 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 = 0, int c = 0) {
- cout << "a =" << a << endl;
- cout << "b =" << b << endl;
- cout << "c =" << c << endl;
- }
- int main(int argc, const char * argv[]) {
- // 调用函数
- PrintValue(5);
- return 0;
}
规则 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(5, 8, 9);
- // 调用函数 第二种: 我们给第二个参数设特定值, 它前面所有参数必须赋值, 所以可以
- PrintValue(5, 8);
- /*
- 调用函数 第三种: 当一个默认参数有特定值时, 它前面所有的参数都必须赋值,
- 我们给第三个默认参数设特定值 也就是说第一, 二个参数也必须赋值 所以不可以
- */
- // 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 0;
- }
函数返回值
C++ 函数返回值类型可以是除数组和函数以外的任何类型
当返回值是指针或引用对象时, 需要注意函数返回值所指的对象必须存在, 因此不能将函数内部的局部对象作为函数返回值, 因为函数内, 局部变量或者对象在函数运行完毕后内存就释放啦
1. 返回引用的函数
函数可以返回一个引用, 目的是为了让该函数位于赋值运算符的左边
格式: 数据类型 & 函数名 (参数列表);
像这样:
- // 全局数组
- int arr[] = {2, 4, 6, 8};
- // 获得数组下标元素
- int & getValueAtIndex(int i) {
- return arr[i];
- }
- int main(int argc, const char * argv[]) {
- cout << "更改前:" << arr[2] << endl;
- // 调用函数, 并且用于计算或者重新赋值
- getValueAtIndex(2) = 10;
- cout << "更改后:" << arr[2] << endl;
- return 0;
- }
2. 返回指针的函数
返回值是存储某种数据类型数据的内存地址, 这种函数称为指针函数
格式: 数据类型 * 函数名 (参数列表);
像这样:
- // 返回指针的函数
- int * getData(int n) {
- // 根据形参, 申请内存空间
- int * p = new int[n];
- // 给申请下来的内存空间赋值
- for (int i = 0; i < n; i++) {
- p[i] = i + 10;
- }
- // 返回这段内存空间的首地址
- return p;
- }
- int main(int argc, const char * argv[]) {
- // 调用函数, 并接收返回值, 不要忘记释放函数中分配的内存
- int * p = getData(5);
- // 打印指针所指向的内存中的内容
- for (int i = 0; i < 5; i++) {
- cout << p[i] << endl;
- }
- return 0;
- }
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 0;
- }
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(5, getMax(8, 9));
- cout <<maxValue << endl;
- return 0;
- }
内联函数
1. 内联函数的概念
使用关键字 inline 声明的函数称为内联函数, 内联函数必须在程序中第一次调用此函数的语句出现之前定义, 这样编译器才知道内联函数的函数休, 然后进行替换
像这样:
- // 判断输入的字符是否为数字
- inline bool isNumber(char c) {
- if (c>= '0' && c <= '9') {
- 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 0;
- }
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(8, 9);
- // 三个整型变量求和
- int sumValue2 = sumWithValue(8, 9, 10);
- // 两个浮点型变量求和
- 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 0;
- }
2. 函数重载与默认参数
当函数重载与默认参数相结合时, 能够有效减少函数个数及形态, 缩减代码规模.
这样我们每种数据类型只保留一个函数即可完成我们的功能, 直接少了两个函数.
像这样:
- // 整型参数求和
- int sumWithValue(int x = 0, int y = 0, int z = 0) {
- return x + y + z;
- }
- // 浮点型参数求和
- double sumWithValue(double x = 0, double y = 0, double z = 0) {
- return x + y + z;
- }
- int main(int argc, const char * argv[]) {
- // 两个整型变量求和
- int sumValue1 = sumWithValue(8, 9);
- // 三个整型变量求和
- int sumValue2 = sumWithValue(8, 9, 10);
- // 两个浮点型变量求和
- 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 0;
}
如果使用默认参数, 就不能对参数个数少于默认个数的函数形态进行重载, 只能对于多于默认参数个数的函数形态进行重载.
像这样
- // 求和的参数, 并且使用默认参数, 最多三个整型参数求和
- int sumWithValue(int x = 0, int y = 0, int z = 0) {
- 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(8, 9);
- // 求和, 给三个特定值
- int sumValue2 = sumWithValue(8, 9, 10);
- // 求和, 有 4 个整型参数
- int sumValue3 = sumWithValue(8, 9, 10, 11);
- cout << sumValue1 << endl;
- cout << sumValue2 << endl;
- cout << sumValue3 << endl;
- return 0;
- }
函数模板
从而上面可以看出, 它们是逻辑功能完全一样的函数, 所提供的函数体也一样, 区别仅仅是数据类型不同, 为了统一的处理它们, 引入了函数模板.
现在我们的函数从 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(3, 5);
- // 调用模板函数
- double sumValue2 = sumWithValue(3.2, 5.1);
- cout <<sumValue1 << endl;
- cout << sumValue2 << endl;
- return 0;
- }
当用用函数模板与具体的数据类型连用时, 就产生了模板函数, 又称为函数模板实例化
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 出现之前使用的, 它还有定义类的作用, 不直观, 也会在一些其它地方编译时报错.
总结:
可能对于初学者来说, 函数有点不是很好理解, 包括我当初也是, 不要想得过于复杂, 其实它就是一段有特定功能的代码, 只不过我们给这段代码起了个名字而已, 这样就会提高代码的可读性和易维护性.
本系列文章会持续更新! 大家踊跃的留下自己的脚印吧!
来源: https://juejin.im/post/5c333439e51d4551d044e85d