目录
1.分支语句和循环语句
2.函数
经过上次的学习,相信大家已经对C语言有了一个初步的认识,知道了它的由来和发展历程,也初步了解了一个程序的写法,变量的定义等,这次呢我们就来更上一层楼,来看看C语言当中分支和循环以及函数的功能,话不多说,我们开始吧。
1.分支语句和循环语句
(1)什么是语句?
顾名思义,语句就是一句话,一句话结束了就叫做一个语句,中文里我们以句号结束一个语句,C语言里我们于法规定以分号(;),例如:printf("hello world"); 这是我们上节课见到的第一个C语言代码,这就叫一个语句,可以理解为只要以分号结尾了,就可以叫做一个语句,大家理解意思即可,不必深究,这里展开讲解是为了我们后面引出分支和循环。
(2)选择分支语句-if else语句
if语句是第一个分支语句,很显然if else语句的功能是实现选择分支,它的基本形式如此:
#include <stdio.h>
int mian()
{
int a=0;
printf("你想好好学变成吗?选1:好好学;选其他数字:不好好学");
scanf("%d",&a);
if(1==a)
{
printf("恭喜你获得了一份年薪30w的工作");
}
else //只要不满足if语句里的条件就进入else,所以不必写条件
{
printf("没有一家公司录用你");
}
return 0;
}
注释:
// 如果表达式的结果为真,则语句执行。
//只要表达式结果不为0就为真。
大家可以把这段代码拷到自己的编译器上跑一下,输入一下你的选择, 我们都对英语有一个大概的掌握,通过字面翻译大家应该都能看出来这段代码想表达什么,意思大概为:如果你选择1,那么你就以后就有了一份好工作,如果你选择其他数字,那么以后将没有公司录用你,所以大家要好好学编程哦,希望大家以后都能找到好工作,走上人生巅峰。好,回到正题,if else形式的语句就叫分支语句,通过满足不同条件执行不同的选项,它还可以这样写:
#include <stdio.h>
int main()
{
if(//满足条件)
{
//执行语句
}
else if(//满足条件)
{
//执行语句
}
else if(//满足条件)
{
//执行语句
}
else
{
//执行语句
}
return 0;
}
通过对比发现,这样的写法比上面的少了中间else if语句,是的,这是根据判断条件的繁简来决定,如果你的条件很多,例如在一场考试结束后你要把学生分成好几个档(当然不支持唯成绩论哈,只是个例子),你可以自己写一个分支语句,100~90就是【优秀】,90~80之间的是【良好】80~60为【及格】,<60为【不及格】,这里有四个条件,如果只用if else语句是不是就不够了呢,所以要根据自己定义条件的多少来增加或减少else if语句,讲到这里大家是不是已经了解if else语句了呢?
if else语句还可以嵌套使用哦,例如经过选择进入不同语句后还要进行选择,就可以使用嵌套if,例如;
int main()
{
if(//表达式)
{
if(//表达式)
{
//执行语句
}
}
else
{
//执行语句
}
return 0;
}
大家可以看到if语句里又有一个if语句,这就叫嵌套,嵌套语句在C语言里使用非常广泛。但是大家要注意如果你使用了嵌套if,下面的else语句要和哪个if匹配呢?是这样的,else语句会和自己对齐一列的if匹配,比如上面的else,就和第一个if匹配了,大家在平时写代码的时候一定要注意这个问题。
接下来大家来看这样一段代码:
//代码1
if (//表达式)
{
printf("hello world");
}
printf("hello world");
//代码2
if(//表达式)
{
printf("hello world");
}
else
{
printf("hello world");
}
这两段代码表达的意思是一样的, 就是上述讲到的可以使用一个if语句也可以实现if else语句的效果,但是平时还是推荐大家用if else,因为这样更规范,代码1的写法是一些老手写多了会图省事,虽然无语法错误但是相对随意,代码2更加清晰,也是标准书写形式。
(3)选择分支语句-switch语句
除了if else语句外,还有一种选择分支语句,常常用于多分支情况,我来用代码演示一下:
int mian()
{
switch(//整形表达式)
{
case 1: //case 后面的数字就是switch语句整形表达式可能取到的值
//执行语句
break;
case 2:
//执行语句
break;
case 3:
//执行语句
break;
case 4:
//执行语句
break;
...//列出可能出现的执行语句
default:
//执行语句
break;
}
}
这就是switch语句的大致形式,和if else不同的是,其满足的不再是一个个条件,而是通过确定的整形值(也就是一个确定的数)来决定分支到哪条语句,如果switch后面的整形表达式为1,就进入case 1:为2就进入case 2:以此类推,但是大家肯定发现了每个语句后面都有一个break;那break是什么意思说呢?跳出本次循环,break-中断嘛,所以如果少了break,switch就无法完成分支任务,case 1执行完后会自动执行case 2,一直下去,为了让它执行完一条语句的时候就能即时停止,就凸现出break语句的作用了,最下面的default语句起到了相当于保险的作用,如果整形表达式里的值每一个case都不匹配的话,就会执行default语句,一般来说执行到default语句都是直接退出程序啦,或显示错误信息等,因为并不满足任何一个条件,这样写程序更完善,要养成在switch语句后加上default语句的好习惯,讲完这些我们来看一下它的应用吧:
#include <stdio.h>
int main()
{
int day = 0;
scanf("%d",&day);
switch(day)
{
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五\n");
break;
case 6:
printf("星期六\n");
break;
case 7:
printf("星期天\n");
break;
}
return 0;
}
详细大家已经能看出来此程序想表达的是什么了,输入数字几就会对应星期几,但是如果需求变了,我想输入1~5的时候显示工作日,6~7的时候显示休息日该怎么办呢?相信首先映入大家眼帘的就是模仿上述代码,把星期一到星期五改成工作日,星期六和星期七改成休息日,这样写当然没错啦, 但是我们可不可以方便一些,不写这么冗长,可以的:
#include <stdio.h>
int main()
{
int day = 0;
switch(day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("工作日\n");
break;
case 6:
case 7:
printf("休息日\n");
break;
}
return 0;
}
这样可以实现啦,如果一个case语句没有执行语句的话,就会自动向下进行,当遇到break语句时才会停止,所以当输入1~5时都会自动向下执行,直到打印出了工作日才会break出去,输入6~7也是同样的道理, 是不是很简单呢?
刚刚讲到的嵌套语句在switch也是可以实现的,其实嵌套在C语言里没有很明确的局限,只要是语法范围规定内都可以使用嵌套,下面我在演示一下switch语句的嵌套形式:
#include <stdio.h>
int main()
{
int n = 1;
int m = 2;
switch (n)
{
case 1:
m++;
case 2:
n++;
case 3:
switch (n)
{//switch允许嵌套使用
case 1:
n++;
case 2:
m++;
n++;
break;
}
case 4:
m++;
break;
default:
break;
}
printf("m = %d, n = %d\n", m, n);
return 0;
}
(4)循环语句-while语句
我们已经掌握了if语句: 当条件满足的情况下,if语句后的语句执行,否则不执行。但是这个语句只会执行一次。 但是我们发现生活中很多的实际的例子是:同一件事情我们需要完成很多次。 那我们怎么做呢? C语言中给我们引入了:while语句,可以实现循环。while语句的形式如此:
int mian()
{
while(//表达式)
{
//执行语句
}
return 0;
}
例如我们想实现让计算机替我们打印1~10,我们总不能自己在屏幕上敲出来吧,计算机是为我们服务的,那我们该如何用代码实现呢?这样就可以:
#include <stdio.h>
int main()
{
int i = 1; //定义一个数字
while(i<=10) //当i满足<=10 时,进入循环
{
printf("%d ", i); //打印数字
i = i+1; //i+1,出循环
}
//这时i=2,还要继续进入循环,以此类推
return 0;
}
大家可以仔细推敲一遍这个代码,可能与前面的代码相比有了点难度,但是相信大家都可以理解。
接下来通过一段代码给大家引出continue语句的功能:
#include <stdio.h>
int main()
{
int i = 1;
while(i<=10)
{
if(i == 5)
continue;
printf("%d ", i);
i = i+1;
}
return 0;
}
与上面不同,这次加入了一个if语句和一个continue,大家可以拿到自己的编译器上运行一下,接过我给大家:1 2 3 4,大家可能会对结果很疑惑,肯定就是continue在作祟,我来为大家讲一下continue的作用:continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接 跳转到while语句的判断部分。进行下一次循环的入口判断。这下大家可能明白了,也就是while语句里的i==5时遇到continue不会再打印,而是直接跳出循环再进行判断,而continue之前i的值不会发生改变,所以一直在进行死循环。所以大家屏幕上可能是这样的:
一直有个小光标在闪烁,表示程序没有结束,而是一直在进行,但是进行的时死循环,永不结束。
与continue相似的大家应该想起来是我们上文提到的break,是的,学习C语言的时候应该把它俩放在一起学习,为了防止混淆,我们再来复习一下break语句的功能吧:在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。
可以这么理解,遇到break就直接往下走结束循环,遇到continue往上走要再判断循环。
我们再来两段代码加深一下印象吧:(讲解我会直接写在代码里)
//代码1
include <stdio.h>
int main()
{
int ch = 0; //定义一个变量
while ((ch = getchar()) != EOF) //getchar函数和scanf相似,获取用户从键盘输出的字符存在ch里,然后再判断用户输出的是不是EOF,如果是,不进入循环,如果不是,输出ch。
putchar(ch); //putchar也就相当于printf函数,输出ch。
return 0;
}
//代码2
#include <stdio.h>
int main()
{
while ((ch = getchar()) != EOF) //同上
{
if (ch < ‘0’ || ch > ‘9’) //符号'||'我们后期会讲,它表示:或者,意思即用户输入的是0字符或者9字符其中一个时,执行continue,向上判断while条件
continue;
putchar(ch);
}
return 0;
}
(5)循环语句-for语句
我们已经知道了while循环,但是我们为什么还要一个for循环呢? while循环和for循环一样同问循环语句,方便大家选择,首先来看看for循环的语法:
int main()
{
int i=0;
for(i=0;i<=x;i++) //i<=x即和while循环的判断条件一样,满足才会执行下面的语句
{
//执行语句
}
return 0;
}
大家来用for循环实现一下打印1~10的程序吧:
#include <stdio.h>
int main()
{
int i = 0;
//for(i=1/*初始化*/; i<=10/*判断部分*/; i++/*调整部分*/)
for(i=1; i<=10; i++) //i++的意思就是i=i+1,我们后期会展开讲这些操作符
{
printf("%d ", i);
}
return 0;
}
我们接下来对比一下for循环和while循环:
int main()
{
int i = 0;
//实现相同的功能,使用while
i=1;//初始化部分
while(i<=10)//判断部分
{
printf("hehe\n");
i = i+1;//调整部分
}
//实现相同的功能,使用while
for(i=1; i<=10; i++)
{
printf("hehe\n");
}
return 0;
}
可以发现在while循环中依然存在循环的三个必须条件,但是由于风格的问题使得三个部分很可能偏离 较远,这样查找修改就不够集中和方便。所以,for循环的风格更胜一筹。 for循环使用的频率也最高。当然大家自行选择。
break和continue在for循环中,我们发现在for循环中也可以出现break和continue,他们的意义和在while循环中是一样的。
一些建议: 1. 不可在for 循环体内修改循环变量,防止 for 循环失去控制。 2. 建议for语句的循环控制变量的取值采用“前闭后开区间”写法。
int i = 0;
//前闭后开的写法
for(i=0; i<10; i++)
{}
//两边都是闭区间
for(i=0; i<=9; i++)
{}
接下来我们介绍一些for循环的其他形式:
#include <stdio.h>
int main()
{
//形式1
for(;;)
{
printf("hehe\n");
}
//形式2
int x, y;
for (x = 0, y = 0; x<2 && y<5; ++x, y++)//同'||','&&'意为:与,也就是当x<2与y<5同时成立时,才会进入for循环
{
printf("hehe\n");
}
return 0;
}
形式1即没有判断条件,即循环条件里一直为真,所以形式1是死循环,将一直打印hehe;形式2里大家可以发现变量变成了两个,这也是满足语法的,只不过写这种形式要注意两个变量都要改变,比较麻烦不是吗,所以一般我们不写这种形式的代码,比较难懂。
(6)循环语句- do while语句
又一个循环语句,do while,照例还是先看它的书写形式:
int main()
{
do
循环语句;
while(//表达式)
}
它的特点是在do后面会先执行一次,再进入判断条件,即会比while循环多执行一次,使用的场景有限,所以不是经常使用。
#include <stdio.h>
int main()
{
int i = 10;
do
{
printf("%d\n", i);//先打印,再进入while判断
}while(i<10);
return 0;
}
break和continue也同样适用于do while语句哦,由于用得实在少,这里不再代码演示了。
这就是C语言里的循环语句,下次博客呢,我会给大家写几个循环的实战题目,比较有趣,我先挖好坑,下次带你们写~
2.函数
(1)函数是什么?
数学中我们常见到函数的概念。但是你了解C语言中的函数吗? 维基百科中对函数的定义:子程序 在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method, subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组 成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
C语言中函数的分类: 1. 库函数 2. 自定义函数
(2)库函数
为什么会有库函数? 1. 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想 把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格 式打印到屏幕上(printf)。 2. 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。 3. 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。 像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员 进行软件开发。 那怎么学习库函数呢? 这里我们简单的看看: www.cplusplus.com
简单的总结,C语言常用的库函数都有: IO函数 字符串操作函数 字符操作函数 内存操作函数 时间/日期函数 数学函数 其他库函数
库函数必须知道的一个秘密就是:使用库函数,必须包含 #include 对应的头文件,这也就是为什么printf函数要#include <stdio.h>了。
(3)自定义函数
如果库函数能干所有的事情,那还要程序员干什么? 所有更加重要的是自定义函数。 自定义函数和库函数一样,有函数名,返回值类型和函数参数。 但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。
我们先来看一下C语言里函数由哪些元素组成:
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type //返回类型
fun_name //函数名
para1 //函数参数
举几个栗子:写一个找出两个数最大值的函数:
#include <stdio.h>
//get_max函数的设计
int get_max(int x, int y)
{
return (x>y)?(x):(y);//三目操作符。后期我们会讲,其意思就是x>y吗?大于返回值就是x,小于返回值就是y。
}
int main()
{
int num1 = 10;
int num2 = 20;
int max = get_max(num1, num2);
printf("max = %d\n", max);
return 0;
}
写一个函数可以交换两个整形变量的内容:
#include <stdio.h>
void Swap1(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
void Swap2(int *px, int *py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int num1 = 1;
int num2 = 2;
Swap1(num1, num2);
printf("Swap1::num1 = %d num2 = %d\n", num1, num2);
Swap2(&num1, &num2);
printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
return 0;
}
Swap1和Swap2都是交换变量的函数,Swap1是不是简单易懂?对了,因为Swap2里遇到了我们还没见过的东西,但是说实话,Swap2要更高级,用的是指针,我们马上就会讲。
(4)函数的参数
实际参数(实参): 真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类 型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
形式参数(形参): 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配 内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在 函数中有效。
上面Swap1和Swap2函数中的参数 x,y,px,py 都是形式参数。在main函数中传给Swap1的num1, num2和传给Swap2函数的&num1,&num2是实际参数。
这里我们对函数的实参和形参进行分析:
代码对应的内存分配如下:
这里可以看到Swap1函数在调用的时候,x,y拥有自己的空间,同时拥有了和实参一模一样的内容。所 以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。
(5)函数的调用
传值调用:函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
传址调用:传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操 作函数外部的变量。
练习 1. 写一个函数可以判断一个数是不是素数。 2. 写一个函数判断一年是不是闰年。 3. 写一个函数,实现一个整形有序数组的二分查找。 4. 写一个函数,每调用一次这个函数,就会将num的值增加1。(这些练习我也会在下一个博客里讲)
(6)函数的嵌套调用和链式访问
函数和函数之间可以有机的组合的。
函数的嵌套调用:
#include <stdio.h>
void new_line()
{
printf("hehe\n");
}
void three_line()
{
int i = 0;
for(i=0; i<3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}
函数的嵌套调用:把一个函数的返回值作为另外一个函数的参数。
#include <stdio.h>
#include <string.h>
int main()
{
char arr[20] = "hello";
int ret = strlen(strcat(arr,"bit"));//这里介绍一下strlen函数,也是库函数,所以要引头文件,string.h
printf("%d\n", ret);
return 0;
}
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//结果是啥?
return 0;
}
(6)函数的声明和定义
函数声明: 1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,无关 紧要。 2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。 3. 函数的声明一般要放在头文件中的。
函数定义: 函数的定义是指函数的具体实现,交待函数的功能实现。
test.h的内容 放置函数的声明
#ifndef __TEST_H__
#define __TEST_H__
//函数的声明
int Add(int x, int y);
#endif //__TEST_H__
test.c的内容 放置函数的实现
#include "test.h"
//函数Add的实现
int Add(int x, int y)
{
return x+y;
}
大家看不懂没关系,等下一次博客我会给大家实操,看一遍大家也就懂了
(7)函数递归
什么是递归? 程序调用自身的编程技巧称为递归( recursion)。 递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复 杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可 描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。 递归的主要思考方式在于:把大事化小
递归的两个必要条件 存在限制条件,当满足这个限制条件的时候,递归便不再继续。 每次递归调用之后越来越接近这个限制条件。
练习1: 接受一个整型值(无符号),按照顺序打印它的每一位。 例如: 输入:1234,输出 1 2 3 4
#include <stdio.h>
void print(int n)
{
if(n>9)
{
print(n/10);
}
printf("%d ", n%10);
}
int main()
{
int num = 1234;
print(num);
return 0;
}
递归就是自己调用自己,需要的函数功能也是自己,所以自己不断调用自己,这就叫递归。
练习2: 编写函数不允许创建临时变量,求字符串的长度。
#incude <stdio.h>
int Strlen(const char*str)
{
if(*str == '\0')
return 0;
else return 1+Strlen(str+1);
}
int main()
{
char *p = "abcdef";
int len = Strlen(p);
printf("%d\n", len);
return 0;
}
练习3: 求n的阶乘。(不考虑溢出)
int factorial(int n)
{
if(n <= 1)
{
return 1;
}
else
{
return n* factorial(n-1);
}
}
练习4: 求第n个斐波那契数。(不考虑溢出)
斐波那契数列(Fibonacci sequence),又称黄金分割数列、兔子数列,是数学家列昂纳多·斐波那契于1202年提出的数列。
斐波那契数列为1、1、2、3、5、8、13、21、34……此数列从第3项开始,每一项都等于前两项之和,递推公式为F(n)=F(n-1)+F(n-2),n≥3,F(1)=1,F(2)=1。
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
但是我们发现有问题; 在使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。 使用 factorial 函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。
为什么呢? 我们发现 fib 函数在调用的过程中很多计算其实在一直重复。 如果我们把代码修改一下:
int count = 0;//全局变量
int fib(int n)
{
if(n == 3)
count++;
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
最后我们输出看看count,是一个很大很大的值。 那我们如何改进呢? 在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: `stack overflow(栈溢 出) 这样的信息。 系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递 归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢 出。 那如何解决上述的问题: 1. 将递归改写成非递归。 2. 使用static对象替代nonstatic局部对象。在递归函数设计中,可以使用static对象替代nonstatic局 部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销, 而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
比如:
//求n的阶乘
int factorial(int n)
{
int result = 1;
while (n > 1)
{
result *= n ;
n -= 1;
}
return result;
}
//求第n个斐波那契数
int fib(int n)
{
int result;
int pre_result;
int next_older_result;
result = pre_result = 1;
while (n > 2)
{
n -= 1;
next_older_result = pre_result;
pre_result = result;
result = pre_result + next_older_result;
}
return result;
}
提示: 1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。 2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。 3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
好啦,本次的内容到这就结束啦,学习了这么久肯定很累,休息一下,我们下次再见吧,拜拜~