【35天从0开始备战蓝桥杯 -- Day5】

🫧个人主页:小年糕是糕手

💫个人专栏:《C++》《Linux》《数据结构》《C语言》

🎨你不能左右天气,但你可以改变心情;你不能改变过去,但你可以决定未来!


目录

一、输入输出

1.1、单组测试用例

[1°计算 (a+b)/c 的值](#1°计算 (a+b)/c 的值)

[2°与 7 无关的数](#2°与 7 无关的数)

1.2、多组测试用例

情况一

[1°多组输入a+b II](#1°多组输入a+b II)

2°斐波那契数列

3°制糊串

情况二

1°多组输入a+b

2°数字三角形

3°定位查找

情况三

1°字符统计

拓展函数

[2°多组数据a+b III](#2°多组数据a+b III)

1.3、输入时的特殊技巧

[1°含空格字符串的输入技巧 -- 统计数字字符个数](#1°含空格字符串的输入技巧 -- 统计数字字符个数)

[2°数字的特殊处理方式 -- 小乐乐改数字](#2°数字的特殊处理方式 -- 小乐乐改数字)

[1.4、scanf / printf与cin / cout的对比](#1.4、scanf / printf与cin / cout的对比)

1°格式控制差异

2°性能差异

数字游戏

[求第 k 小的数](#求第 k 小的数)

3°优化方案

二、函数

2.1、概述

1°普通参数

2°数组

3°字符串

4°全局变量

2.2、返回值

2.3、声明和定义

2.4、函数调用

1°传值调用

2°引用

3°效率比较

2.5、函数重载

练习

[1°最大数 max(x,y,z)](#1°最大数 max(x,y,z))

拓展函数

max

min

[2°求正整数 2 和 n 之间的完全数](#2°求正整数 2 和 n 之间的完全数)

3°素数个数

4°素数对

5°素数回文数的个数

6°区间内的真素数

三、递归

3.1、概述

3.2、典型案例解析

1°计算阶乘

迭代

递归

2°斐波那契数列

迭代

递归

练习


一、输入输出

前面我们学过了很多种输入输出,cin / cout、scanf / printf、getline、fgets等等,但是实际上的oj编程题中情况一般都是比较常见的几种,下面为大家做个小结并结合一些习题帮助大家更好理解

1.1、单组测试用例

**1°**计算 (a+b)/c 的值

计算 (a+b)/c 的值

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
    int a,b,c;
    cin>>a>>b>>c;
    int ret=(a+b)/c;
    cout<<ret<<endl;
    return 0;
}

2°与 7 无关的数

与 7 无关的数

cpp 复制代码
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
	int n;
	cin >> n;
	int sum = 0;
	int i = 1;
	while (i <= n)
	{
		//判断i是否是和7无关
		if ((i % 7 != 0) && (i % 10 != 7) && (i / 10 != 7))
		{
			sum += i * i;
		}
		i++;
	}
	cout << sum << endl;
	return 0;
}

相信通过前面的学习这两题的代码大家已经是信手拈来了,也没啥好解释的这两道题,只是让大家体会一下什么是单组测试用例,我们重点来看下面的内容:

1.2、多组测试用例

情况一

这部分我们来看测试数据组数已知的情况

1°多组输入a+b II

多组输入a+b II

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
    int n;
    cin >> n;
    int a, b;
    int ret;
    while (n--)
    {
        ret = 0;
        cin >> a >> b;
        ret = a + b;
        cout << ret << endl;
    }
    return 0;
}
2°斐波那契数列

斐波那契数列

这里我们提供两种思路,其实还有一种递归的思路,下面章节我们会学习,这里留个悬念,思路一我们是循环读取多组输入的数字a,对每个a用迭代法计算第a个斐波那契数并输出。

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
    int n;
    cin>>n;
    int a;
    while(n--)//n次访问
    {
        cin>>a;
        //计算第n个斐波那契数
        int x = 1;
        int y = 1;
        int z = 1;
        while (a > 2)
        {
        	z = x + y;
        	x = y;
        	y = z;
        	a--;
        }
        cout << z << endl;
    }
   return 0; 
}

思路二我们采用计算好前30个斐波那契数,存储到ret数组中,需要第几个就从下标几的位置去取

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
	int n = 0;
	int a = 0;
	int i = 0;
	//计算好前30个斐波那契数,存储到ret数组中 
	//需要第⼏个就从下标⼏的位置去取 
	int ret[40] = { 0, 1, 1 };
	for (i = 3; i < 30; i++)
	{
		ret[i] = ret[i - 1] + ret[i - 2];
	}
	cin >> n;
	int z = 0;
	while (n--)
	{
		cin >> a;
		cout << ret[a] << endl;
	}
	return 0;
}
**3°**制糊串

制糊串

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s, t;
	cin >> s >> t;
	string s1, t1;
	int q, l1, r1, l2, r2;
	cin >> q;
	while (q--)
	{
		cin >> l1 >> r1 >> l2 >> r2;
		s1 = s.substr(l1 - 1, r1 - l1 + 1);
		t1 = t.substr(l2 - 1, r2 - l2 + 1);
		//比较
		if (s1 < t1)
			cout << "yifusuyi" << endl;
		else if (s1 > t1)
			cout << "erfusuer" << endl;
		else
			cout << "ovo" << endl;
	}
	return 0;
}

编程小技巧:

题目中说 "有 q 次询问",意思是程序要处理 q 组测试数据,(也就是对应 q 次循环),我们要针对每次询问,给出一个结果。

其实就是之前的单组测试变成了 q 组测试,在之前的代码上套一层 while 循环即可。当有 q 次询问的时候,while (q--) 是非常方便的方式。然后就按照单组输入的方式处理每组输入的数据就好了

情况二

这部分我们来看测试数据组数未知的情况

1°多组输入a+b

多组输入a+b

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
    int a,b;
    while(cin >> a >> b)
    {
        cout << a + b <<endl;
    }
    return 0;
}

cin >> a; 会返回一个流对象的引用,即 cin 本身。在 C++ 中,流对象 cin 可以被用作布尔值来检查流的状态。如果流的状态良好(即没有发生错误),流对象的布尔值为 true。如果发生错误(如遇到输入结束符或类型不匹配),布尔值为 false


while (cin >> a >> b) 语句中,循环的条件部分检查 cin 流的状态。如果流成功读取到 2 个值,cin >> a >> b 返回的流对象 cin 将被转换为 true,循环将继续。如果读取失败(例如遇到输入结束符或无法读取到 2 个值),cin >> a >> b 返回的流对象 cin 将被转换为 false,循环将停止。

2°数字三角形

数字三角形

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
    int n;
    while(cin >> n)
    {
        for(int i = 1;i <= n;i++)
        {
            for(int j = 1;j <= i;j++)
            {
                cout << j << ' ';
            }
            cout<<endl;
        }
        
    }
    return 0;
}

这个题目其实很类似我们前面写过的,判断质数,乘法口诀表这类

3°定位查找

定位查找

这里为大家展示两种最常见的写法:

cpp 复制代码
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
    int n;
    int arr[20] = {0};
    while(cin >> n)
    {
        for(int i = 0;i < n;i++)
        {
            cin >> arr[i];
        }
        int m;
        cin >> m;
          
        int pos = -1; // 标记位置
        for(int j = 0; j < n; j++) // 从0开始遍历
        {
            if(arr[j] == m)
            {
                pos = j;
                break; // 第一次出现就停止
            }
        }
        
        if(pos != -1)
            cout << pos << endl;
        else
            cout << "No" << endl;
        
        memset(arr, 0, sizeof(arr));
    }
    return 0;
}

我们来解释这第一段代码:我们定义了一个整型n,代表n组数据,然后定义了一个数组暂且全部初始化为0,下面我们进入while循环,定义了一个for循环来给这个数组赋值,下面定义并且输入一个m作为我们的要查找的值,下面定义了一个pos用来标记位置,然后我们遍历数组并且与m进行比较,如果找到了就跳出循环并且给pos位置赋值标记下标,如果没找到pos还是一开始的值,我们打印no即可,最后使用我们之前学过的memset给数组置为0即可

这个思路很简单,但是其实代码的水平不是很高,有点冗余,下面给出优化之后的解法:

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 25;
int arr[N];
int main()
{
	int n = 0;
	int m = 0;
	while (cin >> n)
	{
		//输入n个数字,存放在arr中
		for (int i = 0; i < n; i++)
		{
			cin >> arr[i];
		}
		//输入m
		cin >> m;
		int i = 0;
		//在arr数组中查找m
		for (i = 0; i < n; i++)
		{
		if (m == arr[i])
		{
			cout << i << endl;
			break;
		}
	}
	if (i == n)
		cout << "No" << endl;
}
	return 0;
}

这里我们舍弃了memset,因为这里其实根本不需要去清空数组,上一次的结果会被我们下一次直接覆盖,我们查找的范围也是根据元素个数来的,所以根本不需要重置数组。

情况三

这部分我们来看特殊值结束测试数据的情况

1°字符统计

字符统计

这题我们依旧有两种思路,我们可以逐个字符的读取,然后统计,这里可以使用getchar(),我们也可以一次性读取整个字符串然后统计

cpp 复制代码
#include<iostream>
using namespace std;
int Letters, Digits, Others;
int main()
{
    int ch = 0;
    while ((ch = getchar()) != '?')
    {
        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
            Letters++;
        else if (ch >= '0' && ch <= '9')
            Digits++;
        else
            Others++;
    }
    cout << "Letters=" << Letters << endl;
    cout << "Digits=" << Digits << endl;
    cout << "Others=" << Others << endl;
    return 0;
}

这里将那几个参数定义为全局变量是因为:全局变量 / 静态变量 → 系统自动帮你赋值为 0

拓展函数

下面为大家介绍几个函数帮助大家简化代码:

cpp 复制代码
//头文件
#include <cctype>
函数 作用
isalpha(c) 是否是字母(大小写都算)
islower(c) 是否小写字母 -- 小写字母 a-z
isupper(c) 是否大写字母 -- 大写字母 A-Z
isdigit(c) 是否数字 -- 数字 0-9
isspace(c) 是否空格、换行、制表符

返回值规则 完全一模一样

所有这 5 个函数的返回值规则:

  • 如果【是】 → 返回一个 非 0 的整数(真)
  • 如果【不是】 → 返回 0(假)

下面我们对代码进行优化:

cpp 复制代码
#include<iostream>
#include<cctype>
using namespace std;
int Letters, Digits, Others;
int main()
{
    int ch = 0;
    while ((ch = getchar()) != '?')
    {
        if ((islower(ch)) || (isupper(ch)))
            Letters++;
        else if (isdigit(ch))
            Digits++;
        else
            Others++;
    }
    cout << "Letters=" << Letters << endl;
    cout << "Digits=" << Digits << endl;
    cout << "Others=" << Others << endl;
    return 0;
}
cpp 复制代码
#include<iostream>
#include<cctype>
using namespace std;
int Letters, Digits, Others;
int main()
{
    int ch = 0;
    while ((ch = getchar()) != '?')
    {
        if (isalpha(ch))
            Letters++;
        else if (isdigit(ch))
            Digits++;
        else
            Others++;
    }
    cout << "Letters=" << Letters << endl;
    cout << "Digits=" << Digits << endl;
    cout << "Others=" << Others << endl;
    return 0;
}

下面我们来看方法二:

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int Letters, Digits, Others;
int main()
{
	string s;
	getline(cin, s, '?');
	for (auto e : s)
	{
		if (isalpha(e))
			Letters++;
		else if (isdigit(e))
			Digits++;
		else
			Others++;
	}
	cout << "Letters=" << Letters << endl;
    cout << "Digits=" << Digits << endl;
    cout << "Others=" << Others << endl;
	return 0;
}

这里需要注意我们之前介绍过的getline的第二种用法,其实还有一种方法就是使用getline的第一种写法,然后写个pop_back,也可以满足需求

2°多组数据a+b III

多组数据a+b III

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
    int a, b;
    while (cin >> a >> b)
    {
        if (a == 0 && b == 0)
            break;
        cout << a + b << endl;
    }
    return 0;
}

这里其实还有一种写法,利用到了逗号表达式:逗号隔开的一串表达式的特点:从左向右依次计算,整个表达式的结果是最后一个表达式的结果

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
    int a, b;
    while (cin >> a >> b, a || b)
    {
        cout << a + b << endl;
    }
    return 0;
}

1.3、输入时的特殊技巧

根据我们现在掌握的知识,含空格的字符串,如要读取有 fgetsscanfgetchargetline 四种方式解决,但是有时候,根据题目的情况,不一定非要完整的读取这个带空格的字符串,而是将字符串中空格隔开的每一个字符串,当做一个单词处理更方便,也避免了读取带空格字符串的各种问题。

**1°含空格字符串的输入技巧 --**统计数字字符个数

统计数字字符个数

这个题目其实与字符统计那个题目非常类似,我们依旧可以采取整个字符串一次性读取,然后处理字符串,也可以采取逐个单词的方式处理,下面给出两种方案的代码:

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
    string s;
    getline(cin, s);
    int count = 0;
    for (auto e : s)
    {
        if (isdigit(e))
            count++;
    }
    cout << count << endl;
    return 0;
}
cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
    string s;
    int count = 0;
    while (cin >> s)
    {
        for (auto e : s)
        {
            if (isdigit(e))
                count++;
        }
    }
    cout << count << endl;
    return 0;
}

2°数字的特殊处理方式 -- 小乐乐改数字

小乐乐改数字

cpp 复制代码
#include<iostream>
#include<cmath>
#include<string>
using namespace std;

int main() {
    int n;
    cin >> n;
    int i = 0; //标记⼀下此时处理到多少位
    int ret = 0;
    while (n) {
        if (n % 10 % 2) //如果这⼀位是奇数
            ret += 1 * pow(10, i);
        else
            ret += 0 * pow(10, i);
        n /= 10; //把最后⼀位干掉
        i++;
    }
    cout << ret << endl;
    return 0;
}

其实我们还有一种思路就是将输入的内容当做字符串处理:

cpp 复制代码
#include<iostream>
#include<cmath>
#include<string>
using namespace std;

int main() {
    string s;
    cin >> s;
    for (int i = 0; i < s.size();i++)
    { // 数字字符与对应的数的奇偶⼀致
        if (s[i] % 2) 
        {
            s[i] = '1';
        } 
        else 
        {
            s[i] = '0';
        }
    }
    cout << stoi(s) << endl; // 转换成数字输出
    return 0;
}

1.4、scanf / printf与cin / cout的对比

1°格式控制差异

  • scanfprintf 不能自动识别输入数据的类型,需要手动指定格式字符串,容易出现格式错误。开发者需要确保格式字符串与变量类型匹配,否则会导致未定义行为。
  • cincout 会根据变量类型自动处理输入输出,避免格式化错误。相对 scanfprintf 而且,C++ 的 cincout 更加易用。
  • scanfprintf:格式化输出更精确直观,特别适合复杂格式的输入输出,比如:在要求指定格式输出的时候,printf 函数就比 cout 更加方便和灵活。

区别

  1. cout 默认不会输出六位小数,自动忽略小数点后多余的 0,printf 函数打印浮点数的时候,小数点默认打印 6 位。
  2. cout 在输出的时候不需要指定格式,printf 则需要明确的格式。

2°性能差异

结论scanfprintf 通常比 cincout 快。


原因cincout 由于要考虑兼容 C 语言的输入和输出,封装实现的更加复杂,通常比 scanfprintf 稍慢,但这种差异在大多数应用场景中可以忽略不计。

但是在竞赛的题目中,尤其是当输入、输出数据量较大时,使用 cincout 完成输入输出,经常会出现 Time Limit Exceeded 的情况。而 scanfprintf 就不存在类似的问题。下面给大家准备了两个案例。

数字游戏

数字游戏

我们从这个题目的输入描述应该就不难看出这个题目的数据量是非常大的,题目也提示我们请选择较快的读入方式,下面给出两种输入方式所展现的代码,大家不用看懂代码只需要去对比一下性能差异即可:

cpp 复制代码
#include <iostream>
using namespace std;
int t, x;
int main()
{
	cin >> t;
	while (t--)
	{
		cin >> x;
		int ret = 0;
		while (x)
		{
			int count = 0, high = 0;
			int tmp = x;
			while (tmp)
			{
				//计算最右边的1代表的值
				int low = tmp & -tmp;
				//如果low中剩余的1就是最后⼀个1就是最左边的1 
				if (tmp == low)
				{
					high = low;
				}
				//去掉最右边的1 
				tmp -= low;
				count++;
			}
			if (count % 2 == 0)
			{
				x -= high;
			}
			else
			{
				x ^= 1;
			}
			ret++;
		}
		cout << ret << endl;
	}
	return 0;
}
cpp 复制代码
#include <iostream>
using namespace std;
int t, x;
int main()
{
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d", &x);
		int ret = 0;
		while (x)
		{
			int count = 0, high = 0;
			int tmp = x;
			while (tmp)
			{
				//计算最右边的1代表的值
				int low = tmp & -tmp;
				//如果low中剩余的1就是最后⼀个1就是最左边的1 
				if (tmp == low)
				{
					high = low;
				}
				//去掉最右边的1 
				tmp -= low;
				count++;
			}
			if (count % 2 == 0)
			{
				x -= high;
			}
			else
			{
				x ^= 1;
			}
			ret++;
		}
		printf("%d\n", ret);
	}
	return 0;
}

这是两次的运行时间大家很明显就可以看出差异了

求第 k 小的数

求第 k 小的数

这题我们依旧采用两种输入输出方式进行提交,我们来看看有什么区别:

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5000010;
int arr[N];
int main()
{
	int n, k;
	cin >> n >> k;
	for (int i = 0; i < n; i++)
	{
		cin >> arr[i];
	}
	sort(arr, arr + n);
	cout << arr[k] << endl;
	return 0;
}

我们发现超时了,下面我们一种输入输出方式:

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5000010;
int arr[N];
int main()
{
	int n, k;
	cin >> n >> k;
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &arr[i]);
	}
	sort(arr, arr + n);
	cout << arr[k] << endl;
	return 0;
}

总结一下其实就是 2 个点:

  1. C++ 中为了支持混合使用 cin/coutscanf/printf,C++ 标准库默认会将 cincout 等 C++ 流对象与 stdinstdout 等 C 标准库的流对象同步在一起。这种同步操作意味着每次使用 cincout 时,都会自动刷新 C 标准库的缓冲区,以确保 C++ 和 C 的 I/O 是一致的。这就导致了性能的下降。

  2. 在默认情况下,cincout 之间存在一种绑定关系。这种绑定意味着,每当从 cin 读取数据时,任何之前通过 cout 输出的内容都会被强制刷新到屏幕上。这种绑定也可能导致性能问题,特别是在需要频繁读取大量数据的情况下。

3°优化方案

如果大家还是想要去使用cin和cout也不是不可以只需要加上下面三行代码即可:

cpp 复制代码
//在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下3⾏代码 
//可以提⾼C++IO效率 
ios_base::sync_with_stdio(false);//取消给C语⾔输⼊输出缓冲区的同步
cin.tie(nullptr);//取消了cin和cout的绑定
cout.tie(nullptr);

二、函数

2.1、概述

为大家准备了一篇可以精准解决函数基础概念及基本语法的博客:

【C语言】C语言函数大揭秘:开启编程魔法的钥匙_编程需要用到函数吗-CSDN博客https://blog.csdn.net/2501_91731683/article/details/147687142

1°普通参数

下面为大家强调一些关键点,我们首先来看一个简单的加法函数:

cpp 复制代码
#include<iostream>
using namespace std;
int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a = 0;
	int b = 0;
	//输⼊ 
	cin >> a >> b;
	//调⽤加法函数,完成a和b的相加 
	//求和的结果放在r中 
	int r = Add(a, b);
	//输出 
	cout << r << endl;
	return 0;
}

我们首先是对Add函数有个定义,有了函数之后我们下面调用Add函数的时候,传递给函数的参数a和b称为实际参数,也就是我们常说的实参;定义函数的时候,在函数名Add后的括号中写的x和y,称为形式参数,简称形参。

为什么叫形式参数呢?实际上,如果只是定义了 Add 函数,而不去调用的话,Add 函数的参数 xy 只是形式上存在的,不会向内存申请空间,不会真实存在的,所以叫形式参数。形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形式的实例化。虽然我们提到了实参是传递给形参的,他们之间是有联系的,但是形参和实参各自是独立的内存空间。

我们在调试的可以观察到,xy 确实得到了 ab 的值,但是 xy 的地址和 ab 的地址是不一样的,当 ab 传参给形参 xy 的时候,xy 只是得到了 ab 的值,他们得有自己独立的空间。所以我们可以理解为形参是实参的一份临时拷贝

简单概括一下就是:形参不用就不占空间,用了就去额外开辟空间(地址不同,形参实参互不影响),形参是实参的一份临时拷贝

2°数组

当我们将函数和数组练习起来的时候就要注意下面的几点:

  • 函数的实参的名字和形参的名字可以相同,也可以不同
  • 函数的形式参数要和函数的实参个数匹配
  • 函数的实参是数组,形参也写成数组形式的
  • 形参如果是一维数组,数组大小可以省略不写
  • 形参如果是二维数组,行可以省略,但是列不能省略
  • 数组传参,形参是不会创建新的数组的
  • 形参操作的数组和实参的数组是同一个数组

简单一句话概括就是:函数实参和形参名字可同可不同、个数要匹配;数组传参时形参不新建数组,直接操作原数组,一维形参数组大小可省略,二维只能省略行不能省略列。

数组传参时,形参不会创建新数组,所以形参数组不需要指定大小!但是不可以写成int arr,这样会认为是一个整型变量而不是数组

3°字符串

一句话直接概括就是:字符串传参:普通字符串形参是临时拷贝,修改不影响实参

4°全局变量

全局变量的作用域很大,在整个程序中都可以使用,那么只要把变量、数组等定义成全局变量,在函数中使用,是可以不用传参的。

当然,有时候变量或者数组,定义成全局的时候,是不能解决问题,比如:递归等场景,这时候,就得考虑传参的问题,后面会为大家讲解

2.2、返回值

这里我们只说一点:return语句执行后,函数就彻底返回,后边的代码不再执行:

2.3、声明和定义

我们首先来看一下什么是函数声明什么是函数定义:

cpp 复制代码
//函数声明
int Add(int x, int y);

//函数定义
int Add(int x, int y)
{
	return x + y;
}

声明放前面,定义放后面,主函数能正常调用:

函数使用的时候要注意:1)函数要满足先声明后使用;2)函数的定义是一种特殊的声明;3)函数声明的时候形参的变量名是可以省略的,只保留类型即可:

2.4、函数调用

1°传值调用

写一个1函数Max,求两个整数的较大值:

cpp 复制代码
#include <iostream>
using namespace std;
int Max(int x, int y)
{
	return x > y ? x : y;
}
int main()
{
	int a = 0;
	int b = 0;
	cin >> a >> b;
	int c = Max(a, b);
	cout << c << endl;
	return 0;
}

调用Max函数时,就是传值调用。传值调用就是将实参的数据直接传递给形参。这个过程其实是将实参的值拷贝一份给Max函数使用,这份副本其实就是形参变量。这时形参和实参是不同的变 量,所以对形参的修改,不会影响实参。这种情况下参数传递的方式只能从实参到形参,也就是单向传递。为了理解什么是单向传递,我们看一下下面一个函数:

写一个函数Swap,交换两个整型变量的值:

我们发现函数的思路没有任何问题但是代码结果却和我们想的完全不一样,这是为什么呢?

其实 Swap 函数里的 xy 确实会交换,但 main 里的 ab 丝毫未变。这就是值传递 的特点:形参与实参占用不同内存,对形参的修改不会影响实参,数据单向传递,因此交换失败。一句话概括:xy 是独立空间,交换它们不等于交换 ab

那怎么样才能实现交换呢?下面我们就来看看C++中的引用:

2°引用

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量是同一块内存空间

类型& 引用别名 = 引用对象


  • 引用在定义时必须初始化
  • 一个变量可以有多个引用
  • 引用一旦引用一个实体,再不能引用其他实体

引用就相当于个一个变量起一个别名:

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
	int a = 0;
	// 引⽤:b和c是a的别名 
	int& b = a;
	int& c = a;
	// 也可以给别名b取别名,d相当于还是a的别名 
	int& d = b;
	++d;
	// 这⾥取地址我们看到是⼀样的 
	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	cout << &d << endl;
	return 0;
}

我们来看运行结果发现是一模一样的,我们就相当于给一块空间取了一个名字和三个别名,就类似于家人给我们取的小名一样,他们喊大名或者小名本质上就是在喊我们:

下面我们来对swap函数进行更改:

我们现在就实现了Swap交换函数,其实C++STL中也提供了一个库函数swap,这个函数可以用来交换两个变量也可以交换两个数组(容器的值):

cpp 复制代码
#include <iostream>
#include <utility>
using namespace std;
int main()
{
	int a = 0;
	int b = 0;
	cin >> a >> b;
	cout << "交换前, a = " << a << " b = " << b << endl;
	swap(a, b);//直接使⽤库函数swap交换两个变量 
	cout << "交换后, a = " << a << " b = " << b << endl;

	//swap也可以交换两个数组
	int arr1[4]; // arr1: 0 0 0 0
	int arr2[] = { 10,20,30,40 }; // arr1: 0 0 0 0  arr2: 10 20 30 40
	swap(arr1, arr2); // arr1: 10 20 30 40 arr2: 0 0 0 0

	for (int e : arr1)
		cout << e << " ";
	cout << endl;

	return 0;
}

那么字符串能不能使用引用的方式传参呢?当时是可以的:

cpp 复制代码
#include <iostream>
using namespace std;
void printString(string& s)
{
	cout << s << endl;
	s = "zhui ming zhen zhou";
}
int main()
{
	string s("hello world");
	printString(s);
	cout << s << endl;
	return 0;
}

3°效率比较

采用传值调用过程中,函数传参,将实参传递给形参的时候,形参会创建新的空间,再将实参的数据给形参拷贝一份;但是引用传参的方式,就不存在数据的拷贝,只是在形参的部分建立引用的关系,形参就是实参。所以引用传参的效率要高于传值调用的方式。


💡小提示:数组在传参的时候,形参和实参本来就是同一个数组,所以数组传参的时候,不需要使用引用参数。

2.5、函数重载

函数重载 + 引用巩固超详细版:

【C++】C++入门 -- 函数重载、引用_c++ 重载引用符号-CSDN博客https://blog.csdn.net/2501_91731683/article/details/154997756?spm=1001.2014.3001.5501

练习

关于函数的题目其实非常多这里为大家挑选一些较为典型的案例:

1°最大数 max(x,y,z)

最大数 max(x,y,z)

cpp 复制代码
#include<iostream>
#include<iomanip>
using namespace std;

int MAX(int x, int y, int z)//存储最大值
{
	int m = x > y ? x : y;
	return m > z ? m : z;
}

int main()
{
	int a, b, c;
	cin >> a >> b >> c;
	double m = ((double)MAX(a, b, c) / ((MAX(a + b, b, c) * MAX(a, b, b + c))));
	cout << fixed << setprecision(3) << m << endl;

	return 0;
}
拓展函数

这里为大家拓展库函数max和min:

max

max

在 C++ 中,max 函数用于返回两个值中的较大值。它是 C++ 标准库 <algorithm> 头文件中的一个函数。

max 函数可以用于各种类型,包括内置类型(如 intdouble)以及用户自定义类型(如类或结构体),只要这些类型支持比较操作。


cpp 复制代码
#include <algorithm>
template <class T>
const T & max(const T & a, const T & b); //默认⽐较 
template <class T, class Compare>
const T& max(const T& a, const T& b, Compare comp); //⾃定义⽐较器

参数:

  1. a:要比较的第一个值。
  2. b:要比较的第二个值。
  3. comp(可选):自定义比较函数对象或比较函数,用于确定 "较大" 值的标准。
  4. 比较函数应当返回一个布尔值,表示第一个参数是否 "小于" 第二个参数。

返回值:

  • 返回 ab 中较大的那个值。如果两个值相等,则返回 a

总结

使用 max 库函数需要包含 <algorithm> 头文件。对于整型、浮点型 等基础数据类型,可以直接使用 max 函数比较大小;但在特殊场景 下(如自定义结构体、类、字符串比较规则等),库函数无法满足需求,需要自定义比较规则来实现大小比较。

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
	int x = 10;
	int y = 20;
	int m = max(x, y);
	cout << "较大值是: " << m << endl;

	return 0;
}
cpp 复制代码
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
bool compareLength(const string& a, const string& b)
{
	return a.size() < b.size(); //这⾥必须给⼀个判断⼩于的标准 
}
int main()
{
	string str1 = "apple";
	string str2 = "banana";
	string max_str = max(str1, str2, compareLength);
	cout << "长度更长的字符串是" << max_str << endl;

	return 0;
}
min

在 C++ 中,min 函数用于返回两个值中的较小值。它和 max 函数类似,也是在 C++ 标准库 <algorithm> 头文件中的一个函数。使用和 max 函数一模一样,只是实现的效果恰好相反。

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
bool compareLength(const string& a, const string& b)
{
	return a.size() < b.size(); //这⾥必须给⼀个判断⼩于的标准 
}
int main()
{
	string str1 = "apple";
	string str2 = "banana";
	string min_str = min(str1, str2, compareLength);

	cout << "长度更短的字符串是" << min_str << endl;
	return 0;
}

学完了之后我们可以对这题的代码进行优化:

cpp 复制代码
#include<iostream>
#include<iomanip>
#include<algorithm>
using namespace std;

int MAX(int x, int y, int z)//存储最大值
{
	int m = max(x, y);
	return max(m, z);
}

int main()
{
	int a, b, c;
	cin >> a >> b >> c;
	double m = ((double)MAX(a, b, c) / ((MAX(a + b, b, c) * MAX(a, b, b + c))));
	cout << fixed << setprecision(3) << m << endl;

	return 0;
}

2°求正整数 2 和 n 之间的完全数

求正整数 2 和 n 之间的完全数

cpp 复制代码
#include<iostream>
using namespace std;
// 求n的所有因数之和
int sumFactor(int n) 
{
    int sum = 0;
    for (int i = 1; i < n; i++) 
    {
        if (n % i == 0) {  // 如果 i 是 n 的因数
            sum += i;      // 加到总和里
        }
    }
    return sum;
}
int main()
{
	int n;
	cin >> n;
    for (int i = 2; i <= n; i++)
    {
        if (i == sumFactor(i))
            cout << i << endl;
    }
	return 0;
}

3°素数个数

素数个数

素数也叫质数,是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数:

cpp 复制代码
#include <iostream>
using namespace std;
// 判断素数:是素数返回 true,不是返回 false
bool check_prime(int m)
{
    for (int i = 2; i < m; i++)
    {
        if (m % i == 0)  // 能被整除 → 不是素数
            return false;
    }
    return true; // 循环结束都没整除 → 是素数
}
int main()
{
    int n;
    cin >> n;
    int count = 0;
    for (int i = 2; i <= n; i++)
    {
        if (check_prime(i)) // 是素数 → 计数
        {
            count++;
        }
    }
    cout << count << endl;
    return 0;
}

其实判断素数还有一种方式我们去利用sqrt()函数:

cpp 复制代码
#include <iostream>
#include <cmath>   // 用 sqrt 必须加这个头文件
using namespace std;
// 判断素数:是素数返回 true,不是返回 false
bool check_prime(int m)
{
    // 原来:i < m
    // 现在:i <= sqrt(m)  (优化核心)
    for (int i = 2; i <= sqrt(m); i++)  
    {
        if (m % i == 0)  // 能被整除 → 不是素数
            return false;
    }
    return true; // 循环结束都没整除 → 是素数
}
int main()
{
    int n;
    cin >> n;
    int count = 0;
    for (int i = 2; i <= n; i++)
    {
        if (check_prime(i)) // 是素数 → 计数
        {
            count++;
        }
    }
    cout << count << endl;
    return 0;
}

4°素数对

素数对

cpp 复制代码
#include <iostream>
#include <cmath>
using namespace std;
bool is_prime(int n)
{
	int j = 0;
	if (n < 2)
		return false;
	for (j = 2; j <= sqrt(n); j++)
	{
		if (n % j == 0)
			return false;
	}
	return true;
}
int main()
{
	int n = 0;
	cin >> n;
	int i = 0;
	int flag = 0;
	for (i = 2; i + 2 <= n; i++)
	{
		if (is_prime(i) && is_prime(i + 2))
		{
			cout << i << " " << i + 2 << endl;
			flag++;
		}
	}
	if (flag == 0)
		cout << "empty" << endl;
	return 0;
}

5°素数回文数的个数

素数回文数的个数

cpp 复制代码
#include<iostream>
#include<cmath>
#include<string>
using namespace std;
//判断是否是素数
bool isPrime(int m)
{
    if (m < 2)
        return false;
	for (int i = 2; i <= sqrt(m); i++)
	{
		if (m % i == 0)
			return false;
	}
	return true;
}
//判断是否是回文数
bool ispalindrome(int m)
{
	int ret = m;
	//将数字转换成字符
	string s = to_string(ret);
	int left = 0;
	int right = s.size() - 1;
	while (left < right)
	{
		char tmp = s[left];
		s[left] = s[right];
		s[right] = tmp;
		left++;
		right--;
	}
	//将字符转换成数字
	int num = stoi(s);
	if (num == m)
		return true;
	else
		return false;
}
int main()
{
	int n;
	cin >> n;
	int count = 0;//统计个数
	for (int i = 11; i <= n; i++)
	{
		if (isPrime(i) && ispalindrome(i))
			count++;
	}
	cout << count << endl;
	return 0;
}

这是我第一遍写的代码,这段代码完全可以跑对这题但是其实还有优化的地方,比如回文数判断按那里我的做法是将数字先转换成string类型的字符串让他有下标然后去对比,最后转换成原数字和之前的数字比较,但是其实可以不用这么麻烦,回文数判断没必要反转 + 转数字 ,回文数不要反转再转数字,直接比较对称字符大家可以自行去尝试优化一下

6°区间内的真素数

区间内的真素数

cpp 复制代码
#include<iostream>
#include<string>
#include<cmath>
using namespace std;
bool isPrime(int m)
{
	if (m < 2)
		return false;
	for (int i = 2; i <= sqrt(m); i++)
	{
		if (m % i == 0)
			return false;
	}
	return true;
}
// 反序后判断是否为素数
bool reversePrime(int m)
{
    string s = to_string(m);
    int left = 0;
    int right = s.size() - 1;
    while (left < right)
    {
        char tmp = s[left];
        s[left] = s[right];
        s[right] = tmp;
        left++;
        right--;
    }
    int ret = stoi(s);
    return isPrime(ret);
}
int main()
{
    int M, N;
    cin >> M >> N;
    int flag = 1;   // 标记是否为第一个输出
    int cnt = 0;    // 统计真素数个数
    for (int i = M; i <= N; i++)
    {
        if (isPrime(i) && reversePrime(i))
        {
            cnt++;
            if (flag)
            {
                cout << i;
                flag = 0;
            }
            else
            {
                cout << "," << i;
            }
        }
    }
    // 若无真素数,输出 No
    if (cnt == 0)
        cout << "No";
    cout << endl;
    return 0;
}

我写的代码基本都已可以看懂并且跑出题目为准,要是想要优化提升效率还需大家自己去尝试,后面C++的内容我也会为大家出一篇博客有关提升代码效率,大家可以期待一下,以上就是函数部分的简单内容,还是需要大家多刷题才能更好掌握

三、递归

3.1、概述

为大家准备了一篇可以初步了解递归的博客(大家直接看递归的内容即可):

【C语言】C语言特别篇--数组与函数的实践 + 函数递归_c语言数组判断函数-CSDN博客https://blog.csdn.net/2501_91731683/article/details/147690415

3.2、典型案例解析

1°计算阶乘

计算阶乘

我们用递归和迭代两种方式为大家解决:

迭代
cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
	int n;
	cin >> n;
	int sum = 1;
	for (int i = 1; i <= n; i++)
	{
		sum *= i;
	}
	cout << sum << endl;
	return 0;
}
递归
cpp 复制代码
#include<iostream>
using namespace std;
int fac(int n)
{
	if (n == 0)
		return 1;
	else
		return n * fac(n - 1);
}
int main()
{
	int n;
	cin >> n;
	cout << fac(n) << endl;
	return 0;
}

这里还是要提醒大家一下:注意递归的中止条件,不然代码会有死循环、程序崩溃等问题

递归在书写的时候,有2个必要条件:

<1>、递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。

<2>、每次递归调用之后越来越接近这个限制条件。

2°斐波那契数列

斐波那契数列

我们依旧使用递归与迭代两种方式为大家解决:

迭代
cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
	int n;
	cin >> n;//数据组数
	while (n--)
	{
		int m;//第几个斐波那契数
		cin >> m;
		int x = 1; int y = 1;
		int z = 0;
		if (m == 1 || m == 2)
			cout << 1 << endl;
		else
		{
			while (m > 2)
			{
				m--;
				z = x + y; // 先把新数算出来(保存旧数据)
				x = y;     // 再挪
				y = z;     // 最后更新
			}
			cout << z << endl;
		}
	}
	return 0;
}
递归
cpp 复制代码
#include<iostream>
using namespace std;
int Fac(int m)
{
	//m表示第几个斐波那契数
	if (m == 1 || m == 2)
		return 1;
	else
		return Fac(m - 1) + Fac(m - 2);
}
int main()
{
	int n;
	cin >> n;//数据组数
	while (n--)
	{
		int m;//m表示第几个斐波那契数
		cin >> m;
		cout << Fac(m) << endl;
	}
	return 0;
}

💡 核心结论先放这

递归 ≈ 层层套娃,占地方,容易炸 循环 ≈ 原地转圈,省空间,更高效


📦 通俗解释:函数调用和栈帧

你可以把内存的栈区 想象成一个摞盘子的架子

  • 每调用一次函数,就往架子上放一个新盘子(这个盘子就是「栈帧」,用来放这个函数里的临时变量)。
  • 函数执行完返回了,才把这个盘子拿走,空间还给系统。

🔄****递归为什么费空间、容易炸?

递归就是自己调用自己,相当于:

  • 你先放了一个盘子(第一层函数)
  • 还没拿走,又在上面放一个(第二层递归调用)
  • 再放一个、再放一个...... 一直摞到很深
  • 直到最后一层函数才开始返回,然后从顶往下一个一个拿走盘子

如果递归层数太多,盘子摞得太高,就会把架子压垮------ 这就是「栈溢出(stack overflow)」,程序直接崩溃。


🔁 循环为什么更高效?

循环是在同一个函数里反复跑逻辑

  • 只需要一开始放一个盘子(整个循环都用这一个盘子)
  • 变量在同一个空间里反复覆盖更新,不会额外占更多盘子
  • 没有频繁的「放盘子 / 拿盘子」开销,所以更快、更省内存

✅ 什么时候用递归?什么时候用循环?

  • 优先用循环:大部分问题(比如计数、遍历),循环效率更高,不会有栈溢出风险。
  • 递归更适合 :逻辑特别复杂、用循环写会非常绕的场景(比如树的遍历、分治算法)。这时候递归代码更简洁、更好懂,哪怕多占一点内存、慢一点,也是值得的。

📌 一句话总结

  • 递归:代码好写,但费内存,深度大了容易崩。
  • 循环:代码可能稍繁琐,但省内存、跑得快,更稳定。
  • 简单问题用循环,复杂问题用递归,看场景选就行。

还有一题和上述的非常类似,大家可以练练手:

求 1+2+3+...+N 的值

cpp 复制代码
#include<iostream>
using namespace std;
int Fac(int m)
{
	if (m < 1)
		return 0;
	else
		return Fac(m - 1) + m;

}
int main()
{
	int N;
	cin >> N;
	int ret = Fac(N);
	cout << ret << endl;
	return 0;
}

练习

https://www.luogu.com.cn/problem/B2144

https://www.luogu.com.cn/problem/B2145

https://www.luogu.com.cn/problem/B2147

https://www.luogu.com.cn/problem/B2148

https://www.luogu.com.cn/problem/B2143


相关推荐
炸膛坦客2 小时前
单片机/C/C++八股:(十七)C++ 中指针和引用的区别
c语言·开发语言·c++
bbbb3652 小时前
算法优化的多层缓存映射与访问调度模型的技术7
算法
Fleshy数模3 小时前
多分类任务下的经典机器学习算法实战:LR、RF、SVM等对比分析
算法·机器学习·分类
草莓熊Lotso3 小时前
Linux IPC 进阶:System V 消息队列与信号量(含内核管理深度解析)
linux·运维·服务器·数据库·c++·人工智能·mysql
风吹乱了我的头发~6 小时前
Day52:2026年3月20日打卡
算法
星马梦缘8 小时前
数据库作战记录1
数据库·sql·mysql
短剑重铸之日10 小时前
《ShardingSphere解读》07 读写分离:如何集成分库分表+数据库主从架构?
java·数据库·后端·架构·shardingsphere·分库分表
njidf10 小时前
用Python制作一个文字冒险游戏
jvm·数据库·python
2401_8318249610 小时前
基于C++的区块链实现
开发语言·c++·算法