2024年C C++最全C语言从入门到入土——函数(1),C C++框架

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

🎄3. 函数的调用

🎁3.1 传值调用

  • 传值调用是将参数的形参传给函数的一种调用方式。
  • 函数的形参和实参分别占用不同的内存单元,对形参的修改不会对实参有影响

上方的 swap1 就是传值调用

🎁3.2 传址调用

  • 传址调用是将函数外部的内存地址传给函数参数的一种调用方式。
  • 通过这种方法可以利用函数直接对原本的参数进行改变

🎄4.函数的嵌套调用和链式访问

🛸4.1 嵌套调用

函数和函数之间可以根据实际需求进行组合调用了,例如

求1~10之间的素数

分析:

素数是指只能被1或自己整除的数,我们可以反过来思考,当其中有其它数可以将它整除的时候,这个数就不是素数,我们只需要寻找一个数的开平方根内的数字,若其中有一个数i可以整除n,那么n就不是质数

#include <stdio.h>
#include <math.h>

int is\_prime(int n)
{
    int i = 0;
    for (i = 2; i <= sqrt(n); ++i)//函数sqrt需要用到math.h的库
    {
        if (n % i == 0)//表示n能被i整除
        {
            return 0;//若有一个满足则不是质数
        }
    }
    return 1;//否则为质数
}

int main()
{
    int i = 0;

    for (i = 1; i < 10; ++i)
    {
        if (is\_prime(i))
        {
            printf("%d ", i);
        }
    }
    return 0;
}

image-20220629025742910

在这其中我们自定义了一个函数,而在自定义函数中,我们还调用了一个 sqrt 函数(这个函数需要配合 math.h 的库函数进行使用)

注意: 函数可以嵌套调用,但是不能嵌套定义

🛸4.2 链式访问

把一个数的返回值作为另一个函数的参数

#include <stdio.h>
#include <string.h>

int main()
{
    char ch[] = "abcdef";
    printf("%u", strlen(ch));//%u打印无符号整型
    return 0;
}

输出结果为:

image-20220629030215986

我们首先看看strlen函数

image-20220629030718799

在这之中有一个size_t,我们可以在编译器中选中这个函数,右击鼠标 -> 选择转到定义

image-20220629031044884
image-20220629031206049

选中size_t ,继续选择转到定义

image-20220629031342144

可以看到,size_t 本质上就是 unsigned int ,则 strlen 的返回值就是 unsigned int

🎄5. 函数的声明和定义

🎈5.1 函数的声明

  1. 函数声明首先要函数存在才能声明,不存在的函数无法进行函数声明
  2. 函数声明要在函数使用之前,满足先声明后使用
  3. 函数声明一般放在头文件中

image-20220629171737175
image-20220629173016032

举个例子🍐

#include <stdio.h>

void test();//函数声明

int main()
{
    test();//调用test函数
    return 0;
}

void test()//函数的实现
{
    printf("haha");
}

但是在有些地方会遇到下面这样的情况(通常在学校会遇到😅)

#include <stdio.h>

int main()
{
    void test(int x);//函数的声明
    test(1);
    return 0;
}

void test(int x)
{
    printf("%d", x);
}

🎈5.2 函数的定义

函数的定义就是指,函数的具体实现

在进行函数声明的时候,只要满足 声明 or 定义使用的前面 就行,所以也出现了这样一种写法

#include <stdio.h>

void test()//test 函数的具体实现
{
    printf("haha");
}

int main()
{
    test();
    return 0;
}

这样的写法,因为函数定义在使用之前,声明的同时也把函数给定义了。

通常情况下,我们写项目的时候应该怎样去规范写法呢?(参考《高质量的C/C++编程》)

test.h的内容

//版权和版本声明

#ifndef TEST\_H //防止test.h被重复引用
#define TEST\_H //使用全大写,符号 . 改成 \_

//包含头文件
#include <stdio.h>
...
#include <math.h>
...
#include "myhander.h"
...

//函数声明
void Add(int x, int y);
void test2();
...
    
//类结构声明
struct Color
{
  ...  
};

#endif

test.c的内容

#include <test.h>
//函数的实现

int Add(int x, int y)
{
    return x+y;
}

...
    

这样的写法通常是用于需要分模块来完成的,也就是需要实现很多功能来实现,具体可以看一下🚀🚀🚀🎄递归实现扫雷游戏🎄

🎄6.函数的递归

🎉6.1 什么是递归?

程序自己调用自己的编程技巧被称为递归。

这种方法,主要是把一个大型复杂的问题层层转化成一个与原问题相似较小规模的问题来解决,这就是递归的策略。

递归的主要思考方式在于:大事化小

🎉6.2 递归的两个必要条件

  1. 存在一个限制条件,能使递归不再继续
  2. 每次递归调用都会更逼近于限制条件

🎉6.3 递归与迭代

我们刚刚认识什么是递归,那么我们接下来就认识一下,递归的具体运用吧😏😏😏

Q1 :求解n阶汉诺塔问题

将A柱子上所有圆盘移动到C柱子上,一次只能移动一个 圆盘,且大圆盘不能在小圆盘的上方

#include <stdio.h>

void hanoi(int n, char a, char b, char c)
{
	if (n == 1)
	{
		printf("%c -> %c\n", a, c);
	}
	else
	{
		hanoi(n - 1, a, c, b);
		printf("%c -> %c\n", a, c);
		hanoi(n-1, b, a, c);
	}
}

int main()
{
	int n = 0;//表示n阶汉诺塔
	scanf("%d", &n);
	hanoi(n, 'A', 'B', 'C');
	return 0;
}

解析:

根据递归的思维,需要把原问题层层递进,把它化简成最简单且与原问题相似的问题。

image-20220629235026027

当A柱子上只剩下1个圆盘的时候,直接将它移动到C柱子上即可

image-20220629235848829

当 A 柱子上有 n 个圆盘的时候,本质上就是把上方 n-1 个圆盘全部移动到 工具柱(B柱) 上,再把最下面的柱子移动到 C 柱,最后把 B 柱子上的圆盘 全放回 A 柱,继续重复以上过程就好了

image-20220630000547815

Q2 : 求斐波那契数列的第n个数 (不考虑溢出)

斐波那契数列指的是这样一串数列:1,1,2,3,5,8,13,21,34,55,89…

从第三项开始,每一项第一等于前两项之和

#include <stdio.h>

int Fibrec(int n)
{
	if (n > 2)
		return Fibrec(n - 1) + Fibrec(n - 2);
	else
		return 1;
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d\n", Fibrec(n));
	return 0;
}

image-20220630023239764

思考:

当我们要计算第50个甚至第100个斐波那契数的时候就会显得十分耗费时间

为什么会有这样的事发生呢?

  • 我们不妨对原函数进行修改
#include <stdio.h>

int count = 0;//定义全局变量

int Fibrec(int n)
{
    ++count;//每调用一次,count+1
	if (n > 2)
		return Fibrec(n - 1) + Fibrec(n - 2);
	else
		return 1;
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d\n", Fibrec(n));
    printf("%d\n", count);
	return 0;
}

image-20220630023857178

可以发现,当计算到第40个斐波那契数的时候,函数已经调用了204668309次。

原因是

  1. 在计算一个较大的斐波那契数的时候,中间会出现很多重复的数据,这也大大降低了运算的速率
  2. 每调用一次函数就会向内存申请一块空间,当函数调用过多的时候,也有可能会造成 栈溢出 的现象stack overflow

(系统分配的空间是有限的,对于一直在栈区开辟空间,最终导致空间耗尽的情况,我们称之为栈溢出)

如何解决上面的问题呢?

  1. 将递归写成非递归的形式(多数都可以用迭代来解决(也就是循环))
  2. 使用 static 代替 nonstatic 局部对象,这样可以减少每次递归调用和返回时的产生和释放 nonstatic 局部对象的开销

我们将上面的问题转化成非递归的形式

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

,也有可能会造成 栈溢出 的现象stack overflow

(系统分配的空间是有限的,对于一直在栈区开辟空间,最终导致空间耗尽的情况,我们称之为栈溢出)

如何解决上面的问题呢?

  1. 将递归写成非递归的形式(多数都可以用迭代来解决(也就是循环))
  2. 使用 static 代替 nonstatic 局部对象,这样可以减少每次递归调用和返回时的产生和释放 nonstatic 局部对象的开销

我们将上面的问题转化成非递归的形式

[外链图片转存中…(img-ZgwQ2ZLo-1715525422167)]
[外链图片转存中…(img-k2kG7lOD-1715525422167)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值