一、数组
数组是由同一种类数据构成的集合。就好比一个班所有同学的身高,一个月的日平均气温,抽样调查的一百个数据...等等,都可以当作一个数组。构建数组是为了对同类的多个数据实行高效管理。
1.数组定义
格式:类型说明 数组名[长度];
int a[10];//定义长度为10的整数型数组,全为空
int a[]; 不行,未指明长度,也无一组员(元素)
int a[]={1,2,3,4};//可以,直接明确组员,长为4
int a[10]={1,2,3,4};//可,前4组员明确,其余为0
当然,字符数组用:char a[10];实数:float x[10]。
表达式也是可以的。何为表达式?见下文。
回到数组的定义,看个题:
对于这个“#define”,叫“宏定义”,在程序开始时定义,格式:
#define 名 内容 ,中间空格,结尾无需“;”。
它可以定义“符号常量”,比如 #define PI 3.14
也可以定义参数,比如 #define S(r) PI*r*r
AD没问题,C也没问题,B有的说不行,其实是可以的(此题无解),只要n在数组前面定义就可以:
2.数组中元素的引用
如何取出数组中的元素?通过编号:a[0]、a[1]、a[2]..要注意的是:从0开始,a[0]代表第一个,a[9]代表第10个。在定义时,int a[5];5代表长度;在取数时, a[5],5代表第6个编号,超出了范围。例子:
看个题(答案D):
再看个:
答案C。一个整数单元要4个字节,实际上我们定义了10个单元,用了3个单元,到底是12还是40呢?操作一下:
可以看出,前面3个数,后面7个0,都划分了内存,这一块地盘就是数组a的了,10个单元40个字节。
3.从键盘输入数组元素值。
我们需要用到scanf()函数,先看下面的方法:
可以看出,从键盘输入4个数,只有第一个赋值给了a[0],从第二a[1]到第四a[3]都赋值了0【scanf不认空格,认次数】,再后面6个数没有,为空,输出了乱七八糟。
然而,字符串却可以一次性赋值给字符数组,如下:
为什么呢?因为在C语言中,字符的长度都是一个单位(字节),一个装满后接着装第二个、第三个...
可以发现当scanf从键盘接收了更多的字符串,超过了原本定义的长度,数组自动加长,以匹配输入的字符。
所以,输入不同的整数,不能一次性放入,那就通过循环来一个一个放入。如下:
4.产生随机数组。
有时为了操作元素较多的数组(比如测试排序),键盘输入或直接赋值都太慢了,太烦琐了,能不能让电脑随机产生一个数组呢?比如一个50个元素的整数数组,数的大小在1~100之间?
答案是可以的。这时用到C语言的随机函数rand(),试一下:
确实产生了20个整数,rand()产生1~32767之间的随机数。有个问题:就是再次运行,还是这些数,没有改变。没有达到随机变化的目的。要解决这个问题,我们还要另外一个辅助函数srand(),随机(rand)的种子(seed)函数,用法如下:
确定产生了不同的随机数。如何把这些变我们需要的数呢?比如从1到100之间。可以这样:先rand()%100,用取余的方法,取余后得到0~99,还需要+1,即rand()%100+1。
如果是要求从200~500呢?可用公式:
rand()%范围长度+最小值,即:rand()%301+200。
知道了随机数的产生办法,50个0~1的随机小数如下:
这样在初始数组时,不用一个个输入了。
5、数组三值(最大、最小、平均)
要找出一组数的最值,肯定是要比较,要比较就要有参照值。我们不防设两个临时变量:最大值max、最小值min,开始都为数组的第一个数a[0],即a[0]就是参照值。
int max,min;
max=a[0];
min=a[0];
最大值:从数组的第2个数开始,和max比较,如果大于max,就把该数放在max,如果小就过;再拿第3个数和max比较,如果小就过,如果大于max,就用大的替换掉原来的小的,然后是第4个、第5个...这样保证了max的值始终是在前面比较的数中,是最大的,最后比较完数组所有的数,这个max就是最大值。
for(i=0;i<50;i++){//从第2个开始循环也可以
if(a[i]>max)max=a[i];}如果大则max换成大的,相当于取出置顶。
最小值则正好相反。代码如下:
这样解决了最值问题。下面看看平均值。公式为:
平均值=总和÷个数
涉及到求和,可以预设变量s=0,循环中通过s+=a[i]得到总和。平均值也要设个变量,一般变量名可用英文单词的三个字母缩写代指定,就好象中国(CHN)美国(USA),平均值avg或ave之类,总和也常用sum。这样:avg=s/50。
完整代码:
6.二维数组
一维数组是线性结构,一条龙,那二维数组是平面结构,方块,有行有列,矩阵。
多维数组最高8维正常。
二、表达式
1.表达式
表达式就是不直接告诉你结果,出个式子,让你计算思考,主要有7种:
算术表达式:“+-*/%”组合,比如b*b-(a*c)
赋值表达式:“=”组合,比如a+=a*(a=5)
逗号表达式:“,”组合,比如y=(x=a+b,b+c,x+c)
关系表达式:“><=!=”组合,比如a+b==c
逻辑表达式:“||&&!”组合,比如!b==c||d<a
位运算表达式:“|&<<^”组合,比如0x0f&a(取低4位)
条件表达式:“?:”组合,比如(b>c)? b:c
2.运算符
表达式由运算符组合而成,常见运算符如下:
来几个例子:
当组合在一起时,哪个优先计算呢?一般是:单目运算符>算术运算符>关系运算符>逻辑运算符(不包括!)>条件运算符>赋值运算符>逗号运算符。
3. 单目运算符
C语言的单目运算符(操作一个变量;双目,操作两个变量)主要包括逻辑非(!)、按位取反(~)、自增(++)、自减(--)、正负号(+/-)、类型转换((类型))、指针与地址(*和&)、长度运算符(sizeof)等,这些运算符仅需一个操作数即可完成运算。
逻辑非运算符(!):对操作数的逻辑值取反,真变假,假变真。
按位取反运算符(~):对操作数的二进制位逐位取反(1变0,0变1),是唯一的单目位运算符。
自增/自减运算符(++/--):前缀或后缀形式,使变量值增加或减少1。
正负号运算符(+/-):表示数值的正负,如-5或+10。
类型转换运算符((类型)):强制转换操作数的数据类型,例如(float)a。
指针与地址运算符(*和&):*用于解引用指针(获取指针指向的值),&用于获取变量的内存地址。
长度运算符(sizeof):返回数据类型或变量所占字节数,如sizeof(int)。
优先级与结合性:单目运算符通常具有较高的优先级(仅次于括号),且多为右结合性(如*p++等价于*(p++))。
!=优先于=,+优先于<<,&优先于=。
*/%同级别,>优先于&&,&&优先于=。
异或^还可以互换两数。
三、考题设计
【题目:设计一个考试程序,题目为10个判断题,考试完成统计出分数。】
1.思路
①我们先把题目显示在屏幕上,让考生看到题目内容,当然是一个题目一个题目来。②然后提示考生怎么答题,比如:考生从键盘输入y表示正确、n表示错误。③等待考生输入,这时要用到键盘输入函数scanf()。④判断、统计考生的答案。⑤下一题,重复上述4步。
2.试试三个题
先用一个题目调试一下:
上面就是一个判断题的答题过程。选择题类似处理。
当然考题不只一个,多个题目确实有点麻烦,每个题目答案还不一样。
先从简单的答案思考,每个题目答案只有一个字符,判断题要么对y要么错n,选择题ABCD中的一个,这样是不是可以把正确答案放入事先设定一个数组中?每个题目考生的回答,依次和正确答案比较,正确加分,错误不加分。我们也可以依次把考生答题放入另一个数组,然后一起for循环比较两个数组,统计分数。
再举例三个题目调试如下:
为了简化美观,可以把两个printf合成一个。这种处理,原句还是一长句,只是在输入时看的更清楚些。
先声明两个整数,i用于循环,s用于统计分数。再声明两个字符数组,ans[]用于存入考生答题结果,ok[]存放正确答案。然后依次printf输出题目、提示和scanf()用于接收答案。这个scanf()有点坑,就是第二次用的时候,前一次有个回车符占了一个位子,前面要有个空位“ %c”。
答题完以后,用一个for循环,比较两个数组的相同个数,有一个加10分,累计出结果,输出。
我们回头发现,程序很零乱,如果考题很多,50、100的话,要不停重复几个语句,晕死。
3.集中处理。
3.1考题一次输入
10个考题可以理解为10行文本。C处理多行字符串,真是有些吃力。Python一句搞定,C墨迹半天。
先看看多行文本的集中一次输入:
不讲版面美观易阅读,也可如下一句输入:
如何取出其中一句?还真有点麻烦,思路就是先找了换行符“\n”的位子,然后按要求取出换行符之间的内容。比如取出第四句,那就是在第3-4个“\n”之间。
先找了“\n”的位子看看,调试一下:
即第1行在数组的位子是0-19,第2行21-36,第3行38-53,第4行55-67,第5行69-81,第6行83-末。
3.2试错
如何取出其中一行,并赋值给变量呢?举个例:
看样子strcat()函数用不上了。但是可以用下面这个函数:strncpy(target, source + 4, 10);
// 从source的位置4开始复制10个字符到target
出现了不正常字符,是因为缺乏字符串终止符'\0',加上: target[10] ='\0'; // 确保字符串正确终止
这种方法的前提:是知道所截字符串的长度,而本例中,每行(每个判断题)的长度不是固定的,每行都要单独计算长度,有没有更简单的办法?
3.3重拟思路
我们好比遇到了这样一个纵队:
“***|***|****|**|***|**|****|...”
每两小队之间用“|”隔开,各个小队长短不一,开始和末尾是没有“|”的,第1小队直接开始,到第一个|结束;第2小队从第1个|到第2个|之间...第n个是从第n-1个~n个|之间。如果要提取第5小队 ,那就是第4~5个|之间。
如果n很大,纵队一眼看不到头,有几百,而现在要提取第350个小队?最简单的想法是:我们先数杆“|”,数到第349个小杆再说。
按这个思路,我们设定一个动态行号no,从1开始(开始即第一行),每遇到一个“\n”,行号加1,当no等于要提取的行号时,进行下一步操作。
3.4调试
代码调试如下:
语句:if(qus[i]=='\n')no+=1;//每遇一个\n,行号加1
实际上,此时此次循环是“\n”,下一个字符才是我们所需要提出的起点。
语句:if(no==n&&qus[i]!='\n'){//行号对,且不是\n ,意思是已经来到了下一个循环
temp[j]=qus[i]; //开始提取,一个个提取。当然temp事先声明为临时字符串。
j++;//临时串的序号自增。j事先声明,为temp序号。
过程中,修改n的值,再看看第1句和最后一句,没问题。
接下来,我们把这个功能块定义为一个函数,函数接收一个数字参数(第几题),返回一个字符串(题目)。
字符有返回函数,定义为指针类型。
返回字符串,定义为静态(static)类型。
如果临时行号no已经大于要提取的行n了,直接跳出循环。如下:
测试成功。
3.5完整代码
先集中定义考题qus[],为一个字符串,每个题目之间用“\n”分开。整体输入,一目了然。
再定义答案ok[],根据考题顺序校对答案。同时定义一个空串,用于存放考生作答。
将取出考题功能,定义为一个函数q(n),n代表第几题,返回一个字符串,打印在屏幕上。
主循环作三件事:输出考题、等待考生输入、比对答案并统计分数。
主循环完成后,输了分数。结果如下:
要改进的地方:当no>n时跳出这一句,可以放到第一个if判断里面,即当遇到“\n”判断一下,不需要每次循环都判断一次,提高了效率。如下:
回顾:集中输入题目和答案,定义函数,简化循环体,这样形成一个模版。如果是选择题设计,可一次输出两行(一行题目、一行选项)即可。