软件培训第二讲——函数、数组、字符串
文章目录
01 函数
(1)什么是函数
首先举一个餐厅雇佣机器人的例子
餐厅问题: 你开了一家餐厅,雇佣了一个机器人做服务员。机器人比较傻,每次顾客想要点一杯水都需要对他说:先走到厨房,再拿出一个杯子,倒入水,然后拿着杯子给我。久而久之,大家都觉得十分繁琐。于是你为机器人增加了设定:每当顾客说“给我一杯水”,机器人就自动的执行上面的几步。
这种设定,其实就是一种函数的封装,我们向机器人发出 “给我一杯水” 的指令,就是一次对函数的调用。
函数 就是将一系列具有一定功能和意义的代码封装以重用的方式。
(2)为什么要用函数
很多小会员可能都会有这样的一个疑问:为什么要用函数?
下面就举一个小例子告诉大家为什么要用函数
首先,我们定义一个计算式
y
(
x
)
=
{
x
2
(
x
为
偶
数
)
x
−
1
(
x
为
奇
数
)
y(x)=\left\{ \begin{aligned} \frac{x}{2}\quad (x为偶数) \\ x-1\quad (x为奇数) \end{aligned} \right.
y(x)=⎩⎨⎧2x(x为偶数)x−1(x为奇数)
如果我要重复使用这个计算式很多遍,每次都要写好多,太麻烦了!!
假如我们可以像
a
=
神
奇
的
函
数
(
b
,
c
)
a = 神奇的函数(b, c)
a=神奇的函数(b,c)
这样的方法去实现计算式的功能,那我们就省去了大量重复的代码
(3)函数怎么用
函数既然这么好,如何才能正确的定义和使用函数呢?
这就要从函数的四要素出发
1.函数的返回类型
2.函数的名称
3.函数的参数
4.函数的内容
觉得不好记?
没关系,我们分开来讲一讲。
1.函数的返回类型
函数的返回类型就是当系统执行过你的程序之后,应该交还给你的东西的属性
仍然拿前面的机器人的例子打比方:机器人执行一系列操作之后,带回来一杯水,这一杯水的属性 “水” 就是机器人的“返回类型”。
C语言的返回类型有int , double , float 等基本类型,还有自定义类型
比如经过各种计算,你的函数最后得到一个int整型值,那么在定义函数的时候就规定这是一个int型函数
而说到函数的返回类型,又不能不提与它密切关联的函数返回值。返回值的类型需与返回类型一致。
说明
int myFunc(int a, int b) {
//函数内容
//....
return xxx;
}
上面定义的这个函数中,在myFunc前面的int便是函数的返回类型,它说明了这个函数是一个int型函数,它的返回值也是一个int型的值;在函数的结尾部分,有一个return xxx 这里的xxx便是函数的返回值,当调用这个函数时,它返回给调用此函数的程序的值便是这里的xxx
但也有一种情况并不需要return一个结果。有些时候我们调用函数不一定必须得到一个结果,可能函数本身在调用时进行的输入输出就已经达到了我们的需求,这时我们就把函数定义为void型,即
void myFunc(int a, int b){
//函数内容
//...
}
这时,函数不再向调用它的函数返回任何内容。return可以被省略,也可以写出但是后面不接任何内容。
可以简单的理解,return用法好比顾客说:机器人,给我拿一杯水!而void用法好比顾客说:机器人,给我催一催菜!
2.函数的名称
同变量名称一样,函数也应该有自己的名称,这是指定函数的唯一指标。
对于名称,应当使他能够简明扼要的表达函数大概能做的事情。比如函数的目的为输出一段星花,那么我们起名printStar应当是比较合理的一种。
如果命名没有章法,当我们写大型程序的时候,调用函数就会变得非常痛苦。
3.函数的参数
有些时候我们需要给函数输入一些参数才能够使用。比如我们给机器人设定了找钱函数。当顾客需要找钱时,他首先要声明顾客有多少钱,然后声明顾客花了多少钱,最后机器人得出结果。但是我们在设定功能时并没有办法预测顾客有多少,花了多少钱。机器人只知道他需要对两个数字进行减的操作。
像这样,调用函数时需要传入的值,就是函数设定时的参数。
规定:
1.不论是否需要参数,在函数的定义是都应该在名字后面加上括号。
2.参数的定义与主函数中变量定义一样。如果需要一个int型,就定义一个int 变量
3.参数与主函数中传入的变量并没有直接的关系
4.函数的内容
函数的内容就是函数的主体,规定了函数要做什么。就是函数的算法实现
(4)函数应该如何存在于程序中
函数在程序中的定义有一下几点要求:
*函数的定义不可以在其他函数中进行。
*函数定义位置的选择有两个:
1.定义在调用这个函数的函数上面
2.在调用这个函数的下面定义,但要现在上面声明这个函数
注:若使用第二种,声明应包括函数的返回值类型,名称和参数列表
关于这两点,我们可以举一个简单的例子。我们把函数的定义理解为学习一个知识。在学习(定义)过之后,我们就可以自由的使用(调用)。如果我们把A函数定义在B函数中,那么我们每一次调用B函数都相当于重新学习了一次A函数,相当于一样的知识重复的学习了,这肯定是不合理的。因此,函数的定义都应该时独立的,在其他函数之外的。这“其他函数”,自然包括我们的主函数main。
代码示例1
#include <stdio.h>
int Six(int a) {
a = a * 100 + a * 10 + a;
return a;
}
int main() {
int num = 0; //养成好习惯,给变量赋初值
scanf("%d", &num);
int result = Six(num);
printf("%d", result);
return 0;
}
代码示例2
#include <stdio.h>
int Six(int a); //也可写成 "int Six(int)",或者用其它形参名代替"a",只要保证形参的顺序及数量和下面的函数定义一致即可
int main() {
int num = 0; //养成好习惯,给变量赋初值
scanf("%d", &num);
int result = Six(num);
printf("%d", result);
return 0;
}
int Six(int a) {
a = a * 100 + a * 10 + a;
return a;
}
(5)函数应该如何使用
这里记住以下几个要点即可
- 函数经过定义就可以通过调用它的名字来使用它
- 如果函数有参数,则使用时应当填满所有参数
–如果函数不含参数,也不可以丢掉空括号 - 如果函数没有返回值,则直接作为一句话输入在程序中即可
–如果函数有返回值,则应该利用一个变量继承这个返回值。
(6)令人困扰的问题:形式参数与实际参数
我们先来看一段代码
#include <stdio.h>
void change(int a, int b);
int main() {
int a = 0, b = 10;
printf("a = %d, b = %d\n", a, b);
change(a, b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
void change(int a, int b) {
int temp;
temp = a;
a = b;
b = temp;
}
试着运行一下上面的代码。看看它的运行结果与你预期的是否一致。
输出结果
a = 0, b = 10
a = 0, b = 10
上面的代码看似完成了交换a, b的值的功能,而程序的运行结果却是两次输出内容保持一致。下面我们就来讲一下这个问题。
我们从函数的值传递讲起。
首先,main函数在调用change函数时,将main函数中a的值,也就是0传递给了change中的变量a,又将main函数中b的值,也就是10传递给了change中的变量b。这时,main函数与change函数没有其它任何关联:main中的a只是它的值与change中的a相等,而change函数中的a发生任何改变都不会影响到main中的a变量。这就好比你在主函数中写的代码是
change(0, 10);
主函数只是将其变量的值传给了change函数,它们只是名字相同,而没有任何关系。这好比我有一张表格,然后我去打印店复印了一份给你,你即使把那个表格撕掉,都不会影响我的表格。(其中的我就是main函数,你就是change函数)
在以上前提下,change函数中的a变量和b变量再怎么交换都不会影响到main函数中的变量。
那我们该用什么方式去解决“使用函数去交换两个变量的值的问题”呢?
这就是涉及到指针的知识了。等你们对指针有一定的了解后,我们会讲解这方面的知识点的。
02 数组
什么是数组
顾名思义,数组就是一堆数放在一起的组合。在C语言中,这“一堆数”被描述为类型相同的一系列变量。
为什么要使用数组
这里我们想一个实例。比如我们要记录一个学生的学号,我们可以简单的通过int StudentNum = 1001来实现。
但如果要记录100名学生的学号,不仅定义赋值变得十分麻烦,今后对数据进行操作也非常的繁琐。这时我们一般要使用到数组。
数组的定义
数组的定义为:
<类型说明符> <数组名>[<常量表达式>]
- 类型说明符,即基本数据类型(int,char等),这规定了数组内容的类型。
- 数组名,名称的定义应当具有含义
- 常量表达式,指定了数组的长度,这个数字必须是大于零的常量
例如:
float mark[100];
char str[200];
这两种都是标准的定义方法。
但要注意:不可以利用变量动态的输入数组长度
数组定义时长度不可以简单地利用变量定义,也不可以是除了大于零整数的其他内容
int n = 10; int a[n]; //数组的大小不能是变量
int b[10.3]; //数组大小不能是浮点数
int n = 10; int c[n+10]; //数组大小不能是变量表达式
int d[]; //数组大小不明确
数组的赋值
数组的赋值可以通过等号来实现。通过初值表对数组进行赋值的方法,是比较基础的方法。
要求为:
初值表需要用 {} 括起来,每个元素用 “,” 分割开。
初值表的内容可以少于定义的长度,但绝对不可以超出该长度。如果少于定义长度,系统会用默认的值填满数组。
如果在定义数组时没有指定长度,但是给出了初值表,那么数组长度就等于初值表内容个数.
举例
int a[5] = {1, 2, 3, 4, 5}; //可以,标准的赋值
int a[4] = {1, 2, 3, 4, 5}; //不可以,超出范围
int a[5] = {1, 2, 3}; //可以,系统会用0补足
int a[] = {1, 2, 3}; //可以,系统自动为数组a规划长度3
数组的下标
数组的下标是数组的灵魂。在我们定义了数组a之后,直接通过方括号和元素所在的索引值就可以得到该位置的元素。
如
int a[5] = {1, 2, 3, 4 ,5}; //此数组的索引是0, 1, 2, 3, 4
printf(“%d”,a[2]); //输出3
注意:数组的索引是从0开始的。
有了数组的下标,我们就可以用另一种方式来对数组进行快速的赋值和操作,那就是循环。
基础变量之所以没有办法处理大型数据,除了定义繁琐以外,还有另外一个问题:那就是没有办法快速的调用他们。
而数组之所以可以快速地调用,就是因为能够标志一个变量的指标:变量的名字在数组中的遍历非常的方便。
a[i]代表的就是数组中的第i+1个元素(索引值+1)。数组的元素的名字也是一个变量,而且这个变量刚好与我们在进行循环遍历时要用到的循环变量比较相近,因此在数组的输入输出等操作中,我们更多的是使用循环。
03 字符串
字符串的另一个名字应该更加让人感到亲切:即字符数组。所谓字符串,就是用字符组成的数组。他既然是一个数组,那么他的元素排布,初值定义等等就应该与数组完全相同。不过字符串有一点比较特殊:字符串实际占用的单元数量为有效字符串长度+1,因为字符串要求最后一位存放 ‘\0’。在定义字符串时,应当考虑到这一点。
这个’\0’其实是ASCII码中的NULL,是为了提示系统 “字符串到这里已经结束了” 。什么意思呢?
我们在定义数组时,期待的是能够快速的处理数组中的每一个元素,重点在于数组的每一项内容。比如我们定义了学号数组,我们期望能对其中的任何一个学号都可以快速地找到,进而进行改变。但是我们定义字符串时,期待的是能够对字符数组这个整体进行输入输出。比如字符串中存放的是 “i love kexie\0” ,我们自然希望对一整句话进行操作,而不是对其中的元素空格,字母e等等进行操作。因此我们需要一个’\0’来提示系统,字符串到此结束了,对字符串整体的读写已经可以停止了。
字符串的定义和赋值
字符串的定义和赋初值与数组几乎一样。(以下两种表达式的效果完全一样)
char str[5] = {'l', 'o', 'v', 'e', '\0'};
char str[] = “love”; // 系统会自动地补上’\0‘
如果一个数组的长度大于字符串常量的长度,剩余空间全部会被补上 '\0’
但同样,我们很少利用初值表对字符串进行操作,更多的,我们是通过函数来进行。
字符串的输入
一般地,我们常用以下两种方法来读入字符串
- scanf(“%s”, str); 以空格或者回车作为结束标志,就可以完成对字符串的输入。输入时应当传入字符数组首地址,其实就是字符串名。当接收一个字符串时遇到空格,则认为该字符串已经结束。
gets(str);gets_s(str, maxLengthOfTheString) 以回车作为结束标志,可以输入有空格的字符串(gets函数从C11起已被移除,可使用gets_s代替,其第二个传入参数为字符串最大长度,不能超过str字符数组的长度)
字符串的输出
- printf(“%s”, str);
- puts(str);
两种输出方式并没有太大区别,但要注意,输出字符串都是从传入的字符串首地址开始输出,因此需要弄清楚想要输出的内容的首地址。