C语言笔记(二)--- 数组、函数

2018-06-22  本文已影响0人  坚持到底v2

七、数组

在C语言中,数组属于构造数据类型。
数组根据元素的类型不同,数组又可以分为 数值数组字符数组指针数组结构数组 等各种类别。
本章介绍 数值数组字符数组

1. 一维数组的定义和引用

定义: 类型说明符 数组名 [常量表达式];
例如:int a[10]; float b[10],c[20]; char ch[20];

下标从 0 开始;
不能在 [] 中使用变量来表示元素的个数,但是可以是符号常数或常量表达式。

引用:数组名[下标]

其中下标只能为整型常量或整数表达式。
如为小数时,C编译将自动取整。。

初始化

给数组赋值的方法除了用赋值语句对数组元素逐个赋值外,还可以采用初始化赋值和动态赋值的方法。

初始化赋值形式举例: int a[10]={0,1,2,3,4,5,6,7,8,9};

C语言对数组的初始化赋值有以下几点规定:

2. 一维数组程序举例:

main() {
 int i,max,a[10];
 printf("input 10 numbers:\n");
 for (i=0;i<10;i++)
   scanf("%d",&a[i]);

 max=a[0];
 for (i=1;i<10;i++)
  if(a[i]>max) max=a[i];

 printf("maxmum=%d\n",max);
}

例2:排序

main() {
 int i,j,p,q,s,a[10];
 printf("input 10 numbers:\n");
 for (i=0;i<10;i++)
   scanf("%d",&a[i]);

 for (i=0;i<10;i++) {
   p=i;q=a[i];
   for(j=i+1;j<10;j++)
     if(q<a[j]) {p=j;q=a[j];}

   if(i!=p) { 
     s=a[i];
     a[i]=a[p];
     a[p]=s;
   }
   printf("%d",a[i]);
 }
}

3. 二维数组的定义和引用

定义:类型说明符 数组名[常量表达式1][常量表达式2]
表达式1 为第一维下标的长度,
表达式2 是第二维下标的长度

例如:int a[3][4]; 说明了一个三行四列的数组,数组名为 a,其下标变量的类型为整型。
该数组的下标变量共有 3*4 个。

二维数组在概念上是二维的,但在实际的硬件存储器中却是连续编址的,也就是说存储器单元是按一维线性排列的。
如何在一维存储器中存放二维数组,可有两种方式:
一种是按行排列,即放完一行之后顺次放入第二行。
另一种是按列排列,即放完一列后再顺次放入第二列。
在C语言中,二维数组是按行排列的。即:先存放 a[0] 行,再存放 a[1] 行,最后存放 a[2] 行。

二维数组的初始化

可以按行分段赋值,也可以按行连续赋值。
例如:

int a[5][3]={{80,75,92},{61,65,71},{59,63,80},...};
int a[5][3]={80,75,92,61,65,59,....};

对于二维数组初始化赋值的说明:

4. 字符数组:

char c[10];
由于字符型和整型通用,也可以定义为 int c[10] ,但这时每个数组元素占2个字节的内存单元。

字符数组初始化的时候未赋初值的元素也是0值。
同样也可以在赋初值时省去数组长度。

5. 字符串和字符串结束标志:

在C语言中没有专门的字符串变量,通常用一个字符数组来存放一个字符串。
前面介绍字符串常量时,已说明字符串总是以 \0 做为串的结束符。
因此当把一个字符串存入一个数组时,也把结束符'\0'存入数组,并以此做为字符串是否结束的标志。
有了 \0 标志后,就不必再用字符数组的长度来判断字符串的长度了。

C语言允许用字符串的方式对数组作初始化赋值。例如:
char c[]=('C',' ','p',... }; 可写为: char c[]={"C program"}; 或去掉 {} ,写为 char c[]="C program";

\0是C编译系统自动加上的。
由于采用了 \0 标志,所以在用字符串赋初值时一般无需指定数组的长度,而由系统自行处理。

6. 字符数组的输入输出:

在采用字符串方式后,字符数组的输入输出将变得简单方便。
除了上述用字符串赋初值的办法外,还可以用 printf函数scanf函数 一次性输出输入一个字符数组中的字符串,而不必使用循环语句逐个地输入输入每个字符。例如:

main()  {
  char c[]="BASIC\nBASE";
  printf("%s\n",c);
}

注意格式字符串 %s 表示输出的是一个字符串,而在输出表列中给出数组名即可,不能写为 printf("%s\n",c[]);

又如:

main()  {
 char st[15];
 printf("input string:\n");
 scanf("%s",st);
 printf("%s\n",st);
}

本例中字符数组长度为15,因此输入的字符串长度必须小于15,以留出一个字节用于存放结束标志'\0'。
应该说明的是,对一个字符数组,如果不作初始化赋值,则必须说明数组长度。
还应该特别注意的是,当用 scanf函数 输入字符串时,字符串中不能含有 空格 ,否则将以 空格 做为串的结束符。

例如,当输入的字符串含有空格时,运行情况为:

input string:
this is a book

输出为:

this

从输出结果可以看出空格以后的字符都未能输出。
为了避免这种情况,可多设几个字符数组分段存放含有空格的串。
程序改写如下:

main() {
 char st1[6],st2[6],st3[6],st4[6];
 printf("input string:\n");
 scanf("%s%s%s%s",st1,st2,st3,st4);
 printf("%s %s %s %s\n",st1,st2,st3,st4);
}

前面介绍过,scanf 的各输入项必须以地址方式出现,如&a &b等。
但是现在却是以数组名方式出现的,这是为什么呢?
这是由于C语言中规定,数组名就代表了该数组的首地址。
整个数组是以首地址开头的一块连续的内存单元。如有字符数组 char c[10] ,在内存可表示如下: c[0] c[1] ... c[9]

设数组c的首地址为2000,也就是说 c[0] 单元地址为2000.则数组名c就代表这个首地址。
因此在c前面不能再加地址运算符 &
在执行 printf("%s",c) 时,按 数组名c 找到首地址,然后逐个输出数组中的各个字符直到遇到字符串终止地址 \0 为止。

7. 字符串处理函数

使用输入输出的字符串函数时需要包含头文件 stdio.h,使用其他字符串函数应包含头文件 string.h

(1)字符串输出函数puts (字符数组名)

char c[]="BASIC\ndBASE"; puts(c);
从上面的例子可以看出puts函数中可以使用转义字符,因此输出结果为两行。
puts函数 完全可以由 printf函数 取代。
当需要一定格式输出时,通常使用 printf函数

(2)字符串输入函数gets (字符数组名)

例如:

#include "stdio.h"
main() {
 char st[15];
 printf("input string:\n");
 gets(st);
 puts(st);
}

可以看出当输入的字符串中含有空格时,输出仍为全部字符串。
说明 gets函数 并不以空格做为字符串输入结束的标志,而只以回车做为输入结束。
这是与 scanf函数 不同的。

(3)字符串连接函数strcat (字符数组名1,字符数组名2);

功能是把 字符数组2 中的字符串连接到 字符数组1 中字符串的后面,并删去字符串1后的串标志 \0
本函数返回值是 字符数组1 的首地址。
例子:

#include "string.h"
main() {
  static char st1[30]="My name is ";
  int st2[10];
  printf("input your name:\n");
  gets(st2);
  strcat(st1,st2);
  puts(st1);
}

上面的例子是初始化赋值的字符数组与动态赋值的字符串连接起来,要注意的是,字符数组1应定义足够的长度,否则不能全部装入被连接的字符串。

(4)字符串拷贝函数strcpy (字符数组名1,字符数组名2);

功能:把 字符数组2 中的字符串拷贝到 字符数组1 中。
串结束标志 \0 也一同拷贝

例子:

#include "string.h"
main() {
 char st1[15],st2[]="C Language";
 strcpy(st1,st2);
 puts(st1);
 printf("\n");
}

注意 字符数组1 应有足够的长度,否则不能全部装入所拷贝的字符串。

(5)字符串比较函数 strcmp(字符数组名1,字符数组名2);

功能:按照ASCII码顺序比较两个数组中的字符串,并由函数返回值返回比较结果。

字符串1=字符串2,返回0
字符串1>字符串2,返回值>0
字符串1<字符串2,返回值<0

(6)测字符串长度函数strlen(字符数组名)

功能:返回字符串的实际长度,不包含 \0 的长度

例子:

#include <string.h>
main() {
 int k;
 static char st[]="C language";
 k=strlen(st);
 printf("The length of the string is %d\n",k);
}

8. 程序举例:


第八章 函数

1. 函数分类

2. 库函数

C语言提供了极为丰富的库函数,这些库函数又可从功能角度分为以下几类:

(1)字符类型分类函数

用于对字符按ASCII码分类:字母、数字、控制字符、分隔符,大小写字母等。

(2)转换函数

用于字符或字符串的转换;
在字符量和各类数字量(整型、实型等)之间进行转换;
在大、小写之间进行转换。

(3)目录路径函数

用于文件目录和路径操作。

(4)诊断函数

用于内部错误检测。

(5)图形函数

用于屏幕管理和各种图形功能。

(6)输入输出函数

用于完成输入输出功能。

(7)接口函数

用于与DOS、BIOS和硬件的接口。

(8)字符串函数

用于字符串的操作和处理

(9)内存管理函数

用于内存管理

(10)数学函数

用于数学函数计算

(11)日期和时间函数

用于日期、时间转换操作。

(12)进程控制函数

用于进程管理和控制。

(13)其他函数

用于其他各种功能

大部分函数需要自己去查阅相关手册。

函数可以递归调用,但不允许嵌套声明,即函数体内不允许再声明函数。
另外,main是主函数,他可以调用别的函数,但别的函数不能调用他。
因此,C程序总是从main函数开始。

3. 函数定义的一般形式:

函数的定义形式:

类型标识符 函数名(形式参数列表) {
  声明部分
  语句
  使用return返回值
}

例如:

int max(int a,int b) {
 if (a>b) return a;
 else return b;
}

在主函数中调用上面声明的函数:

main() {
 int max(int a,int b);
 int x,y,z;
 printf("input two numbers:\n");
 scanf("%d%d",&x,&y);
 z=max(x,y);
}

在main函数中对要调用的函数进行函数说明,函数说明和函数定义是两回事

4. 函数的返回值

函数的返回值通过 return 表达式;return (表达式); 返回,
返回类型以函数类型为准,不一致时系统自动进行类型转换。
如果函数值为 整型,在函数定义时可以省去类型说明
不返回函数值的函数,可以明确定义为“空类型”-void,一旦定义函数为空类型后,就不能在主调函数中使用被调函数的函数值了。
为了使程序有良好的可读写并减少出错,凡不要求返回值的函数都应定义为空类型。

5. 函数调用的方式:

在C语言中,可以用以下几种方式调用函数:

main() {
  int i=8;
  printf("%d %d %d %d",++i,--i;i++;i--);
}

如按照 从右至左 的顺序求值是 8 7 7 8,从左至右的顺序求值是 9 8 8 9,
TurboC是自右至左,如果不清楚,上机一试便可知晓。

6. 被调用函数的声明和函数原型

在主调函数中调用某函数之前应对该被调用函数进行说明(声明),这与使用变量之前要先进行变量说明是一样的。
在主调函数中对被调函数做说明的目的是使编译系统知道被调函数返回值的类型,以便在主调函数中按此种类型对返回值做相应的处理。

其一般形式为:
类型说明符 被调函数名(类型 形参,类型 形参...);

类型说明符 被调函数名(类型,类型...);

括号内给出了形参的类型和形参名,或只给出形参类型。
这便于编译系统进行检错,以防止可能出现的错误。

C语言又规定在以下几种情况可以省去主调函数中对被调函数的函数说明:

7. 函数的递归调用

如 计算 n! ,著名的 Hanoi塔问题 等都是使用递归调用的很好例子:

Hanoi塔:
一块板上有3根针,A,B,C。
A针上套有64个大小不等的圆盘,大的在下,小的在上。
要把这64个圆盘从A针移到C针上,每次只能移动一个圆盘,移动可以借助B针进行。
但在任何时候,任何针上的圆盘都必须保持大盘在下,小盘在上。
求移动的步骤。

本题算法分析如下:
设A上有n个盘子
如果n=1,则将圆盘从A移动到C
如果n=2,则

如果n=3,则:

到此,完成了3个圆盘的移动过程。
从上面的分析可以看出,当n>=2时,移动的过程可以分解为3个步骤:
第一步 把A上的n-1个圆盘移到B上
第二步 把A上的一个圆盘移动C上
第三步 把B上的n-1个圆盘移到C上;其中第一步和第三步是类同的。

当n=3时,第一步和第三步又分解为类同的三步,即把n-1个圆盘从一个针移到另一个针上,这里的n=n-1.显然这是一个递归过程,据此算法可编程如下:

move(int n,int x,int y,int z) {
 if (n==1)
    printf("%c-->%c\n",x,z);
 else {
   move(n-1,x,z,y);
   printf("%c-->%c\n",x,z);
   move(n-1,y,x,z);
 }
}

main() {
 int h;
 printf("\ninput number:\n");
 scanf("%d",&h);
 printf("the step to moving %2d diskes:\n",h);
 move(h,'a','b','c');
}

8. 数组做为函数参数

数组用作函数参数有两种形式:
一种是把数组元素(下标变量)做为实参使用;
另一种是把数组名做为函数的形参和实参使用。

使用数组名做为函数参数时应注意:

9. 局部变量和全局变量

10. 变量的存储类别

静态存储方式和动态存储方式

静态存储方式:是指在程序运行期间分配固定的存储空间的方式
动态存储方式:是在程序运行期间根据需要进行动态的分配存储空间的方式。

用户存储空间可以分为3个部分:

全局变量全部存放在 静态存储区 ,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。
在程序执行过程中他们占据固定的存储单元,而不动态地进行分配和释放;

动态存储区存放以下数据:

对以上这些数据,在函数开始调用时分配动态存储空间,函数结束时释放这些空间。
在c语言中,每个变量和函数有两个属性:数据类型和数据的存储类别。

11. auto变量

函数中的局部变量,如不专门声明为 static 存储类别,都是动态地分配存储空间的,数据存储在动态存储区内。
函数中的形参和在函数中定义的变量都属此类,在调用该函数时系统会给他们分配存储空间,在函数调用结束时就自动释放这些存储空间。
这类局部变量被称为自动变量。
自动变量使用 关键字auto 作存储类别的声明。

例如:

int f(int a) {
 auto int b,c=3;//定义b,c为自动变量
 ...
}

a是形参,b,c是自动变量,对c赋初值3.
执行完f函数后,自动释放a,b,c所占的存储单元。
关键字auto 可以省略,auto不写则隐含定为“自动存储类别”,属于动态存储方式。

12. 用static声明局部变量:

有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,这时就应该指定局部变量为“静态局部变量”,用 关键字static 进行声明。

f(int a) {
 auto b=0;
 static c=3;
 b=b+1;
 c=c+1;
 return (a+b+c);
}

main() {
 int a=2,i;
 for (i=0;i<3;i++)
   printf("%d",f(a));
}

对静态局部变量的说明:

13. register变量:

为了提高效率,C语言允许将局部变量的值放在CPU中的寄存器中,这种变量叫“寄存器变量”,使用 关键字register 作声明。

例如:

int fac(int n) {
 register int i,f=1;
 for(i=1;i<=n;i++)
   f*=i;
 return f;
}

main() {
  int i;
  for (i=0;i<=5;i++)
    printf("%d!=%d\n",i,fac(i));
}

说明:

14. 用extern声明外部变量。

外部变量(即全局变量)是在函数的外部定义的,他的作用域是从变量定义处开始,到本程序文件的末尾。
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件终了。
如果在定义点之前想引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”。
表示该变量是一个已经定义的外部变量。
有了此声明,就可以从“声明”处起,合法地使用该外部变量。

上一篇 下一篇

猜你喜欢

热点阅读