学习目标
- 一维数组的声明和使用
- 二维数组的声明和使用
- 字符串的声明、赋值、比较和连接
- ASCII码和ctype.h中的字符函数
- 正确认识“++”、“+=”等能修改变量的运算符
- fgetc和getchar的使用方法
- 了解不同操作系统中换行符的表示
- 掌握fgets的使用方法并了解gets的“缓冲区溢出”漏洞
- 学会用常量表简化代码
一、数组
程序1: 输入5个数,逆序输出
#include<stdio.h> #define maxn 8 int a[maxn]; int main() { int x, n = 0; while(scanf("%d", &x) == 1){ a[n++] = x; } for(int j = n-1 ; j >= 0; j--) printf("%d ",a[j]); return 0; }
最后的^D 用来结束输入【即Ctrl +D】
# 在算法竞赛中,常常难以精确计算出需要的数组大小,数组一般会声明得稍 大一些。在空间够用的前提下,浪费一点不会有太大影响,所以即使是 输入5个数,保险起见完全可以定义数组大小为8
语句:a[n++]=x —— 做了两件事:首先赋值a[n]=x,然后执行n=n+1
# 对于变量n,n++和++n都会给n加1,但当它们用在一个表达式中时,行为有所差别:n++会使用加1前的值计算表达式【即n的原值】,而++n会使用加1后的值计算表达式【即n+1后的值】 ———— 谁在前面,就先用谁 【n++,n在前mian所以用n的原值,++n,++在前面,就将n先+1】
为什么要把a的定义放在main函 数的外面?
只有放在外面时,数组a的大小才可以开得很大;放在main函数内时,数组稍大就会异常退出
# 所以,比较大的数组应尽量声明在main函数外,否则程序可能无法运行
但数组不能进行赋值操作:
在程序3-1中,如果声明的是“int a[maxn],b[maxn]”,是不能赋值b=a的
如果要从数组a复制k个元素到数组b,可以这样做 —— memcpy(b,a,sizeof(int)*k)
当然,如果数组a和b 都是浮点型的,复制时要写成 —— memcpy(b,a,sizeof(double)*k)。另外需要注意的是, 使用memcpy函数要包含头文件string.h
如果需要把数组a全部复制到数组b中,可以写得简单 一些:memcpy(b,a,sizeof(a))
程序2:开灯问题
有n盏灯,编号为1~n。第1个人把所有灯打开,第2个人按下所有编号为2 的倍数的开关(这些灯将被关掉),第3个人按下所有编号为3的倍数的开关(其中关掉的灯 将被打开,开着的灯将被关闭),依此类推。一共有k个人,问最后有哪些灯开着?输 入n和k,输出开着的灯的编号。k≤n≤1000
样例输入:
7 3
样例输出:
1 5 6 7
我的思路: n个灯 k个人,如果 灯的编号 % 人的编号 == 0,就将灯的状态取反 ————> 可以利用 数组 + 双层for循环 + 状态值 实现
#include<stdio.h> #include<string.h> #define maxn 1010 int a[maxn]; int main() { int n,k,first =1; memset(a,0,sizeof(a)); scanf("%d %d",&n,&k); for(int i = 1;i <= k;i++){ for(int j = 1;j <= n;j++){ if(j % i == 0) a[j] = !a[j]; } } for(int i = 1;i <= n;i++){ if(a[i]){ //如果第i个灯是亮的,即a[i] = 1 if(first) first = 0; else printf(" "); printf("%d",i); } } printf("\n"); return 0; }
技巧:
- memset(a,0,sizeof(a))—— 作用是把数组a清零,在string.h中定义
- 为了避免输出在最开始和最后输出多余空格,设置了一个标志变量first,可以表示当前要输出的变量是否为第一个。第一个变 量前不应有空格,但其他变量都有
采用样例数据测试时【即第1 5 6 7个灯是亮的】,可以在 程序 最后加入 printf("%d",a[1]); 输出结果是1,即
a[j] = !a[j];
取反后,由0变1,或由1变0
程序3:蛇形填数
在n×n方阵里填入1,2,…,n×n,要求填成蛇形。例如,n=4时方阵为:
上面的方阵中,多余的空格只是为了便于观察规律,不必严格输出。n≤8。
我的思路:。。。我不会 =_=...... =__= .....=___=....
用二维数组解决 —— int a[n][n] ——> 然后从a[0][n] --> a[n][n]初始化 ---> a[n][n]--->a[n][0] ---->a[0][0]--->a[0][n-1]。。。。for循环 【但含有多次不定长的转向,短时间内无法做出来】
书的思路:
也是二维数组,从1开始依次填写。设“笔”的坐标为(x,y),则一开始x=0,y=n-1,即第0行,第n-1列 (行列的范围是0~n-1,没有第n列)。“笔”的移动轨迹是:下,下,下,左,左,左, 上,上,上,右,右,下,下,左,上。总之,先是下,到不能填为止,然后是左,接着是 上,最后是右。“不能填”是指再走就出界(例如4→5),或者再走就要走到以前填过的格子 (例如12→13)。如果把所有格子初始化为0,就能很方便地加以判断。
#include<stdio.h> #include<string.h> #define maxn 20 int a[maxn][maxn]; int main() { int n, x, y, tot = 0; scanf("%d", &n); memset(a, 0, sizeof(a)); tot = a[x=0][y=n-1] = 1;//从1开始写 while(tot < n*n) { while(x+1<n && !a[x+1][y]) a[++x][y] = ++tot; //【1】 while(y-1>=0 && !a[x][y-1]) a[x][--y] = ++tot; //【2】 while(x-1>=0 && !a[x-1][y]) a[--x][y] = ++tot; //【3】 while(y+1<n && !a[x][y+1]) a[x][++y] = ++tot; //【4】 } for(x = 0; x < n; x++) { for(y = 0; y < n; y++) printf("%3d", a[x][y]); printf("\n"); } return 0; }
【1】:这一行中,保持y不变,x每次移动加一,从上往下“填”数字,同时笔在往下移动的时候要满足 x不能超过n,因为是 n* n的数字阵列,且为了循环填完外层数后逐步循环填内层数,要保证下一次将要“填数”的位置 是0,即还没有填入数字【填上数后 !a[x][y] = 0】
此时x = 0,y =2 在a[x][++y]打印16,即y = 3,此时开始循环判定,y + 1 <n true,但 a[x][y+1] 已被填入 “1”,无法继续往右填,开始往下写(下一轮循环)
如果去除所有的预判是否过界:【去掉 && !a[x][y] 】
其余过程同理
# 在很多情况下,最好是在做一件事之前检查是不是可以做,而不要做完再后 悔。因为“悔棋”往往比较麻烦
# 如果x+1<n为假,将不会计算“!a[x+1][y]”,也就不会越界 ———— &&是短路运算符 【这里提过】
为什么是++tot而不是tot++?
#include<stdio.h> int main(){ int i = 1; printf("%d\n",i); i++; printf("%d\n",i); ++i; printf("%d\n",i); return 0; }
回顾刚才提到的:
# 对于变量n,n++和++n都会给n加1,但当它们用在一个表达式中时,行为有所差别:n++会使用加1前的值计算表达式【即n的原值】,而++n会使用加1后的值计算表达式【即n+1后的值】 ———— 谁在前面,就先用谁 【n++,n在前mian所以用n的原值,++n,++在前面,就将n先+1】
二、字符数组
a