一.零碎知识回顾
1.数组下标从0开始,比如定义 int arr[10]有十个int类型的元素,访问是从arr[0]到arr[9];
2.数组完全初始化与非完全初始化,完全初始化是int arr[3]={0,1,2};
非完全初始化是int arr[3]={1},则其剩下两个元素没有初始化,默认arr[1]和arr[2]存的是0;
如果是 char类型的非完全初始化,则默认是字符'\0';
3.sizeof()是操作符,计算变量所占内存空间的大小,单位是字节。
4.strlen是库函数,是计算字符串长度的,统计的是字符串中‘\0'之前出现的字符个数
5.例如b=a++,先赋值,后自增;b=++a,先自增,后赋值。自减同理
6.static用法:
1.让变量放在栈上,放在静态区
2.让全局变量只能在本文件中使用
二.EOF用法详解:
EOF(End-of-File)是C语言中表示文件结束的标志符号。它是一个预定义的常量,在标准库头文件 <stdio.h>
中定义为整数常量。
EOF是一个整数类型的宏定义,扩展为负数常量表达式(通常为-1)。它被头文件中的好几个函数作为返回值使用,来表明已经到达文件尾或者示意一些其他的错误条件。它也被作为一个值来表示无效的字符。
VS中定义为#define EOF (-1)
一般我们见到EOF是在循环中配合scanf函数或者是getchar函数使用的
2.1
scanf函数:scanf函数是有返回值的。
一般来说,它的返回值是成功读取的元素个数。但当遭遇读取失败时,它的返回值便是 -1 (也就是它自己返回一个EOF) 。
而若是一个元素都还没成功读入的时候就遇到了读取失败或EOF,那它直接就会返回-1,不管后面再输入了什么。
scanf函数一般不会读取空格符和换行符
2.2
getchar函数
getchar函数会读取所有字符,包括空格符和换行符。
一般来说,它的返回值是成功读取的元素个数。但当遭遇读取失败时,它的返回值便是 -1 (也就是它自己返回一个EOF) 。
所以while循环读的时候,我们也能看到换了行的
scanf和getchar函数如何知道我这次输入完了?
不管是scanf函数还是getchar函数其实都是在按下回车后对输入缓存区的一次清理,scanf可能会有残余,残余可能会影响下一次读取判断
在windows系统下ctrl+z为结束标志,在linux系统下ctrl+d为结束标志
当系统在检测缓冲区中是否含有流结束标志时,有两种检测方式:阻塞式和非阻塞式
阻塞式:指的是只有在回车键按下之后,才会对缓冲区中是否含有ctrl+z组合键进行检查。需要注意的是,当缓冲区中含有可读数据时,ctrl+z就不是结束标志了(上面已经说过了)。我们需要明白,ctrl+z产生的并不是一个普通的ASCⅡ码值,也就是说,它不会跟其他从键盘上输入的字符一样,它是不能够存放在输入缓冲区中的!!!
非阻塞式:指的是一旦按下ctrl+d之后立即响应。如果之前没有输入字符,那么ctrld就是流结束标志。如果之前已经输入了字符,那么ctrld此时就相当于回车不再是流结束标志,并且具有回车的功能(将输入字符送入缓冲区),并且这个回车自己也会进入缓冲区!!!
当然了windows系统一般采用阻塞式检查(ctrl+z),linux系统一般采用非阻塞式检查(ctrl+d)
如何终止此次循环,或者为什么最后一个字符出现乱码?
当我们输入一行字符按下回车就会将他们送到输入缓冲区中进行存储,系统此时会进行检测,看缓冲区中是否含有可读的数据或者ctrlz这样的结束标志。所以,当输入的^Z前面有其它可读的字符时,系统检测到前面的可读数据会认为该缓冲区中的数据可读,因此就会出现上面提到的”不知名符号“。
简单的说就是如果^Z字符跟着普通的数据后面,它就被当成字符读入,存储为ascall码对应的字符;
如果是单独一个^Z系统就会识别出它是文件结束标识符,读的就是EOF,这个宏定义的字符。所以就返回-1了
EOF的作用也可以总结为:当终端有字符输入时,Ctrl+Z产生的EOF相当于结束本行的输入,将引起getchar()新一轮的输入;当终端没有字符输入或者可以说当getchar()读取新的一次输入时,输入Ctrl+Z,此时产生的EOF相当于文件结束符,程序将结束getchar()的执行。
EOF是一个特殊的标记,用于指示输入流的结束。在C语言的标准库中,EOF通常被定义为-1,这个值被用来表示在输入过程中没有更多的数据可以读取。由于ASCII码的值范围是0到127,不可能出现-1,因此使用-1作为EOF的值不会引起与任何有效ASCII字符的冲突。
所以如果输入ABCD+CTRL Z出现ABCD+乱码就知道为什么了。因为ASCII码里面没有CTRL Z,所以输出乱码或者乱图
总结来说,EOF的值为-1,这在C语言中用于指示输入流的结束,它不与任何有效的ASCII字符冲突,因此可以用作文件结束的标志。
三.指针大小问题
四.变长数组:
4.1变长数组的使用问题来源:
C99之可变长度数组(VLA)
C99:
1994年,由ANSI/ISO联合委员会开始修订C标准
1999年,1994年对C语言的修订引出了ISO 9899:1999的发表,它通常被称为C99
C11:
2011年,国际标准化组织(ISO)和国际电工委员会(IEC) 旗下的C语言标准委员会(ISO/IEC JTC1/SC22/WG14)正式发布了C11标准
c99标准中,新增了可变长度数组:Variable-length array (VLA);C11中VLA变为可选项,不是语言必备的特性。
在C99标准之前,数组的大小不能使用变量。
C99中引入了变长数组的概念,数组的大小是可以使用变量的,但是数组不能进行初始化
4.2变长数组的使用问题
变长数组中的“变”不是指可以修改已创建数组的大小,一旦创建了变长数组,它的大小则保持不变。这里的“变”指的是:在创建数组时,可以使用变量指定数组的长度。(普通数组只能用常量或常量表达式指定数组的长度)
1.变长数组VLA只能是局部变量数组
2.变长数组VLA不能在定义的时候进行初始化
3.变长数组VLA必须是自动存储类别,即不能使用extern或static存储类别说明符
4.变长数组VLA不等于动态数组,本质还是静态数组,也就是说,数组的长度在变量的整个生命周期中是不可变的
5.由于变长数组只能是局部变量,且必须是自动存储类别,因此变长数组分配在栈上
6.可变长数组对于多维数组也适用(如array[a][b] )
示例:
#include <stdio.h>
int main(void)
{
int a=0;
int b=0;
scanf("%d %d",&a,&b);
char array[a][b];
printf("sizeof(array)=%d\n",sizeof(array));
return 0;
}
根据上面关于数组的几项注意点,下面列出几种错误的示例:
int a=2;
int b=3;
char array1[a][b] = {1,2,3,4,5,6}; //错误,变长数组VLA不能在定义的时候进行初始化
char array2[a][b] = {{1,2,3},{4,5,6}}; //错误,变长数组VLA不能在定义的时候进行初始化
static char array3[a][b]; //错误,变长数组VLA必须是自动存储类别
另外如果变量是const int a=0;那么实际上char array[a]就不是一个变长数组了,也不用考虑这么多规则了
五.循环与选择流程的注意
注意辨别if,else if,if else 的配合使用,注意break和continue的使用规则,注意switch与case(必须使用常量值)
一般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;
}
for循环:初始化部分也不止可以初始化一个变量
六.小练习
6.1练习1.计算1!+2!+......+10!
#include<stdio.h>
int main()
{
int n=0;
scanf("%d",&n);
int ret=1;
int sum=0;
for(int i=1;i<=n;i++)
{
ret=ret*i;
sum+=ret;
}
printf("%d\n",sum);
return 0;
}
6.2练习2:在一个有序数组中查找具体的数字N
如果一个个找,时间复杂度O(N),所以可以用二分
后续我还会更新算法中的二分,以及二分的leetcode题目,以及二分如何控制左边和右边的范围。
int main()
{
//数组如果有n个元素,最坏的情况下要找n次
int arr[10] = { 1,2,3,4,5,6,17,18,19,110 };
int k = 7;//查找7
int left = 0;
int right = 9;
int flag = 0;
while (left<=right)
{
int mid = (left + right) / 2;//求出中间元素的下标
if (arr[mid] > k)
{
right = mid - 1;
}
else if (arr[mid] < k)
{
left = mid + 1;
}
else
{
printf("找到了,下标是:%d\n", mid);
flag = 1;
break;
}
}
if (flag == 0)
printf("找不到\n");
return 0;
}
此算法时间复杂度为O(logN);
6.3练习3:演示多个字符从两端移动,向中间汇聚:
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <stdlib.h>
int main()
{
//char arr[] = "bit";//[b i t \0]
// 0 1 2 3
//strlen(arr)-1;//找右
char arr1[] = "welcome to bit!!!!!";
char arr2[] = "*******************";
int left = 0;//左下标
int right = strlen(arr1)-1;
while (left<=right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
Sleep(1000);//Sleep函数是实现睡眠,单位是毫秒
system("cls");//system是一个库函数,可以执行系统命令,cls是清空屏幕的一个命令
left++;
right--;
}
printf("%s\n", arr2);
return 0;
}
6.4练习4:实现非常简易版的密码登录
#include <stdio.h>
int main()
{
//假设密码是:字符串“123456”
int i = 0;
char password[20] = {0};//字符数组,可以存放字符串
int flag = 0;
for (i = 0; i < 3; i++)
{
printf("请输入密码:>\n");
scanf("%s", password);//数组名本质上就是地址,所以不需要取地址
//
//判断两个字符串是否相等,要使用strcmp函数,不能直接使用==
//int ret = strcmp(password, "123456");
//如果第一个字符串小于第二个字符串,返回<0的数字
//如果第一个字符串大于第二个字符串,返回>0的数字
//如果第一个字符串等于第二个字符串,返回0
//
if (0 == strcmp(password,"123456"))
{
printf("登录成功\n");
flag = 1;
break;
}
else
{
printf("第%d次密码错误\n", i + 1);
}
}
if (flag == 0)
{
printf("三次密码均输入错误,退出程序\n");
}
return 0;
}
6.5练习5:猜数字游戏简单实现
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void menu()
{
printf("**************************\n");
printf("****** 1. play ******\n");
printf("****** 0. exit ******\n");
printf("**************************\n");
}
//rand函数是专门用来生成随机数的
//rand函数返回的是0~RAND_MAX(32767)之间的一个随机数
//#define RAND_MAX 0x7fff
//
//
//rand函数在使用之前要使用一个srand函数来设置随机数的生成器
//srand函数在程序中只要调用一次就可以了,不需要频繁调用
//
//
//时间戳
//C语言中,time函数会返回时间戳
//
//NULL - 空指针
//
void game()
{
int num = 0;
//1. 生成随机数
int ret = rand()%100+1;
//printf("%d\n", ret);
//2. 猜数字
while (1)
{
printf("请猜数字:>");
scanf("%d", &num);
if (num < ret)
{
printf("猜小了\n");
}
else if (num > ret)
{
printf("猜大了\n");
}
else
{
printf("恭喜你,猜对了\n");
break;
}
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
//打印菜单
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();//game函数中是猜数字的整个路基逻辑
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
6.6 goto语句与练习6
C语言中提供了可以随意滥用的 goto语句和标记跳转的标号。
从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。
但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过
程。
例如:一次跳出两层或多层循环。
多层循环这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。
goto语言真正适合的场景如下:
练习6.一个关机程序的实现
int main()
{
char input[20] = {0};
system("shutdown -s -t 60");
again:
printf("请注意你的电脑在1分内关机,如果输入:我是猪,去取消关机\n");
scanf("%s", input);
//判断
if (strcmp(input, "我是猪") == 0)
{
printf("小乖乖,这就取消关机\n");
system("shutdown -a");
}
else
{
goto again;
}
return 0;
}
6.7练习7:打印100-200内的素数
//素数(质数):只能被1 和 它本身整除的数字
int main()
{
int i = 0;
for (i = 101; i <= 200; i+=2)
{
//判断i是否为素数
//拿2~根号i之间的数字试除
int flag = 1;//假设i是素数
int j = 0;
for (j = 2; j <= (int)sqrt(i); j++)
{
if (i % j == 0)
{
flag = 0;
break;
}
}
if (flag == 1)
{
printf("%d ", i);
}
}
return 0;
}
6.8练习8:给两个数,求两个数的最大公约数(辗转相除法)
辗转相除法的原理:
a / b = q 余 r,除数b和余数r能被同一个数整除,那么被除数a也能被这个数整除。或者说,除数与余数的最大公约数,就是被除数与除数的最大公约数。即被除数与除数的最大公约数,就是除数与余数的最大公约数。
/int main()
//{
// int m = 0;
// int n = 0;
// //输入
// scanf("%d %d", &m, &n);//18 24
// int k = 0;
// while (k=m % n)
// {
// m = n;
// n = k;
// }
// printf("%d\n", n);
//
// return 0;
//}