6.1数组
数据可以存放在变量里,每一个变量有一个名字,有一个类型,还有它的生存空间。如果我们需要保存一些相同类型、相似含义、相同生存空间的数据,我们可以用数组来保存这些数据,而不是用很多个独立的变量。数组是长度固定的数据结构,用来存放指定的类型的数据。一个数组里可以有很多个数据,所有的数据的类型都是相同的。
6.1.1初试数组
如何写一个程序计算用户输入的平均数?输入-1代表输入结束
不需要记录输入的每一个数;计算sum然后除法
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);
}
且输出所有大于平均数的数?
需要记录读入的所有数。
#include<stdio.h>
int main()
{
int x;
double sum=0;
int cnt=0;
int number[100];//定义数组
scanf("%d",&x);
while(x!=-1){
number[cnt]=x;//对数组中元素赋值
sum+=x;
cnt++;
scanf("%d",&x);
}
if(cnt>0){
printf("%f\n",sum/cnt);
int i;
for (i=0;i<cnt;i++){//遍历数组
if(number[i]>sum/cnt){
printf("%d\n",number[i]);//使用数组中元素
}
}//程序存在安全隐患
使用数组的常见步骤:
定义数组—赋值数组—遍历数组(使用数组中的元素)
6.1.2定义数组
<类型>变量名称[元素个数]
int number[100];
元素数量必须是整数
C99之前:元素数量必须是编译时刻确定的字面量,不能是变量,不能是动态的程序运行过程中产生的数字
数组
是一种容器(放东西的东西),所有的元素具有相同的数据类型;
一旦创建,不能改变大小;数组中的元素在内存中是连续依次排列的
数组中的单元可以出现在复制号的左边或者右边;在赋值号左边的叫做左值
数组的单元
数组的每个单元就是数组类型的一个变量
使用数组是放在[ ]中的数字叫做下标或索引,下标从0开始计数
有效的下标范围
编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读还是写
一旦程序运行,越界的数组访问可能导致问题,导致程序崩溃。
Segmentation fault
换了电脑不能运行的原因之一(另一个是指针)
所以这是程序员的责任来保证程序只使用有效的下标值:
[0,数组的大小-1]
6.1.1节中我们的代码是危险的,因为输入的数据可能超过100个。
解决方案一:读满100个就不可以再读
解决方案二:C99特性,先让用户输入它要输入的个数,然后再创建数组
Int a[0] :
长度为0的数组,编译可以通过,可以存在但无用
因为即使是0也是越界了
字符也可以做下标
6.1.3数组例子:统计个数
写一个程序,输入数量不确定的[0,9]的范围内的整数,统计每一种数字出现的次数,输入-1表示结束
(不需要记录输入的数值,而是要记录输入的这个数曾经输入过多少次,设置的变量是记录次数的。)
#include<stdio.h>
int main(void)
{
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;
}
tips:
初始化数组是必要的,并且需要通过循环才能完成
Const int number 通过用这种方式来设定数组大小
Magic number:是指在程序中经常使用的数字,比如数组的大小,在for的条件里常常用到,我们使用const int 来声明,能够让其他人更加理解和明白代码中数字的含义
6.2函数的定义与使用
6.2.1初见函数
求素数的和
可以将判断一个数是不是素数的那部分代码放入函数中
#include<stdio.h>
int main()
{
int m,n;
int sum=0;
int cnt=0;
int i=1;
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;
}
判断素数部分代码可以单独取出
#include<stdio.h>
int isPrime(int i)
{
int ret=1;
int k;
for(k=2;k<i-1;k++){
if(i%k==0){
ret=0;
break;
}
}
return ret;
}
int main()
{
int m,n;
int sum=0;
int cnt=0;
int i=1;
scanf("%d %d",&m,&n);
//m=10;n=31
if(m==1)m=2;
for(i=m;i<n;i++){
if(isPrime(i)){
sum+=i;
cnt++;
}
}
printf("%d %d\n",cnt,sum);
return 0;
}
求出1到10,20到30,35到45的三个和
用一个函数去求三段的和
(“代码复制”是程序质量不良的表现)
void sum(int begin,int end)//函数头 返回类型 函数名 参数表
{
int i;//函数体
int sum=;
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,45);
return 0;
}
6.2.2函数定义和调用
函数
是一块代码,接收0个或者多个参数,做一件事情,并返回0个或者1个值。
函数结构:函数头(返回类型 函数名 参数)+函数体
()的含义是起到了表示函数调用的重要作用,即使没有参数也需要()
讲函数指针的时候我们再来讲调用函数时一定要加上()的原因
DEV C++编译器中我们通常在调试的时候是使用下一步(主程序),但是如果我们要进入一个函数,一步一步的执行一个函数中的步骤,我们使用单步进入的按钮
函数名(参数值);
如果有参数,则需要给出正确的数量和顺序
这些值会被按照顺序依次用来初始化函数中的参数
6.2.3 从函数中返回值
int isPrime(int i)
{
int ret=1;
int k;
for(k=2;k<i-1;k++){
if(i%k==0){
ret=0;
break;
}
}
return ret;
}
return的作用:停止函数的执行,并送回一个值。
return有两种写法:
1.return; 2.return 表达式;
一个函数中是可以出现多个return语句的,而且return不一定要放在最后,但是这不好,不符合我们单一出口的理念。有很多个出口可以传递值到外面
可以赋值给变量,有时也可以丢弃(有时候我们要的是副作用)
用void做返回类型的函数,可以没有return,但一定不能使用带值的return
如果函数有返回值,必须使用带值的return
6.3函数的参数与变量
6.3.1函数原型
函数先后关系:
1.如果我们不在主函数中声明函数,那么函数应该写在主函数的上面,这是因为C 的编译器是自上而下的顺序分析的你的代码的。
2.如果我们不是将函数写在上面,那么我们就需要在主函数中先声明这个需要使用 的函数。
(通常我们使用第二种顺序)LLVM是一个严格的编译器。
int isPrime(int i);
函数的原型声明:声明不是函数,只有函数头,可以在int main之外去定义
函数原型的目的就是告诉编译器这个函数长什么样子
函数原型里可以不写参数的名字,只写类型
int isPrime(int i)
{
…
}
函数的定义:具体的代码(函数头+函数体);
原型声明在写主函数里是旧版本的C,现在函数的原型声明在写主函数的外面
6.3.2参数传递
调用函数
如果函数有参数,调用函数时必须传递给它数量、类型正确的值
表达式包括:字面量,变量,返回值,计算结果。
如果传入的类型不匹配
调用函数时的值与参数的类型不匹配是C语言传统上最大的漏洞
编译器总是悄悄的替你把类型转换好,但这很可能不是你所期望的
后续的语言,C++/java在这方面很严格
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;
}//错误示例,这样的代码不能交换ab值
C语言在调用函数时,永远只能传值给函数
当传递参数给函数的时候,我们永远传递的是值,是把5和6传递过去,虽然函数参数中的a和b与主函数中的a和b同名,但是实际上他们是没有任何关系的
形式参数与实际参数
传值:每一个函数是有它自己的变量空间,参数也位于这个独立的空间中,和其他 的函数没有关系。对于函数参数表中的参数,叫做形式参数,调用函数时给的值,叫做实际参数
正是因为我们这种古老的方式来称呼实际参数,导致我们容易误会这个实际参数就是进入到函数内部实际参加预算的参数,而事实上,我们调用函数的时候不是传递进去变量,而是传递进去值了
C语言中都是传值。正确的理解方式:是参数与值的关系。
6.3.3本地变量(局部变量、自动变量)
本地变量是指在函数每次运行中产生的变量空间中的变量,而不是主函数里面所声明的变量
函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,成为本地变量
定义在函数内部的变量就是本地变量
参数也是本地变量。和本地变量具有一样的生存期和作用域
自动:生存期是自动的
生存期&作用域
生存期:开始到消亡
作用域:在代码的什么范围内可以访问这个变量
对于本地变量,这两个问题的答案是同一的:大括号内——块
Main里面声明的变量的作用域不包括自己定义的函数大括号之中
main函数有自己的变量空间;我们定义的函数也有自己的变量空间
本地变量的规则
本地变量是定义在块内的(就是大括号)
可以是函数,也可以是语句
甚至可以直接随便拉一对大括号来定义变量
程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了
在块外面定义的变量在块里面仍然有效
块里面定义了和外面同名的变量时,则里面的变量掩盖了外面的变量(Java不是这样)
不能在同一个块里面定义同名的变量
本地变量不会被默认初始化
参数在进入函数的时候会被初始化
6.3.4函数细节
当函数没有参数时
void f(void) or
void f(): ?
在传统C上,第一种表示没有参数;第二种表示参数未知,但并不表示没有参数
C99中编译不出错,但是传递的值可能会出错
12行编译器认为要两个int的传递;
但是25行我们要两个double,这两个整数传递到double那里去,就出错了
C语言不允许函数的嵌套定义
Return (i):
圆括号没有任何意义;其实仍然是返回i的值,易发生误解
6.4二维数组
6.4.1二维数组
int a[3] [5];
通常理解为3行5列的矩阵
二维数组的遍历
for(i=0;i<3;i++)
{
for(j=o;j<5;j++)
{
a[i][j]=i*j;
}
}
a[i,j]表达的即为a[j]
int a[][5]=
{
{0,1,2,3,4},
{2,3,4,5,6},
};
二维数组的初始化
列数必须给出,行数可以由编译器来数
每一行一个{},逗号分隔
最后的逗号可以存在,有古老的传统
如果省略,表示补零
也可以用定位(* C99 ONLY),一连串数字也可
二维数组在内存中的排列是连续依次的,里面也可以不带大括号
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){
result=1;
}
}
}
//检查对角线
numofo=numofx=0;
for(i=0;i<size;i++){
if(board[i][i]==1){
numofx++;
}else{
numofo++;
}
}
if(numofo==size){
result=0;
}else if(num0fx==size){
result=1;
numofo=numofx=0;
for(i=0;i<size;i++){
if(board[i][size-i-1]==1){
numofx++;
}else{
numofo++;
}
}
怎么对一行遍历
怎么对一列遍历
怎么判断对角线
这是二维数组里经常做的事情
int check(int i, int j)
{
int result;
for (i = 0; i < sizeip>
{
numofo = 0;
numofx = 0;
for (j = 0; j < sizejp>
{
if (board[i][j] == 1)
numofx++;
else
numofo++;
}
if (numofo == size)
result = 0;
else if (numofx == size)
result = 1;
}
return result;
}