06——一维数组,二维数组,字符数组与字符串

第一部分:一维数组
我们之前所学过的数据类型都属于基本数据类型(整型、浮点型、字符型)。实际上C语言不仅仅可以操作基本数据类型,还可以操作构造数据类型(如结构体、数组等)。
数组是有序数据的结合,数组中的每个元素都属于同一种数据类型。
每个数组都有一个数组名,数组名代表了数组的起始地址。
数组中的元素由下标(或索引)标识,下标是从0开始到n-1(数组长度为n)的整数。
示例:一个拥有10个元素的int型数组a
int a[10];
其元素是a[0]、a[1]、a[2]、a[3]、a[4]、a[5]、a[6]、a[7]、a[8]、a[9]
数组同基本数据类型变量一样,必须要先定义再使用
1、数组的定义:
数据类型 数组名[长度]
其中长度必须是整数必须大于0。以下的定义都是合法的
int a[10];
float f[10];
int b[2*3];
获取数组元素:数组名[下标]。下标可以是变量、常量或常量表达式。
数组在定义后,就在内存中划分了一块空间用于存储数组。以int a[n]为例(n大于0):
内存中划分出一块空间用于存储数组,这块空间的大小是sizeof(int)*n,划分成n块来顺序存储a[0]~a[n-1]。数组名代表这块空间的首地址(也就是a[0]的地址)
计算数组所占内存空间的大小:sizeof(a);//计算数组a占内存空间大小
思考:已知一个数组(如int a[n]),在只知道数组名,不知道数组元素个数的情况下,如何算出数组中元素个数(即求出n的值)?
答案:sizeof(a)/sizeof(a[0]);
2、数组的初始化:
1)数组初始化的一般形式:
类型 数组名[数组长度]={值1,值2……};
示例:int a[10]={0,1,2,3,4,5,6,7,8,9};
将花括号内的值一一对应赋值到数组各个元素中
2)如果只给数组一部分元素赋值,则剩下的会变成0
示例:int a[10]={1,2,3};
则a[3]及以后的值都是0
如果想使一个int型数组中元素全部为0可以这样写:
int a[10]={0};
3)对数组内全部元素赋值时,由于元素个数已经确定,因此可以不指定数组长度。
示例:int a[5]={1,2,3,4,5};<---等价--->int a[]={1,2,3,4,5};
如果采取第二种写法,系统就会根据后面花括号内的个数来分配数组长度是5。但若数组长度与提供初值个数不同,则数组长度不可省略。
示例:定义一个数组,反序输出
#include<stdio.h>
int main()
{
    int i,a[]={0,1,2,3,4,5,6,7,8,9};//定义数组并初始化,系统分配数组长度是10
    for(i=9;i>=0;i--)
    {
        printf("%d ",a[i]);//将数组反序输出
    }
    printf("\n");
    return 0;
}
练习1:使用数组存储斐波那契数列前30项,并输出
答案:
#include <stdio.h>
#define MAX 30
int main()
{
    int i,fibonacci[MAX];
    fibonacci[0]=1;
    fibonacci[1]=1;
    for(i=2;i<MAX;i++)
    {
        fibonacci[i]=fibonacci[i-1]+fibonacci[i-2];
    }
    for(i=0;i<MAX;i++)
    {
        printf("%d\t",fibonacci[i]);
    }
    printf("\n");
    return 0;
}
练习2:从键盘输入10个学生的成绩,如果遇到大于100或者小于0的成绩需要提示输入错误重新输入。之后计算10个学生的总成绩和平均成绩
答案:
#include <stdio.h>
#define MAX 10
int main()
{
    int a[MAX];
    int i,sum=0;
    float avg=0;
    for(i=0;i<MAX;i++)
    {
        printf("请输入第%d个学生的成绩:",i+1);
        scanf("%d",&a[i]);
        if(a[i]<0 || a[i]>100)
        {
            printf("输入非法数据,请重新输入!\n");
            i--;//退回上次循环状态
        }
    }
    for(i=0;i<MAX;i++)
    {
        sum += a[i];
    }
    avg = (float)sum/MAX;
    printf("总成绩是%d\n平均成绩是%f\n",sum,avg);
    return 0;
}
练习3(选做):使用数组法解决约瑟夫环问题
约瑟夫入狱,监狱内共有33个犯人。某日33名犯人围成一圈,从第一个犯人开始报数,报到数字7的犯人出列,被枪毙,下一名犯人重新从1开始报数。依次类推,直至剩下最后1名犯人可被赦免。聪明的约瑟夫在心里稍加计算,算出了最后枪毙的位置,他站在这个位置,最终避免了自己被枪毙,逃出升天。
问:约瑟夫算出的是哪个位置?
提示:对于约瑟夫环问题来说,需要解决4个问题
⒈需要一个长度为33的数组,数组内需要存储什么?
⒉如何解决数组循环的问题?
⒊如何解决“逢7一杀”这个逻辑?
⒋如何处理“已死之人”?
答案:
#include<stdio.h>
int main()
{
    int fanren[33]={0};//用0/1表示犯人是否存活,0表示存活,1表示死亡
    int i,j,k=-1;
    for(i=0;i<32;i++)//外层循环表示要枪毙32个犯人
    {
        for(j=0;j<7;j++)//内层循环表示每隔7个犯人
        {
            k++;//表示当前犯人下标
            if(k==33)//越界处理
                k=0;
            if(fanren[k]==1)//如果该犯人已死,则应在报数中-1
                j--;
        }
        fanren[k]=1;//找到第7个犯人
        printf("第%d号已被枪毙\n",k+1);
    }
    for(i=0;i<33;i++)
    {
        if(fanren[i]==0)//跳过所有已被枪毙的犯人
            printf("第%d号存活\n",i+1);
    }
    return 0;
}
练习4(选做):从键盘输入10个各不相同的整数,存储在数组中,使用冒泡排序法将数组排序并输出
冒泡排序:是一种简单的排序算法
1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3)针对所有的元素重复以上的步骤,除了最后一个。
4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
答案:
#include <stdio.h>
#define MAX 10
int main()
{
    int a[MAX];
    int i,j,tmp;
    //输入10个数
    for(i=0;i<MAX;i++)
    {
        scanf("%d",&a[i]);
    }
    //输入完毕
    //冒泡排序
    for(i=0;i<MAX-1;i++)//外层循环表示循环MAX-1次
    {
        for(j=0;j<MAX-i-1;j++)//内层循环表示操作当前的数组元素下标
        {
            if(a[j]>a[j+1])//如果比它后面的大就交换
            {
                tmp = a[j];
                a[j] = a[j+1];
                a[j+1] = tmp;
            }
        }
    }
    //冒泡排序end
    //打印输出
    for(i=0;i<MAX;i++)
    {
        printf("%d ",a[i]);
    }
    printf("\n");
    return 0;
}

第二部分:二维数组
C语言中不仅可以存储一维数组,还可以存储二维数组。二维数组类似矩阵,有行/列等
1、二维数组的定义:数据类型 数组名[常量表达式1][常量表达式2];
示例:
int a[2][3];//定义一个2*3的二维int型数组
float f[3][4];//定义一个3*4的二维float型数组
2、二维数组的存储方式:
C语言对二维数组的存储方式是:将二维数组视为一种特殊的一维数组,它的元素又是一个一维数组。例如,对于二维数组:
int a[3][4];
它的存储方式是:
a[0]-------a[0][0]  a[0][1]  a[0][2]  a[0][3]
a[1]-------a[1][0]  a[1][1]  a[1][2]  a[1][3]
a[2]-------a[2][0]  a[2][1]  a[2][2]  a[2][3]
在内存中的存储顺序是:
a[0][0]->a[0][1]->a[0][2]->……->a[2][3]
获取数组元素:类似一维数组:数组名[下标1][下标2]
3、二维数组的初始化
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};
类似于一维数组,如果有未写的则默认为0
3)可以对二维数组的部分元素赋值:例如:
int a[3][4]={{1},{5},{9}};
则赋值结果是a[0][0]=1,a[1][0]=5,a[2][0]=9
int a[3][4]={{1},{0,6},{0,0,11}};
则赋值结果是a[0][0]=1,a[1][1]=6,a[2][2]=11
这种方法对非0元素较少时比较方便。
4)可以提供全部元素的初值,这样常量表达式1(即第一个下标)可以缺省不写,系统会根据输入的多少来计算行数。但常量表达式2(即第二个下标)不可缺省。例如:
int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
则系统自动计算出这个二维数组是a[3][4]
思考:如果int a[][4]={1,2,3,4,5,6,7,8,9,10};则该二维数组是多少?
答案:同样是a[3][4],只不过10以后的两个元素未初始化,变成默认值0

注意:C语言对于数组下标越界不予检查。因此对于数组的操作要十分注意其边界值。如果指定了数组长度而初始化元素个数多余指定的数组元素个数,如:
int a[2][3]={1,2,3,4,5,6,7,8,9};
则会只取前n个数据进行初始化(n是指定长度,如这里n=6),后面的剩余数据舍弃。编译期间编译器会报warning。

练习1:自定义一个3*4的矩阵,输出矩阵中值最大的元素,并输出其数组下标
答案:
#include <stdio.h>
#define LINE 3
#define COL 4
int main()
{
    int a[LINE][COL]={55,11,77,33,22,88,44,100,121,66,555,99};//自定义
    int i,j,max,x=0,y=0;
    max=a[x][y];
    for(i=0;i<LINE;i++)
    {
        for(j=0;j<COL;j++)
        {
            if(a[i][j]>max)
            {
                x=i;
                y=j;
                max=a[i][j];
            }
        }
    }
    printf("最大元素是%d\n数组下标是%d和%d\n",max,x,y);
    return 0;
}
练习2:打印杨辉三角型前10行
杨辉三角型:杨辉三角型是形如以下矩阵的三角形:
1
1 1
1 2 1
1 3 3  1
1 4 6  4  1
1 5 10 10 5 1
……
答案:
#include <stdio.h>
#define LINE 10
int main()
{
    int yanghui[LINE][LINE]={0};
    int i,j;
    //构造杨辉三角型
    for(i=0;i<LINE;i++)
    {
        for(j=0;j<=i;j++)
        {
            if(j==0)
                yanghui[i][j]=1;
            else if(i==j)
                yanghui[i][j]=1;
            else
                yanghui[i][j]=yanghui[i-1][j-1]+yanghui[i-1][j];
        }
    }
    //构造杨辉三角型end
    //打印杨辉三角型
    for(i=0;i<LINE;i++)
    {
        for(j=0;j<LINE;j++)
        {
            if(yanghui[i][j]!=0)
                printf("%-4d",yanghui[i][j]);
        }
        printf("\n");
    }
    //打印杨辉三角型end
    return 0;
}

4、使用数组的注意事项:
1)二维数组的第一个下标可以省略不写,编译器会根据我们初始化值的多少计算出数组的第一个下标。而第二个下标不可以省略不写。例如:
int a[3][]={1,2,3,4,5};//非法
如果这样写,编译器在分配内存的过程中不知道需要分配多大的空间(因为第二个下标未定),因此会报错。
2)不能为数组整体赋值。如:
int a[10]=1;
int a[10];a={1,2,3,4,5,6,7,8,9,10};
以上2种都是错误的

第三部分:字符数组与字符串
1、字符数组的定义:
字符数组的定义方法与之前介绍的一维数组的类似。例如:
char c[10];
2、字符数组的初始化:
字符数组的初始化与之前介绍的一维数组的类似。例如:
char c[]={'I',' ','a','m',' ','a',' ','s','t','u','d','e','n','t'};
练习:输入以下代码,编译,运行,查看结果。
#include<stdio.h>
int main()
{
    char diamond[][5]={{' ',' ','*'},{' ','*',' ','*'},{'*',' ',' ',' ','*'},{' ','*',' ','*'},{' ',' ','*'}};
    int i,j;
    for(i=0;i<5;i++)
    {
        for(j=0;j<5;j++)
        {
            printf("%c",diamond[i][j]);
        }
        printf("\n");
    }
    return 0;
}
3、字符串
如果我们一个字符一个字符键入的话,不仅操作不便,而且还容易出现错误。因此对于字符型数组,我们可以使用字符串来操作。
字符串:由双引号""引起来的字符序列。
字符串中每个字符都视为一个字符型数组的元素,存储占1字节。
字符串的结束标志是'\0'
/*************************************关于'\0'************************************************************/
'\0'是转义字符,在ASCII表中的编码为0(NULL)。它不是一个可见的字符,而是一个空操作符,即什么都不做。用'\0'表示字符串结束标志不会产生其他的附加操作,只是提供一个标识。
/*************************************关于'\0'end*********************************************************/
思考:0、'0'、'\0'、"0"相同么?有什么区别?
答案:
⒈0代表数字0
⒉'0'代表字符零,在ASCII表中的编码是48,是一个可见字符
⒊'\0'代表NULL,在ASCII表中的编码是0,是一个不可见字符、空操作符,作为字符串的结束标志。
⒋"0"代表字符串"零",这是一个字符串,实际上等价于'0''\0'(字符0后面加字符串结束标志'\0')

使用字符串常量对字符数组进行初始化:
char c[]={"I am student"};
或者省略花括号直接写成:
char c[]="I am student";
使用这种方式为字符数组赋值,访问数组内元素的方法同一维数组
c[0]--->I
c[1]--->' '(空格)
……
如果使用字符串常量给字符数组赋值,则字符数组的最后一个元素不是't'而是'\0'
char c[]="China";<---等价--->char c[]={'C','h','i','n','a','\0'};//多出的'\0'是字符串结束标志
因此,字符串与字符数组有稍许差别,不完全等价。字符数组若不人为添加,末尾是不会出现'\0'的,而字符串末尾系统会自动添加'\0'表示字符串结束。
输出字符串不必使用for()循环遍历整个数组,直接使用%s格式控制符即可。
printf("%s",c);
示例:
int main()
{
    char c[]="I am a Student.";
    printf("%s\n",c);
    return 0;
}
当然,我们也可以使用%s在scanf()函数中直接输入一个字符串:
scanf("%s",c);
不过,使用%s输入整个字符串需要注意以下2点:
⒈因为C语言不检查数组下标越界问题,因此如果直接键入字符串给字符数组,需要将字符数组设置的足够大,这样才不会丢失数据。
⒉输入字符串给字符数组,注意scanf()的第二个参数不要带&(取地址符)。因为数组名就代表了数组的地址。
示例:
int main()
{
    char c[64];//申请一块足够大的空间作为输入的字符串的存储空间
    scanf("%s",c);
    printf("%s\n",c);
    return 0;
}

第四部分:字符串处理函数
C语言提供各种处理字符串的函数,可以简便地处理字符串。
字符串处理函数在头文件string.h中,使用时不要忘了包含头文件
1、字符串输入输出函数:puts()和gets()
1)puts()函数
函数原型:
int puts(char *string);
使用方法:
puts(字符数组名 或 字符串指针);
说明:
puts()函数将一个字符串输出到标准输出流中,再输出到屏幕上。注意puts()函数只能输出字符串,不能进行其他操作
puts()函数等价于:printf("%s\n",string);
示例:
#include<stdio.h>
#include<string.h>
int main()
{
    puts("Hello World!");
    return 0;
}
2)gets()函数
函数原型:
char* gets(char *string);
使用方法:
gets(字符数组名);
说明:
gets()函数从标准输入流中获得一个字符串,直至接收换行符\n或EOF为止。将获得的字符串储存到字符数组内,读取出的换行符转换成'\0'
注意:
gets()函数不会检查待存储的字符数组的大小,若没有接收到换行符或EOF则会无限制读取下去。因此待存储的字符数组一定要足够大。
事实上gets()函数缺点很多,因此我们不推荐使用。推荐使用scanf()或fgets()函数。
gets()函数和puts()函数一次只能操作一个字符串,不能写成
gets(str1,str2);
gets()和scanf("%s")的区别:当键入空格时,scanf("%s")认为该字符串已经结束,空格后的是下一个字符串内容。而gets()函数则会当空格做本字符串的一部分。

2、字符串连接函数:strcat()
函数原型:
char* strcat(char *s1,const char *s2);
使用方法:
strcat(字符数组1,字符数组2);
说明:
strcat是string catenate的缩写,作用是把后一个字符串(字符数组2)拼接到前一个字符串(字符数组1)之后,结果存放在字符数组1中。
示例:
#include<stdio.h>
#include<string.h>
int main()
{
    char str1[30]="Hello ";
    char str2[]="World!";
    strcat(str1,str2);
    printf("%s\n",str1);
    return 0;
}
注意:
1)字符数组1的空间需要足够大以能够存储得下两个字符串,否则数组长度不够,编译阶段会报warning,并且不会输出正确结果。
2)两个字符串拼接时,字符数组1之后的'\0'会取消,只在拼接后的新的字符数组之后添加'\0'
练习:自定义2个字符数组,不使用系统提供的strcat()函数,实现strcat()函数功能。
答案:
#include<stdio.h>
int main()
{
    char s1[30]="Hello ";
    char s2[]="World!";
    int i=0,j=0;
    while(s1[i]!='\0')//寻找s1中'\0'的位置
    {
        i++;
    }
    while(s2[j]!='\0')//开始字符串拼接,直至s2出现'\0'(即字符串2结束)为止
    {
        s1[i]=s2[j];
        i++;
        j++;
    }
    s1[i]='\0';//不要忘了添加字符串结束符'\0'
    printf("%s\n",s1);//输出
    return 0;
}

3、字符串复制函数:strcpy()和strncpy()
1)strcpy()函数
函数原型:
char* strcpy(char *str1,const char *str2);
使用方法:
strcpy(字符数组1,字符数组2或字符串2);
说明:
strcpy()是string copy的缩写,表示“字符串复制函数”,将字符数组2或字符串2的内容复制给字符数组1。
示例:
#include<stdio.h>
#include<string.h>
int main()
{
    char s1[10],s2[]="China";
    strcpy(s1,s2);
    printf("%s\n",s1);
    return 0;
}
注意:
1)字符数组之间不能互相赋值。以下这种方式是非法的:
char s1[];
char s2[]="China";
s1=s2;
因此如果给字符数组间赋值,必须使用strcpy()函数。
2)字符数组1的空间必须足够大以能够容纳字符数组2或字符串2的所有字符。这点与strcat()相同。
3)strcpy()的两个参数,第一个参数必须是字符数组名,第二个参数可以是字符数组名或直接是一个字符串常量。例如:
strcpy(s1,"China");
4)如果字符数组2或字符串2比字符数组1短,则在复制操作后,字符数组1内没被复制所覆盖掉的内容将会保留
练习:自定义2个字符数组,不使用系统提供的strcpy()函数,实现strcpy()函数功能。
答案:
#include<stdio.h>
int main()
{
    char s1[30];
    char s2[]="Hello World!";
    int i=0;
    while(s2[i]!='\0')
    {
        s1[i]=s2[i];
        i++;
    }
    s1[i]='\0';//不要忘了添加字符串结束符'\0'
    printf("%s\n",s1);
    return 0;
}

2)strncpy()函数
函数原型:
char* strncpy(char *s1,const char *s2,int n);//实际上第三个参数是size_t型
使用方法:
strncpy(字符数组1,字符数组2或字符串2,长度);
说明:
类似strcpy()函数,只不过是取出字符数组2或字符串2前n个字符复制到字符数组1中。
注意:n的值不能超过字符数组2或字符串2的总长度。

4、字符串比较函数:strcmp()
函数原型:
int strcmp(const char *s1,const char *s2);
使用方法:
strcmp(字符数组1或字符串1,字符数组2或字符串2);
说明:
字符串比较的规则是:将字符串1和字符串2的第一个字符拿出比较ASCII码,如果字符串1大则返回一个正数,如果字符串2大则返回一个负数。如果相等则比较下一个字符。直至遇到不同字符或遇到字符串结尾'\0'。如果两个字符串相同,则返回0。
示例:
#include<stdio.h>
#include<string.h>
int main()
{
    char s1[]="Hello";
    char s2[]="World";
    if(strcmp(s1,s2)>0)
    {
        printf("s1字符串大\n");
    }
    else if(strcmp(s1,s2)<0)
    {
        printf("s2字符串大\n");
    }
    else
    {
        printf("两个字符串相同\n");
    }
    return 0;
}
注意:
字符数组或字符串之间不能直接使用关系运算符来比较大小,以下的操作都是非法的:
str1<str2;str1==str2;str1>str2;
必须使用strcmp函数来比较字符串
练习:自定义2个字符数组,不使用系统提供的strcmp()函数,实现strcmp()函数功能
答案:
//注意:这里不是main()函数而是另一个自定义函数。有关函数的使用方法我们将在接下来的课程中学习
#include<stdio.h>
int my_strcmp(char *s1,char *s2)
{
    int i=0,j=0;
    while(s1[i]!='\0' && s2[j]!='\0')
    {
        if(s1[i]>s2[j])
            return 1;
        else if(s1[i]<s2[j])
            return -1;
        i++;
        j++;
    }
    if(s1[i]!='\0' && s2[j]=='\0')
        return 1;
    else if(s1[i]=='\0' && s2[j]!='\0')
        return -1;
    return 0;
}
int main()//主函数,程序入口
{
    char s1[]="Hello";
    char s2[]="World";
    switch(my_strcmp(s1,s2))//在这里调用my_strcmp()函数
    {
    case 1:
        printf("s1大\n");break;
    case 0:
        printf("一样大\n");break;
    case -1:
        printf("s2大\n");break;
    }
    return 0;
}

5、测字符串长度函数:strlen()
函数原型:
int strlen(const char *s);//实际不是int类型而是size_t类型
使用方法:
strlen(字符数组 或 字符串);
说明:
strlen()函数是string length的缩写,其功能是测试字符串的长度(不包括'\0')
示例:
#include<stdio.h>
#include<string.h>
int main()
{
    char s1[]="Hello World";
    printf("长度是%d\n",strlen(s1));
    return 0;
}
注意:
sizeof()与strlen()的区别:
sizeof()的长度是整个字符数组的总长度,其中包括'\0';而strlen()不包括'\0'。例如:
char s1[]="Hello";
则sizeof(s1)=6,而strlen(s1)=5。
练习:自定义1个字符数组,不使用系统提供的strlen()函数,实现strlen()函数功能
答案:
#include<stdio.h>
int my_strlen(char *s1)
{
    int i;
    for(i=0;s1[i]!='\0';i++);
    return i;
}
int main()
{
    char s1[]="Hello World";
    printf("%d\n",my_strlen(s1));
    return 0;
 

猜你喜欢

转载自blog.csdn.net/qq_34427165/article/details/81225679