数组与字符串

132 阅读6分钟

数组

数组的概念

  • 数组是内存中连续相同类型的变量空间

数组的地址

#include <stdio.h>
int main(int argc, char const *argv[])
{

    int a[10];
    printf("a的地址 = %p\n",a);
    printf("&a的地址 = %p\n",&a);
    for(int i = 0;i<10;i++){
     printf("a[%d]的地址 = %p\n",i,&a[i]);
    }
    printf("&a+1的地址 = %p\n",&a+1);
    printf("a+1的地址 = %p\n",a+1);
    printf("&a[0]+1的地址 = %p\n",&a[0]+1);
    return 0;
}

打印结果

a的地址 = 0x7ffeed1e2100
&a的地址 = 0x7ffeed1e2100
a[0]的地址 = 0x7ffeed1e2100
a[1]的地址 = 0x7ffeed1e2104
a[2]的地址 = 0x7ffeed1e2108
a[3]的地址 = 0x7ffeed1e210c
a[4]的地址 = 0x7ffeed1e2110
a[5]的地址 = 0x7ffeed1e2114
a[6]的地址 = 0x7ffeed1e2118
a[7]的地址 = 0x7ffeed1e211c
a[8]的地址 = 0x7ffeed1e2120
a[9]的地址 = 0x7ffeed1e2124
&a+1的地址 = 0x7ffeed1e2128
a+1的地址 = 0x7ffeed1e2104
&a[0]+1的地址 = 0x7ffeed1e2104
  • 数组名是一个地址,因此是一个常量
  • 当定义一个数组的时候,编译器已经为数组分配了内存空间
  • 数组名a是当前数组的首地址,等于取地址符号(&)之后的地址,也等于第一个元素的地址
  • 因为a 是int数组,因此它内部的每一个元素都是占用四个字节
  • a+1是取的是4个字节之后地址
  • &a+1取的是数组长度*4个字节之后的地址

数组的初始化

  • 在定义数组的同时进行赋值,称为初始化
  • 全局数组如果不初始化,编译器将其初始化为0,局部数组如果不初始化,内容为随机值
#include <stdio.h>
int b[10];
int main(int argc, char const *argv[])
{
    int a[10];
    printf("局部数组 a[");
    for(int i = 0;i<10;i++){
     printf("%d,",a[i]);
    }
    printf("]\n");
    printf("全局数组 b[");
    for(int i = 0;i<10;i++){
     printf("%d,",b[i]);
    }
    printf("]\n");
    return 0;
}

打印结果:

局部数组 a[0,0,0,0,0,0,0,0,-486964928,32766,]
全局数组 b[0,0,0,0,0,0,0,0,0,0,]
初始化的几种方式
   int a[10]={1,2,3,4,5,6,7,8,9,10}; // 定义一个数组,同时初始化所有成员变量
   int a[10] ={1,2,3};// 初始化前三个成员,后面多有的元素都设置为0
   int a[10] ={0};// 所有的成员都设置为0
   int a[] ={1,2,3,4,5};// 如果数组中不定义元素个数,定时的时候必须初始化

数组的长度 使用sizeof(a)/sizeof(a[0])

二维数组

  • 二维数组在概念上是二维的,其下标在两个方向上变化,对其访问一般需要两个下标
  • 在内存中并不存在二维数组,二维数组实际上的硬件存储器是连续编址的,也就是说在内存中只有一维数组,即放完一行之后顺次放入第二行,和一维数组的存放方式是一样的
#include <stdio.h>
int main(int argc, char const *argv[])
{
    int a[3][4];
    for(int i = 0;i<3;i++){
        for(int j = 0;j<4;j++){

            printf("a[%d][%d]的地址=%p\n",i,j,&a[i][j]);
        }
    }
    return 0;
}

打印结果: 地址都是连续的

a[0][0]的地址=0x7ffedfeec0f0
a[0][1]的地址=0x7ffedfeec0f4
a[0][2]的地址=0x7ffedfeec0f8
a[0][3]的地址=0x7ffedfeec0fc
a[1][0]的地址=0x7ffedfeec100
a[1][1]的地址=0x7ffedfeec104
a[1][2]的地址=0x7ffedfeec108
a[1][3]的地址=0x7ffedfeec10c
a[2][0]的地址=0x7ffedfeec110
a[2][1]的地址=0x7ffedfeec114
a[2][2]的地址=0x7ffedfeec118
a[2][3]的地址=0x7ffedfeec11c
二维数组的初始化
可以连续赋值
#include <stdio.h>
int main(int argc, char const *argv[])
{
    int a[3][4] ={1,2,3,4,5,6,7,8,9,10,11,12};

    for(int i = 0;i<3;i++){
        for(int j = 0;j<4;j++){

            printf("a[%d][%d]= %d\n",i,j,a[i][j]);
        }
    }
    return 0;
}

打印结果:

a[0][0]= 1
a[0][1]= 2
a[0][2]= 3
a[0][3]= 4
a[1][0]= 5
a[1][1]= 6
a[1][2]= 7
a[1][3]= 8
a[2][0]= 9
a[2][1]= 10
a[2][2]= 11
a[2][3]= 12
部分初始化

部分初始化,会依次给前面的元素赋值,后面的元素为0

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int a[3][4] ={1,2,3,4,5};

    for(int i = 0;i<3;i++){
        for(int j = 0;j<4;j++){

            printf("a[%d][%d]= %d\n",i,j,a[i][j]);
        }
    }
    return 0;
}

打印结果:

a[0][0]= 1
a[0][1]= 2
a[0][2]= 3
a[0][3]= 4
a[1][0]= 5
a[1][1]= 0
a[1][2]= 0
a[1][3]= 0
a[2][0]= 0
a[2][1]= 0
a[2][2]= 0
a[2][3]= 0
分块初始化
#include <stdio.h>
int main(int argc, char const *argv[])
{
    int a[3][4] ={{1},{2},{3}};

    for(int i = 0;i<3;i++){
        for(int j = 0;j<4;j++){

            printf("a[%d][%d]= %d\n",i,j,a[i][j]);
        }
    }
    return 0;
}

打印结果: 这相当于给每个行首都赋值,其余都为0

a[0][0]= 1
a[0][1]= 0
a[0][2]= 0
a[0][3]= 0
a[1][0]= 2
a[1][1]= 0
a[1][2]= 0
a[1][3]= 0
a[2][0]= 3
a[2][1]= 0
a[2][2]= 0
a[2][3]= 0
初始化中不指定行

如果在定义二维数组的时候同时进行了初始化,可以不指定行,但是必须制定列,这样根据初始化的元素的个数可以推算出行数

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int a[][3] ={1,2,3,4,5};

    for(int i = 0;i<3;i++){
        for(int j = 0;j<4;j++){

            printf("a[%d][%d]= %d\n",i,j,a[i][j]);
        }
    }
    return 0;
}

打印结果: 推算出来的行数就是2,因为执意打印后面,都是越界的信息

a[0][1]= 2
a[0][2]= 3
a[0][3]= 4
a[1][0]= 4
a[1][1]= 5
a[1][2]= 0
a[1][3]= -557449013
a[2][0]= -557449013
a[2][1]= -995024111
a[2][2]= -347676352
a[2][3]= 32766

二维数组的长度
#include <stdio.h>
int main(int argc, char const *argv[])
{
    int a[][3] ={1,2,3,4,5};
    printf("a的大小:%lu\n",sizeof(a)); //24
    int total = sizeof(a)/sizeof(a[0][0]);

    int row = sizeof(a)/sizeof(a[0]);

    int colum = sizeof(a[0])/sizeof(a[0][0]);

    printf("a的个数数:%d\n",total); //6

    printf("a的行数:%d\n",row); //2
    printf("a的列数:%d\n",colum); //3

    return 0;
}

二维数组的地址
#include <stdio.h>
int main(int argc, char const *argv[])
{
    int a[][3] ={1,2,3,4,5};


    for(int i = 0;i<2;i++){
        for(int j = 0;j<3;j++){
          printf("a[%d][%d]地址 = %p\n",i,j,&a[i][j]);
        }
    }
    printf("a = %p\n",a); // 表示当前的首地址
    printf("&a = %p\n",&a); /// 表示是地址的地址
    printf("a[0] = %p\n",&a[0]); /// 第一行的地址
    printf("a[0][0] = %p\n",&a[0][0]); /// 第一行第一个元素的地址

    printf("a+1 = %p\n",a+1); // 跳过一行
    printf("&a+1 = %p\n",&a+1); /// 跳过整个数组
    printf("a[0]+1 = %p\n",&a[0]+1); /// 跳过一行
    printf("a[0][0]+1 = %p\n",&a[0][0]+1); /// 跳到下一个元素位置
    return 0;
}

  • 数组名为数组首元素的地址,二维数组的第0个元素为一维数组

字符数组与字符串

  • C语言中没有字符串这种数据结构,可以通过char的数组来替代
  • 字符串一定是一个char的数组,但是char的数组未必是一个字符串
  • 数字0(和'\0'等价)结尾的char数组才是一个字符串,但是如果char数组没有以数字0结尾,那么就不是一个字符串,只是普通的字符数组,所以字符串是一种特殊的char数组
#include <stdio.h>
int main(int argc, char const *argv[])
{
     char s[] = {'a','b','c','d'};
     printf("s = %s\n", s); ///abcdP'�� 因为没有0结尾 后面也在打印,但是乱码

     char s1[] = {'a','b','c','d',0};

     printf("s1 = %s\n", s1); ///abcd


     char s2[] = {'a','b','c','d',0,'H','E','l','l','o'};

     printf("s2 = %s\n", s2); ///输出也是abcd 因为输出的是字符串 当输出到0的时候自动就结束了

    return 0;
}

字符串的初始化

#include <stdio.h>
int main(int argc, char const *argv[])
{

     /// 如果不指定长度,并且没有0作为结束符, 字符数组的长度就是有多少字符就是多长,
     /// 当打印成字符串的时候 因为没有0作为结束符,因为后面会出现乱码
     char buf[] ={'a','b','c'}; 
     printf("buf =%s\n",buf);

    //  /// 如果指定了长度,后面没有赋值的元素,自动补0 
     char buf2[100] = {'a','b','c'}; 
     printf("buf2 =%s\n",buf2);
     //// 保存的是"Hello\0"
    char buf3[] = "Hello"; 
    return(0);

    return 0;
}

字符串的输入与输出

scanf
#include <stdio.h>
int main(int argc, char const *argv[])
{
     char str[100];
     scanf("%s",str);
     printf("输入的字符串 = %s\n",str);

    return 0;
}

scanf 是从标准输入文件Stdin中读取字符串 保存在char数组中

  • 如果遇到空格 或者\n 会提前结束读取
  • 如果存放读取字符的空间不足,会继续向后存放,会造成内存污染
gets()

` #include<stdio.h>

char * gets(char *s)`

从标准输入文件读取字符,并保存在s指定的内存空间,直到出现换行符号或者读到文件结尾为止 参数: s 字符串首地址 返回值: 如果成功,返回的是读入的字符串,如果失败 返回的是null

gets和scanf的区别

  • gets允许输入的字符串含有空格
  • scanf 不允许含有空格

由于scanf和gets无法知道字符串的大小,必须遇到换行符或者文件的结尾为止才接收输入,因此容易导致字符数组越界

#include <stdio.h>
int main(int argc, char const *argv[])
{
     char str[100];
     gets(str);
     printf("输入的字符串 = %s\n",str);

    return 0;
}

fgets
   # include<stdio.h>
   char * fgets(char *s,int size,FILE *stream)
  • 从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符,读到文件结尾或是已读了size-1个字符为止,最后会自动加上字符‘\0’作为字符串结束
  • 参数 s:存放的首地址,size :指定最大读取字符串的长度(size-1),stream:文件指针,如果读取见哦安输入的字符串,固定是stdin
  • 返回值: 成功,返回读取的字符串,失败,返回null

fgets在读取一个用户通过建安输入的字符串的时候,同时把用户输入的回车作为字符串的一部分,通过scanf和gets输入一个字符的时候,不包含结尾的'\n',但是通过fgets结尾多个‘\n’.fgets函数是安全的,不存在缓冲区溢出的问题

puts
   # include<stdio.h>
   int puts(const char *s)

从标准设备中输出s字符串,在输出完成后自动输出一个'\n' 参数 s 字符串首地址 返回值 成功 非负数 失败 -1

#include <stdio.h>
int main(int argc, char const *argv[])
{
     char str[100];
     gets(str);
     printf("输入的字符串 = %s\n",str);

     puts(str);

    return 0;
}

fpus
    #include<stdio.h>
    int fputs(const char *s,FILE * stream)

将s所指定的字符串写入到stream 指定的文件中,字符串‘\0’ 不会写入到文件中

  • 参数 s 字符串首地址 stream 文件指针,如果把字符串输出到屏幕.固定写为stdout
  • 返回值 成功为0 失败为 -1 fputs是puts的文件操作版本,但是fputs不会自动输出一个‘\n’

其他字符串操作函数

strlen
    #include<string.h>
    size_t strlen(const char *s)

计算制定字符串s的长度,不包含字符串结束符'\0'

  • 参数 s 字符串首地址
  • 返回值字符串s的长度 size_t 为unsigned int 类型
#include <stdio.h>
int main(int argc, char const *argv[])
{
    char str[] = "abc\0def";
    int len = strlen(str);
    printf("str =%s\n", str);
    printf("len = %d\n",len); // len = 3
    return 0;
}

计算字符串的长度到'\0'结束

字符串追加