循环
while循环
- 当while循环的循环条件满足时,会重复执行大括号中的循环体语句。当循环条件不满足时推出while循环
- 循环执行之前会有一次是否满足循环条件的判断,所以有可能循环一次也没有被执行
- 条件成立时循环继续的条件
while(循环条件){
循环体;
}
实例:使用while循环判断数字的位数
int main(){
int i = 0;
int count = 1;
printf("请输入一个数字:");
scanf("%d",&i);
i /= 10;
while(i>0){
count++;
i /= 10;
}
printf("这个数字一共 %d 位",count);
return 0;
}
do while循环
在进入循环的时候不做检查,而是在执行完一轮循环体的代码之后,再来检查循环的条件是否满足,如果满足则继续下一轮循环,不满足则结束循环
do{
循环体;
}while(循环条件);
while循环和do while循环的区别
- while循环是先判断循环条件,再决定是否执行循环体
- do while循环则是先执行循环体,在判断是否满足条件
实例:使用do while循环来判断数字的位数
int main(){
int i;
int count = 0;
printf("输入一个数字:");
scanf("%d",&i);
do{
i /= 10;
count++;
}while(i>0);
printf("你输入的是%d位数字",count);
return 0;
}
注意:while循环的循环体内要有跳出循环的机会,否则将永远无法离开循环,成为死循环
程序的调试
- 测试程序常使用边界数据,如有效范围两端的数据、特殊的倍数等
- 在程序适当的地方插入printf来输出变量的内容
猜数字游戏
使用随机数生成函数rand()需要引入stdlib.h包,为了每次生成的随机数不一样,我们需要生成随机数种子,我们使用时间来帮助生成随机数种子,因此要引入time.h包。
生成随机数种子方法:
srand((unsigned)time(NULL));
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//猜数字游戏
int main(){
int i = 0;
int a = 0;
int c = 0;
srand((unsigned)time(NULL));
i = rand()%100;
printf("猜数字游戏,请输入1-100的数字\n");
do{
printf("输入数字:");
scanf("%d",&a);
c++;
if(a>i){
printf("太大了\n");
}else if(a<i){
printf("太小了\n");
}
}while(a!=i);
printf("恭喜你猜对了,使用了%d次机会",c);
return 0;
}
整数逆序
int main(){
int i = 1234567800;
int ret = 0;
int t = 0;
while(i>0){
ret = i%10;
t = t*10 + ret;
i /= 10;
printf("%d",ret);
}
//printf("%d",t);
return 0;
}
for循环
for循环的运行步骤为
-
判断条件是否满足,满足:执行循环体,不满足:退出for循环 -
执行完循环体后,执行改变动作 -
再次进行循环条件的判断 重复以上操作… -
直到判断条件为假,退出for循环
for(初始值;判断条件;改变动作){
循环体;
}
for循环求阶乘
int main(){
int n = 3;
int fact = 1;
int i;
for(i=2;i<=n;i++){
fact *= i;
}
printf("%d",fact);
return 0;
}
for循环改造为while循环,只需要将for循环的初始值放在外面,判断条件作为while循环的退出条件,改变初始值的操作放在while循环的循环体内,例如:
int main(){
int n = 3;
int fact = 1;
int i=2;
while(i<=n){
fact *=i;
i++;
}
printf("%d",fact);
return 0;
}
几类循环的使用场景
- for:有固定循环次数
- do while:必须先执行一次
- while:其他情况
跳出循环
- break:跳出循环
- continue:跳过这轮循环剩下的语句,进入下一轮循环
- goto:可以直接到达程序指定的位置。
break和continue都只能跳出当前层的循环,如果我们需要跳出多层循环,有两种方法,使用break接力或使用goto语句。
演示
#include<stdio.h>
int main(){
int a=0;
int b=0;
int c=0;
for(a=1;a<5;a++){
for(b=1;b<5;b++){
if(a==3){
c=1;
break;
}
}
if(c==1){
break;
}
printf("%d",a);
}
for(a=1;a<5;a++){
for(b=1;b<5;b++){
if(a==3){
goto out;
}
}
printf("%d",a);
}
out:
return 0;
}
循环嵌套
练习
练习:输出100以内的素数
#include<stdio.h>
int main(){
int s=2;
while(s<=100){
int sign = 0;
int i = 0;
for(i=2;i<s;i++){
if(s%i==0){
sign = 1;
break;
}
}
if(sign==0){
printf("%d ",s);
}
s++;
}
return 0;
}
练习:凑硬币,使用1角、2角、5角凑出指定的金额
#include<stdio.h>
int main(){
int one;
int two;
int five;
float m;
printf("请输入需要凑的金额(元):");
scanf("%f",&m);
for(five=0;five<=m*10/5;five++){
for(two=0;two<=m*10/2;two++){
for(one=0;one<=m*10;one++){
if(five*5+two*2+one==m*10){
printf("%d个5角 %d个2角 %d个1角\n",five,two,one);
}
}
}
}
return 0;
}
练习:1+1/2+1/3+…+1/n
#include<stdio.h>
int main(){
int i=0;
int n=0;
double sum;
printf("求1到1/n的和,输入:");
scanf("%d",&n);
for(i=1;i<=n;i++){
sum+=1.0/i;
}
printf("1到1/%d的和为:%f",n,sum);
}
练习:1-1/2+1/3-1/4+…+1/n
#include<stdio.h>
//练习:1-1/2+1/3-1/4+...+1/n
int main(){
int i=0;
int n=0;
double sum;
printf("求1到1/n的和,输入:");
scanf("%d",&n);
for(i=1;i<=n;i++){
if(i%2==0){
sum-=1.0/i;
}else{
sum+=1.0/i;
}
}
printf("1到1/%d的加和减为:%f",n,sum);
}
练习:正序分解整数
#include<stdio.h>
int main(){
int x = 0;
int temp= 0;
int size = 1;
printf("输入:");
scanf("%d",&x);
temp=x;
while(x>9){
size*=10;
x/=10;
}
do{
printf("%d ",temp/size);
temp=temp%size;
size/=10;
}while(size>0);
return 0;
}
练习:求两个整数的最大公约数
#include<stdio.h>
int main(){
int a = 0;
int b = 0;
int min;
int ret = 1;
int max = ret;
printf("输入两个数:");
scanf("%d %d",&a,&b);
if(a>b){
min = b;
}else{
min = a;
}
for(ret = 1;ret<=min;ret++){
if(a%ret==0){
if(b%ret==0){
if(ret>max){
max = ret;
}
}
}
}
printf("%d 和 %d 的最大公约数为:%d",a,b,max);
return 0;
}
补充:使用辗转相除法求两个整数的最大公约数
#include<stdio.h>
int main(){
int a = 0;
int b = 0;
int t;
printf("输入两个数:");
scanf("%d %d",&a,&b);
while(b!=0){
t = a%b;
a = b;
b = t;
}
printf("最大公约数为:%d",a,b,a);
return 0;
}
练习:输出指定条件的整数集
#include<stdio.h>
int main(){
int a;
int cnt=0;
int i,j,k;
printf("输入一个数:");
scanf("%d",&a);
if(a>6){
printf("输入数字超过6");
return 0;
}
for(i=a;i<=a+3;i++){
for(j=a;j<=a+3;j++){
for(k=a;k<=a+3;k++){
if(i!=j && j!=k && i!=k){
printf("%d%d%d",i,j,k);
cnt++;
if(cnt%6==0){
printf("\n");
}else{
printf(" ");
}
}
}
}
}
return 0;
}
练习:水仙花数
#include<stdio.h>
int main(){
int x;
int i=1;
int first = 1;
scanf("%d",&x);
while(i<x){
first*=10;
i++;
}
i=first;
while(i<first*10){
int t = i;
int sum = 0;
while(t!=0){
int d = t%10;
int p = 0;
int a = 1;
t /= 10;
while(p<x){
a *= d;
p++;
}
sum +=a;
}
if(sum==i){
printf("%d\n",i);
}
i++;
}
return 0;
}
练习:打印九九乘法表
#include<stdio.h>
int main(){
int i = 0;
int j = 0;
for(i=1;i<10;i++){
for(j=1;j<=i;j++){
printf("%d*%d=%d ",j,i,i*j);
}
printf("\n");
}
}
练习:求序列前n项和
#include<stdio.h>
int main(){
int i,j;
double k=2;
double p=1;
double sum=0;
printf("input:");
scanf("%d",&i);
for(j=1;j<=i;j++){
sum+=k/p;
double t=k;
k = k+p;
p = t;
}
printf("output:%f",sum);
return 0;
}
数据类型(翁帆)
sizeof
- 是一个运算符,给出某个类型或变量在内存中所占据的字节数
负数的表达
在c语言中,负数以补码的形式来表达。
例如:char类型的数据,00000000-111111111 表示的是从-128~127
1000000011111111表示的是-128 -1,当这个数据的二进制最高位为1时,被认为是以补码的形式表示的负数。
如果不想要表示负数,可以在数据生命前加unsigned关键字
当我们使用代码来验证时,a输出的是-1,b输出的是255
#include<stdio.h>
int main(){
char a = 255;
unsigned char b = 255;
printf("%d\n",a);
printf("%d\n",b);
return 0;
}
整数是以纯二进制方式进行计算的
- 11111111+1 ===> 100000000 ===> 0
- 01111111+1 ===> 10000000 ===> -128
- 10000000-1 ===> 01111111 ===> 127
8进制和16进制
- 一个以0开始的数字字面量是8进制
- 一个以0x开始的数字字面量是16进制
- %0用于8进制,%x用于16进制
- 8进制和16进制只是如何把数字表达为字符串,与内部如何表达数字无关
选择整数类型
为什么整数要有那么多种?
没有特殊需要,就选择int
- 现在的CPU的字长普遍是32位或64位,一次内存读写就是一个int,一次计算也是一个int,选择更短的类型不会更快,甚至可能更慢
- 现代的编译器一般会设计内存对齐,所以更短的类型实际在内存中有可能也占据一个int的大小
unsigned与否只是输出的不同,内部计算是一样的
浮点类型
- float 4字节 字长32位 scanf:%f printf:%f,%e
- double 8字节 字长64位 scanf:%lf printf:%f,%e
- float类型只有小数点后7位是有效地,double类型小数点后15位是有效的,超过有效位的数值会出现误差
%e 表示数值以科学记数法表达
浮点数输出精度
- 在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做四舍五入
例如:输出结果保留两位小数
#include<stdio.h>
int main(){
double d = 2;
printf("%.2f",d);
return 0;
}
超过范围的浮点数
- printf输出inf表示超过范围的浮点数:±∞
- printf输出nan表示不存在的浮点数
- 无穷大不能用整数来表示,只能在浮点数的运算中出现
浮点运算的精度
- 带小数点的字面量是double而不是float
- float需要用f或F后缀来表明身份
浮点数的内部表达
- 浮点数在计算时是由专用的硬件部件实现的
- 计算double和float所用的部件是一样的
选择浮点数类型
- 如果没有特殊需要,只是用double
- 现代CPU能直接对double做硬件运算,性能不会比float差,在64位的计算机中,数据存储的速度也不会比float慢
字符类型
char是一种整数,也是一种特殊的类型:字符。这是因为
- 用单引号表示的字符字面量:‘a’,‘1’
- ''也是一个字符
- printf和scanf里用%c来输出字符
字符的输出输出
如何输入’1’这个字符给char c?
#include<stdio.h>
int main(){
char c;
scanf("%c",&c);
printf("%d\n",c);
printf("%'c'\n",c);
return 0;
}
字符计算
- 一个字符加一个数字得到ASCII码表中那个数之后的字符
- 两个字符的减,得到它们在表中的距离
大小写转换
- 字母在ASCII表中是顺序排列的
- 大写子母和小写字母是分开排列的,并不在一起
- ‘a’-'A’可以得到两个字母在ASCII表中的距离
逃逸(转义)字符
用来表达无法打印出来的控制字符或特殊字符,它由一个反斜杠“\”开头,后面跟上另一个字符,这两个字符合起来,组成一个字符
- \b 回退一格
- \t 到下一个表格位
- \n 换行
- \r 回车
- \" 双引号
- \’ 单引号
- \\ 反斜杠本身
自动类型转换
当运算符的两边出现不一致的类型时,会自动转换成较大的类型
- 大的意思是能表达的数的范围更大
- char->short->int->long->long long
- int->float->double
强制类型转换
要把一个量强制转换成另一个类型,通常是把较大的类型强制转换成较小的类型
此时需要注意安全性问题,因为小的变量有时候无法表达大的量
逻辑类型bool
必须首先导入头文件#include<stdbool.h>,之后才可以使用bool和true、false。但是在c语言中true和false依然是用整数1、0来表示,所以printf输出时依然使用%d
逻辑运算
逻辑运算符
逻辑运算是对逻辑量进行的运算,结果只有0或1。逻辑量是关系运算或逻辑运算的结果。
- ! 逻辑非 !a 如果a是true结果就是false,如果a是false结果就是true
- && 逻辑与 a&&b 如果a和b都是true,结果就是true;否则就是false
- || 逻辑或 a||b 如果a和b一个是true,结果就是true;两个都是false,结果为false
优先级
短路
逻辑运算是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算。
条件运算
条件运算符
- count=(count>20)?count-10:count+10;
- 结果=条件?条件满足时操作:条件不满足时操作
优先级
- 条件运算符的优先级高于赋值运算符,但是低于其它运算符
函数
什么是函数?
- 函数是一块代码,接收零个或多个参数,做一件事情,并返回零个或一个值
- 可以先想象成数学中的函数y=f(x)
函数结构
返回值类型 函数名(参数){
函数体;
}
调用函数
#include<stdio.h>
void cheer(){
printf("cheer\n");
}
int main(){
cheer();
return 0;
}
函数原型
上面的函数在main方法的前面,如果自定义函数要放在main方法的后面,需要在程序的开始声明函数的原型。否则有可能会报错。
- 函数头,一份好结尾,就构成了函数的原型
- 函数原型的目的是告诉编译器这个函数长什么样子(名称、参数、返回类型)
#include<stdio.h>
void cheer();
int main(){
cheer();
return 0;
}
void cheer(){
printf("cheer\n");
}
参数传递
- 调用函数时给的值与参数的类型不匹配是C语言传统上最大的漏洞
- 编译器总是悄悄替你把类型转换好,但是这很可能不是你所期望的
- 后续的语言,C++、Java在这方面更严格
- C语言在调用函数时,只会传值给函数
局部变量
- 函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称为局部变量
- 定义在函数内部或方法内部的变量都是局部变量
- 参数也是局部变量
变量的生存周期
变量的作用域
数组
c语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组中的特定元素可以通过索引访问,第一个索引值为0
声明数组
数组类型 数组名[数组容量]
初始化数组
数组类型 数组名[数组容量] = {元素1,元素2...}
需要注意的是,如果初始化数组时数组容量不为空,则数组的元素个数不能大于数组容量。
int arr[5] = {1,2,3,4,5};
int arr[] = {1,2,3,4,5,6};
访问数组元素
数组元素可以通过数组名加索引进行访问。元素的索引放在方括号内
printf("%d",arr[0]);
二维数组
-
int a[3][5];
-
通常可以理解为a是一个3行5列的矩阵
二维数组的遍历
使用一个双层for循环进行遍历。例如:
#include<stdio.h>
int main(){
//二维数组初始化
int a[2][2] = {1,2,3,4};
int i,j;
//二维数组遍历
for(i=0;i<2;i++){
for(j=0;j<2;j++){
printf("%d ",a[i][j]);
}
}
}
数组变量是特殊的指针
- 数组变量本身表达地址,所以,int a[10];int *p=a; //无需用取地址符&
- 但是数组的单元表达的是变量,需要用&取地址,a==&a[0]
- []运算符可以对数组做,也可以对指针做:p[0]<==>a[0]
- *运算符可以对指针做,也可以对数组做:*a=25;
- 数组int b[] 等价于 int *const b
枚举enum
枚举是C语言中的一种基本数据类型,它可以让数据更简洁,更易读。语法:
enum 枚举名{
枚举元素1,枚举元素2,...
}枚举变量;
练习:输出张三 李四 王五三个人的年龄
#include<stdio.h>
int main(){
enum Name{
zhangsan=21,lisi=22,wangwu=23
}name;
printf("张三的年龄:%d\n",name = zhangsan);
printf("李四的年龄:%d\n",name = lisi);
printf("王五的年龄:%d\n",name = wangwu);
return 0;
}
取地址符号&
- scanf("%d",&i)里的&
- 获得变量的地址,它的操作数必须是变量
练习:输出某个变量的地址
#include<stdio.h>
int main(){
int i;
printf("%p",&i);
return 0;
}
指针变量
- 指针变量的值是内存的地址,是具有实际值的变量在内存中存放的地址
- 普通变量的值是实际的值
指针变量的声明
int i;
int* p = &i;
int* p,q;
int *p,q;
int *p;
在上面声明指针变量的例子中,我们这样描述:指针变量p指向整形变量i,因为p存放的是i在内存中的地址
使用指针输出地址变量
#include<stdio.h>
int main(){
int i=0;
int *p = &i;
printf("%p",p);
return 0;
}
访问地址符号*
- *是一个单目运算符,用来访问指针的值所表示的地址上的变量
- 可以做右值也可以做左值 int k = *p; *p = k+1;
指针访问和修改变量
#include<stdio.h>
int main(){
int i=0;
int *p = &i;
printf("使用指针访问,i:%d\n",*p);
printf("使用指针修改变量后,i:%d\n",*p=26);
return 0;
}
练习:输出一个整形数组的最大值和最小值
#include<stdio.h>
void swap(int a[],int len,int *min,int *max);
int main(){
int arr[6]={2,45,6623,7,43,2};
int min,max;
minmax(arr,6,&min,&max);
printf("arr最小值:%d\n",min);
printf("arr最大值:%d\n",max);
}
void minmax(int a[],int len,int *min,int *max){
int i;
*min=a[0];
*max=a[0];
for(i=0;i<len;i++){
if(*min>a[i]){
*min=a[i];
}
if(*max<a[i]){
*max=a[i];
}
}
}
指针最常见的错误
- 定义了指针变量,还没有指向任何变量,就开始使用指针
c语言求数组长度
求c语言中数组的长度,我们可以先使用sizeof方法获取数组的所占的字节数,然后再除以数组元素的长度,即得到数组的长度
#include<stdio.h>
int main(){
int a[3]={1,2,3};
int size = sizeof(a)/sizeof(a[0]);
printf("%d\n",size);
return 0;
}
使用指针获取数组指定元素的值
#include<stdio.h>
int main(){
int arr[6]={1,2,3,4,5,6};
int *p = &arr[0];
printf("数组arr第2个元素值:%d\n",*(p+1));
printf("数组arr第3个元素值:%d\n",arr[0]+2);
return 0;
}
数组变量是特殊的指针
- 数组变量本身表达地址,所以,int a[10];int *p=a; //无需用取地址符&
- 但是数组的单元表达的是变量,需要用&取地址,a==&a[0]
- []运算符可以对数组做,也可以对指针做:p[0]<==>a[0]
- *运算符可以对指针做,也可以对数组做:*a=25;
- 数组int b[] 等价于 int const *b
指针与const
int i;
const int *p1 = &i;
int const *p2 = &i;
int *const p3 = &i;
判断哪个被const了的标志是const在*的前面还是后面。如果const在*前面,表示它所指向的东西不能被修改。如果const在*后面,表示指针不能被修改。
因此,前两种const在*前面,表示两个指针所指向的东西不能修改。第三种const在*后面,表示指针不能被修改。
转换
const数组
- const int a[] = {1,2,3,4,5,6};
- 数组变量已经是const的指针了,这里的const表明数组的没个单元都是const int
- 所以只能通过初始化进行赋值
保护数组值
-
因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值 -
为了保护数组不被函数破坏,可以设置参数为const int sum(const int a[],int length);
|