c语言三个数求最大值_C语言入门——第五周笔记

741dbf9a9a095016f52bac0116e37ada.png
大哥的眼睛变了,是万花筒?
又想给我下幻术吗?
《火影之雪殇》——纹路著

通知

这么多天来感谢各位的关注!!!

五月初,我会写一篇关于今年全国一、二、三卷的感想,以及更加有质量的数学文章。期待能够和大家一样,感受着那份属于数学的独特的美~

数组与函数

初见数组

先思考一个问题,如何记录很多数?

难道要不断的定义int num1 num2 num3.................?

所以,我们开始学新的手段——数组。

题目:需要算输入的数的平均值,并且列出所有大于平均值的数。

#include 

这个程序存在安全隐患,是什么? 后面说

定义数组

定义数组

  • <类型>变量名称[元素数量];
  • int grades[100];
  • double weight[20];
  • 元素数量必须是整数
  • C99之前:元素数量必须是编译时刻确定的字面量

数组

  • 是一种容器(放东西的东西)。特点:
  • 其中所有的元素具有相同的数据类型;
  • 一旦创建,不能改变大小
  • *(数组中的元素在内存中是连续依次排列的)

一个茶杯就如同一个数组,专门用来放东西的,你可以放咖啡,放茶叶,放果汁。并且C99之后,数组就可以是变量了!!

int a[10]

  • 一个int的数组
  • 10个单元:a[0],a[1],…,a[9]

dfb1df032a6236777cfa842008b66c87.png
  • 每个单元就是一个int类型的变量
  • 可以出现在赋值的左边或右边(左边的叫写入,右边的叫读入)
  • a[2]=a[1]+6;
  • *在赋值左边的叫做左值

数组的单元

  • 数组的每个单元就是数组类型的一个变量
  • 使用数组时放在[]中的数字叫做下标或索引,下标从0开始计数:
  • grades[0]
  • grades[99]
  • average[5]

有效的下标范围

  • 编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读还是写
  • 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃
  • 出现segmentation fault可能是因为数组出错了
  • 但是也可能运气好,没造成严重的后果
  • 所以这是程序员的责任来保证程序只使用有效的下标值:[0,数组的大小-1]

来看下面这一段代码

#include 

在文章最前面的一个代码里,我们说我们写的那个代码(为方便观看:代码在下面)有安全隐患,这个安全隐患就是,如果用户输入的数大于99,那么就会导致程序出错。

#include 

那么,怎么解决这个问题呢?

我们有2个办法。

  1. 当用户输入完第99个数,就停止读取。
  2. 可已让用户提前输入,要输入的数的个数。

我们就用方法2来修改代码

#include 

长度为0的数组?

  • int a[0];
  • 可以存在,但是无用

因为,此时下标(下标必须大于0)的最大值为0-1,为-1,负值,而我们读数都是从0开始读,所以长度为0 的数组没有任何意义。但是呢,在运行程序的时候他能通过。

用数组做散列计算

写一个程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数,输入-1表示结束。

#include 

初见函数

现在呢,我们要求所有素数的和。我们写出下面这一段代码:

#include 

我把其中的一段代码单独拿出来:

int 

这一段代码所做的事情就是去求素数。但是呢,这一段代码使得整个for循环看着很大,而他的功能又很单一 。

我们可以这样做,我们把这一段代码取出来(具体看下面的代码)

#include 

这样呢,我们就写出了一个属于我们自己的函数。并且当我们需要这个函数的时候,就可以直接调用。其实,我们在最初写hello world的时候就用到了函数——printf,printf是C语言标准库里的函数。

对于一大段代码里,如果某一小段的功能很单纯,那么我们就可以单独把它拿出来,下次直接调用就行了。这个被调用的函数,我们就可以把它称为函数

我们来看这样一个例子:求出1到10、20到30和35到45的三个和。

按照我们传统的写法,我们可以这样来写。

#include 

我们发现对于for循环的3段代码几乎一模一样!他们的功能是完全一样的。这件事情,我们在程序里称为“代码复制”。“代码复制”是程序不良的表现,这说明你代码的质量不好!因为,你将来要去做修改,要去做维护的时候,你很可能遇到你不是只要维护一处的问题,而是维护很多处。这就是代码不良的表现。

我们可以这样来改

void 

此时,我们的主函数很简单了。我们制作了一个自己的函数,当我们后面需要的它的时候,就可以直接调用了。

函数的定义和使用

什么是函数?

  • 函数是一块代码,接受零个或多个参数,做一件事情,并返回零个或一个值
  • 可以先想象成数学中的函数:
  • y=f(x)

函数定义

void 

void sum(int begin,int end)为函数头;大括号内的代码称为函数体;void称为返回类型,并且这个void表示返回0个值;sum表示函数名;{int begin,int end}表示参数表。

调用函数

  • 函数名(参数值);
  • ()起到了表示函数调用的重要作用
  • 即使没有参数也需要()
  • 如果有参数,则需要给出正确的数量和顺序
  • 这些值会被按照顺序依次用来初始化函数中参数

函数返回

  • 函数知道每一次是哪里调用它,会返回到正确的地方。

从函数中返回

我们之前有一个程序是能够返回函数值的。不知道你有没有注意到

#include 

在这个代码里,我们定义了ret并进行了赋值,最后又return ret。其实,我们很早很早就接触了return。之前见到的形式是return 0;上面代码中的isPrime代表着要返回一个值,同时前面我们也说过,如果是void就意味着返回零个值。

return的作用是停止函数的执行,并送回一个值。它的写法有两种:

  1. return;
  2. return 表达式;

第一种也就是不返回值,第二种即返回一个函数值。

一个函数中可以有多个return。但是这就不满足我们前面所说的单一出口,如果有多个return,就意味着可以出来很多种值,这对我们后期的修改和维护都是有很大影响的。

从函数中返回的值

  • 可以赋值给变量
  • 可以再传递给函数
  • 甚至可以丢弃(因为有时我们并不需要返回一个值,仅仅需要它的副作用,这个很常见)

在没有返回值的函数中,我们用

  • void函数名(参数表)
  • 不能使用带值的return
  • 可以没有return
  • 调用的时候不能做返回值的赋值(因为他不返回东西,所以你不可能把它赋给某样东西)
  • 如果函数有返回值,则必须使用带值的return·······

函数原型

我们在写自己的函数时一定要写在main函数的上方,因为C的编译器是自上而下顺序分析你的代码,如果你写到后面,在这之前出现的函数,他就不知道那是什么。所以,你必须要把调用的函数写在前面,这样它才能检查你对函数的调用是否正确。

但有时,我们想先写一个主体函数,把各个细节放在主体函数的下面,这时怎么办?

我们可以在主函数上面做一个声明

#include 

这样编译器就提前知道有这样的一个函数了。

函数原型就是把函数头取出来,以分号“;” 结尾,就构成了函数的原型。函数原型的目的是告诉编译器这个函数长什么样,比如名称;参数(数量及类型);返回类型。其次,原型里可以不写参数的名字,但是一般仍然写上,这是为了方便我们人类读者去读懂它。

参数传递

  • 如果函数有参数,调用函数时必须传递给它数量、类型正确的值
  • 可以传递给函数的值是表达式的结果,这包括:
  • 自变量
  • 变量
  • 函数的返回值
  • 计算的结果

如果调用函数时给的值与参数的类型不匹配是C语言传统上最大的漏洞;编译器总是悄悄替你把类型转换好,但是这很可能不是你所期望的;后续的语言,C++/Java在这方面很严格。

以前我们做过交换a和b的值的代码,但看下面这样的代码能交换a和b的值吗?

#include 

结果

8386743686810e92cafa5dbbe6348c20.png

swap里面的a,b和主体函数里面的a,b没有任何关系。 这里仅仅把5给了a,把6给了b,后面交换后跟主体函数没有任何关系,显然这样的代码不能交换a和b的值。当然,实在不行,还能这样理解:既然返回值最多返回一个,很明显我这里超过1个,肯定不能完成转换功能。

C语言在调用函数时,永远只能传值给函数。

  • 每个函数有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系
  • 过去,对于函数参数表中的参数,叫做“形式参数”,调用函数时给的值,叫做“实际参数”
  • 由于容易让初学者误会实际参数就是实际在函数中进行计算的参数,误会调用函数的时候把变量而不是值传进去了,所以我们不建议继续用这种古老的方式来称呼它们
  • 我们认为,它们是参数和值的关系

本地变量

  • 函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称作本地变量
  • 定义在函数内部的变量就是本地变量
  • 参数也是本地变量

变量的生存期和作用域

  • 生存期:什么时候这个变量开始出现了,到什么时候它消亡了
  • 作用域:在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)
  • 对于本地变量,这两个问题的答案是统一的:大括号内——块

我们用上次运行的程序swap来解释上面这一段话。

1df7a8098bd716a0ef7387f50e713f18.png
我们先在第7行设置断点(用调试功能来看程序是如何运行的)

31e236e3f935658f48ba877f2fdad8fc.png

刚开始,我们发现在程度读取到第7行之前,a=5,b=6,t=not found in current context;

9f05232ba77d8e5f3216ef6ccb661619.png
点单步进入,我们就进入了swap这个函数里了

23cd0b1c68ff15d649aedd2b9a6a8efe.png

这个时候,我们发现a=5,b=6,t=0;继续点击“下一步”程序开始读取第14行

9e37613f3358723e99c8e33902034842.png

这个时候,a=5,b=6,t=5;再点击“下一步”

58d54b863d808fbd4fbeec612e3685f2.png

继续点击“下一步”

3e213dae5ecd382d4cdfcb00a0716362.png

25f7a21153b2f7bddb67699665e3f314.png

我们发现printf并没有可以输出的值。这是因为当a和b进入了swap后,在这个小的函数中它有了生存期和作用域,当它离开的时候,这些也都随之没有了。

9124cf8a9e726be71c4174c46681dd89.png
但是,编译器最后还是默认地输出了a,b最开始的值

一小段总结:上面表述这么多,就是想说一个单独函数内部的变量称为“本地变量”,因为只有进入这个函数中,他们才有可能会出现(之所以说可能,是因为有可能你在这个函数外定义了一个和他一模一样的变量),这时他们也就才有了生存期和作用域,当你离开这个函数时,他们的生存期和作用域也就随之消失了,并且,每一个单独函数都会有一个“{}”,我们又称这个“大括内{}”为“块”。而这些变量都在“{}”里,也就是在“块”内。

上面没看懂没关系(突然感觉到自己总结能力有点差,抱歉)~下面有具体的总结

本地变量的规则

  • 本地变量是定义在块内的
  • 它可以是定义在函数的块内
  • 也可以定义在语句的块内

例:

cd734a9cd04537f7419cba8505762291.png

我们现在加一个if语句,这个i在这个块(大括号)内,当进入这个if语句里,你才能找到i,i才有效也即有生存期,接着它发挥作用,也就有了作用域;当你a>b,也就不进入if语句里,这个i也就起不到作用了;

c1460794d6fa6a20db15ca1d5b90ac33.png

你看,我在块外面写一个i++,编译器就告诉我,我并没有定义i。所以当离开了块,i就不存在了。

  • 程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了。
  • 块外面定义的变量在里面仍然有效

例:

d0190fcee59645d0c66077348b9c083a.png
并没有任何错误
  • 块里面定义了和外面同名的变量则掩盖了外面的

例:

e800c5be7b425cd143e3551157e0968a.png
  • 不能在一个块内定义同名的变量

例:

e6db79ab68cf14f38c4359dc6dd3b076.png
  • 本地变量不会被默认初始化
  • 参数在进入函数的时候被初始化了

我们还有一些其他事需要去琢磨:

当没有参数时,我们是写成

  • void f(void);
  • 还是
  • void f();

在传统C中,它表示f函数的参数表未知,并不表示没有参数。

当你写void f(void)时,等于直接告诉编译器,我的这个函数不接收任何参数。

所以,我们在写程序时,最好明确参数表中参数的类型。

其次,我们要注意参数表中的“逗号”,不是“逗号运算符”而是“标点符号”。具体例子看下面:

  1. f(a,b)
  2. f((a,b))

1中的逗号就是“标点符号”;而2中的逗号为“逗号运算符”,这是因为它在外面又加了一层括号,此时代表输出一个参数。

C语言不允许函数嵌套定义:我们可以在一个函数里放一个函数的声明,但不能放他的定义(body)

再举个例子:

  • int i,j,sum(int a,int b); //这个在C语言里面是允许的
  • return (i); //我们说return后面应该跟一个表达式,这里i加了一个括号,也变成了表达式,但是会让人误解,误解return是一个函数。

关于main:

  • int main()也是一个函数
  • 要不要写成int main(void)? 这个可以写,代码并不会出错
  • return的0有人看吗?在函数结束时,要把这个0返回给要调用它的那个地方,返回给那一小段代码,那一小段代码会检查main到底返回了一个什么样的东西,然后他会报给你的操作系统,报告给你的windows到底返回了一个什么样的值,这个值对有些人来说是有意义的。在Windows中你可以在P处理文件调用你写的那个程序,然后跟上一句if erroelevel 1。如果你的程序返回的是1的话,去做什么什么事情。传统上一个程序返回0,表示他正常的运行结束了,如果返回一个非0的值表示它运行什么地方出现了错误;对于Unix,你可以用echo$?,来看返回的值。所以return的0是有人看的,是可以看的,是可以起作用的。
  • Windows:if errorlevel 1…
  • Unix Bash : echo$s?
  • Csh:echo $status

二维数组

我们前面说了一位数组,这个数组只有一个下标的,所以叫做一位数组,这个数组是线性的。除了一维的,二维的,三维的,甚至跟多维度的数组都可以做。我们来看看二维数组怎么做?

  • int a[3][5];
  • 通常理解为a是一个3行5列的矩阵(当然你也可以说是5行3列。我们用3行5列是考虑到计算机内部的排列,习惯性的说这是一个3行5列的矩阵)

9c479ac7b57a0957b992d029c8cb0a2c.png

二维数组的遍历:

for
  • 和一维数组一样,我们需要遍历,这里是每行每列的遍历。
  • a[i][j]是一个int
  • 表示第i行第j列上的单元

二维数组的初始化

int 
  • 列数是必须给出的,行数可以由编译器来数
  • 每行一个{},逗号分隔
  • 最后的逗号可以存在,有古老的传统
  • 如果省略,表示补零
  • 也可以用定位(* C99 ONLY)

看一个二维数组的例子:大家玩过tic-tac-toe的游戏吗?(反正我之前没玩过)

  • 读入一个3×3的矩阵,矩阵中的数字为1表示该位置上有一个X,为0表示O
  • 程序判断这个矩阵中是否有获胜的一方,输出表示获胜一方的字符X或O,或输出无人获胜

e33c324d1b484a4aafa9c99b51e6ccb3.png
图片来源:Wikipedia.prg

这个游戏中文里我们叫他井字棋。就是有一个3×3的矩阵,如果有一行或者一列或者斜着的窦唯O或者X,就表示你赢了。(规则和五子棋一样!)所以呢,我们要做的很简单,我们并不是要去实现这个游戏,只是说,如果程序读到了这个矩阵,能不能判断出来,现在这个矩阵上面的情况,它的XX和OO的分布是不是有一方赢了,然后输出谁赢了,或者都没赢。就是做这样一件事情。

我们来看一下代码

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值