day06 while 循环和 do 循环 、 缓冲区 、 一维数
每日英语:
index:索引号,偏移量
array:数组
size:大小
回顾:
1. 数据类型转换
隐式转换:小转大,有转无,整转浮
强制转换:目标数据类型变量 = (目标数据类型)源类型变量
推荐使用:提高代码的可读性
2. C语言的三大结构
顺序结构,分支结构,循环结构
2.1 顺序结构
从上到下执行
2.2 分支结构
功能:多选一
两类:条件分支和开关分支
条件分支:
1. if
2. if...else
3. if else if ... else if
4. if ... else if ... else if ... else
开关分支:
switch...case
2.3 循环结构
功能:让一组数据重复多次执行
三类:for循环,while循环,do…while循环
for 循环:
for(1; 2; 3) {
4;
}
七种形式
作业:求1+…+100的和 打印10x20的*方阵
/*案例演示*/
#include <stdio.h>
int main(void) {
//1+2+...+100
int i;
int sum = 0;
for(i = 1; i <= 100; i++) {
sum += i;
}
printf("sum = %d\n", sum);
// 20*10方阵
for(int m = 0; m < 10; m++){
for(int n = 0; n < 20; n++) {
printf("*");
}
printf("\n");
}
return 0;
}
2.4 循环结构之while循环
2.4.1 语法格式
while(循环控制表达式) {
循环语句;
}
执行流程:
第一步骤:首先执行循环控制表达式
如果它的执行结果为真,那么就执行循环语句
如果它的执行结果为假,while循环直接结束
第二步骤:如果循环语句执行完毕,再接着执行第一步骤
例如:
int i = 0;
while(i < 5) {
printf("i = %d\n", i);
i++;
}
while死循环:
while(1) {
printf("hello world");
}
2.4.2 注意
如果while循环语句只有一条,{}可以不用加(由衷建议最好加上去)
参考代码:while.c
/*while演示*/
#include <stdio.h>
int main(void) {
int i = 0;
while(i < 5) {
printf("i = %d\n", i);
i++;
}
/*
// while循环实现1+2+...+100
int j = 1;
int sum = 0;
while(j < 101) {
sum += j;
j ++;
}
printf("sum = %d\n", sum);
*/
printf("\n");
i = 0;
while(++i < 5) { // 先计算i++,后赋值
printf("i = %d\n", i);
}
printf("\n");
i = 0;
while(i++ < 5) { // 先看i=0,表达式为真,后计算i+1=0+1=1
printf("i = %d\n", i);
}
return 0;
}
结果:
i = 0
i = 1
i = 2
i = 3
i = 4
i = 1
i = 2
i = 3
i = 4
i = 1
i = 2
i = 3
i = 4
i = 5
2.5 循环结构之do-while循环
2.5.1 语法格式
do {
循环语句;
}while(循环控制表达式); //切记此分号不可省略
执行流程:
第一步骤:先执行循环语句; //第一次没有做判断直接运行
第二步骤:然后执行循环控制表达式
如果为真,继续执行循环语句,后续依次重复,知道循环控制表达式为假,循环立即结束
**切记:**do…while 循环语句至少执行一次!
例如:
do {
printf("hello,world\n");
}while(0);
结果:打印一次hello world
例如:
int i = 0;
do {
printf("i = %d\n", i);
i++;
}while(i < 5);
参考代码:dowhile.c
/*do...while演示*/
#include <stdio.h>
int main(void) {
do {
printf("hello,world\n");
}while(0);
int i = 0;
do {
printf("i = %d\n",i);
//i++;
}while(++i < 5);
int m = 0;
int sum = 0;
do {
sum += m;
m++;
}while(m < 101);
printf("sum = %d\n", sum);
return 0;
}
结果:
hello,world
i = 0
i = 1
i = 2
i = 3
i = 4
sum = 5050
3. goto语句
3.1 作用
可以让cpu跳转到任何一条语句去执行
应用:linux内核操作系统代码大量使用goto语句
3.2 语法:
goto 标签名; //就是要跳转运行的地方
标签命名符合标识符的命名规则
3.3 两种使用形式:
3.3.1 形式1:
标签吗:
语句
...
goto 标签名;
例如:
label:
printf("1.\n");
goto label;
printf("2.\n");
**执行流程:**先打印1,然后执行goto label,跳转到label标签继续执行,继续打印1,然后执行goto
继续执行goto label,最后形成死循环
3.3.2 形式2:
goto 标签名;
...
标签名:
语句;
例如:
goto label;
printf("2.\n");
label:
printf("1.\n"); // 直接跳转到label标签,打印1
参考代码:goto.c
/*goto语句演示*/
#include <stdio.h>
int main(void) {
// 形式1标签在goto语句前面
int a = 1;
label:
printf("1.\n");
if(1==a) {
a = 0; // 目的是退出goto形成的死循环
goto label;
}
printf("2.\n");
//形式2:标签在goto语句的后面
goto label2;
printf("3.\n");
label2:
printf("4.\n");
// 利用goto实现1+2+...+100
int n = 1;
int sum = 0;
next:
if(n <= 100) {
sum += n;
n++;
goto next;
}
printf("sum = %d\n", sum);
return 0;
}
结果:
1.
1.
2.
4.
sum = 5050
3.4 linux内核源码中goto语句经典使用模板
需求:运行一个程序,程序要分配三块内存,只要有一块内存分配失败,程序立刻结束,并且把之前分配成功的内存释放桂怀 给操作系统
参考代码:goto2.c
/*goto经典模板演示*/
#include <stdio.h>
int main(void) {
int ret = 0; // 0:表示分配内存成功,1:表示分配失败
printf("分配第一块内存\n");
if(1==ret) {
goto free1;
}
printf("分配第二块内存\n");
ret = 1; // 模拟第二块内存分配失败
if(1==ret) {
goto free2;
}
printf("分配第三块内存\n");
//ret = 1; 模拟第三块内存分配失败
if(1==ret) {
goto free3;
}
printf("三块内存分配成功\n");
return 0;
free3:
printf("释放第二块内存\n");
free2:
printf("释放第一块内存\n");
free1:
return ret; // 给操作系统返回错误
}c
4. 空语句
4.1 语法:
仅仅包含一个;
例如:
; //空语句,起到一个延时的作用,因为Cpu执行空语句,也需要消耗时间
4.2 应用场景
用于实现一个空循环
例如:
for(;;); // 空死循环,让CPU跑到这里别再往下运行了,并且CPU在这里玩命的空转
int i = 100000;
for(; i >= 0; i--); // 空循环100000次,每次CPU会消耗时间,此代码起到延时作用
也就是让CPU在这里稍微等一等,这个延时时间不精确
如果要的到一个精确的延时时间,后续课程会讲
4.3 不要做一下可悲的事情
int i;
for(i = 0; i < 5; i++); { // 不小心加了一个分号,结果是有效循环变空循环
printf("i = %d\n", i)
}
int i = 0;
while(i < 2); { //有效循环变空循环
printf("i = %d\n", i);
}
切记:在while / for 循环中的圆括号后面误写了;就会意外的将有效循环变空循环
参考代码:empty.c
/*空语句演示*/
#include <stdio.h>
int main(void) {
/*
// 空死循环
printf("1\n");
for(;;); //让CPU在这里玩命空转,再也不向下运行
printf("2\n");
*/
// 有效空循环,起到延时作用
int i = 900000000;
printf("1\n");
for(; i >= 0; i--);
printf("2\n");
//注意:正常代码
printf("3\n");
unsigned long a = 900000000;
for(; a > 0; a--);
printf("4\n");
//注意:死循环代码
printf("3\n");
unsigned long a = 900000000;
for(; a >= 0; a--); // 无符号数减到0再减1变负值,又回滚到0了,而判断是基于:m>=0,永远成立
printf("4\n");
return 0;
}
// 可悲的事情 for格式的
int c = 0;
for(; c < 5; c++); // 有效的循环变为了空循环
printf("c = %d\n", c); //c = 5
//while格式的
int d = 0;
while(d < 5); {
printf("d = %d\n", d); // 不打印
}
第七课:数组
1. 明确
计算机程序最终都是玩内存,万内存前提是先分配内存,目前掌握的分配内存的方式就一种:定义变量
例如:
int a;
int b;
int c;
....
int zzzzz..;
如果要大量数据类型一致的变量,按照以上编码实现,代码极其冗余和繁琐
问:能够有一种方法来解决以上问题呢?既可以分配大量内存也可以保证数据类型一致,关键还可以让代码变得简单呢?
答:有,这就是C语言第二种分配内存方法:数组
2. 数组特点
- 也是一种分配内存你的方法
- 并且分配的内存数据类型是一致的,
- 可以大量分配内存
- 并且分配的内存是连续的
3.定义数组(数组分配内存)的语法格式
元素数据类型 数组名[数组长度(又称数组元素个数)] = {初始化列表,如果有多个,用逗号分开};
例如:
int a[5] = {1, 2, 3, 4, 5};
语义:连续分配5个元素的内存空间,每个元素的数据类型都是int类型,每个元素占4字节内存空间
所以最终连续分配了20个字节内存空间,并且每个元素的值,也就是内存存储的值,分别是1, 2, 3, 4, 5
类似:int a = 1; int b = 2; int c = 3; int d = 4;
此时此刻脑子立马浮现一个内存分分布示意图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IZRO6ljG-1619017973440)(E:\C语言\c-study-notes\tarena\img\数组.png)]
4. 玩数组的注意事项
-
数组分配内存是连续的
-
数组下标就是数组中元素的编号(专业术语叫索引号,index),下标从0开始
例如:
int a[5] = {1, 2, 3, 4, 5};
元素数值 下标 叫法 1 0 第0个元素 2 1 第1个元素 3 2 第2个元素 -
数组长度又称数组元素个数,而不是整个数组占用内存的大小
例如:
int a[5]; // 数组长度 = 数组元素个数 = 5,而数组分配内存大小是20个字节
-
数组名就是整个数组分配的内存的首地址,它等于数组第0个元素的首地址
-
数组中元素的访问是通过运算符"[]"结合下标来进行访问的
格式:数组元素值 = 数组名[数组元素对应的下标];
例如: int a[5] = {1, 2, 3, 4, 5}
第0个元素值 = 1 = a[0];
第1个元素值 = 2 = a[1];
第2个元素值 = 3 = a[2];
**结论:**以第三个元素为例:得到一下规律:
打印第三个元素值: printf("%d\n", a[3]);
修改第3个元素的值为250(修改对应的内存值):a[3] = 250; // 结果是a[3]由原来的4变成250
获取第三个元素值对应的内存首地址:printf("%p\n", &a[3]);
通过第三个元素的首地址来获取对应的数值: printf("%d\n", ***&a[3]) // ***&a[3]最终等价于a[3]
-
切记切记:千万注意数组的越界访问(笔试题必考)
例如:
int a[5] = {1, 2, 3, 4, 5} // 目前操作系统给你连续分配嘞20个字节内存 printf("%d\n", a[4]); // 打印第四个元素值,合法 printf("%d\n", a[5]); // 打印不存在的第五个数组,也就是查看访问第5个元素的内存 // 显然就是内存越界访问,但是获取的值乱七八糟的 a[5] = 250; // 越界访问,关键还对无权访问的内存进行非法修改,操作系统只能干丢你的程序 也就是程序立马崩溃
参考代码:array.c
/*数组演示*/
#include <stdio.h>
int main(void) {
int a[6] = {1, 2, 3, 4, 5, 6};
// 正向打印每个元素值
for(int i = 0; i < 5; i++) {
printf("a[%d] = %d\n", i, a[i]);
}
// 将每个元素扩大10倍
for(int i = 0; i < 5; i++) {
a[i] *= 10;
}
// 逆向打印数组的值
for(int i = 4; i >= 0; i--) {
printf("a[%d] = %d\n", i, a[i]);
}
// 求内存地址
for(int i = 0; i < 5; i++) {
printf("a[%d]的内存地址是%p\n", i, &a[i]);
}
// 通过首地址获取对应的值
for(int i = 0; i < 5; i++) {
printf("%d\n", *&a[i]);
}
// 通过数组元素首地址修改其值
*&a[3] *= 10;
printf("第三个元素值是%d\n", *&a[3]);
*&a[1] *= 10;
printf("第二个元素值是%d\n", *&a[1]);
*&a[5] /= 2;
printf("第五个元素值的%d\n", *&a[5]);
for(int i = 0; i < 6; i++) {
printf("a[%d] = %d\n", i, a[i]);
}
//越界访问
printf("a[6] = %d\n", a[6]); // 越界访问获取一个乱七八糟的值
a[6] = 250; // 越界访问,修改无权访问的内存,程序直接崩溃
return 0;
}
结果:
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
a[4] = 50
a[3] = 40
a[2] = 30
a[1] = 20
a[0] = 10
a[0]的内存地址是0xbf96b2a4
a[1]的内存地址是0xbf96b2a8
a[2]的内存地址是0xbf96b2ac
a[3]的内存地址是0xbf96b2b0
a[4]的内存地址是0xbf96b2b4
10
20
30
40
50
第三个元素值是400
第二个元素值是200
第五个元素值的3
a[0] = 10
a[1] = 200
a[2] = 30
a[3] = 400
a[4] = 50
a[5] = 3
a[6] = -319214336
*** stack smashing detected ***: ./array terminated
已放弃 (核心已转储)
5. 数组定义的形式:
形式1:
int a[5] = {1, 2, 3, 4, 5};
形式2:
int a[5] = {1, 2, 3}; // a[0]=1,a[1]=2,a[2]=3,其余元素都是0,即a[3]=a[4]=0
形式3:
int a[5] = {0}; // 全0
形式4:
int a[5] = {}; //全是0
形式5
int a[5]; // 只定义不初始化,结果每个元素值都是乱七八糟的随机数,很危险
形式6:
int a[] = {1, 2, 3, 4, 5}; //gcc编译器自动计算出元素个数位5
形式7:
int a[5] = {1, 2, 3, 4, 5, 6, 7, 8}; // gcc直接将无效的元素忽略,6,7,8不要
错误形式:
int a[]; // gcc迷茫了,立马报错
参考代码:array1.c
/*定义数字形式演示*/
#include <stdio.h>
int main(void) {
//形式1:只定义不初始化
int a[5];
for(int i = 0; i <5; i++) {
printf("a[%d] = %d\n", i, a[i]);
}
printf("我是傻逼\n");
//形式2
int b[5] = {1, 2, 3};
for(int i = 0; i <5; i++) {
printf("b[%d] = %d\n", i, b[i]);
}
printf("我是傻逼\n");
//形式3
int c[5] = {0};
for(int i = 0; i <5; i++) {
printf("c[%d] = %d\n", i, c[i]);
}
printf("我是傻逼\n");
// 形式4
int d[5] = {};
for(int i = 0; i <5; i++) {
printf("d[%d] = %d\n", i, d[i]);
}
printf("我是傻逼");
int e[] = {1, 2, 3, 4, 5};
for(int i = 0; i <5; i++) {
printf("e[%d] = %d\n", i, e[i]);
}
printf("我是傻逼");
int f[5] = {1, 2, 3, 4, 5, 6, 7, 8};
for(int i = 0; i <5; i++) { // 如果写成i<8,就成越界访问了
printf("f[%d] = %d\n", i, f[i]);
}
printf("我是傻逼");
int g[];
for(int i = 0; i < 5; i++) {
printf("g[%d] = %d\n", i, g[i]); //报错error: array size missing in ‘g’
}
return 0;
}
结果:
a[0] = 0
a[1] = -1216923024
a[2] = -1216812024
a[3] = -1216815872
a[4] = -1079563492
我是傻逼
b[0] = 1
b[1] = 2
b[2] = 3
b[3] = 0
b[4] = 0
我是傻逼
c[0] = 0
c[1] = 0
c[2] = 0
c[3] = 0
c[4] = 0
我是傻逼
d[0] = 0
d[1] = 0
d[2] = 0
d[3] = 0
d[4] = 0
我是傻逼e[0] = 1
e[1] = 2
e[2] = 3
e[3] = 4
e[4] = 5
我是傻逼f[0] = 1
f[1] = 2
f[2] = 3
f[3] = 4
f[4] = 5
我是傻逼
6. 切记求数组元素个数的公式
元素个数 = sizeof(数组名) / sizeof(数组第0个元素)
例如:
int a[5] = {1, 2, 3, 4, 5};得到一下公式:
数组首地址 = 数组名a = 数组第0个元素首地址=&a[0]
整个数组占用的内存大小 = sizeof(a)=20个字节
第0个元素占用的内存大小=sizeof(a[0]) = 4字节
元素个数=szieof(a) / sizeof(a[0])=20/4=5个元素
如果两个元素地址相减得到的是两个元素之间相差的元素个数,而实际的地址相差元素个数*4
例如:
&a[4] - &a[1] = 3(第4个元素和第1个元素之间相差3个元素,实际地址相差12个字节)
参考代码:array2.c
/*数组公式演示*/
#include <stdio.h>
int main(void) {
int a[5] = {1, 2, 3, 4, 5};
printf("数组a的地址是%p\n", a);
int i;
for(i = 0; i <5; i++) {
printf("第%d个元素的地址是%p\n", i, &a[i]);
}
printf("数组占用的内存大小是%d\n", sizeof(a));
printf("第4个元素地址减去第1个元素地址=&a[4] - &a[1] = %d\n", &a[4] - &a[1]);
printf("数组第0个元素占用的内存大小是%d\n", sizeof(a[0]));
printf("数组元素的个数%d\n", sizeof(a) / sizeof(a[0]));
return 0;
}
结果:
数组a的地址是0xbf7ff898
第0个元素的地址是0xbf7ff898
第1个元素的地址是0xbf7ff89c
第2个元素的地址是0xbf7ff8a0
第3个元素的地址是0xbf7ff8a4
第4个元素的地址是0xbf7ff8a8
数组占用的内存大小是20
第4个元素地址减去第1个元素地址=&a[4] - &a[1] = 3
数组第0个元素占用的内存大小是4
数组元素的个数5
7. 人生认识的第二个标准库函数:scanf
函数功能:从键盘上捕获用户输入的数字,然后将这些数值保存到变量中
使用语法:scanf(“占位符”, 变量的地址);
例如:
int a = 0;
scanf("%d", &a); //只要程序运行到这里,此时程序停止不前,等待用户从键盘上输入数值
//一旦输入完毕按回车键,此函数执行完毕,并且将输入的数值保存到变量a中,此时程序继续向下执行
int a, b, c;
scanf("%d%d%d", &a, &b, &c); //此时用户从键盘输入的方式是:100 空格 200 空格 300 空格 最后回车
// 结果是: a = 100, b = 200, c = 300
// 如果要输入多个数值,中间用空隔分开
参考代码:scanf.c
/*scanf函数演示*/
#include <stdio.h>
int main(void) {
int a = 0;
printf("请输入一个数字:");
scanf("%d", &a); // 从键盘捕获输入的数字保存到变量a中
printf("a = %d\n", a);
int b ,c, d;
printf("请输入三个数字(中间用空格分开):");
scanf("%d %d %d", &b, &c, &d);
printf("b = %d, c = %d, d = %d\n", b, c, d);
return 0;
}
8. 变长数组(了解)
8.1 定义
数组元素个数不定
例如:
int a[5]; //定长数组,长度固定
int len;
scanf("%d", &len);
int a(len); // 变长数组
参考代码:array3.c
/*变长数字演示*/
#include <stdio.h>
int main(void) {
int len;
printf("请输入数组长度:");
scanf("%d", &len);
int a[len]; //定义边长数组
// 给元素赋值
for(int i = 0; i < len; i++) {
a[i] = i + 1;
}
//打印元素值
for(int i = 0; i <len; i++) {
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
9. 多维数组
重点研究到二维数组就可以了,之前的数组又称为一维数组
9.1 定义:
二维数组中每个元素是一个一维数组,也就是二维数组由一维数字组成
二维数组本质还是一维数字,只是形式上将一维数字再次进了分组而已
通过分组得到了所谓的二维数组
9.2 定义二维数组语法格式
也就是用二维数组分配内存
元素数据类型 二维数组名[二维数组长度] [一维数组长度] = {初始化列表};
例如:5是二维数组的个数5个,3是每个二维数组里面一维数组的个数是3个
int a[5][3] = {{1,2,3},{4,5,6},{7,8,9},{10,11,12},{13,14,15}};
语义:
-
a代表一个二维数组,包含5个元素,每个元素又是一个一维数组,而每个一维数字又包含3个数字,每个数字的数据类型是int
结论:二维数组名a就是二维数组的首地址
-
a[0]代表二维数组a的第0个元素,也就是a[0]就是一个一维数组
并且它是二维数组a中第0个元素的首地址
也就是a = a[0]
a[1]就是二维数组中第1个元素的首地址
a[2]就是二维数组中第2个元素的首地址
-
a[0] [0]代表二维数组中第0个元素(一维数组)的第0个元素值
结论:a[i] [j]代表二位数中第i个一维数组元素的第j个元素值
关注公众号,获取更多精彩内容