C语言的类型
整数
- char:一字节(8比特)-128至127
- short:2字节 -32768至32767
- int:取决于编译器(CPU),通常的意义都是"1个字"
- long:取决于编译器(CPU),通常的意义是"1个字"(4或8字节)
- long long:8字节
*整数的内部表达
- 计算机内部一切都是二进制
- 18 -> 00010010
- 0 -> 00000000
- -18 ->?
*如何表示负数
- 十进制用"-"来表示负数,在做计算的时候
- 加减是做相反的运算
- 乘除时当作正数,计算完毕后对结果的符号取反
*二进制负数
- 1个字节可以表达的数:0000 0000 - 1111 1111(0-255)
三种方案:
- 仿照十进制,有一个特殊的标志表示负数
- 取中间的数为0,如100 0000表示0,比它小的是负数,比它大的是正数
- 补码(实际上使用的,方案1和2都有缺陷)
*补码
补码的意义就是拿补码和原码可以加出一个溢出的0
例子:因为0 - 1->-1,所以-1 = (1)0000 0000 - 0000 0001 ->1111 1111
1111 1111被当作纯二进制看待时,是255,被当作补码看待时是-1
同理,对于-a,其补码就是0-a,实际是2的n次方-a,n是这种类型的位数
数的范围
- 对于一个字节(8位),可以表达的是:0000 0000 ->1111 1111
- 其中0000 0000->0 、1111 1111至1000 0000 ->-1至-128 、 0000 0001至0111 1111->1至127
浮点数
float、double、long double
逻辑
bool
指针
自定义类型
类型有何不同
- 类型名称:int、long、double
- 输入输出时的格式化:%d、%ld、%lf
- 所表达的数的范围:char
- 内存中所占据的大小:1个字节到16个字节
- 内存中的表达形式:二进制数(补码)、编码
sizeof
- 是一个运算符,给出某个类型或者变量在内存中所占据的字节数
例如:sizeof(int)、size(i)
- 是静态运算符,它的结果在编译时刻就决定了
- 不要在sizeof的括号里做运算,这些运算不会进行的
unsigned
- 在整形类型前加上unsigned使得它们变成无符号的整数
- 内部的二进制表达没变,变的是如何看待它们。如何输出
- 1111 1111对于char来说是-1,对于unsigned char来说是255
- 如果一个字面量常数想要表达自己是unsigned,可以在后面加上U或者u,255U
- 用L或者l表示long( long)
- unsigned初衷并非扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位
整数越界
整数是以纯二进制方式进行计算的,所以:
- 1111 1111+1 -> 1 0000 0000 ->0
- 0111 1111+1 -> 1 0000 0000 ->-128
- 1000 0000-1 -> 0111 1111 ->127
整数的输入输出
只有两种形式:int或者long long
- %d:int
- %u:unsigned
- %ld:long long
- %lu:unsigned long long
8进制和16进制
- 一个以0开始的数字字面量是8进制
- 一个以0x开始的数字字面量是16进制
- %o用于8进制,%x用于16进制
- 8进制跟16进制只是如何把数字表达为字符串,与内部如何表达数字无关
- 16进制很适合表达二进制数据,因为4位二进制正好是一个16进制位
- 8进制的一位数字正好表达3位二进制
- 因为早期计算机的字长是12的倍数,而非8
选择整数类型
- 为什么整数要有那么多种?为了准确表达内存,做底层程序的需要
- 没有特殊需要就选择int
- 现在CPU的字长普遍是32位或者64位,一次内存读写就是一个int,一次计算也是一个int,选择更短的类型不会更快,甚至可能更慢
- 现代的编译器一般会设计内存对齐,所以更短的类型实际在内存中有可能也占据一个int的大小(虽然sizeof告诉你更小)
- unsigned与否只是输出的不同,内部计算是一样的
浮点数
浮点类型
类型 |
字长 |
范围 |
有效数字 |
float |
32 |
正负(1.20×100,正负inf,NaN) |
7 |
double |
64 |
正负(2.20×100,正负inf,NaN) |
15 |
浮点的输入输出
类型 |
scanf |
printf |
float |
%f |
%f,%e |
double |
%lf |
%f,%e |
科学计数法
- 可选的+或者-符号
- 可以用e或者E
- 整个词不能有空格
- 小数点也是可选的
- 符号可以是正负也可以省略(表示+)
- 例如:-5.67E+16
输出精度
在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做四舍五入的
- printf("%.3f\n",-0.0049);
- printf("%.30f\n",-0.0049);
- printf("%.3f\n",-0.00 49);
超出范围的浮点数
- printf输出inf表示超过范围的浮点数:+无穷
- printf输出nan表示不存在的浮点数
浮点运算的精度
- 带小数点的字面量是double而非float
- float需要用f或者F后缀来表明身份
浮点数的内部表达
- 浮点数在计算时是由专用的硬件部件实现的
- 计算double和float所用的部件是一样的
选择浮点类型
- 如果没有特殊需要,只使用double
- 现代CPU能直接对double做硬件运算,性能不会比float差,在64位的机器上,数据存储的速度也不比float慢
字符
字符类型
char是一种整数,也是一种特殊的类型:字符。这是因为:
- 用单引号表示的字符字面量:'a','1'
- "也是一个字符
- printf和scanf里用%c来输入输出字符
字符的输入输出
- 如何输入'1'这个字符给char c?
scanf("%c",&c);->1
scanf("%d",&i);c=i ;->49
- '1'的ASCII编码是49,所以当c==49时,它代表'1'
- 这是一个49的各自表述
混合输入
有何不同
scanf("%d %c",&i,&c);
scanf("%d%c",&i,&c);
字符计算
- 一个字符加一个数字得到ASCII码表中那个数之后的字符
- 两个字符的减,得到它们在表中的距离
大小写转化
- 字母在ASCII表中是顺序排列的
- 大写字母和小写字母是分开排列的,并不在一起
- ‘a’-‘A’可以得到两段之间的距离,于是a+‘a’-‘A’可以把一个大写字母变成小写字母,反之把这个运算中的‘a’与‘A’互相调换即可把一个小写字母变成大写字母
逃逸字符
用来表达无法印出来的控制字符或特殊字,在那些具有特殊符号的前面加上\即可将其当作普通字符使用
字符 |
意义 |
\b |
回退一格 |
\t |
到下一个表格位 |
\n |
换行 |
\r |
回车 |
" |
双引号 |
' |
单引号 |
\ |
反斜杠本身 |
制表位
- 每行的固定位置
- 一个\t使得输出从下一个制表位开始
- 用\t能使上下两行对齐
逻辑类型
bool
- 在宏定义中加上#include
- 之后就可以使用bool和true跟false
bool的运算
- bool实际上还是以int的手段实现的,所以可以当作int来计算
- 也只能当作int来输入输出
类型转换
自动类型转换
- 当运算符的两边出现不一致的类型时,会自动转换成较大的类型
- 大的意思是能表达的数的范围更大
- char->short->int->long->long long
- int->float->double
- 对于printf,任何小于int的类型会被转化成int;float会被转换成double
- 但是scanf不会,要输入short,需要%hd
强制类型转换
- 要把一个量强制转换成另一个类型(通常是较小的类型),需要:(类型)值
- 比如(int)10.2 (short)32
- 需要注意这个时候的安全性,小的变量不总能表达大的量
- 这个只是从那个变量计算出了一个新的类型的值,它并不改变那个变量,无论值还是类型都不改变
- 强制类型转换的优先级高于四则运算
有些运算
逻辑运算
- 逻辑运算是对逻辑量进行的运算,结果只有0或者1
- 逻辑量是关系运算或逻辑运算的结果
运算符 |
描述 |
示例 |
结果 |
! |
逻辑非 |
!a |
如果a是true结果就是false如果a是fasle结果就是true |
&& |
逻辑与 |
a&&b |
如果a和b都是是true结果就是true否则就是false |
|| |
逻辑或 |
a||b |
如果a和b有一个是true结果就是true两个都是false,结果才是false |
try(尝试)
如何判断一个字符c是否是大写字母?
c >= ‘A’&&c<=‘Z’
逻辑优先级
!>&&>||
总体优先级排名
优先级 |
运算符 |
结合性 |
1 |
() |
从左到右 |
2 |
!+- ++ -- |
从右到左(单目的+和-) |
3 |
/ % |
从左到右 |
4 |
+ - |
从左到右 |
5 |
< <= > >= |
从左到右 |
6 |
== != |
从左到右 |
7 |
&& |
从左到右 |
8 |
|| |
从左到右 |
9 |
= += -= *= /= %= |
从右到左 |
短路
- 逻辑运算是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算了
- 对于&&,左边是false时就不会做右边的运算了
- 对于||,左边是true时就不会做右边的运算了
- 不要把赋值,包括复合赋值组合进表达式
条件运算符
- count = (count>20)?count-10:count+10;
- 当count>20是真的时候执行前者,是假执行后者
- 优先级:条件运算符的优先级高于赋值运算符,但是低于其他运算符
- 不要嵌套条件表达式(很容易出问题)
逗号运算符
- 逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果。逗号的优先级是所有运算符中最低的,所以它两边的表达式会先计算;逗号的组合关系是自左像右,所以左边的表达式会先计算,而右边的表达式的值就留下来作为逗号运算的结果
- 比如在for中使用
- for(i = 1,j=8;i
第七周:函数
7.1函数的定义与使用
#include<stdio.h> int main() { int m,n; int sum = 0; int cnt = 0; int i; scanf("%d %d",&m, &n); // m=10,n=31; if(m == 1) m=2; for(i=m; i<=n; i++){ int isPrime = 1; int k; for(k=2; k<i-1; k++){ if(i%k == 0){ isPrime = 0; break; } } if(isPrime){ sum += i; cnt++; } } printf("%d %d\n", cnt, sum); return 0; } // 把这段代码取出来 int isPrime = 1; int k; for(k=2; k<i-1; k++){ if(i%k == 0){ isPrime = 0; break; } } #include<stdio.h> //求1到10、20到30和35到45的三个和 void sum(int begin,int end) { int i; int sum = 0; for( i = begin;i <= end; i++){ sum += i; } printf("%d到%d的和是%d\n",begin,end,sum); } int main() { sum(1,10); sum(20,30); sum(35,40); return 0; }
什么是函数?
- 函数是一块代码,接收零个或者多个参数,做一件事情,并返回零个或一个值
- 可以先想像成数学中的函数:y=f(x)
函数定义
调用函数
- 函数名(参数值);
- ()起到了表示函数调用的重要性,即使没有参数也需要()
- 如果有参数,则需要给出正确的数量和顺序
- 这些值会被按顺序依次用来初始化函数中的参数
函数返回
函数知道每一次是在哪里调用它,会返回到正确的地方
从函数中返回值
- return停止函数的执行,并送回一个值
- return;
- return 表达式;
- 一个函数里可以出现多个return语句
- 例如:int c, c = max(10,12);=>可以赋值给变量或者再传递给函数甚至可以丢弃,有的时候要的是副作用
没有返回值的函数
- void 函数名(参数表)
- 不能使用带值的return
- 可以没有return
- 调用的时候不能做返回值的赋值
- 如果函数有返回值则必须使用带值的return
7.2函数的参数和变量
函数原型
函数的先后关系
#include<stdio.h> void sum(int begin,int end) { int i; int sum = 0; for( i = begin; i <= end; i++){ sum += i; } printf("%d到%d的和是%d\n",begin,end) } int main() { sum(1,10); sum(20,30); sum(35,45); return 0; }
- 像这样把sum()写在上面,是因为:c的编译器自上而下顺序分析你的代码
- 在看到sum(1,10)的时候,它需要知道sum()的样子
- 也就是sum()要几个参数,每个参数的类型如何,返回什么类型。这样它才能检查你对sum()的调用是否正确
此函数原型
#include<stdio.h> double max(double a,double b) => 这是函数原型 int main() { int a,b,c; a = 5; b = 6; c = max(10,12); =>这中间一段是根据原型判断 printf("%d\n",c); max(12,13); return 0; } double max(double a, double b) =>这是实际的函数头
- 函数头:以分号“;”结尾,就构成了函数的原型
- 函数原型的目的是告诉编译器这个函数长什么样子:名称、参数(数量及类型)、返回类型
- 旧标准习惯把函数原型写在调用它的函数里面
- 现在一般写在调用它的函数前面
- 原型里可以不写参数的名字,但是一般仍然写上
参数传递
调用函数
- 如果函数有参数,调用函数的时候必须传递给它数量、类型正确的值
- 可以传递给函数的值是表达式的结果,这包括:字面量、变量、函数的返回值、计算的结果
int a,b,c; a = 5; b = 6; c = max(10,12); c = max(a,b); c = max(c,23); c = max(max(23,45),a); c = max(23+45,b);
类型不匹配
- 调用函数时给的值与参数的类型不匹配是C语言传统上最大的漏洞
- 编译器总是悄悄替你把类型转换好,但是这很可能不是你所期望的
- 后续的语言,C++/java在这方面很严格
C语言在调用函数时,永远只能传值给函数
#include<stdio.h> void swap(int a,int b); //这是参数 int main() { int a = 5; int b = 6; swap(a,b); //这是值 printf("a = %d b = %d\n",a,b); return 0; } void swap(int a,int b) //参数 { int t = a; a = b; b = t; }
传值
- 每个函数有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系
- 过去,对于函数参数表中的参数,叫做"形式参数",调用函数时给的值叫做"实际参数"(实参与形参)
- 由于容易让初学者误会实际参数就是实际在函数中进行计算的参数,误会调用函数的时候把变量而不是值传进去,所以我们不建议继续使用这种古老的方式来称呼它
- 我们认为,它们是参数和值的关系
本地变量
- 函数的每次运行,就会产生一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称作本地变量
- 定义在函数内部的变量就是本地变量
- 参数也是本地变量
变量的生存期和作用域
- 生存期:什么时候这个变量开始出现了,到什么时候它消亡了
- 作用域:在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)
- 对于本地变量,这两个问题的答案是统一的:大括号内——块
本地变量的规则
1.本地变量是定义在块内的
- 它可以是定义在函数的块内
- 也可以定义在语句的块内
- 甚至可以随便拉一对大括号来定义变量
2.程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了
3.块外面定义的变量在里面仍然有效
4.块里面定义了和外面同名的变量则覆盖了外面的变量(块内的变量优先度更高)
5.不能在一个块内定义同名的变量
6.本地变量不会被默认初始化
7.参数在进入函数的时候被初始化了
其他细节
没有参数时
- void f(void)还是void f();
- 在传统C中,它表示f函数的参数表示未知,并不表示没有参数
逗号运算符?
- 调用函数时的逗号跟逗号运算符字母区分?
- 如下 => 调用函数时的圆括号内的逗号是标点符号,不是运算符
- 逗号:f(a,b) 逗号运算符:f((a,b))
函数里的函数?
C语言不允许函数嵌套定义
这是什么?
- int i,j,sum(int a,int b); //可以这样写但是不建议
- return(i); //加不加括号其实都无所谓,值都一样的,但加上括号会让人误以为这是一个函数,不要这样写没有好处
关于main
- int main()也是一个函数
- 要不要写成int main(void)? //void加不加都一样,但如果上面参数都不打算加,不妨把void写下去
- return的0有人看吗? //是可以看的起作用的,返回0表示正常的运行结束了,返回任何非0的值都是错误的
windows:if error level 1...
Unix Bash:echo $?
Csh:echo $status
第八周:数组
8.1-1初试数组
如何写入一个程序计算用户输入的数字的平均数?(不需要记录输入的每一个数)
#include<stdio.h> int main(){ int x; double sum = 0; int cnt = 0; scanf("%d",&x); while( x != -1){ sum += x; cnt++; scanf("%d",&x); } if( cnt > 0 ){ printf("%f\n",sum/cnt); } return 0; }
- 如何写一个程序计算用户输入的数字的平均数,并输出所有大于平均数的数?
- 思路:必须先记录每一个输入的数字,计算平均数之后,再检查记录下来的每一个数字,与平均数比较,决定是否输出
- 该使用数组啦
#include<stdio.h> int main(){ int x; double sum = 0; int cnt = 0; int number[100]; //=>这里定义了数组,但具有隐患,当存放的内容超过了100个的时候就会出错,所有后续可以进行动态调整 scanf("%d",&x); while( x != -1){ number[cnt] = x; //对数组中的元素赋值 sum += x; cnt++; scanf("%d",&x); } if( cnt > 0){ int i; double average = sum/cnt; for( i = 0;i < cnt; i++){ if( number[i] > average){ printf("%d",number[i]); //遍历数组跟使用数组里面的元素 } } } return 0; }
8.1-2数组的定义和使用
定义数组
1.<类型>变量名称[元素数量];
- int grades[100];
- double weight[20];
2.元素数量必须是整数
3.C99之前:元素数量必须是编译时刻确定的字面量(了解下就行)
数组
是一种容器(放东西的东西),特点是:
- 其中所有的元素具有相同的数据类型;
- 一旦创建,不能改变大小
- *(数组中的元素在内存中是连续依次排列的)
int a[10];
- 一个int的数组
- 10个单元:a[0],a[1],a[2]....a[9];
- 每一个单元就是一个int类型的变量
- 可以出现在赋值的左边或右边:a[2] = a[1]+6;
- *在赋值左边的值叫左值(右边就叫右值咯)
数组的单元
- 数组的每个单元就是数组类型的一个变量
- 使用数组时放在[]中的数字叫做下标或索引,下标从0开始计数:例如1.grades[0]2.grades[99],average[5]
有效的下标范围
- 编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读还是写
- 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃segmentation fault
- 也可能运气好,没造成严重的后果
- 所有这是程序员的责任来保证程序只使用有效的下标值:[0,数组的大小-1]
计算平均数
int x; double sum = 0; int cnt; printf("请输入数字的数量:"); scanf("%d",&cnt); if( cnt > 0){ int number[cnt]; scanf("%d",&x); while( x != -1){ number[cnt] = x; sum += x; cnt++; scanf("%d",&x); } }
长度为0的数组?
int a[0];可以存在,但是没有卵用
8.1-3数组的例子:投票统计
写一个程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数,输入-1表示结束
#include<stdio.h> int main(){ const int number = 10; //数组的大小 int x; int count[number]; //定义数组 int i; for( i=0; i<number; i++){ //初始化数组 count[i] = 0; } scanf("%d",&x); while( x != -1){ if( x >= 0 && x <= 9){ count[x]++; //数组参与运算 } scanf("%d",&x); } for( i = 0;i < number; i++){ printf("%d:%d\n",i,count[i]); //遍历数组输出 } return 0; }
8.2-1数组的运算
在一组给定的数据中,如何找出某个数据是否存在?
#include<stdio.h> /** 找出key在数组a中的位置 @param key 要寻找的数字 @param a 要寻找的数组 @param length 数组a的长度 @return 如果找到,返回其在a中的位置;如果找不到则返回-1 */ int search(int key,int a[], int length); int main(void) { int a[] = {2,4,6,7,1,3,5,9,11,13,23,14,32}; int x; int loc; printf("请输入一个数字:"); scanf("%d",&x); loc = search(x,a,sizeof(a)/sizeof(a[0])); if( loc != -1){ printf("%d在第%d个位置上\n",x,loc); }else{ printf("%d不存在\n",x); } return 0; } int search(int key, int a[], int length) { int ret = -1; int i; for( i = 0; i <= length; i++){ if( a[i] == key){ ret = i; break; } } return ret; }
数组的集成初始化
int a[] = {2,4,6,7,1,3,5,9,11,13,23,14,32};
- 直接用大括号给出数组的所有元素的初始值
- 不需要给出数组的大小,让编译器替你数
int b[20] = {2};
- 如果给出了数组的大小,但是后面的初始值数量不足,则其后的元素被初始化为0
集成初始化时的定位
int a[10] = { [0] = 2,[2] = 3,6, };
- 用[n]在初始化数据中给出定位
- 没有定位的数据接在前面的位置后面
- 其他位置的值补零
- 也可以不给出数组大小,让编译器算
- 特别适合初始数据稀疏的数组
数组的大小
- sizeof给出整个数组所占据的内容的大小,单位时字节
得到数组的个数: sizeof(a)/sizeof(a[0])
- sizeof(a[0])给出数组中单个元素的大小,于是相除就得到了数组的单元个数
- 这样的代码,一旦修改数组中初始的数据,不需要修改遍历的代码
数组的赋值
- 数组变量本身不能被赋值
- 要把一个数组的所有元素交给另一个数组,必须采用遍历
for( i = 0; i<length;i++){ b[i] = a[i]; }
遍历数组
- 通常都是使用for循环,让循环变量i从0到<数组的长度,这样循环体内最大的i正好是数组最大的有效下标
- 常见错误:循环结束条件是 <= 数组长度,或; 离开循环后,继续使用i的值来做数组元素的下标(错误的)
数组作为函数参数时:
- 不能在[]中给出数组的大小
- 不能再利用sizeof来计算数组的元素个数
- 往往必须再用另一个参数来传入数组的大小
8.2-2数组的例子:素数
判断素数
int is Prime(int x); int main(void) { int x; scanf("%d",&x); if( isPrime(x)){ printf("%d是素数\n",x); }else{ printf("%d不是素数\n",x); } return 0; }
从2到x-1测试是否可以整除
int isPrime(int x) { int ret = 1; int i; if( x == 1) ret = 0; for( i = 2;i < x; i++){ if( x % i == 0 ){ ret = 0; break; } } return ret; } //对于n要循环n-1遍,当n很大的时候就是n遍 int isPrime(int x) { int ret = 1; int i; if( x == 1 || (x%2 == 0 && x != 2)) ret = 0; for( i = 3;i < x; i+=2){ if( x % i == 0 ){ ret = 0; break; } } return ret; } //如果x是偶数,立刻结束。否则要循环(n-3)/2+1遍。当n很大的时候就是n/2遍 int isPrime(int x) { int ret = 1; int i; if( x == 1 || (x%2 == 0 && x != 2)) ret = 0; for( i = 3;i < sqrt(x); i+=2){ if( x % i == 0 ){ ret = 0; break; } } return ret; } //只需要循环sqrt(x)遍,sqrt(x)是x的平方根的意思
判断是否可以被已知的且
#include<stdio.h> #include<math.h> int isPrime(int x, int knownPrimes[],int numberOfKnownPrimes); int main(void){ const int number = 10; int prime[number] = {2}; int count = 1; int i = 3; // 为了输出好看,添加测试语句 { int i; printf("\t\t"); for(i=0; i<10; i++){ printf("%d\t", i); } printf("\n"); } while(count < number){ if(isPrime(i,prime,count)){ prime[count++] = i; } // 加入调试输出的语句 { printf("i=%d \tcnt=%d\t", i, count); int i; for(i=0; i<number; i++){ printf("%d\t", prime[i]); } printf("\n"); } i++; } for(i=0; i<number; i++){ printf("%d",prime[i]); if((i+1)%5) printf("\t"); else printf("\n"); } return 0; } int isPrime(int x,int knownPrimes[],int numberOfKnownPrimes){ int ret = 1; int i; for( i=0;i<numberOfKnownPrimes; i++){ if(x%knownPrimes[i] == 0){ ret = 0; break; } } return ret; }
构造素数表
欲构造n以内的素数表
- 令x为2
- 将2x,3x,4x直至ax
- 令x为下一个没有被标记为非素数的数,重复2;直到所有的数都已经尝试完毕
欲构造n以内(不含)的素数表
- 开辟prime[n],初始化其所有元素为1,prime[x]为1表示x是素数
- 令x = 2;
- 如果x是素数,则对于(i = 2;xi < n; i++)令prime[ix]=0
- 令x++,如果x
#include<stdio.h> int main(void){ const int maxNumber = 25; int isPrime[maxNumber]; int i; int x; for( i = 0;i < maxNumber;i++){ isPrime[i] = 1; } for( x = 2;x < maxNumber;x++){ if( isPrime[x] ){ for(i = 2; i*x<maxNumber; i++){ isPrime[i*x] = 0; } } } for( i = 2;i < maxNumber; i++){ if( isPrime[i]){ printf("%d\t",i); } } printf("\n"); return 0; }
8.2-3二维数组
二维数组
- int a[3][5];
- 通常理解为a是一个3行5列的矩阵
a[0][0] |
a[0][1] |
a[0][2] |
a[0][3] |
a[0][4] |
a[1][0] |
a[1][1] |
a[1][2] |
a[1][3] |
a[1][4] |
a[2][0] |
a[2][1] |
a[2][2] |
a[2][3] |
a[2][4] |
二维数组的遍历
for( i = 0;i < 3;i++){ for( j = 0;j < 5; i++){ a[i][j] = i*j; } }
- a[i][j]是一个int
- 表示第i行第j列上的单元
- a[i,j]是什么? //是一个表达式,不是正确的表达二维数组的方式
二维数组的初始化
int a[][5] = { {0,1,2,3,4}, {2,3,4,5,6}, };
- 列数是必须给出的,行数可以由编译器来数
- 每隔一个{},逗号分隔
- 最后一组逗号可以存在,以前的传统
- 如果数组有部分省略没写,编译器会自动补零(表示补零)
- 也可以使用定位
tic-tac-toe游戏
- 读入一个3×3的矩阵,矩阵中的数字为1表示该位置有一个X,为0表示为O
- 程序判断这个矩阵中是否有获胜的一方,输出表示获胜一方的字符X或者O,或输出无人获胜
读入矩阵
const int size = 3; int board[size][size]; int i,j; int numOfX; int numOfO; int result = -1; //-1:没人赢,1:X赢了,0:O赢了 //读入矩阵 for( i = 0;i < size;i++){ for( j = 0;j < size; j++){ scanf("%d",&board[i][j]); } }
检查行
//检查行 for( i = 0; i < size && result == -1; i++){ numOfO = numOfX = 0; for( j = 0;j < size; j++){ if( board[i][j] == 1 ){ numOfX ++; }else{ numOfO ++; } } if( numOfO == size){ result = 0; }else if(numOfX == size){ result = 1; } }
检查列
if( result == -1){ for( j = 0; j < size && result == -1; j++){ numOfO = numOfX = 0; for( i = 0;i < size;i++){ if( board[i][j] == 1){ numOfX ++; }else{ numOfO ++; } } if( numOfO == size){ result == 0; }else if( numOfX == size){ reshult == 1; } } }
检查对角线
numOfO = numOfX = 0; for( i = 0; i < size; i++){ if( borad[i][size-i-1] == 1){ numOfX ++; }else{ numOfO ++; } } if( numOfO == size){ result = 0; }else if(numOfX == size){ result == 1; }