数组与函数
数组
数据可以存放在变量里,每一个变量有一个名字,有一个类型,还有它的生存空间。如果我们需要保存一些相同类型、相似含义、相同生存空间的数据,我们可以用数组来保存这些数据,而不是用很多个独立的变量。数组是长度固定的数据结构,用来存放指定的类型的数据。一个数组里可以有很多个数据,所有的数据的类型都是相同的。
初始数组:
如何写一个程序计算用户输入的数字的平均数,并输出所有大于平均数的数?
#include<stdio.h>
int main()
{
int x;
double sum = 0;
int cnt = 0;
int number[100]; //定义一个数组,一个有100int的数组
scanf("%d", &x);
while(x!=-1){
number[cnt]=x; //对数组中的元素赋值
/*
{
int i;
printf("%d\t",cnt); // \t相当于tab键
for(i=0; i<=cnt; i++){
printf("%d\t", number[i]);
}
printf("\n");
}
*/
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]);
}
}
}
return 0;
}
}
这个程序存在安全隐患,没有判断cnt会不会超过number[100]
定义数组:
<类型>变量名称[元素数量]
如:
int grades[100]
double weight[20]
元素数量必须是整数
C99之前,元素数量必须是编译时刻确定的字面常量,不能包含变量,不管该变量有没有初始化
数组是一种容器,特点是:
其中所有元素具有相同数据类型;
一旦创建,不能改变大小;
数组中的元素在内存中是连续依次排列的
一个int的数组,10个单元,a[0],a[1],a[2],a[3],a[4]...a[9]
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] | a[8] | a[9] |
每个单元就是一个int的变量;
可以出现在赋值符号左边或者右边,左边的叫左值,右边的叫右值;
a[2] = a[1] = 6 : 读取a[1]的值,加上6,写给a[2]
使用数组时,放在[ ]里的数字叫下标或索引,下标从0开始计数;
有效的下标范围:
编译器和运行环境都不会检查数组下标是否会越界,无论是做读还是写;
一旦程序运行,越界的数组访问可能造成问题,严重的可能造成程序崩溃
segmentation fault
所以程序员有责任来保证使用有效的数组下标[0, 数组大小-1]
可以先让用户输入有多少数字要计算,再定义数组,可以用C99的新功能实现。C99允许用变量指明数组长度
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; //对数组中的元素赋值
/*
{
int i;
printf("%d\t",cnt); // \t相当于tab键
for(i=0; i<=cnt; i++){
printf("%d\t", number[i]);
}
printf("\n");
}
*/
sum+=x;
cnt ++;
scanf("%d", &x);
}
长度为0的数组可以存在,但没用a[0]
字符可以做下标:
如a['A']
字符以ASCII码存储,ASCII对应数字
数组例子:统计个数:
写一个程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数,输入-表示结束
#include<stdio.h>
int main()
{
const int number = 10; //用变量number指明数组大小,C99才允许
int count[number]; //定义数组
int x;
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++){ //用for循环遍历数组,输出每一个元素的值
printf("%d:%d\n", i ,count[i]);
}
return 0;
}
函数的定义与使用
初见函数:
求和:求出1到10、20到30、35到45的三个和
#include<stdio.h>
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;
}
使用函数可以让程序结构变得更简洁直观,提高代码的利用率,减少同类代码重复出现,可以重复调用。
函数的定义和使用:
函数是一块代码,接受零个或多个参数,做一件事情,并返回零个或一个值
调用函数:
格式: 函数名(参数值);
() 起到了表示函数调用的重要作用,即使没有参数也需要()
如果有参数,则需要给出正确的参数数量和顺序
这些值会被按照顺序依次用来初始化函数中的参数
函数返回:
函数知道每一次是哪里调用它,会返回到正确的地方(会返回到调用它的下面那个地方)
从函数中返回值:
return 停止函数的运行,并送回一个值
return; 或 return 表达式;
一个函数里可以出现多个return语句,但违背“单一出口原则”
int max(int a, int b) // 定义一个函数max
{
int ret;
if(a>b){
ret = a;
}else{
ret = b;
}
return ret;
}
int a, b, c;
a = 5;
b = 6;
c = max(10,12); //可以赋值给变量
c = max(a,b);
c = max(c,23);
c = max(max(c,a),5); //可以再传递给函数
printf("%d\n", max(a,b));
max(12,13); //也可以丢弃
没有返回值的函数:
void 函数名(参数表)
不能使用带值的return,也可以没有return
调用的时候不能做返回值的赋值(c = sum(12,13))
void sum(int begin, int end)
{
int i;
int sum;
for(i=begin; i<=end; i++){
sum += i;
}
printf("%d到%d的和是%d\n", begin, emd, sum);
}
如果函数有返回值,就必须用带值的return
函数原型:
函数先后关系:
void sum(int begin, int end)
{
int i;
int sum;
for(i=begin; i<=end; i++){
sum += i;
}
printf("%d到%d的和是%d\n", begin, emd, sum);
}
int main()
{
sum(1,10);
sum(20,30);
sum(35,45);
return 0;
}
像这样把定义函数写在上面,是因为:
C的编译器自上而下顺序分析代码
看到sum(1,10)时,编译器要知道sum()要几个参数,每个参数什么类型,返回什么类型
这样才能检查对sum的调用是否正确
void sum(int begin, int end); //声明
int main()
{
sum(1,10);
sum(20,30);
sum(35,45);
return 0;
}
void sum(int begin, int end) //定义
{
int i;
int sum;
for(i=begin; i<=end; i++){
sum += i;
}
printf("%d到%d的和是%d\n", begin, emd, sum);
}
函数原型:
函数头以分号 ";" 结尾,就构成函数原型;
函数原型目的是告诉编译器这个函数长什么样
名称
参数(数量及类型)
返回类型
旧标准习惯把函数原型写在调用它的函数里面,现在一般写在调用它的函数前面
原型里可以不写参数名字,但是为了用户可读性,建议写上
参数传递
调用函数:
如果函数有参数,调用函数时必须传给它数量、类型正确的值;
可以传递给函数的值是表达式的结果,包括:
字面量
变量
函数的返回值
计算的结果
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语言的漏洞,后续的java/C++在这方面很严格
C语言在调用函数时,只能传递值给函数
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;
}
这样不能交换a,b的值
传值:
每个函数有自己的变量空间,参数也位于这个独立的变量空间中,和其他函数没有关系
过去,对于函数参数表中的参数,叫做“形式参数”,调用函数时给的值叫做“实际参数”
我们认为,形式参数和实际参数是参数和值的关系
本地变量:
函数的每次运行就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,叫做“本地变量”
定义在函数内部的变量就是本地变量
参数也是本地变量
本地变量的规则:
本地变量是定义在块内的;
可以是定义在函数的块内;
也可以是定义在语句的块内;
甚至随便拉一对大括号{}来定义变量;
程序运行进入这个块之前,其中的变量不存在,离开这个快,变量就消失了
块外面定义的变量在里面仍然有效
块里面定义了和外面同名的变量则掩盖了外面的
不能在一个块里定义同名的变量
本地变量不会被默认初始化
参数在进入函数时被初始化了
没有参数时:
void f(void) 明确表示这个函数不接收任何参数
void f() 不代表没有参数,而表示函数的参数表未知,不知道有没有参数,不知道参数类型,不知道参数数量
逗号运算符:
f(a,b) 调用函数时,"," 是标点符号
f((a,b)) "," 是运算符
C语言不允许函数嵌套定义,不能在函数里定义函数
二维数组:
int a[3][5];
通常理解为a是一个3行5列的矩阵
二维数组可被看作一种特殊的一维数组,即它的元素为一维数组。比如“int a[3][4];”可以看作有三个元素,每个元素都为一个长度为 4 的一维数组。而且 a[0]、a[2]、a[3] 分别是这三个一维数组的数组名。
在 C 语言中,二维数组中元素排列的顺序是按行存放的,即在内存中先顺序存放第一行的元素,再存放第二行的元素,这样依次存放。
二维数组的遍历:
for(i=0; i<3; i++){
for(j=0; j<5; j++){
a[i][j] = i*j;
}
a[i][j] 是一个int,表示第i行,第j列上的单元
a[i,j] 是运算,结果是j
二维数组的初始化:
1.分行给二维数组赋初值,比如:
int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12},}; 最后一个逗号可以存在
2.也可以将所有数据写在一个花括号内,按数组排列的顺序对各元素赋初值。比如:
int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
但第1种方法更好,一行对一行,界限清楚。第 2 种方法如果数据多,写成一大片,容易遗漏,也不易检查。
3.也可以只对部分元素赋初值。比如:
int a[3][4] = {{1, 2}, {5}, {9}};
它的作用是对第一行的前两个元素赋值、第二行和第三行的第一个元素赋值。其余元素自动为 0。初始化后数组各元素为:
4. 如果在定义数组时就对全部元素赋初值,即完全初始化,则第一维的长度可以不指定(由编译器来数),但第二维的长度不能省。比如:
int a[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12},};
但这种省略的写法几乎不用,因为可读性差。
5. int a[3][4]={0};
二维数组“清零”,里面每一个元素都是零。
二维数组输出
在讲述一维数组的时候说过,“数组的元素只能一个一个输出”,二维数组也不例外。在一维数组中
是用一个 for 循环进行输出,而二维数组元素的输出要使用两个 for 循环嵌套。
# include <stdio.h>
int main(void)
{
int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int i; //行循环变量
int j; //列循环变量
for (i=0; i<3; ++i)
{
for (j=0; j<4; ++j)
{
printf("%-2d\x20", a[i][j]); //“%-2d”,其中“-”表示左对齐,如果不写“-”则默认表示右对齐;“2”表示这个元素输出时占两个空格的空间,所以连同后面的 \x20 则每个元 } //素 输出时都占三个空格的空间。
printf("\n");
}
return 0;
}