程序与算法
算法设计原则
- 分而治之:复杂的程序可以分解成若干简单子程序
- 模块化:常用程序模块是可以重复使用的
C程序的编写、编译和运行
C程序的构成
- 预处理命令
- 函数
- 语句
- 单词
- 注释 //
注意书写规范
C程序编写、编译、链接
- c源程序的编写,扩展名为.c的源文件
- 编译器编译,扩展名为.obj的目标程序
- 链接器链接,扩展名.exe的可执行程序
C语言基础语法
数据类型
常用数据类型
- 整数 int
- 浮点数 float、double
浮点型常量:
小数形式:123.45(小数点不可以省略)
指数形式:aE5、21E4(E前后必定有数字)
变量
变量的作用:在程序执行过程中,临时存储数据
变量的定义:设定程序中要使用的变量名和数据类型
语法格式:
数据类型 变量名1,变量名2,…变量名n;
变量没有初始值会报错
变量的命名
变量名是标识符(identifier),标识符用于表示程序中的各类元素的名字
标识符必须以字母或下划线打头,以字母、数字、和下划线组成字符序列
关键字
特定字
词法
C语言中大小写严格区别
输入数据输出结果
scanf
作用:从键盘按格式读取数据并赋给变量
scanf(“格式控制符”,地址变量);
取地址运算符:&
printf
printf(“描述信息[格式控制符]”[,变量]);
格式控制符
一定要匹配
%d整型
%f浮点型
%c字符型
表达式
表达式及其值
操作数:根据操作数的个数,运算符可以分为单目运算符、双目运算符和三目运算符
单目运算符:-(取负),+(取正)
双目运算符:+、-、*、/、%
注意:单独的常量、变量和函数调用也是表达式
注意:表达式是有值的
运算符
自增自减运算符:++、–(前置和后置形式)
自增自减运算符的操作数必须是变量
算术表达式
- 算数运算符:+、-、*、/、%
- 求余运算符(%):结果为两数相除的余数,两侧的数据必须为整型数据(12%10=2获取个位)
- 除法:
(1)任意两个整数相除,结果为整数(后面的小数位会丢失,去除低位)x/10%10
(2)如果两边有一个为浮点数,结果为浮点数
数学函数
添加#include<math.h>
pow(x,y)表示x的y次幂
sqrt(x)求x的平方根
cos(x)
sin(x)
fabs(x)求绝对值
exp(x)求ex
log(x)求ln(x)
赋值表达式
赋值运算符”=“ 左边只能是单个变量
问:编译出现错误提示: lvalue required as left operand of assignment ,是什么意思?
答:
下面的语句在编译时会产生如题的错误提示,原因是左边不是变量。
x+1=2;
2=x;
Dev c++ 中错误提示显示 lvalue required as left operand of assignment
Visual C++6.0 中错误提示显示 '=' : left operand must be l-value
该错误提示的意思是:赋值运算符的左操作数要求为左值(lvalue)。
左值表示存储在内存中的对象,可以用&获得它的内存地址,现在,我们学到的唯一左值是变量。
赋值运算符要求它的左操作数必须是左值,也就是=左边要用变量。
表达式的优先级和结合方法
格式化输入输出
输出数据的对齐、精度、显示方式
对齐
左对齐:
\n换行
\t将光标移至下一个水平制表符位置
右对齐:
%[-0m]d格式字符 | 说明 |
---|---|
m | 指定输出的最小宽度,符号位占一列;若实际长度超过m,则按实际位数输出 |
-m | 指定m列中数据左对齐 |
0m | 指定m列中空位填0 |
精度
浮点数格式控制符 | 说明 |
---|---|
%[-m.n]f | m :指定输出的最小宽度,符号位、小数点各占一列;若实际长度超过m,则按实际位数输出。.n :指定小鼠部分所占的位数。未指定n时,小数点后输出6为小数 |
%[-m.n]e | 指定输出的最小宽度,符号位、小数点、指数e各占一列;若实际长度超过m,则按实际位数输出。.n :指定小鼠部分所占的位数。未指定n时,小数点后输出6为小数 |
%[m]g | 自动选择%f或%e格式输出m :指定输出的最小宽度,符号位、小数点各占一列;若实际长度超过m,则按实际位数输出。 |
输入格式及其妙用
输入函数格式:
scanf(“格式控制字符串”,地址列表);
格式控制字符串:格式控制符、普通字符
scanf的工作原理:
从格式控制字符串的左边开始:
当遇到格式控制符,从键盘输入中读匹配的数据;匹配,则读入数据,不匹配,则结束
当遇到普通字符,与键盘输入的下一个字符比较,相同,则继续;不同则结束
scanf函数:
-设置输入的格式
-从键盘按格式读取数据,并赋给变量
分数输入
#include<stdio.h>
int main(){
float x,n,m,i,j;
printf("请输入线索\n 向北走的路");
scanf("%f",&x);
printf("然后向东走(m/n):");
scanf("%f/%f",&m,&n);
printf("最后向北走(i/j):");
scanf("%f/%f",&i,&j);
printf("\n请按以下路线行走\n");
printf("向北走%.2f米\n",x);
printf("在向东走%.2f米\n",x*m/n);
printf("最后向北走%.2f米\n",x*i/j);
return 0;
}
scanf附加说明项——数据截取
附加说明项 | 说明 |
---|---|
%lf | 输入double类型的格式控制符 |
%md | 截取m列数据作为输入 |
%*md | 跳过m列数据,不输入 |
身份证出生日期的截取
#include<stdio.h>
int main(){
int i,j,k;
printf("请输入身份证号:");
scanf("%*6d%4d%2d%2d%*4d",&i,&j,&k);
printf("\n您的生日是:%d年%d月%d日\n",i,j,k);
return 0;
}
关系表达式
关系运算符 |
---|
> |
< |
>= |
<= |
== |
!= |
运算结果:
如果关系成立,运算结果为1(表示”真“);
关系不成立,结果为0(表示”假“);
关系表达式:
由关系运算符将两个子表达式连接起来构成关系表达式
关系表达式的值:
真/假
运算符优先级:
算数运算符 优先于 关系运算符 优先于 赋值运算符
>,>=,<,<= 优先于 ==,!=
注意:
"==“关系运算符,运算结果为"0或1”;
"="为赋值运算符,将表达式的值赋值给变量。
逻辑表达式
逻辑运算符 |
---|
! |
&& |
|| |
a | b | !a | !b | a&&b | a||b |
---|---|---|---|---|---|
非0 | 非0 | 0 | 0 | 1 | 1 |
非0 | 0 | 0 | 1 | 0 | 1 |
0 | 非0 | 1 | 0 | 0 | 1 |
0 | 0 | 1 | 1 | 0 | 0 |
优先级 | 逻辑运算符 | 结合性 |
---|---|---|
高 | 逻辑非! | 右结合 |
中 | 逻辑与&& | 左结合 |
低 | 逻辑或|| | 左结合 |
混合运算中的优先级 |
---|
优先级 | 运算符 |
---|---|
高 | ! |
| | 算数运算符 |
| | 关系运算符 |
| | && |
v | || |
低 | 赋值运算符 |
逻辑表达式的短路计算
与逻辑短路
或逻辑短路
选择表达式
分支
分支:根据某个条件作出不同选择,决定下一步执行的操作
条件:逻辑表达式
分支结构:单分支、双分支、多分支
单分支if语句
语法形式:
if(表达式)语句
表达式是条件,一定写在圆括号()内
语句只能是一条语句,可以是空语句,或者是复合语句
双分支if-else语句
语法形式:
if(表达式)
语句1
else
语句2
注意:
关键字else不能单独使用
例题:五位回文数判断:
#include <stdio.h>
int main(){
int n,a1,a2,a4,a5;
printf("请输入一个五位数");
scanf("%d",&n);
a1=n%10;
a2=n/10%10;
a4=n/1000%10;
a5=n/10000%10;
if(a1==a5&&a2==a4){
printf("%d是回文数",n);
}else{
printf("%d不是回文数",n);
}
return 0;
}
多分支else-if级联、开关语句(switch语句)
if语句的嵌套
if(表达式)
语句1
if(表达式)
语句1
else
语句2
else配对问题——最近匹配原则(else前面最近的为配对的if)
提倡缩进的编程风格
开关语句(switch)
根据表达式的不同值做出选择,决定下一步执行的操作
语法形式:
switch(表达式){//表达式的类型和“常量1~n”的类型必须一直,只能整型、字符型、枚举型
case 常量1://case起标号作用,要求case后面的常量表达式的值各不相同
语句序列1//语句序列通常最后一句为break,退出switch语句
case 常量1:
语句序列1
……
case 常量n:
语句序列n
default:
语句序列n+1//语句序列n+1可以缺省,不出现
}
小结:
在switch语句中合理使用break,能表达不同执行流程
能使用switch的场合也可以使用if语句
在使用if的地方使用switch,要看是否满足switch语句中各表达式的条件
程序调试
设置断点F9
程序执行至断点,会暂停
进入调试界面F5
出现Debug菜单及工具栏
Variables窗口(查看各变量的值)
Watch窗口(可以输入表达式并显示其值)
控制单步执行程序F10
执行一条语句,暂停,跟踪程序执行流程
观察变量或表达式的值
当每步执行时,查看值的变化,分析与预想的是否一致,找出错误点
结束调试+F5
循环结构
for语句
语法形式
for(表达式1;表达式2;表达式3)
循环体语句
表达式1:通常给循环控制变量赋初值,仅执行一次
表达式2:循环条件,用来判断是否继续执行循环
表达式3:计算表达式3
例题:1~100求和
#include <stdio.h>
int main(){
int sum,i;
sum=0;
for(i=1;i<=100;i++){
sum+=i;
}
printf("%d\n",sum);
return 0;
}
注意:
for语句书写灵活,三个表达式均可以省略,但作为分隔符的分号必须写
小结:
for语句适用于重复次数确定的情况
循环三要素
- 循环变量初值
- 循环满足的条件
- 循环体中对循环变量的改变
例题:输出100~999中所有个位和百位之和为9的整数,并且每行输出8个(排版问题:换行控制)
#include <stdio.h>
int main(){
int i=100,a,count=0;
for(i;i<=999;i++){
a=i%10+i/100;
if(a==9){
printf("%d\t",i);
count++;
if(count==8){//换行控制
printf("\n");
count=0;
}
}
}
return 0;
}
例题:国王的小麦(int的表示范围是-2147483648~2147483647)所以要用double表示
#include <stdio.h>
#include <math.h>
int main(){
double i=1,total=0;
for(i;i<=64;i++)
{
total+=pow(2,i-1);
printf("%.0lf\n",total);
}
return 0;
}
例题:判断素数(提前跳出循环/缩小循环范围/break跳出循环)
#include <stdio.h>
#include <math.h>
int main(){
int i,n,flag=0;
printf("请输入一个整数:");
scanf("%d",&n);
for(i=2;i<n;i++){
//for(i=2;i<sqrt(n);i++){
//改进:如果i是n的因子,n/i=j,j也是m的因子,可以缩小范围:2~sqrt(n)
if(n%i==0){
flag=0;
i=n;//当条件不满足提前跳出循环
//break;
}
else{
flag=1;
}
}
if(flag){
printf("%d是素数\n",n);
}else{
printf("%d不是素数\n",n);
}
return 0;
}
//改进
#include <stdio.h>
#include <math.h>
int main(){
int i,n,k;
printf("请输入一个整数:");
scanf("%d",&n);
k=sqrt(n);
for(i=2;i<=k;i++){
if(n%i==0){
break;
}
}
if(i<=k){
printf("%d不是素数\n",n);
}else{
printf("%d是素数\n",n);
}
return 0;
}
while语句
语法形式:
while(表达式)//先条件判断,再执行循环体,
语句
例题:1~100求和
#include <stdio.h>
int main(){
int i=0,sum=0;
while(i<=100){
sum+=i;
i++;//使循环趋于结束的语句
}
printf("sum=%d\n",sum);
return 0;
}
小结:
先判断,再循环
循环三要素
- 循环变量初值
- 循环条件
- 循环体,包含对循环变量的改变,使循环趋于结束
do-while循环
语法形式:
do//先执行循环体,再进行条件判断
语句
while(表达式);
例题:1~100求和
#include <stdio.h>
int main(){
int i=0,sum=0;
do{
sum+=i;
i++;//使循环趋于结束的语句
}while(i<=100);
printf("sum=%d\n",sum);
return 0;
}
三种循环语句的比较
两种循环:
- 计数控制循环:事先能知道循环重复执行的次数
循环变量:初值、终值 - 标记控制循环:事先不知道循环重复执行的次数,循环体中获取数据
标记值:表示循环结束
使用三种循环语句的一般原则
- 如果循环次数已知,技术控制的循环
for语句“计数”循环 - 如果循环次数未知,条件或标记控制的循环
while语句“当型”循环 - 如果循环体至少要执行一次
do-while语句“直到”循环
循环嵌套
例题:输出图案,共9行,*数目逐行加一(图案靠左)
#include <stdio.h>
int main(){
int i,j;
for(i=1;i<10;i++){
for(j=0;j<i;j++){
putchar('*');
}
printf("\n");
}
printf("\n");
return 0;
}
例题:输出图案,共9行,*数目逐行加一(图案靠右)
#include <stdio.h>
int main(){
int i,j;
for(i=1;i<10;i++){
for(j=1;j<=10-i;j++){
putchar(' ');
}
for(j=1;j<=i;j++){
putchar('*');
}
printf("\n");
}
printf("\n");
return 0;
}
例题:输出图案,共9行,*数目逐行加2(图案居中)
#include <stdio.h>
int main(){
int i,j;
for(i=1;i<10;i++){
for(j=1;j<=10-i;j++){
putchar(' ');
}
for(j=1;j<=2*i-1;j++){
putchar('*');
}
printf("\n");
}
printf("\n");
return 0;
}
例题:打印九九乘法表
#include <stdio.h>
int main(){
int i,j,acc;
for(i=1;i<=9;i++){
for(j=1;j<=i;j++){
acc=i*j;
printf("%d*%d=%d\t",i,j,acc);
}
printf("\n");
}
return 0;
}
循环中的流程转移控制
流程转移控制 | 说明 |
---|---|
break | 退出循环;转到循环之后的下一条语句 |
continue | 中断本次循环;开始下一次循环 |
goto | 转向语句;无条件转移语句;和标号配合使用 |
例题:break示例:输入一组非负的数,计算累加和;当输入数据为负数时,停止输入。
#include <stdio.h>
int main(){
int sum=0,a;
while(1){
scanf("%d",&a);
if(a<0)break;
sum+=a;
}
printf("%d\n",sum);
return 0;
}
例题:continue示例:输出100~150之间不能被3整除的数
#include <stdio.h>
int main(){
int a;
for(a=100;a<=150;a++){
if(a%3==0)
continue;
printf("%d\n",a);
}
return 0;
}
常用算法举例
穷举法
穷举法分为:
穷举某个整数型的取值范围——顺序列举,自然数
——韩信点兵
穷举某个字符数值的取值范围——逻辑判断
——破窗问题
例题:韩信点兵
穷举一组整型数的取值范围——排列列举,多重循环
——百鸡问题
——鸡兔同笼
数据形式为一些元素的组合——组合列举,组合是无序的
#include <stdio.h>
int main(){
int a=1;
while(1){
if(a%5==1&&a%6==5&&a%7==4&&a%11==10){
break;
}
a++;
}
printf("%d",a);
return 0;
}
例题:百文百鸡
#include <stdio.h>
int main(){
int a,b,c;
for(a=0;a<15;a++){
for(b=0;b<20;b++){
for(c=0;c<100;c++){
if((a+b+c)==100&&(15*a+9*b+c)==300){
printf("%d只公鸡,%d只母鸡,%d只小鸡\n",a,b,c);
}
}
}
}
return 0;
}
例题:谁打烂了玻璃
A说:不是我
B说:是C
C说:是D
D说:C冤枉了他
有三个人说了真话
#include <stdio.h>
int main(){
char k;
for(k='A';k<='D';k++){
if((k!='A')+(k=='C')+(k=='D')+(k!='D')==3){
printf("打烂玻璃的人是%c\n",k);
}
}
return 0;
}
穷举法(枚举法)小结
基本思想:
从所有可能的情况中搜索正确的答案,对于一种可能的情况,计算其结果,判断结果是否符合要求
如果不满足则搜索下一个可能的情况
如果符合要求,则表示寻找到一个正确答案
使用穷举法,需要明确答案的取值范围,在指定的范围内搜索答案
使用循环语句和条件语句逐步验证候选答案的正确性,实现求解
累加求和
例题:求圆周率
#include <stdio.h>
int main(){
double sum=0,n=1,t=1;
while(1/t>=10e-6){
sum+=n/t;
n=-n;
t+=2;
}
printf("%lf",sum*4);
return 0;
}
累加求和的解决方法
找规律
- 累加和初始化为0或第一项
- 关键是寻找累加项的构成规律(通项)
基本数据类型
整型
有符号整型的划分 |
---|
32位机上各类整型的范围
类型 | 关键字 | 位数(字节) | 范围(最左边一位符号位0正1负) |
---|---|---|---|
基本整型 | int | 32位(4) | 231-1(-2147483648~2147483647) |
短整型 | short int | 16位(2) | 215-1 (-32768~32767) |
长整型 | long int | 32位(4) | 231-1(-2147483648~2147483647) |
short<=int<=long
64位机上可以使用long long int类型
无符号整型的划分 |
---|
类型 | 关键字 | 位数(字节) | 范围 |
---|---|---|---|
无符号基本整型 | unsigned int | 32位(4) | 231-1(0~4294967295) |
无符号短整型 | unsigned short | 16位(2) | 216-1 (0~65535) |
无符号长整型 | unsigned long | 32位(4) | 231-1(0~4294967295) |
整型常量的书写格式(仅仅是书写格式,计算机内部都是以二进制存储)
整型常量的默认数据类型是int型
为强调一整型常量为长整型,可于其后加上l或L
为强调一整型常量为无符号,可于其后加上u或U
十进制
八进制:以数字0开头,由0~7数字组成八进制数字串
十六进制:由0x或0X开头,由09数字和AF(或a~f)字母组成十六进制字符串
八进制或十六进制常用于表示存储单元的地址
整型数据的读写
%d以十进制形式读写整数
%u以无符号十进制形式读写整数
%hd以十进制形式读写短整型数据
%ld以十进制形式读写长整型数据
%o以八进制形式读写整数
%x以十六进制形式读写整数
数据溢出
无符号型整数运算过程中发生溢出,会将结果对2n 取模
有符号型整数运算过程中发生溢出,结果不确定:结果出错、程序报错、崩溃等等
浮点型
保存符号,指数,小数位
类型 | 关键字 | 字节数 | 数值范围 |
---|---|---|---|
单精度浮点型 | float | 4 | +/-(10-38~1038) |
双精度浮点型 | double | 8 | +/-(10-308~10308) |
许多系统将浮点型常量默认处理为双精度
在数的后面加字母f或F,可以使编译系统将其按单精度处理
浮点数的有效位数
浮点数只是近似地表示实数
关键字 | 有效位数 |
---|---|
float | 6~7 |
double | 15~16 |
有效位以外的数字可能有些误差,是无意义的数
###浮点型数据的读写
%f读写float型数据
%lf读写double型数据(读入要用%lf,写出可以用%f)
字符型及其应用
字符型常量:用单引号括起来的单个字符
A-Z(65-90)相差32
a-z(97-122)
0-9(48-57)
字符型常量与变量的区别
'a’是字符常量,在内存中存放的值为97
a是变量,可以用来保存值
字符变量
定义字符变量的格式:char 变量名列表
字符输出
格式:%[-m]c
将对应数据转换为相应的ASCII字符,并输出
字符编程
例题:大小写转换
#include <stdio.h>
int main(){
char ch1;
printf("请输入一个字母:");
scanf("%c",&ch1);
if(ch1>=65&&ch1<=90){
ch1+=32;
printf("%c\n",ch1);
}else if(ch1>=97&&ch1<=122){
ch1-=32;
printf("%c\n",ch1);
}else{
printf("请输入正确的字母\n");
}
return 0;
}
字符读与写
读字符:字符输入
scanf("%c",&c1);//从键盘输入一个字符,并赋值给c1
getchar();//读入从键盘输入的一个字符,并将其返回
写字符:字符输出
printf("%c",c1);//输出一个字符
printf("%c",'o');
putchar(c1);//c1可以是字符型常量、字符型变量、或整型变量
//功能:在屏幕当前光标位置处,显示c1所表示的一个字符
例题
#include <stdio.h>
int main(){
putchar(getchar());
return 0;
}
getchar复用
#include <stdio.h>
int main(){
char c;
c=getchar();
putchar(c);
putchar(c-32);
return 0;
}
getchar单独使用,起到暂停的作用
#include <stdio.h>
int main(){
printf("54*98=%d\n",54*98);
getchar();
return 0;
}
字符输入的注意事项
按enter键、tab键、空格键,都输入了一个字符
常见问题
如果问题中有多个输入字符的语句时,容易出现输入错误
原因:上一次输入结束时按的enter键,成为了下一个输入语句的输入
例题:
#include <stdio.h>
int main(){
char c1;
putchar(getchar());
c1=getchar();
putchar(c1);
printf(“程序结束”);
return 0;
}
例题:统计输入英文句子中字符的个数,语句以(.)结束
#include <stdio.h>
int main(){
int count=0;
printf("请输入字符:");
while(getchar()!='.'){
count++;
}
printf("有字符%d个",count);
return 0;
}
scanf(" %c",&c1);
%c前加空格,在输入时可以跳过空白字符,包括换行符
例题:统计输入英文句子中非空字符的个数,句子以()结束。
#include <stdio.h>
int main(){
int count=0;
char ch1;
scanf(" %c",&ch1);
while(ch1!='.'){
count++;
scanf(" %c",&ch1);
}
printf("%d",count);
return 0;
}
转义字符
转移字符:以""开头的特殊字符
转义字符 | 转义字符的含义 | ASCII代码 |
---|---|---|
‘\n’ | 回车换行,enter键 | 10 |
‘\t’ | 横向跳到下一制表位置,tab键 | 9 |
‘\b’ | 退格,backspace键 | 8 |
‘\r’ | 回车,但不换行 | 13 |
’ \ ’ ’ | 单引号符 | 39 |
’ \ " ’ | 双引号符(仅在字符串中才要反斜杠) | 34 |
‘\a’ | 鸣铃 | 7 |
数字转义字符
‘\ddd’ | 1~3位八进制ASCII码所代表的字符 |
---|---|
‘\xhh’ | 1~2位十六进制ASCII码所代表的字符 |
例如:
横向跳格的八进制ASCII码是011
‘\011’——tab
enter的十六进制ASCII码是0a
‘\x0a’——enter
类型转换
C中混合运算的问题
类型转换方式有量种:
- 隐式类型转换:由C编译器按照某种预定的规则进行自动转换
- 显式类型转换:由程序员在程序中用类型转换运算符设计的转换
隐式转换规则:
char,short->int->unsigned->long->float->double
显式类型转换通常用于自动类型转换不能起作用时(强制类型转换)
格式:
(类型名)表达式
(double)5/3
float x; (int)x%3
强制类型转换的作用
- 用于截取浮点数的整数与小数部分
y=3.14;
(int)y//获取y的整数部分;
y-(int)y;//获取y的小数部分
- 将整数y的值四舍五入
int x;
float y;
x=(int)(y+0.5);
- 赋值运算的类型转换
- 变量=表达式
- 两边数据类型不一致,一律自动转换为左边变量的类型
int i;
i=3.8;//赋值运算中隐式转换规则无效
printf("%d",i);
函数
函数的定义
例题:有一元二次方程,f(x)=x2-10x+9。求f(3)
#include <stdio.h>
int f(int x);//定义在后,使用在前,就需要函数声明
int main(){//主调函数
int a;
a=f(3);
printf("%d\n",a);
return 0;
}
int f(int x){//被调函数
int y;
y=x*x-10*x+9;
return y;
}
函数定义:
- 具有特定的功能的、相对独立的模块,能够被多次使用
函数分类:
- 标准库函数
- 自定义函数
函数要素:
- 定义
[函数类型] 函数名([形式参数表]){
[声明部分]
[语句部分]
}
- 声明(定义在后,使用在前,就需要函数声明)
函数声明也叫函数原型
C中所有标识符遵循先定义后使用的原则
若函数调用在先,定义在后,需要函数声明
函数声明的目的:向系统提供函数名、参数(数量和类型)、返回类型等相关信息
位置:出现在函数调用之前
函数声明的形式
函数类型 函数名(类型名 形式参数1,类型名 形式参数2,...);
//分号
函数类型 函数名(形参类型1,形参类型2,...);
- 调用
函数调用可以出现在表达式、函数的参数、函数的语句中
函数定义是必须的,而声明不是必须的
同一函数只能定义一次,却可以进行多次声明
函数的数据传递
函数的参数传递
- 数据从主调函数传递给被调函数
- 实参到形参的数据传递是单向的值传递
函数的返回值传递
- 数据从被调函数返回主调函数
- 以函数类型为准
函数的参数
- 有参函数(形参与实参)
-
形参
a. 只能类似定义变量的形式呈现
b. 定义时不占内存
c. 调用后释放内存 -
实参
a. 可以时变量、常量、表达式
b. 要求有确定的值 -
实参、形参的类型、数量、顺序要一致
-
多个实参中间用逗号隔开
-
实参到形参的数据传递是单向的值传递
- 没有参数的函数
函数类型 函数名()
函数的返回值
- 函数的返回值是通过函数中的return语句,传递出来的
return 表达式;
return;
- 其功能:结束被调函数,返回到主调函数,并待会一个返回值
- return不是必须的
- 一个函数也可以出现多个return语句
- 没有返回值的函数
- void 函数名(参数表)
- 不能使用带表达式的return(可以没有return)
- 函数调用不能作为表达式的一部分
void 函数名([形参列表])
{
声明部分
语句部分
return;//return后面不能带表达式
}
void printstar()
{
int i;
printf("******************\n");
}
- 有返回值的函数,必须使用带表达式的return
double 函数名([形参列表])
{
声明部分
语句部分
return 2.0*4;//当return语句中的表达式的值类型与函数类型不一致时,以函数类型为准
}
or
int 函数名([形参列表])
{
声明部分
语句部分
return 2*4;
}
- 没有返回值的函数调用不能作为表达式的一部分参与运算,只能以函数调用语句形式出现
函数的调用过程的机制
- 计算实参列表中各表达式的值;
- 执行控制流程从主调函数转移到被调函数,系统为被调函数的变量分配内存空间
- 将前面计算得到的实参值依次赋值给对应的形参变量
- 从被调函数的函数体起始位置开始执行该函数的语句,直到遇到return语句或遇到函数体的花括号 “}” 为止
- 执行控制返回到主调函数的调用点,并用返回值(若有的话)替代函数调用,接着执行主调函数中的后续语句
函数通过参数(输入)和返回值(输出)传递数据
函数的创建及应用
自定义函数
- 编写函数的步骤可分为(求最大值函数)
- 确定函数头(函数名,形参个数及类型,函数类型)
- 编写函数体(实现函数功能)
- 在主调函数中调用、测试(函数名([实参]))
#include <stdio.h>
int max(int x,int y)// 确定函数头(函数名,形参个数及类型,函数类型)
{
int z;//编写函数体(实现函数功能)
if(x>y)
z=x;
else
z=y;
return z;
}
int main()
{
printf("%d\n",max(3,5));// 在主调函数中调用、测试(函数名([实参]))
return 0;
}
例题:编写求n!的函数,计算1!+……+n!
#include <stdio.h>
long factorial(int n)
{
long fac=1,i;
for(i=1;i<=n;i++)
{
fac*=i;
}
return fac;
}
int main()
{
int i,sum=0;
for(i=1;i<=3;i++)
{
sum+=factorial(i);
}
printf("%d\n",sum);
return 0;
}
例题:判别素数的函数,求100~300之间的素数
#include <stdio.h>
int prime(int n){
int i;
for(i=2;i<n;i++)
{
if(n%i==0)
return 0;
}
return 1;
}
int main(){
int i;
for(i=200;i<=300;i++){
if(prime(i)!=0)
printf("%d是素数\n",i);
}
return 0;
}
函数与变量
函数的嵌套调用
C不允许嵌套定义函数,但可以嵌套调用函数
函数的嵌套调用是指在调用一个函数的过程中,又调用另一个函数
例题:
#include <stdio.h>
int sct(int m,int n){
// int i;
// if(m>n)
// i=m;
// else
// i=n;
// for(i;i<=m*n;i++)
// if(i%m==0&&i%n==0)
// return i;
int s;
s=m*n/gcd(m,n);
return s;
}
int gcd(int m,int n){
int i;
if(m>n)
i=n;
else
i=m;
for(i;i>0;i--)
if(m%i==0&&n%i==0)
return i;
}
int main(){
int m,n;
printf("请输入两个整数");
scanf("%d%d",&m,&n);
printf("%d,%d的最小公倍数是:%d\n",m,n,sct(m,n));
printf("%d,%d的最大公约数是:%d\n",m,n,gcd(m,n));
return 0;
}
函数的递归调用
递归的思想是指把一个规模较大的问题转化成形式相同但规模小一些的问题加以解决。
具有递归特性的问题一般具有如下特点:
- 原始问题可转化为解决方法相同的新问题;
- 新问题的规模比原始问题小;
- 新问题又可转化为解决方法相同的规模更小的新问题,直到终结条件为止
在调用一个函数的过程中又出现直接或间接调用函数本身的情况,成为函数的递归调用
两种形式的递归
- 直接递归:f1中调用f1;
- 间接递归:f1中调用f2、f2再调用f1
例题:阶乘
#include <stdio.h>
long factorial(int n){
if(n==0)
return 1L;
else
return n*factorial(n-1);
}
int main(){
int n;
printf("请输入要求的阶乘:");
scanf("%d",&n);
printf("%ld\n",factorial(n));
return 0;
}
变量的作用域
变量的生存期和可见性
- 生存期:变量占有存储空间的时间期限
- 可见性:是在程序中的哪个部分可以引用该变量
- 变量的生存期是一个时间概念,而可见性是一个空间概念
- 变量可见一定存在,但存在不一定可见
变量的作用域
- 是指可以合法访问变量的范围,它是一个空间概念,由变量定义的位置来确定
- 变量定义的两种位置
- 在所有函数之外
- 在块内
- 局部变量和全局变量
局部变量
- 在块内定义的变量成为内部变量,内部变量只在所在块内有效,故称局部变量
- 函数体是典型的块,所以在函数声明语句部分定义的变量是局部变量
- 形式参数只在函数中有效,也是局部变量
- 公共作用域范围内,外层同名变量暂时被屏蔽
- 不能再同一个块内定义同名的变量
全局变量
- 在所有函数之外定义的变量叫做外部变量
- 外部变量的有效范围为从定义变量的位置开始到所在文件的结束,可以由其有效范围内的多个函数共用,因此也称为全局变量或全程变量
- 全局变量没有初始化,默认值为0
- 公共作用域范围内,全局变量暂时被同名局部变量屏蔽
- 全局变量在程序的整个执行期间都独自占有固定的内存单元,并保留其值,在整个程序的运行期(不管在函数内外),总是存在
- 利用全局变量交换变量值
选择变量的原则
- 当变量只在某个函数或复合语句内使用时,不要定义成全局变量
- 当多个函数引用同一个变量时,在这些函数上面定义全局变量,而且定义部分尽量靠近这些函数
变量的存储类型
例题:学校进行长跑训练,规定学生第一天训练300米,第二天训练337.5米,第三天训练379.688米,……,后一天训练量时前一天的1+1.0/8倍
输出2-4天的训练量
#include <stdio.h>
void run_length(){
static double length=300;//static是存储类型说明符,length称为静态存储型变量,只执行一次
length*=(1+1.0/8);
printf("%.2f米\n",length);
}
int main(){
printf("第二天:");
run_length();
printf("第三天:");
run_length();
printf("第四天:");
run_length();
return 0;
}
static变量特点
- static变量保留最新的修改值
- static修饰的语句只执行一次
静态存储方式
在程序编译时分配存储空间,在程序运行期间一直占用固定的存储单元,直到程序运行结束
全局变量
加static修饰的局部变量
动态存储方式
在程序运行期间,根据需要进行动态的分配和释放存储空间
形式参数、未加static修饰的局部变量属于动态存储方式
指定存储类型的变量定义格式
存储类型说明符 数据类型名 变量名表;
局部变量的存储类型
存储类型说明符
auto(自动)
- 由于auto可缺省,所以,函数内所有未加存储类别说明符定义的局部变量均为自动变量
- 自动变量是局部变量
- 自动变量用之则建,用完即撤,节省存储空间
register(寄存器)
- 寄存器变量存储在CPU的寄存器,操作速度远快于存储在内存中的普通变量
- register只能用于局部变量和函数形参
- 对于循环次数较多的循环控制变量、循环体内反复使用的变量及形参等均可定义为寄存器变量
- 现代编译器自动将使用频繁的变量放在寄存器中,所以不需要使用register关键字
static(静态)
- 与自动变量相同的:静态变量的作用域也是从其定义的位置起,到函数体(或复合语句)结束为止。
- 自动变量每次调用时都会重新初始化
- 静态局部变量在系统编译时就初始化(仅一次),static变量中保存最新的数据,静态局部便来给你在定义时未赋初值,编译时会自动初始化为0
全局变量的存储类型
全局变量缺省extern,所以又称"外部变量",静态存储方式,编译时将全局变量分配在静态存储区
初值是在编译时赋给的(仅赋值一次),若在定义时为赋初值,编译时会自动初始化为0(字符型为’\0’)
extern(外部)可缺省又称外部变量,静态存储方式
- 扩展变量的作用域,使它可被程序中各个函数所引用
- 如果全局变量不在文件开头定义,其有效范围只限于定义处到文件尾
- 如果定义点之前的函数要引用该全局变量,则应该在引用前用关键字extern对该变量引用性声明,以告诉编译器该变量在本文件的某处已经被定义
static(静态)
- 表示该变量只可以被定义它的文件中的各函数所引用
函数小结
数组
- 数组是由基本数据类型按一定规则组成的
- 数组由一系列元素构成,这些元素均属于同一数据类型
- 数组是有序数据的集合,各元素在内存中是连续存放的
- 根据数组的组成规则可分为一维数组、二维数组、多维数组
- 根据元素数据类型可分为整型数组、实型数组、字符型数组等
- C语言中把字符串定义为字符数组,即数组元素为字符型的数组
一维数组
只包含一个下标的数组称为“一维数组”
定义形式
类型说明符 数组名[数组长度];
例如:
int a[20];
char c[10];
float x[15];
int d,array[5];
针对数组长度,下列定义是合法的
int a[10];//数组长度可以是常量
int b[10+10];//或常量表达式
float x[15-5];
数组必须先定义后使用
C编译系统没有对下标进行越界检查
C语言规定不能一次引用整个数组
允许使用a[i]的形式访问每个元素
可以像使用普通变量一样使用a[0],a[1],……
一维数组的初始化
定义数组时可对数组元素赋初值
例如:
int a[3]={0,1,2};
如果数组没有初始化,系统会用默认值对它初始化,即外部数组或静态数组赋0值,自动数组赋随机值
初始化数组时,初始值的个数可以比数组元素的个数少,未提供初始值的元素为0
int a[10]={1,2,3,4,5};
int b[10]={0};
如果对全部数组元素赋初始值,可以不指定数组长度
对数组初始化时,初始化元素不能大于数组长度
int a[5]={1,2,3,4,5};
//等价于
int a[]={1,2,3,4,5};
//但不能省略[]
int a[5]={1,2,3,4,5,6};//错误
一维数组应用
例题:求Fibonacci数列前20项
f1=1
f2=1
fn=fn-1+fn-2(n>=3)
#include <stdio.h>
int fib(int n){
int arr[30];
if(n==0)
arr[0]=1;
else if(n==1)
arr[1]=1;
else
arr[n]=fib(n-1)+fib(n-2);
return arr[n];
}
int main(){
int i;
for(i=0;i<20;i++)
printf("%d\n",fib(i));
}
例题:统计分数,由键盘输入50个学生的考试成绩,统计出各分数段的人数
#include <stdio.h>
void main(){
int i;
int score,num[3]={0};
printf("请输入成绩\n");
for(i=0;i<50;i++){
printf("成绩%d\t",i+1);
scanf("%d",&score);
if(score>=0&&score<60)
num[0]++;
else if(score>=60&&score<85)
num[1]++;
else if(score>=85&&score<=100)
num[2]++;
else{
printf("成绩输入无效,请重新输入");
i--;
}
}
printf("不及格人数:%d,良好人数:%d,优秀人数:%d\n",num[0],num[1],num[2]);
}
例题:输入10个整数,找出其中的最大数和最小数
#include <stdio.h>
int main(){
int arr[10]={0};
int i,max,min;
for(i=0;i<10;i++){
printf("请输入整数%d\t",i+1);
scanf("%d",&arr[i]);
}
max=arr[0];
min=arr[0];
for(i=0;i<10;i++){
if(arr[i]>max)
max=arr[i];
if(arr[i]<min)
min=arr[i];
}
printf("最小数为:%d\t最大数为:%d\n",min,max);
return 0;
}
例题:用冒泡法对10个整数排序
对于n个数,共进行n-1轮排序
第一轮比较n-1次,确定最大值
……
第n-1轮比较n-(n-1)次,确定最小值
#include <stdio.h>
int main(){
int arr[10];
int i,j,a,flag=0;
for(i=0;i<10;i++){
printf("请输入整数%d\t",i+1);
scanf("%d",&arr[i]);
}
for(i=1,flag=0;i<=9;i++,flag=0){
for(j=1;j<=10-i;j++){
if(arr[j-1]>arr[j])
{
a=arr[j-1];
arr[j-1]=arr[j];
arr[j]=a;
flag=1;
}
}
if(flag==0)
break;
}
printf("the sorted numbers:\n");
for(i=0;i<10;i++)
printf("%d\n",arr[i]);
return 0;
}
字符数组与字符串
字符数组的定义方法和普通数组的定义方式,初始化方式相同,引用字符数组中的元素与普通数组类似
字符串应用举例
例题:输入一个句子(一行字符),统计其中有多少个单词。句子中各单词之间用一个以上的空格隔开
#include <stdio.h>
int main(){
char str[255];
int i,num=1;
gets(str);
if(str[0]!=' ')num=1;
for(i=0;str[i];i++)
if(str[i]==' '&&str[i+1]!=' ')
num++;
printf("words:%d\n",num);
return 0;
}
例题:连接两个串:把字符串str2连接到字符串str1之后,如果字符数组str1的存储空间不够,直到把str1填满为止
#include <stdio.h>
int main(){
char str1[255],str2[255];
int i,j,k;
gets(str1);
gets(str2);
for(i=0;i<255;i++){
if(str1[i]=='\0'){
str1[i]=' ';
j=i;
}
}
for(j+1,k=0;j<255;j++,k++)
str1[j]=str2[k];
printf("%s\n",str1);
return 0;
}
例题:统计字母个数
输入一个由大写字母(<100个)组成的字符串,求字符串中A-Z各字母的个数;
#include <stdio.h>
int main(){
int count[26];
char str[100];
int num[100],i,j;
for(i=0;i<26;i++){
count[i]=65+i;
num[i]=0;
}
gets(str);
for(i=0;str[i];i++){
for(j=0;j<26;j++)
if(str[i]==count[j])
num[j]++;
}
for(i=0;i<26;i++){
printf("%c:%d\n",count[i],num[i]);
}
return 0;
}
字符串处理函数
例题:从键盘输入三个英文单词,输出其中最大的单词(单词大小按字典顺序)