引言
芜湖~小伙伴们,终于到了呼声最高的函数专项了,本章小杨还是采用了之前的自带锚点的功能的版本,方便大家随时来看,记得收藏,我相信小伙伴们学完这章肯定会对函数有个新的了解,对函数的认知会更加全面和系统。同理,除了有关解释部分,一定也要对代码里的注释要有所研究,会有你意想不到的惊喜。
好了废话不多说,小伙伴们开始吧,冲冲冲!!!!
函数及其使用
目录
1.函数的定义
2.小练习
3.参数传递–指针
4.参数传递–引用
5.数组作为参数传递
6.数组作为返回值
7.练习
8.变量的作用范围
9.变量的存储类别
10.内联函数
11.函数的重载
12.参数的默认值
13.函数指针
14.嵌套的调用
15.递归的调用
16.练习题
1.函数的定义和使用
函数是一段可以重复使用的代码,用于执行特定任务。在C++中,函数通过返回类型、函数名称和参数列表来定义。我们通常会用一个函数来实现一个功能,在程序的每个地方,当我们的程序需要调用该功能的时候,我们就可以利用该函数。
- 代码示例:
#include <iostream>
using namespace std;
/*
函数的定义
函数的定义由函数类型、函数名和参数组成,称为函数头。
函数名是标识符,必须遵守标识符命名规则。
由两个大括号括起来的是函数体。
函数类型(返回值)
函数类型就是函数返回值的类型。
函数可以没有返回值,此时函数类型为void。
函数必须指定返类型。
函数参数
形参(形式参数),定义函数时括号中的参数
实参(实际参数),调用函数时括号中的参数。
*/
int mySum(int n) // n是形参
{
int sum = 0;
for (int i = 1; i <= n; i++)
{
sum += i;
}
return sum;
}
// 没有返回值的函数,类型为void。
void myFunc()
{
cout << "myFunc()" << endl;
return;
}
// main称为主调函数,mySum称为被调函数。
int main()
{
cout << mySum(10) << endl; // 10是实参
cout << mySum(9) << endl;
return 0;
}
2.小练习
小伙伴们,让我们先来对这个函数的定义和使用来个小练习,保证自己对函数的定义和使用都有了清晰的认知。
1.求两个数中的较大值
2.求m的n次方
- 代码示例练习1:
#include <iostream>
using namespace std;
// 函数可以先声明再定义
int myMax(int x, int y); // 声明函数
int main()
{
cout << myMax(1, 100) << endl;
return 0;
}
int myMax(int x, int y)
{
if (x > y)
{
return x;
}
return y;
}
- 代码示例练习2:
#include <iostream>
using namespace std;
// 编写函数计算m的n次方
float myPower(float m, int n);
int main()
{
int m = 2;
int n = 3;
cout << myPower(m, n) << endl;
cout << "main m: " << m << endl;
return 0;
}
float myPower(float m, int n)
{
float a = 1.0;
for (int i = 0; i < n; i++)
{
a *= m;
}
m++;
cout << "myPower m: " << m << endl;
return a;
}
3.参数传递–指针
在C++中,参数可以通过指针和引用传递。通过指针和引用传递参数可以允许函数修改原始数据,因为它们传递的是数据的地址或引用,而不是数据的副本。我们是不可以用数值来传递的,我们从上边函数的定义就可以知道,我们用数值传递时候,我们改变的是形参,而我们要实现功能,是改变的实参,所以我们用指针和引用时我们可以直接改变实参。
- 代码示例:
#include <iostream>
using namespace std;
// 定义函数交换m,n的值。
void my_swap(int* p1, int* p2);
int main()
{
int m = 10;
int n = 20;
int* p1 = &m, * p2 = &n;
cout << "main: " << "p1: " << p1 << " p2: " << p2 << endl;
my_swap(p1, p2);
cout << "main: " << "m: " << m << " n: " << n << endl;
cout << "main: " << "&m: " << &m << " &n: " << &n << endl;
cout << "main: " << "p1: " << p1 << " p2: " << p2 << endl;
cout << "main: " << "&p1: " << &p1 << " &p2: " << &p2 << endl;
return 0;
}
void my_swap(int* p1, int* p2)
{
int tmp = *p1; // 通过解引用取得main中m的值
*p1 = *p2; // 通过p1解引用取得main中m,并修改m的值
*p2 = tmp; // 通过p2解引用取得main中n,并修改n的值
cout << "my_swap: " << "p1: " << p1 << " p2: " << p2 << endl;
cout << "my_swap: " << "&p1: " << &p1 << " &p2: " << &p2 << endl;
// 被调函数中修改形参的指针指向,不应影响主调函数中实参的指针指向。
p1 = &tmp;
p2 = &tmp;
}
4.参数传递–引用
解释说明同指针,但和指针有所不同
- 指针和引用的不同:
- 指针:
可以为空 (nullptr)。
可以指向不同的对象(通过重新赋值)。
必须使用 * 运算符来解引用。
可以通过指针来修改指向的值。
可以通过指针来获取变量的地址。 - 引用:
不能为空,必须初始化。
一旦初始化,就不能更改引用的对象。
使用时不需要特殊的解引用语法。
直接操作引用就是操作原始对象。
通常用于函数参数,以允许函数修改原始数据。 - 代码示例:
#include <iostream>
using namespace std;
// 定义函数交换m,n的值。
void my_swap(int& a, int& b);
int main()
{
int m = 10;
int n = 20;
my_swap(m, n);
cout << "m: " << m << " n: " << n << endl;
return 0;
}
void my_swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
5.数组作为参数传递
数组作为参数传递时,实际上传递的是指向数组首元素的指针。我们经常会在需要处理数组数据的函数中,如排序、搜索或计算数组中的统计信息中调用数组作为参数传递。
- 代码示例:
#include <iostream>
using namespace std;
// 数组作为参数传递,传递的是数组的首地址
void my_reverse(int a[], int len);
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
my_reverse(a, 5);
for (int i = 0; i < 5; i++) cout << a[i] << ", ";
cout << endl;
return 0;
}
void my_reverse(int a[], int len)
{
int m = len / 2;
for (int i = 0; i < m; i++)
{
int tmp = a[i];
a[i] = a[len - i - 1];
a[len - i - 1] = tmp;
}
}
6.数组作为返回值
有时候我们需要返回多个值的函数中,尤其是当这些值形成一个序列或集合时,我们就可以选用数组作为返回值,虽然C++不允许直接返回数组,但可以通过返回指向数组的指针来模拟这一行为。
- 代码示例:
#include <iostream>
using namespace std;
/*
C++中函数不能直接返回一个数组,但是可以通过返回指针来实现返回一个数组。
C++中很少使用在被调函数中生成数组并返回给主调函数的方式,
如果一定要使用,使用动态内存分配方式生成数组并返回。
*/
int* func();
int main()
{
int* p = func();
for (int i = 0; i < 5; i++)
{
cout << p[i] << ", ";
}
cout << endl;
delete[]p;
}
int* func()
{
int* a = new int[5];
for (int i = 0; i < 5; i++)
{
a[i] = i + 1;
}
return a;
}
7.练习题
了解了数组可以作为参数传递,也可以作为返回值,我们不妨来做个小练习,// 编写拼接字符串函数, p1为目标字符串,p2为源字符串。
提示:p1,p2作为参数,p1作为返回值
- 代码示例:
#include <iostream>
using namespace std;
// 编写拼接字符串函数, p1为目标字符串,p2为源字符串
void str_concat(char* p1, char* p2);
int main()
{
char c1[20] = "abcd";
char c2[20] = "efg";
str_concat(c1, c2);
cout << "c1: " << c1 << endl;
return 0;
}
void str_concat(char* p1, char* p2)
{
// 计算p1的长度,并把p1移动到字符串末尾
p1 += strlen(p1);
cout << "*p1: " << (int)*p1 << endl;
// 从p1末尾开始拼接p2.
while (*p2 != '\0')
{
*p1 = *p2;
p1++;
p2++;
}
// 拼接完成后设置字符串结束标志
*p1 = '\0';
}
8.变量的作用范围
在C++中,变量的作用范围(Scope)是指程序中变量可以被访问和使用的区域。一个变量的作用范围由其声明位置决定,在函数这一章我们把变量分为两种来看,后续还有其他我们讲到的时候会说,这里我们先说到全局变量和局部变量。
- 代码示例:
#include <iostream>
using namespace std;
// 全局变量
int i = 100;
void func();
int main()
{
i++;
func();
cout << "main: i: " << i << endl;
return 0;
}
void func()
{
// 使用全局变量i
cout << "func: i: " << i << endl;
i++;
cout << "func: i: " << i << endl;
// 局部变量
int i = 0;
// 使用局部变量i
cout << "func: i: " << i << endl;
i++;
cout << "func: i: " << i << endl;
// 通过::访问全局变量
cout << "func: ::i: " << ::i << endl;
}
9.变量的存储类别
在C++中,变量的存储类别(Storage Class)决定了变量的生命周期、作用域和初始化方式。C++提供了几种不同的存储类别(还有其他几种不常见的不做陈列)主要说明这个静态变量。
-
自动存储期限(Auto Storage Class):
默认存储类别,无需显式声明。
作用域:块作用域(如函数内部或复合语句内部)。
生命周期:从声明点到作用域结束。
初始化:未初始化。 -
静态存储期限(Static Storage Class):
使用 static 关键字声明。
作用域:块作用域或全局作用域。
生命周期:程序执行期间一直存在。
初始化:默认初始化为0(全局静态变量)或未初始化(局部静态变量)。 -
注册存储期限(Register Storage Class):
使用 register 关键字请求将变量存储在CPU寄存器中,以加快访问速度。
作用域:块作用域。
生命周期:从声明点到作用域结束。
初始化:未初始化。
存储类别的选择对程序的性能和资源管理有重要影响。正确使用存储类别可以帮助优化程序内存使用和执行效率。
- 代码示例:
#include <iostream>
using namespace std;
int func(int n);
int main()
{
for (int i = 1; i < 5; i++)
{
cout << func(i) << endl;
}
return 0;
}
int func(int n)
{
static int m = 1;
int i = n;
m = m * i;
return m;
}
10.内联函数
内联函数(Inline Function)是C++中的一种函数优化机制。当你在函数声明前加上 inline 关键字时,你是在告诉编译器尽可能地用函数体中的代码替换每一次函数调用,从而减少函数调用的开销。这种替换过程称为函数展开。
内联函数通常用于以下情况:
- 函数体较小:内联函数的代码行数较少,这样展开后的代码不会使程序体积过大。
- 频繁调用:如果某个函数被频繁调用,那么将其内联可以减少调用栈的深度,提高性能。
- 避免性能损失:对于那些对性能要求极高的代码,内联可以减少函数调用的开销。
内联函数的声明和定义通常放在头文件中,因为它们可能会被多个源文件包含。
- 代码示例:
#include <iostream>
using namespace std;
inline int add(int a, int b);
int main()
{
int a = 10;
for (int i = 0; i < 50; i++)
{
// cout << a +i << endl;
cout << add(a, i) << endl;
}
return 0;
}
int add(int a, int b)
{
return a + b;
}
11.函数的重载
函数重载(Function Overloading)是C++中的一种特性,允许在同一作用域内存在多个同名函数,只要它们的参数列表不同即可。函数的重载可以根据参数的数量、类型或者顺序来区分。
当调用重载函数时,编译器会根据传递给函数的参数来决定调用哪个函数版本,这个过程称为函数重载解析
函数重载的条件:
- 相同的函数名:重载的函数必须具有相同的名字。
- 不同的参数列表:参数列表的不同可以体现在参数的数量、类型或者顺序上。
- 在同一作用域内:重载的函数必须在同一作用域内,例如同一个类或者同一个全局作用域。
- 代码示例:
#include <iostream>
using namespace std;
int myAdd(int a, int b);
float myAdd(int a, float b);
int main()
{
int a1 = 10, a2 = 20;
float b1 = 11.1, b2 = 22.2;
cout << myAdd(a1, a2) << endl;
cout << myAdd(a1, b1) << endl;
return 0;
}
int myAdd(int a, int b)
{
cout << "myAdd(int a, int b)" << endl;
return a + b;
}
float myAdd(int a, float b)
{
cout << "myAdd(int a, float b)" << endl;
return a + b;
}
12.参数的默认值
在C++中,函数参数可以具有默认值。如果在调用函数时没有为这些参数提供值,将自动使用默认值。这允许函数更加灵活,因为它们可以适应多种不同的调用情况。
- 代码示例:
#include <iostream>
using namespace std;
/*
声明或定义函数时,可以预先为函数指定默认参数值。
默认值通常在函数声明处给出。
默认值必须从右向左顺序定义,当形参有默认值时,其后所有的形参都要有默认值。
*/
int myMax(int a, int b , int c );
int main()
{
int a = 1, b = 100, c = 1;
cout << myMax(a, b, c) << endl;
cout << myMax(a, b) << endl;
cout << myMax(a) << endl;
return 0;
}
int myMax(int a, int b , int c )
{
int max = a;
if (max < b)
{
max = b;
}
if (max < c)
{
max = c;
}
return max;
}
13.函数指针
函数指针是一个指向函数的指针,它存储了函数的地址。在C++中,函数指针可以用来调用函数、传递函数作为参数或者将函数作为返回值。函数指针对于实现回调函数、事件处理和某些设计模式(如策略模式)非常有用。
- 代码示例:
#include <iostream>
using namespace std;
int myAdd(int a, int b);
int main()
{
// 定义函数指针,用于指向具有两个int层参数,并且返回值为int型参数的函数。
int (*fp)(int, int);
// 函数名就是函数的首地址
cout << "myAdd: " << myAdd << endl;
// 函数指针fp指向myAdd函数
fp = myAdd;
// 使用函数指针调用函数
int a = fp(10, 20);
cout << "a; " << a << endl;
return 0;
}
int myAdd(int a, int b)
{
return a + b;
}
14.嵌套的调用
嵌套的调用指的是在一个函数的执行过程中调用另一个函数。这种调用可以是直接的,也可以是嵌套多层的。嵌套调用通常用于将复杂的问题分解为更小的子问题,然后分别解决。我们经常会在遇到一个问题时,碰到另一个问题,我们可以把这个问题作为新的问题去解决,放在另一个函数里。
- 代码示例:
#include <iostream>
using namespace std;
// 计算n个阶乘结果的求和。 1! + 2! + 3! ... + n!
int f1(int n);
// 计算m的阶乘
int f2(int m);
int main()
{
cout << f1(5) << endl;
return 0;
}
int f1(int n)
{
int sum = 0;
for (int i = 1; i <= n; i++)
{
sum += f2(i);
}
return sum;
}
int f2(int m)
{
int n = 1;
for (int i = 1; i <= m; i++)
{
n *= i;
}
return n;
}
15.递归的调用
递归的调用是指函数直接或间接地调用自身。递归是一种强大的编程技术,它可以将复杂的问题简化为多个相似的子问题。递归函数通常有一个或多个基案(基本情况),当满足这些基案时,递归调用会停止。递归和嵌套类似不过是在调用自己的函数。
- 代码示例:
#include <iostream>
using namespace std;
/*
n!,当n = 0 或 1时,n! = 1,当n > 1时, n = n*(n-1)!
*/
long long power(long long n)
{
long long r;
if (n > 1)
{
r = n* power(n - 1);
}
else
{
r = 1;
}
return r;
}
int main()
{
cout << power(4) << endl;
}
16.练习题
学完了有关函数的所有基础知识怎么能不来两道练习题呢。
1.递归和非递归的方式来计算斐波那契数列的结果
2.将一个数字字符串转换为对应的整数
3.编写函数,删除字符串中第一个指定的字符。
4.计算最后一个单词的长度
- 练习代码展示1:
#include <iostream>
using namespace std;
// 计算斐波那契数列的结果
int fib(int n);
// 非递归方式计算斐波那契数列的结果
int fib1(int n);
int main()
{
for (int i = 0; i < 50; i++)
{
cout << fib1(i) << ", ";
}
cout << endl;
return 0;
}
int fib(int n)
{
if (n == 0)
{
return 0;
}
else if (n == 1)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
}
int fib1(int n)
{
if (n == 0)
{
return 0;
}
else if (n == 1)
{
return 1;
}
else
{
int a1 = 0, a2 = 1, tmp;
for (int i = 2; i <= n; i++)
{
tmp = a2;
a2 = a1 + a2;
a1 = tmp;
}
return a2;
}
}
- 练习代码展示2:
#include <iostream>
using namespace std;
// 将一个数字字符串转换为对应的整数
int myConvert(const char* p);
int main()
{
int i = myConvert("12345");
cout << "i: " << i << endl;
return 0;
}
int myConvert(const char* p)
{
int n = 0;
for (int i = 0; p[i] != '\0'; i++)
{
n = n * 10 + (p[i] - '0');
}
return n;
}
- 练习代码展示3:
#include <iostream>
using namespace std;
// 编写函数,删除字符串中第一个指定的字符。
void delChar(char* p, char c);
int main()
{
char c[20] = "abcd ef";
delChar(c, 'e');
cout << c << endl;
return 0;
}
void delChar(char* p, char c)
{
for (int i = 0; p[i] != '\0'; i++)
{
if (p[i] == c)
{
for (int j = i; p[j] != '\0'; j++)
{
p[j] = p[j + 1];
}
break;
}
}
}
- 练习代码展示4:
#include <iostream>
using namespace std;
bool isAlpha(char c)
{
if (c >= 'a' && c <= 'z') return true;
if (c >= 'A' && c <= 'Z') return true;
return false;
}
int main()
{
/*
计算最后一个单词的长度。
最后一个字母,到最后一个空格后的第一个字母,之间的算一个单词
"abc 'a'! "
*/
char c[] = "abc 'abcde'! ";
int count = 0;
int len = strlen(c);
for (int i = len - 1; i >= 0; i--)
{
if (isAlpha(c[i]))
{
count++;
if (!isAlpha(c[i - 1]))
{
break;
}
}
}
cout << "count: " << count << endl;
return 0;
}
结语
小伙伴们,经过指针,数组还有函数专项,我相信大家对C++的认识已经不是一个小新手了,有关C++的基础知识还有最后一点我们就可以讲解,有关C++和面向对象了,大家一定要加油呀。我相信到了这一步大家对我的肯定和支持我相信都没有辜负,大家接下来也要持续关注哦哟,第一次看到这里的小伙伴们也一定要点个收藏加关注,小杨会持续为大家写出更优质更好的内容。争取帮助到大家在C++的学习路上少走一些弯路。