本文部分代码可能缺少#include <stdio.h>
请大家在使用时自行加上!!!
一个.c文件中只能有一个main函数;一个工程中可以有多个.c文件。
1.数据类型
char | 字符数据类型 |
---|---|
short | 短整型 |
int | 整型 |
long | 长整型 |
long long | 更长的整型 |
float | 单精度浮点数 |
double | 双精度浮点数 |
查看数据类型字节数(针对32位或者64位编译器)
int类型可表示的最大整数为2^31-1=65535
说明:明明是32个Bit为什么是31次方,因为最高位是符号位,不参与计算。那为什么要减1呢?因为我们在计算时把0也算进去了。打个比方,假如是8位的编译器,可以存128个,但是正数从零开始,到127,如果要求最大数就要减1。
那么针对16位的编译器
各种类型占多少字节呢?
对于16位的(signed int)整型最大数可表示为2^15-1=32767
计算机中单位:
bit - 比特位 byte - 字节 =8bit kb - 1024byte mb - 1024kb
gb - 1024mb
2.变量、常量
2.1变量
变量分类:局部变量、全局变量
//全局变量 - {}外部定义
int a = 100;
int main()
{
//局部变量 - {}内部定义
//当局部变量和全局变量名字冲突的情况下,局部优先
//不建议把全局变量和局部变量名字写成一样
int a = 10;
printf("a = %d\n",a);//a = 10
return 0;
}
2.1.1作用域和生命周期
除此之外还可以使用两个.c文件,只需声明变量即可
生命周期
变量的生命周期:变量的创建和销毁之间的时间段
局部变量的生命周期:进入局部范围生命开始,出局部范围生命结束
全局变量的生命周期:整个程序的生命周期
2.2 常量
2.2.1 字面常量
无实际含义,但存在。
2.2.2 const修饰的常变量
a为变量,定义a=20后打印出a=20
int main()
{
//a为常变量 - 具有常属性(不能改变的属性)
const int a = 10;
printf("%d\n",a);
return 0;
}
被const修饰以后,a不能再修改
**误区:**在创建数组时需要使用常量,此时int n = 10;n为变量,纵使使用const,n为常变量但n的本质还是一个变量,所以报错。
2.2.3 #define定义的标识符常量
#include <stdio.h>
#define min 100
int main()
{
int a = min;
//min = 2000;//标识符常量不能修改
printf("a = %d\n",a);
return 0;
}
2.2.4 枚举常量
可以赋初值,当赋2时,就是2,3,4
3. 字符串
定义:双引号引起的一串字符
注:字符串以\0结尾为标志
arr1的字符串是abc\0,打印到c后有\0,不再继续打印
arr2打印abc后后面的数字未知,继续打印
我们手动添加一个\0后充分证明\0是字符串的结束标志
当我们计算字符串长度时,\0不算字符串内容,仅仅只是一个标志。
引入strlen时会报错,只需在前面插入
#include <string.h>
此时arr1的长度为3,arr2的长度为一个随机值
4.转义字符
#include <stdio.h>
int main()
{
printf("(are you ok??)"); //??) - ] 三字母词
//printf("are you ok\?\?)");//使用\转义,这样?就能够被顺利打印出
return 0;
}//部分编译器以无法显示出三字母词
printf("%d",100);打印整型
printf("%c",'a');//打印字符
printf("%s","ab");//打印字符串
字符串长度
strlen遇到 \0 停止读取
\t\v\0will\n 字符串长度为3
易错:若八进制为\182 那么就拆分为\1 和82,一共就是三位,\1、8、2
#include <stdio.h>
#include <string.h>
int main()
{
printf("%d\n",strlen("c:\tes\\\t\328\\t.c"));
return 0;
}
4.1 进制转换
(1)十进制转化为八进制
[1].67D=101Q
67=26+20=1 000 001=1 0 1(先转化为二进制)
1000000 1
[2].10.68D=Q(精确到小数点后2位)
整数部分按照上述[1]的方法,小数部分精确到小数点后几位,小数部分就乘以几次8,然后从上往下的顺序读取。
10D=23+21=1 010=12Q
0.68*8=7.44 取7
0.44*8=3.72 取3
10.68D=12.73Q
(2)八进制转化为十进制
130Q=88D
1 3 0 =1 *8^2+3 *8^1+0 *8^0=88
(3)十六进制转十进制
23daH=D
23da=2 *16^3+3 *16^2+d *16^1+a *16^0=9178D
(4)十进制转为十六进制
27.68H=D(精确到小数点后2位)
27=24+23+2^1=1 1010=19
小数部分0.68*16=10.88 取a
0.88*16=14.08 取e
27.68H=19.aeD
5.数组
5.1 一维数组的创建和初始化
5.1.1 创建
定义:一组相同类型的元素的集合
int arr[10];
//输入数组时,没有&
scanf("%s",arr);//数组名本就是一个地址,没有&
分析:ch5打印到\0停止;而ch6从b开始往后打印时,一直没有遇到\0,便一直打印知道栈溢出
C99 标准中引入一个概念:变成数组
支持在数组创建的时候,用变量指定大小,但数组不能初始化;部分编译器支持此操作。
int n = 10;
int arr[n];
5.1.2 初始化
5.2 一维数组的使用
int sz sizeof(arr) / sizeof(arr[0]);//计算数组个数
5.3 一维数组在内存中的存储
1.一维数组在内存中连续存放
2.随着数组下标的增长,地址由低到高变化
5.4 二维数组的创建和初始化
5.5 二维数组在内存中的存储
二维数组在内存中也是连续存放
一行内部连续,换行也是连续。
5.6 数组作为函数参数
数组传参。调用部分(实参部分)写的数组名,形参部分,既可以 写成数组,也可以写成指针,但本质上都是指针,数组传参时传过去的时数组首元素地址
有两个例外
- sizeof(数组名) - 数组名表示整个数组 - 计算整个数组的单位大小(单位是字节)
- &数组名 - 数组名表示整个数组 - 取出的是整个数组的地址
练习:将数组按照升序输出
void bubble_sort(int arr[],int sz)
{
int i = 0;
//趟数
for (i = 0;i < sz - 1;i++)
{
int j = 0;
//一个数字一趟交换次数
for(j = 0;j < sz - 1 - i;j++)
{
if (arr[j] > arr[j+1])
{
int tmp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = tmp;
}
}
}
}
#include <stdio.h>
int main()
{
int arr[] = {10,9,8,7,6,5,4,3,2,1};
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort (arr,sz);
//printf("%d",ret);
return 0;
}
6.函数
6.1 函数是什么
6.2 函数分类
(1)库函数;(2)自定义函数
6.2.1 库函数
编译器发布的时候携带了一些库函数,在实际操作中,频繁大量出现的一些操作不需要进行实现,提高我们的效率。
https://www.cplusplus.com/
https://msdn.itellyou.cn/
以strcpy库函数为例
#include <string.h> //调用头文件
int main()
{
char arr1[20] = {};
char arr2[] = {"abc"};
strcpy(arr1,arr2);//strcpy作用是copy值
printf("%s",arr1);
return 0;
}
6.2.2 自定义函数
自定义函数和库函数一样,有函数名,返回值类型和函数参数。但不一样的是这些都是我们可以自己设计的。
比较两数大小
int max(int x,int y)
{
if(x>y)
return x;
else
return y;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d",&a,&b);
int mm = max(a,b);
printf("%d\n",mm);
return 0;
}
两数进行交换
由此可见并没有交换成功。
分析:在main的主函数中,a = 10,b = 20 都有各自的地址,而在test函数中的x,y也有各自的地址,交换x,y的值与a,b的值并无瓜葛,因此交换失败。这时我们需要使用指针变量解决问题。
使用指针变量
6.3 函数参数
函数定义时使用的参数为形式参数,
a,b真实传给swap1和swap2的参数为实际参数
6.3.1 实际参数
实际参数也可以为表达式,函数等
6.3.2 形式参数
形式参数在函数调用以后就销毁。
6.4 函数调用
分类:传值调用;传址调用
传值调用:形参与实参之间没有什么必然联系,只是把值传过去。
传址调用:函数内部可操作和函数外部,形参与实参之间有联系,这时就为传址调用。
练习1:使用函数打印出100-200之间的素数
int ss (int n)
{
int j = 0;//此时上面已经赋n为Int类型,无需再次赋值
for (j = 2;j < n;j++)
{
if (n % j == 0)
{
return 0;
}
}
return 1;
}
int main()
{
int i = 0;
for(i = 100;i <= 200;i++)
{
if(ss(i) == 1)
{
printf("%d ",i);
}
}
return 0;
}
练习2:打印出1000-2000之间的闰年
int ryear(int y)
{
if(((y % 4 == 0) && (y % 100 != 0))||(y % 400 == 0))
{
return 1;
}
else
{
return 0;
}
}
int main()
{
int n = 0;
int x = 0;
int count = 0;
for (n = 1000;n <=2000;n++)
{
if (ryear(n) == 1)
{
count ++;
printf("%d ",n);
}
}
printf("count = %d\n",count);
return 0;
}
**易错点:**使用下面这种写法,例如1600,能被4整除以后进入下一个if语句,但也可以被100整除,所以直接return 0,但实际上也可以被400整除,是满足闰年条件的。因为在进入一个if语句之后便不会再进行下面的语句。
练习3:实现整型有序数列的二分查找
int test (int a[],int k,int s)
{
int left = 0;
int right = s - 1;
while(left <= right)
{
int mid = (left+right) / 2;
if(a[mid] < k)
{
left = mid + 1;
}
else if (a[mid] > k)
{
right = mid - 1;
}
else
{
return mid;
}
}
return -1;
}
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int key = 7;
int ss = sizeof(arr) / sizeof(arr[0]);
int asd = test (arr, key ,ss);//数组,要找的数,数组个数
if (asd == -1)
{
printf("找不到\n");
}
else
{
printf("找到了,下标为:%d\n",asd);
}
return 0;
}
练习4:每使用一次函数就加1
void add(int* pa)
{
(*pa)++;
}
int main()
{
int p = 0;
add(&p);
add(&p);
add(&p);//传址调用
printf("%d ",p);
return 0;
}
6.5 函数的嵌套调用和链式访问
6.5.1 嵌套调用
误区:一个函数内不能定义一个函数
void two()
{
printf("hahaha\n");
}
int one()
{
int i = 0;
for(i = 1;i <= 3;i++)
{
two();
}
}
int main()
{
one();//打印三次hahaha
return 0;
}
6.5.2 链式访问
把一个函数的返回值作为另一个函数的参数。
#include <string.h>
#include <stdio.h>
int main()
{
int len = strlen("asdf");
printf("%d\n",len);
//链式访问
printf("%d\n",strlen("asdf"));
return 0;
}
分析:
我们先了解printf函数
因此,在完成printf(“%d”,43)后,第二个printf(“%d”,2).而最外面的第一个printf(“%d”,2),打印出来为1。
6.6 函数的声明和定义
-
函数先声明后使用。当函数定义在main函数之前时,不用声明
-
函数声明一般放在头文件
例如在实际生活中,一个工程由几个工程师同时完成,当某工程师想要汇总所有人的代码时,直接调用头文件。
优点:当我们不想把.c文件直接给其他人但又想让他用到时,用.c和.h文件创建一个静态库,将静态库和,h文件给出去,使用#include <文件名.h>声明,#pragma导入静态库,即可实现函数功能。
6.7 函数递归
练习1:要求输入一个无符号整数,按照输入1234,1 2 3 4的模式输出
分析:1234
1234%10 4
1234/10 =123 % 10 = 3
1234/10 =123/10 = 12 % 10 = 2
1234/10 =123/10 =12 ./ 10 = 1 % 10 = 1
进行递归时函数内部具体操作见下图。
练习2:编写函数求字符串长度,不允许函数创建临时变量
int ss(char* arr)
{
if(*arr != '\0')
{
return 1+ss(arr+1);
}
else
{
return 0;
}
}
int main()
{
char arr[] = "two";
printf("%d",ss(arr));
return 0;
}
递归与迭代
练习1 求阶乘
方法一:递归写法
int jc(int m)
{
if(m <=1)
{
return 1;
}
else
{
return m * jc(m-1);
}
}
int main()
{
int n = 0;
scanf("%d",&n);
printf("%d\n",jc(n));
return 0;
}
方法二:迭代法
int main()
{
int n = 0;
scanf("%d",&n);
int i;
int sum = 1;
//循环也被称为迭代
for (i = 1;i <= n;i++)
{
sum = i * sum;
}
printf("%d\n",sum);
return 0;
}
练习2 斐波那契数列
#include <stdio.h>
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n-1)+fib(n-2);
}
int main()
{
int n = 0;
scanf("%d",&n);
int te = fib(n);
printf("%d\n",te);
return 0;
}
7. 操作符
7.1 算数操作符
% 操作符两端必须为整数
7.2 移位操作符
左移操作符 | << |
---|---|
右移操作符 | >> |
7.2.1 左移操作符
#include <stdio.h>
int main()
{
int a = 3;
//左移操作符针对的是二进制位
int b = a << 1;
printf("%d\n",b);
return 0;
}
a=3是int类型,int类型是4个字节,一个字节8个bit,一共为32bit,a转化为二进制为00000000000000000000000000000011,整体向左移一位,右边空缺用0补充,那么结果为00000000000000000000000000000110,b=6
整体原则就是,左边丢弃,右边补0 。
7.2.2 右移操作符
1.算术右移
右边丢弃,左边补原符号位
当为正时,符号位本就为0,为负数时为1
整数在内存中存储的是补码。
一个整数在二进制中的表示有三种:原码,反码,补码
负数原码,反码,补码的计算,以-1为例:
-
原码:-1的二进制位
整数最高位表示符号位,最高位1表示负数
10000000 00000000 00000000 00000001(原码) -
反码:符号位不变,其他位按位取反,1变0,0变1
11111111 11111111 11111111 11111110(反码) -
补码:反码二进制序列加1
11111111 11111111 11111111 11111111(补码)
对于正整数,原码,反码,补码相同。
2.逻辑右移
右边丢弃,左边补0
去判断当前右移操作符为逻辑右移还是算术右移时,只需使用负数,以-1为例,输出为-1是说明32个bit全为1,左边最高位补原符号位,属于算术右移。
7.3 位操作符
位操作符 | 含义 |
---|---|
& | 按位与 |
| | 按位或 |
^ | 按位异或 |
操作数必须为整数 | |
0 ^ a = a;
a ^ a = 0
练习:在不借助第三个变量的情况下,交换a = 3,b = 5的值
方法一:
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("a = %d,b = %d\n",a,b);
a = a + b;
b = a - b;
a = a - b;
printf("a = %d,b = %d",a,b);
return 0;
}
缺点:数字太大可能产生溢出
方法二:
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("a = %d,b = %d\n",a,b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a = %d,b = %d",a,b);
return 0;
}
分析:
a = a ^ b;
b = a ^ b = a ^ b ^ b = a ^ 0 = a;
a = a ^ b = a ^ b ^ a = b;
7.4 赋值操作符
= 赋值
== 判断
7.5 单目操作符
a + b //+ 双目操作符,有两个操作数
单目操作符:只有一个操作数
7.5.1 !操作符
7.5.2 sizeof操作符讲解
分析:打印第一个值时,a + 2结果放s中,sizeof结果取决于s,不论a类型是否为整型,short字节数为2;
一个.c文件到一个.exe文件需要经历编译>>链接>>运行三个阶段,而s = a + 2在编译阶段已经完成,到运行时只运行了打印2,因此在执行打印s的操作时,s的值并没有改变,还是5。
char c[] ={"china"} //sizeof c = 6 \0也计算在内
char c[] = {'c'.'h','i','n','a'};//sizeof c = 5
7.5.3 ~操作符
int main()
{
int a = 0;
//~按(二进制)位取反,0变1,1变0
printf("%d\n",~a);
return 0;
}
对于int a=0,二进制为00000000 00000000 00000000 00000000
~a为 11111111 11111111 11111111 11111111(补码)
而%d输出的是原码,(原码按位取反符号位不变,得到反码,加一得到补码。相反补码减一,按位取反得到原码。)因此需要将~a减1,符号位不变,按位取反,
得到10000000 00000000 00000000 00000001,最终打印出-1
具体原码,反码,补码的介绍请参考7.2.2 右移操作符。
7.5.4 前置++ 后置++
int main()
{
int a = 1;
int b = a++;//后置++,先使用,后++
printf("%d\n",b);//b=1
printf("%d\n",a);//a=2
int c = 10;
int d = ++c;//前置++,先++,后使用
printf("%d\n",d);//d=11
printf("%d\n",c);//c=11
return 0;
}//前置-- 后置-- 同理
7.5.5 & 和 * 操作符
7.5.6 (类型)
3.14本质上是一个double类型,因此需要强制类型转换。
int main()
{
int a =(int)3.14;
printf("%d\n",a);
return 0;
}
下面这个例子有异曲同工之妙
#include <stdio.h>
int main()
{
int a[] = {10000,5000,2000,1000,500,200};
int b[] = {100,50,25,10,5,1};
double w = 0;
int i;
scanf("%lf",&w);
int n = 100 * w;
printf("NOTAS:\n");
for (i = 0;i < 6;i++)
{
printf("%d nota(s) de R$ %d.00\n",n / a[i],a[i] / 100);
n %= a[i];
}
printf("MOEDAS:\n");
for (i = 0;i < 6;i++)
{
printf("%d moeda(s) de R$ %.2lf\n",n / b[i],b[i] / 100.0);
//%lf不对结果本质进行改变,%.2lf出现的意义就是将结果保留两位小数
//若想将结果变为浮点数,就需要将被减数进行强制转换,原先是整型,100.0强制变为浮点型,随后输出的结果就是浮点型,随后使用%.2lf保留两位小数
n %= b[i];
}
return 0;
}
7.6 关系操作符
>= | 大于等于 |
---|---|
<= | 小于等于 |
!= | 不等于 |
== | 测试是否等于 |
两个字符串的比较不能用== |
7.7 逻辑操作符
&& | 逻辑与 |
---|---|
|| | 逻辑或 |
可用于if语句 | |
7.8 条件操作符(三目操作符)
? :
exp1 ? exp 2 : exp 3
7.9 逗号表达式
int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = (b = a + 2,c = b - 1,a = c + 2);
//逗号表达式从左向右计算,前面表达式可能影响后面表达式
//整个表达式结果是最后一个表达式结果
printf("%d\n",d);//d=4
return 0;
}
7.10下标引用、函数调用、结构成员
- 下标引用
- 函数调用
- 结构成员
结构体.成员名
7.11 整型提升
7.12 算术转换
精度由高到低排列:
long double
double
float
unsigned long int
long int
unsigned int
int
当操作数属于不同类型时,就需要将其中一个操作数的类型向另一个操作数类型转换,否则无法正常运行,通常是由精度低的向高的转换。
操作符属性
1.优先级
2.结合性
3.控制求值顺序
8. 关键字
c语言的关键字
1.c语言提供,不能自己创建关键字
2.变量名不能是关键字
常见关键字
误区:define 使用 #define
include 使用#include 均为预处理指令
auto 是自动的 - 每个局部变量都是auto修饰的
extern 用来申明外部符号
register 寄存器关键字
#include <stdio.h>
int main()
{
//大量频繁被使用的数据,存放在寄存器中,提升效率
register int num = 1;//建议num的值存到寄存器中,是否存在寄存器中由编译器决定
return 0;
}
8.1 typedef 类型重定义
#include <stdio.h>
typedef unsigned int nu;//unsigned int重定义为nu
int main()
{
unsigned int a = 100;
nu b = 200;
printf("%d\n",a);//a=100
printf("%d",b);//b=200
return 0;
}
8.2 static 静态的
- 修饰局部变量
static修饰局部变量,改变局部变量的生命周期。(本质上改变了变量的存储类型)
void test()
{
int a = 1;//第一次走完程序,a=2销毁,第二次重新创建变量a=1
a++;
printf("%d",a);
}
int main()
{
int i = 0;
while (i<10)
{
test();
i++;
}
return 0;
}//打印出2222222222
void test()
{
static int a = 1; //第一次出这个程序,a=2没有销毁,那么这一行代码没有意义了。
a++;
printf("%d",a);
}
int main()
{
int i = 0;
while (i<10)
{
test();
i++;
}
return 0;
}//打印出 23456789
-
修饰全局变量
static修饰全局变量使得全局变量只能在自己所在的源文件(.c)内部使用
全局变量,在其他源文件内部可以被使用,是因为全局变量具有外部链接属性但是被static修饰之后,就变成了内部链接属性,其他源文件不能链接到这个静态的全局变量。 -
修饰函数
static修饰函数,使得函数只能在自己所在的源文件内部使用,不能在其他源文件内部使用。
本质上:static将函数的外部链接属性转变为内部链接属性。
和static修饰全局变量一样!!
9.常量和宏
define 是一个预处理指令
1.define 定义符号
#define max 100
int main()
{
printf("%d\n",max);
return 0;
}
2.define定义宏
#define asd(x,y) x + y
int main()
{
int a = 1;
int b = 2;
printf("%d\n",asd(a,b));//3
printf("%d\n",3*asd(a,b));//3*1+2=7
return 0;
}
此时若3*asd(a,b)想要得到9,进行如下修改:
#define asd(x,y) ((x)+(y))
10.指针
指针有两层意思,第一,指针是地址;第二,指针存到变量,指针变量也称为指针。
int main()
{
char a = 'h';//a在内存中要分配的空间为1个字节
printf("%p\n",&a);//%p 打印地址
char * pe = &a;//pe用来存放地址,学名为指针变量
//* 说明pe为指针变量
//char 说明pe所指的对象是char类型
//若为int类型,a在内存中分配的空间为4个字节,打印出来的地址为四个字节中的第一个
return 0;
}
一个内存单元为一个字节,存在指针中的
指针用来存放地址
指针需要多大空间,取决于地址的存储需要多大空间
指针大小相同!!
32位 32 bit - 4 byte
64位 64bit - 8byte
我这里编译器是64位,所以是8个字节
10.1 指针和指针类型
指针类型意义
1.指针类型决定了指针解引用的权限有多大。
char 1 byte int 4 byte double 8 byte
2.指针类型决定了,指针每走一步的步长
10.2 野指针
指针指向的位置是不正确的。随机的,没有明确限制的
1.指针未初始化
2.越界访问
当i = 10时,p就指向arr[10],p++之后就越界
3.指针指向空间释放
避免野指针应注意:
1.指针初始化
2.小心指针越界
c语言本身不会检查指针越界行为
3.指针指向空间释放及时置null
4.指针使用之前检查有效性
指针变量不知道指向什么时,指向null,指向的空间被释放也指成Null;指向有效空间时,指向有效地址;
10.3 指针运算
10.3.1 指针加减整数
10.3.2 指针-指针
用指针-指针的方法求字符串个数,实现strlen功能
10.3.3 指针的关系运算
vp可以和后一个地址空间比较,不能和第一个之前的元素地址比较。因此更推荐第一种做法
10.4 指针和数组
数组名是数组首元素地址
p是首元素地址,首元素地址+i就是下标为i元素的地址
10.5 二级指针
10.6 指针数组
本质还是数组
11.结构体
11.1 结构体成员访问
11.2 结构体传参
结构体传参时要传结构体地址
每一个函数调用都会在内存栈区上开启一块空间。
12.分支语句
语句定义:由分号(;)隔开的就是一条语句
12.1 if语句
if(表达式)
语句;
if(表达式)
语句1;
else
语句2;
//多分支
if(表达式1)
语句1;
else if(表达式2)
语句2;
else if(表达式3)
语句3;
else
语句4;
//if else后面控制多条语句时,可以带上{},否则默认执行一条语句
误区:
(1)表达式两边运算符同时写的问题
age=60>18为真,所以左边为1,此时就是1<26为真,打印出青年。此方法编写有误。
正确写法:
else if (age >= 18 && age < 26)
(2)else匹配问题
我们错误的认为结果为haha,但真正的答案确实空,为什么?
乍一看else和第一个if语句对齐,但else只和最近的if语句匹配,编译器默认将代码自动对齐,如下图,因此返回为空。
反思:平时代码书写要规范;可以使用{ },避免引起误会。
书籍:《高质量c/c++编程》
练习1:打印出1-100之间的奇数
int main()
{
int i = 0;
for(i = 1;i <= 100;i++)
{
if(i % 2 == 1)
printf("%d ",i);
}
//for (i = 1;i <=100; i+=2)//第一位是1,加2,一次可以产出1,3,5,7,9这样的数字
return 0;
}
练习2:输入三个数按照从大到小的顺序输出
int main()
{
int a = 0;
int b = 0;
int c = 0;
scanf("%d %d %d",&a,&b,&c);
if (a < b)
{
int t = a;
a = b;
b = t;
}
if (b < c)
{
int t = b;
b = c;
c = t;
}
if (a < c)
{
int t = a;
a = c;
c = t;
}
printf("%d %d %d\n",a,b,c);
return 0;
}
//scanf在搭配%d等使用时切记不要带\n等
//int t只能在if语句中呈现,当在scanf上面出现时,t在进入下一条循环中会携带有上一条语句中的值
//打印时当改变a,b,c的顺序时,if循环语句中应稍作调整
练习3:找出两个数中的最大公约数
int main()
{
int m = 0;
int n = 0;
scanf("%d %d",&m,&n);
//找出两个数中最小的,然后递减
int max = m > n ? m : n;
while(1)//目的是让其 一直循环
{
if (m % max == 0 && n % max == 0)
{
printf("最大公约数为:%d\n",max);
break;
}
max--;
}
return 0;
}
方法二:辗转相除法
int main()
{
int m = 0;
int n = 0;
int t = 0;
scanf("%d %d",&m,&n);
while(t = m % n)//当余数为0时,循环截止,最大公约数为n
{
m = n;
n = t;
}
printf("最大公约数为%d\n",n);
return 0;
}
练习4:打印出1000-2000中的闰年
判断是否为闰年?
1.能否被4整除,且不能被100整除
2.能被400整除
方法一:
int main()
{
int year = 0;
for (year = 1000;year <= 2000;year++)
{
if (year % 4 == 0)
{
if (year % 100 != 0)
{
printf("%d ",year);
}
}
if (year % 400 == 0)
{
printf("%d ",year);
}
}
return 0;
}
方法二:
int main()
{
int year = 0;
for (year = 1000;year <= 2000;year++)
{
if (((year % 4 == 0)&&(year % 100 != 0))||(year % 400 == 0))
{
printf("%d ",year);
}
}
return 0;
}
练习5:打印100-200之间的素数
方法一:
int main()
{
int n = 0;
for (n = 100;n <= 200;n++)
{
int i = 0;
for(i = 2;i < n;i++)
{
if(n % i == 0)
{
break;//若当中有数取模后为0,说明不是素数,跳出当前程序
}
}//----来到这
if(n == i)
{
printf("%d ",n);
}
}
return 0;
}
方法二:
还可以使用另一种思路,例如101,我们只需要去找2 - sqrt(101)之间的数字去尝试
//#include <math.h>
int main()
{
int n = 0;
for (n = 100;n <= 200;n++)
{
int i = 0;
int flag = 1;
for(i = 2;i < n;i++)
//for(i = 2;i <= sqrt (n);i++)
{
if(n % i == 0)
{
flag = 0;
break;
}
}
if(flag == 1)
{
printf("%d ",n);
}
}
return 0;
}
12.2 switch语句
易错点:switch(m) m必须为整型
Int char long均为整型,而float 、double等不是整型。
#include <stdio.h>
int main()
{
int day = 0;
scanf("%d",&day);//格式化输入,不能随意加\n或者空格
switch (day)
{
default://无先后顺序区分
printf("error");
break;
case 1:
case 2:
case 3:
case 4:
case 5:
printf("工作日");
break;
case 6:
case 7:
printf("休息");
break;
}
return 0;
}
例题:
结果:m=5 n=3
误区1:在没有break的情况下,case 1,2,3是要依次走下去的;
误区2:当n走在嵌套的switch语句下的case 2时,case 2的break只是跳出嵌套的switch语句,并非整个程序(当在switch语句中),case还是要继续执行。
13.循环语句
while do…while for
13.1 while语句
while(表达式)
循环语句;
使用while之后,便会一直打印
break 和 continue
在while循环中,break用于永久的终止循环
并没有结束,陷入死循环。
在while循环中,continue用于跳过本次循环,跳过continue后面的代码(打印,i++),直接去while 的判断部分,看是否进行下一次循环。
13.1.1 getchar
在读取时,遇到一个错误或者是文件结束,返回EOF。正确读取时,返回的是字符的ascii值。ascii值是整型;其二,getchar返回时有可能是EOF,EOF本质上是-1,-1也是整数,getchar返回类型为int类型。
EOF 文件结束标志 本质上是-1
int main()
{
int ch = getchar();//获取字符
//printf("%c\n",ch);
putchar(ch);//Putchar打印字符,与上一条语句有异曲同工之妙
return 0;
}
或者
int main()
{
int ch = 0;
//ctrl+z -- getchar 读取结束
while ((ch=getchar()) != EOF)
putchar(ch);
return 0;
}
使用场景
场景1:在输入123以后,后面的确认密码直接全部输出。
分析:在输入123以后敲入回车。此时编译器在读取时读取到的是123\n,此时scanf只是把123拿走,\n还留在缓冲区,因此getchar默认为是我们的输入,此时打印出输入失败。
场景2:
在场景1的基础上进行改进,只需要将\n解决就可以了。因此在输入密码之后使用getchar()
此时看似成功,但在实际情况中,我们输入的密码还可能存在字母,符号等情况
例如当我们输入123 asds时,同样出现了场景1中的问题,后面的确认密码一起出现。
分析:在输入123 asds后,电脑读取到的信息是123 asds\n,scanf同样只带走123,而getchar只能带走一个字符,缓冲区中还存在很多字符,因此电脑默认我们已经输入ch的值,直接反馈出输入失败。
场景3:
对于上述 的情况进行改进,由于我们在输入密码之后要回车,在读取时默认为\n,因此我们只需要将\n作为密码输入结束的标志。
13.2 for语句
初始化 判断 调整
for(表达式1;表达式2;表达式3)
循环语句;
break、continue在for循环中
//break
int main()
{
int i = 0;
for(i = 1;i <= 10;i++)
{
if (i == 5)
break;
printf("%d ",i);
}
return 0;
}
//1 2 3 4
结果毫无疑问,同样break在for循环中,永久终止循环。
//continue
int main()
{
int i = 0;
for(i = 1;i <= 10;i++)
{
if (i == 5)
continue;
printf("%d ",i);
}
return 0;
}
//1 2 3 4 6 7 8 9 10
分析:在for循环中,先执行i=1,随后执行i <=10 ,如果小于,执行打印,打印完以后进行i++,而i=5时,在执行continue之后,其身后的语句不进行执行,不打印5,直接跳到下一步,下一步就是i++
此时for循环三个部分省略,会不段打印。
分析:i=0时,运行j=0,j<3,打印,J++重复3词,随后i++,i=1,i=1<2,同样执行j的for循环,总共打印六次
分析:关键在于i=0时,j由1加加到3,随后i++,此时i=1<2,再去执行j的for循环时,j不再是初始的0,而是上一次留下的3,因此不再打印。
int main()
{
int i = 0;
int k = 0;
for(i = 0,k = 0;k = 0;k++,++i)//k++ ++i无本质区别,都是一样的
k++;
return 0;
}
问:循环了多少次?
答案:0
for循环中的判断语句k = 0 此时=并非判断,而是赋值,此时k = 0为假,因此循环0次。
练习:n!
int main()
{
int n = 0;
int w = 1;
int i = 0;
scanf("%d",&n);
for(i = n;i>0;i--)
{
w *= i;//等同于w = w * i
}
printf("%d\n",w);
return 0;
}
练习:输入一个数n,计算n!+...+1!
int main()
{
int w = 1;
int i = 0;
int j = 0;
int sum = 0;
int n = 0;
scanf("%d",&n);
for(j = 1;j <= n;j++)
{
for(i = 1;i <= j;i++)
{
w = w * i;
}
sum += w;
}
printf("%d\n",sum);
return 0;
}
当n=3时
易错点:并没有给w重新赋值,在执行j=3时,还保留上一次j=2时的值w=2
j=1 i=1 w=1*1 sum=0+1=1
j=2 i=1 w=1*1
i=2 w=1*2=2 sum=1+2*1
j=3 i=1 w=2*1 //此时w就有一个初值,为2
i=2 w=2*2*1
i=3 w=2*3*2*1=12 sum=15(错误答案)
修正:
int main()
{
int w = 1;
int i = 0;
int j = 0;
int sum = 0;
int n = 0;
scanf("%d",&n);
for(j = 1;j <= n;j++)
{
w = 1; //重点!!!计算n的阶乘之前,把w初始为1
for(i = 1;i <= j;i++)
{
w = w * i;
}
sum += w;
}
printf("%d\n",sum);
return 0;
}
//sum=9
方法二:
int main()
{
int w = 1;
int i = 0;
int sum = 0;
int n = 0;
scanf("%d",&n);
for(i = 1;i <= n;i++)
{
w = w * i;
sum += w;
}
printf("%d\n",sum);
return 0;
}
13.3 do…while
do
循环语句;
while(表达式);//先执行再判断。循环体至少要执行一次
- break
- Continue
依旧陷入死循环
1.有序数组中查找具体某个数字,二分法
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int k = 7;
int se = sizeof(arr) / sizeof(arr[0]);//求得数组个数
int left = 0;
int right = se - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
printf("下标为:%d\n",mid);
break;
}
}
if (left > right)
{
printf("没有找到\n");
}
return 0;
}
2.为展现出
welcome to China
w##############a
we############na
...
welcome to China 的效果,编写代码
#include <stdio.h>
#include <string.h>
#include <windows.h>
int main()
{
char arr1[] = "welcome to China";
char arr2[] = "################";
int left = 0;
int right = strlen(arr1)-1;//使用strlen需要string.h头文件
while(left <= right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n",arr2);
//使用Sleep 需要调用windows.h头文件
Sleep(1000);//睡眠时间1000ms=1s Sleep 首字母大写
system("cls");//清空屏幕
left++;
right--;
}
//printf("%s\n",arr2);//为使显示效果更好
return 0;
}
3.编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录成,如果三次均输入错误,则退出程序。
#include <stdio.h>
#include <string.h>//使用stramp时需要调用string.h头文件
int main()
{
int i = 0;
char password[20] = {0};
for (i = 1;i <=3;i++)
{
printf("请输入密码:");
scanf("%s",password);
//if(password == "123456") //比较两字符串首字符的地址,并没有比内容
//error!!! 两个字符串比较不能用 == 只能用strcmp
if (strcmp(password,"123456") == 0)
{
printf("登陆成功\n");
break;
}
else
{
printf("密码错误,请重新输入\n");
}
}
if (i == 3)
{
printf("登录次数已超过三次,退出程序");
}
return 0;
}
13.4 goto语句
goto语句使用场景:
for(...)
{
for(...)
{
for(...)
{
if(disaster)
{
goto error;
}
}
}
}
error:
if(...)
直接跳出整个循环,比break更快捷
在输入我是猪之后,电脑取消关机,否则电脑在60秒之后关机
#include <string.h> //使用strcmp时调用头文件
#include <stdlib.h> //使用system时使用头文件
int main()
{
char arr[20] = {0};
//system - 执行系统命令
system("shutdown -s -t 60");//调用shutdown,-s 关机 -t 设置在多少秒之后关机
again:
printf("请输入:我是猪,否则电脑将在60s以后关机\n");
scanf("%s",arr);
if (strcmp(arr,"我是猪") == 0)
{
system("shutdown -a");
}
else
{
goto again;//给一个机会,重新输入
}
return 0;
}