函数(2)

简介: 函数(2)

在讲之前再讲一下代码要规范:如下

//代码1
#include<stdio.h>
test()
{
  printf("hehe\n");
}
int main()
{
  int ret=test();
  printf("%d\n", ret);
  return 0;
}


我们可以看出test是有返回值的。

(1)C语言规定:函数的返回类型不写的时候,默认是返回的是int类型。

建议:真的不需要函数返回值的时候,明确写void。

(2)为什么返回的是5呢?(我们现在知道就行,不用深入了解)

①除main外,其余函数没有写return,取决于编译器的实现(返回的一般,是函数执行完的最后一条指令所产生的结果)

②main,没写return,默认返回0

(3)printf函数的返回值是打印在屏幕上字符的个数。

//代码2
#include<stdio.h>
void test()
{
  printf("hehe\n");
}
int main()
{
  test(100);
  return 0;
}

如上:虽可运行,但不严谨

C的不严谨:不需要传参时,应写void;否则完全可能传参,只是函数没有接收。

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

函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的


5.1  嵌套调用

函数的嵌套调用,即在一个被调函数内部,又调用了其他的函数。

1、代码演示:

//嵌套调用
#include<stdio.h>
void new_line(void)
{
  printf("hehe\n");
}
void three_line(void)
{
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    new_line();//调用new_line三次
  }
}
int main()
{
  three_line();//调用three_line
  return 0;
}


2、图示如下:

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


5.2  链式访问

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

例子:

//代码1
#include<stdio.h>
#include<string.h>
int main()
{
  char arr[20] = "hello";
  int ret = strlen(arr);//计算字符串长度,注统计‘\0’之前的字符个数
  printf("%d\n", ret);
  printf("%d\n", strlen(arr));//strlen的返回值作为printf的参数
  return 0;
}


如上,我们应该能体会到什么是链式访问了。

我们再举一个链式访问的经典例子:

//代码2
#include<stdio.h>
int main()
{
  printf("%d", printf("%d", printf("%d", 43)));
  //printf的返回值是打印在屏幕上字符的个数
  return 0;
}

tip:printf函数的返回值是打印在屏幕上字符的个数。

程序输出:4321

6.  函数的声明和定义

6.1  函数的声明

1、告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。(声明只是告诉你有,但是真的有没有取决于定义)

声明的一般形式如下:

          ①返回类型   函数名(形参列表); 如:int  Add(int x,int y);

          ②返回类型   函数名(形参类型列表); 如:int  Add(int ,int );

好的风格:建议使用①;  

注:函数的声明以“;”结束,不能省略。

2、函数的声明一般出现在函数的使用之前,要满足先声明后使用。

3、函数的声明一般放在头文件中的

6.2  函数的定义

函数的定义是指函数的具体实现,交代函数的功能实现


说明:函数为什么要声明?

代码在编译的时候,要进行代码的扫描,是顺序从前往后扫描的。

1、当函数定义出现在函数调用之后时,在主调函数前,采用函数原型对被调用函数进行声明。

当不声明,编译器会警告,如下:

2、函数定义出现在函数调用之前,可不用进行函数声明。(函数的定义也是一种特殊的声明)

#include<stdio.h>
//函数的声明
int Add(int x, int y);
int main()
{
  int a = 0;
  int b = 0;
  scanf("%d %d", &a, &b);
  int ret = Add(a, b);
  printf("%d", ret);
  return 0;
}
//函数的定义
int Add(int x, int y)
{
  return x + y;
}

这就学会了函数的定义和声明吗?


函数的声明和定义,不是这样用的,以上只是它的一种运用场景,只是它的语法展示,真正在工程中,函数的定义和声明,我们是怎么写的呢?

头文件:.h——放置函数的声明

源文件:.c——放置函数的实现(定义)

如要写一个函数实现求两个整数的和:

1、创建一个源文件:add.c——放置函数的实现(定义)


2、创建一个头文件:add.h——放置函数的声明


3、测试——再创建一个源文件:test.c


在工程中想要使用(调用)Add函数,也是如库函数一样要打招呼。

库函数:使用库函数,必须包含 #include 对应的头文件。

自定义函数:也如库一样,必须包含#include对应的头文件。

如上截图:库函数的头文件名一般用<>引用,自定义函数的头文件名一般用“”引用。

我们本可以在一个.c文件写的,为什么分成三个文件呢?好处是什么呢?


1、模块化开发(分工)

例:要实现一个计算机

      加法——a(程序员做)——add.c  add.h

      减法——b——sub.c  sub.h

      乘法——c——mul.c  mul.h

      除法——d——div.c   div.h

如果不分开,模式化开发,在一个.c文件a、b、c、d四个人怎么同时写了?,还会使代码变得混乱。  

分开,模式化开发每个人都可以同时写,写完后再组成一个.c文件就完成了计算机了。


2、代码的隐藏(可以使用,但不知道源代码)

(1)具体步骤:项目名——属性——配置属性——常规——配置类型——改成静态库——应用——确定——ctrl+f7——在文件中生成xx.lib文件——用记事本查看——显示乱码(2)在另一个项目使用:需要导入xx.lib和xx.h文件

      还需要在主调函数前:#pragma  comment(lib,"xx.lib"),就可使用了

现在知道不再一个.c文件写有隐藏代码的好处就行了,不用深入理解,后期我会在博客中写的。

总结:

函数的声明我们一般放置在头文件中

函数的实现我们都是放置在源文件中

7.  函数的递归

7.1  什么是递归

 程序调用自身的编码技巧称为递归(recursion)。

  递归做为一种算法在程序设计语言中广泛应用。一个函数(或过程)在其定义(或说明)中 有直接或间接调用自身 的一种方法。

    它通常把一个 大型复杂 的问题 层层转化 为一个与原问题 相似 的规模较小的问题来求解。

    递归策略 , 只需少量 的程序就可 描述出解题过程 所需要的 多次重复计算 ,大大地 减少 了程序的代码量。

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

最简单的递归:

#include<stdio.h>
int main()
{
  printf("hehe\n");
  main();//递归:在函数内自己调用自己
  return 0;
}

f10调试:


7.2  递归的两个必要条件

1、存在限制条件,当满足这个限制条件的时候,递归便不继续(递归出口)

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


7.2.1  练习1

接受一个整型值(无符号),按照顺序打印它的每一位。

例如:

输入:1234,输出1 2 3 4

void print(unsigned int n)
{
  if (n > 9)//限制条件
  {
    print(n / 10);//递归调整,接近限制条件
  }
  printf("%d ", n % 10);
}
int main()
{
  unsigned int num = 0;
  scanf("%d", &num);//1234
  //写一个函数打印num的每一位
  print(num);
  return 0;
}


递归的理解

递——递推

归——回归

先递推后回归。


7.2.2  练习2

编写函数不允许创建临时变量,求字符串的长度。

#include<stdio.h>
int my_strlen(char* str)
{
  if (*str != '\0')
  {
    return 1 + my_strlen(str + 1);//str+1,字符指针+1,向后跳一个字符
  }
  else
  {
    return 0;
  }
}
int main()
{
  char arr[] = "bit";
  int ret = my_strlen(arr);//数组名其实是首元素地址
  printf("%d\n", ret);
  return 0;
}

(1)数组名其实是首元素的地址

(2)字符指针+1,向后跳一个字符。(那整形指针+1,向后跳四个字符)


总结:递归一般都要有两个必要条件(一般与if搭配)

1、有限制条件

2、每一次递推越来越接近限制条件

相关文章
|
Python
Python跳动的爱心完整代码
Python跳动的爱心完整代码
1854 0
|
新零售 供应链
阿里云电子签:助企业打通业务数字化“最后1公里”
阿里云电子签:助企业打通业务数字化“最后1公里”
1830 3
阿里云电子签:助企业打通业务数字化“最后1公里”
|
人工智能 算法 物联网
阿里云百炼最新能力升级,你都有哪些期待?
阿里云百炼大模型服务产品全新升级,探索算法和技术创新,共享阿里集团内环业务的模型构建经验。
1460 4
|
开发框架 前端开发 数据安全/隐私保护
【Flutter 前端技术开发专栏】Flutter 中的布局与样式设计
【4月更文挑战第30天】本文探讨了Flutter的布局和样式设计,关键点包括:1) 布局基础如Column、Row和Stack用于创建复杂结构;2) Container、Center和Expanded等常用组件的作用;3) Theme和Decoration实现全局样式和组件装饰;4) 实战应用如登录界面和列表页面的构建;5) 响应式布局利用MediaQuery和弹性组件适应不同屏幕;6) 性能优化,避免过度复杂设计。了解并掌握这些,有助于开发者创建高效美观的Flutter应用。
365 0
【Flutter 前端技术开发专栏】Flutter 中的布局与样式设计
|
Dubbo 前端开发 Java
Dubbo3 服务原生支持 http 访问,兼具高性能与易用性
本文展示了 Dubbo3 triple 协议是如何简化从协议规范与实现上简化开发测试、入口流量接入成本的,同时提供高性能通信、面向接口的易用性编码。
16955 91
|
10月前
|
消息中间件 监控 Cloud Native
云原生架构下的数据一致性挑战与解决方案####
在数字化转型加速的今天,云原生架构以其轻量级、弹性伸缩和高可用性成为企业IT架构的首选。然而,在享受其带来的灵活性的同时,数据一致性问题成为了不可忽视的挑战。本文探讨了云原生环境中数据一致性的复杂性,分析了导致数据不一致的根本原因,并提出了几种有效的解决策略,旨在为开发者和企业提供实践指南,确保在动态变化的云环境中保持数据的完整性和准确性。 ####
GitHub星标4000!清华大牛的CTF竞赛入门指南,真的太香了!
想进入网络安全行业、实现从学校到职场的跨越,参加CTF竞赛是很好的成长途径。 通俗而言,CTF是模拟“黑客”所使用的技术、工具、方法等手段发展出来的网络安全竞赛,有了手段之后需要的就是经验与黑客感(HackorFeel)。 CTF赛题涉及的领域很广,市面上也早有在知识广度上均有所覆盖的CTF书籍,但没有深入单一领域的内容,尤其是Pwn方向的。 Pwn是网络安全攻防最有魅力的部分,对于原教旨攻防人士来说,Pwm才是原汁原味的技术体现。二进制Pwn一直是CTF比赛的热点和难点。
|
Java Spring
Spring框架: Spring Bean的生命周期是什么样的?
Spring Bean经历实例化、属性赋值、初始化和销毁四个阶段: 1. 反射创建Bean实例。 2. 注入属性值通过setter方法。 3. 执行用户定义的初始化方法(init-method)。 4. 不再需要时,调用销毁方法(destroy-method)。生命周期中,自动操作与用户扩展点并存。
194 0
【Python 基础】Python中的实例方法、静态方法和类方法有什么区别?
【5月更文挑战第6天】【Python 基础】Python中的实例方法、静态方法和类方法有什么区别?
|
Linux 编译器 C语言
CentOS 快速安装Python3和pip3
CentOS是经常使用的Linux系统之一,特别是作为服务器使用,其只自带了Python2,但是现在使用更广泛的是Python3,因此需要自行安装,同时为了更方便地安装第三方库,还需要安装pip3。