一维数组和字符数组

一维数组和字符数组

数组的定义
C 语言提供的数组,通过一个符号来访问多个元素 数组内存储的数据有如下 特点: ​ 1、具有相同的数据类型 ​ 2、使用过程中需要保留原始数据 一维数组的定义格式
类型说明符 数组名 [常量表达式];
int a[10]; //定义一个整型数组,数组名为a,它有10个元素
声明数组时要遵循以下规则: (1)数组名的命名规则和变量名的相同,即遵循标识符命名规则。 (2)在定义数组时,需要指定数组中元素的个数,方括号中的常量表达式用来表示元素的 个数,即数组长度。 (3)常量表达式中可以包含常量和符号常量,但不能包含变量。也就是说,C 语言不允许 对数组的大小做动态定义,即数组的大小不依赖于程序运行过程中变量的值。 错误的声明示例(最新的 C 标准支持,但是最好不要这么写)
int n;
scanf("%d", &n); /* 在程序中临时输入数组的大小 */
int a[n];
数组声明的其他常见错误如下:
float a[0]; /* 数组大小为 0 没有意义 */
int b(2)(3); /* 不能使用圆括号 */
int k=3, a[k]; /* 不能用变量说明数组大小*/
整型数组在内存中的表现形式,以int a[2]为例。 ​ 1、每个元素都是整型元素,占用4字节 ​ 2、数组元素的引用方式是"数组名[下标]",访问数组a中的元素方式是a[0]、a[1],注意数组元素下标从0开始。
一维数组的初始方法
(1)在定义数组时对数组元素赋初值
int a[10]={0,1,2,3,4,5,6,7,8,9}
(2)给部分元素进行赋值,表示只对前五个元素赋初值,后五个元素的值为0
int a[10]={0,1,2,3,4}
(3)对数组中的元素全部复制为0
int a[10]={0}
int a[10]={0,0,0,0,0,0,0,0,0,0}
(4)对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组的长度
int a[]={1,2,3,4,5};
数组的访问越界
数组越界指的是尝试访问数组元素时使用了超出数组有效索引范围的索引值。数组的有效索引范围是从0到数组长度减一,因为在C语言中,数组的索引是从0开始的。如果使用了超出这个范围的索引值,就会导致数组越界错误。 当发生数组越界时,程序可能会表现出不可预测的行为,因为它可能会访问到未分配给数组的内存地址上的数据,这可能会导致程序崩溃、产生错误的计算结果,或者导致其他不确定的结果。 举例来说,在一个长度为5的数组中,有效的索引范围是0到4。如果尝试使用索引5来访问数组元素,就会导致数组越界。
#include <stdio.h>
#include <string.h>

int main(){
    int a[5]={0,1,2,3,4};
    int i=10;
    int j=20;
    a[5]=6; //越界 
    a[6]=7; //此处越界会改变变量j的值 
    printf("i=%d\n",i);
    printf("j=%d\n",j);
    return 0;
}
程序运行的结果为i=10 j=7,但是我们并未对变量j进行赋值,这就是数组访问越界后产生的结果,读者可以思考,我们是先定义i再定义j,为什么不是改变i的值,而是改变了j的值? 编译器并不检查程序对数组下标的引用是否在数组的合法范围内。这种不加检查的行为有好处也有坏处,好处是不需要浪费时间对有些已知正确的数组下标进行检查,坏处是这样做将无法检测出无效的下标引用。一个良好的经验法则是:如果下标值是通过那些已知正确的值计算得来的,那么就无须检查;如果下标值是由用户输入的数据产生的,那么在使用它们之前就必须进行检查,以确保它们位于有效范围内。
数组的传递
一维数组的传递,数组长度无法传递给子函数 C语言的函数调用方式是值传递
#include <stdio.h>
#include <string.h>

void print(int b[],int len) //形参 
{
    int i;
    for(i=0;i<len;i++)
    {
        printf("%d",b[i]);
    }
    b[4]=20;
    printf("\n");
 } 

int main(){
    int a[5]={0,1,2,3,4}; //实参 
    print(a,5);
    printf("a[4]=%d\n",a[4]);
    return 0;
}  
在主函数计算数组的大小时,可以用sizeof函数去判断数组的大小(sizeof(a)/sizeof(int)),但是将上面的代码放入void时,步入子程序调试,读者会发现,sizeoof得出的大小是8字节,并不是传入的20字节,这是因为参数在传递时,弱化为了指针,这一部分知识会在指针部分进行讲解。
字符数组初始化及传递
字符数组的定义方法与前面介绍的方法类似
char c[10];
初始化的方法有三种,可以对每个字符进行单独赋值初始化,也可以对整个数组进行初始化,最后的方法也是工作中最常用的。
c[0]='I';c[1]=' ';c[2]='a';c[3]='m';c[4]='';c[5]='h';c[6]='a';c[7]='p';c[8]='p';c[9]='y';
char c[10]={'I','a','m','h','a','p','p','y'}
char c[10]= "hello"
因为 C 语言规定字符串的结束标志为'\0',而系统会对字符串常量自动加一个'\0',为了保证处理方法一致,一般会人为地在字符数组中添加'\0',所以字符数组存储的字符串长度必须比字符数组少 1 字节。例如,char c[10]最长存储 9 个字符,剩余的 1个字符用来存储'\0'。
#include <stdio.h>
#include <string.h>

void print(char c[]) //形参 
{
    int i=0;
    while(c[i]) //字符数组存储字符串,必须存储结束符'\0'
    {
        printf("%c",c[i]);
        i++;
    }
    printf("\n");
 } 

int main(){
    char c[5]={'h','e','l','l','o'};
    char d[5]="how";
    printf("%s\n",c); //越界
    printf("%s\n",d);
    print(d);
    return 0;
}  
本人使用Dev-C++,所以出现越界问题时,编译器提示在定义的字符数组内太多参数了([Error] too many initializers for 'char [5]'),其他编译器如王道龙哥使用Clion则会在第一个hello后出现乱码。这是因为printf 通过%s 打印字符串时,原理是依次输出每个字符,当读到结束符'\0'时,结束打印,但是当前字符串并没有给'\0'预留位置,在内存视图看来,'\0'并没有在hello结束的位置,他们两个中间还有其他数值,这就是出现乱码的原因。
scanf读取字符串
#include <stdio.h>
#include <string.h>

int main(){
    char c[10];
    char d[10];
    scanf("%s",c);
    printf("%s\n",c);
    scanf("%s%s",c,d);
    printf("c=%s,d=%s",c,d); 
    return 0;
}  
scanf函数通过%s读取字符串,对c和d分别输入对应的内容,注意scanf函数会忽略空格和回车,所以在输入有空格分隔的字符串时,需要使用多个参数进行接收。
get函数和puts函数
get函数类似于scanf函数,用于读取标准输入。由于scanf函数在读取字符串遇到空格会自动结束的问题,可以使用gets函数进行处理。 get函数格式
char *gets(char *str);
gets 函数从 STDIN(标准输入)读取字符并把它们加载到 str(字符串)中,直到遇到换行符(\n)。get函数不会忽略空格,而是将其作为字符串的一部分读取到缓冲区中,遇到换行符后不会存储换行符,而是将其翻译成空字符'\0'。 put函数类似于prinft函数,用于输出标准输出。相对于printf函数,puts函数只能用于输出字符串,同时多打印一个换行符,等价于printf("\n")。 put函数格式
int puts(char *str);
#include <stdio.h>
#include <string.h>

int main(){
    char c[10];
    gets(c);
    puts(c);
    return 0;
}  
str系列字符串操作函数
str 系列字符串操作函数主要包括 strlen、strcpy、strcmp、strcat 等。strlen 函数用于统计字符串长度,strcpy 函数用于将某个字符串复制到字符数组中,strcmp 函数用于比较两个字符串的大小,strcat 函数用于将两个字符串连接到一起。各个函数的具体格式如下所示。
#include <string.h>
size_t strlen(char *str);
char *strcpy(char *to, const char *from);
int strcmp(const char *str1, const char *str2);
char *strcat(char *str1, const char *str2)
例子:
#include <stdio.h>
#include <string.h>

int mystrlen(char c[]){ //strlen统计字符串长度 
    int i=0;
    /*
    while循环读取数组内容,直到遇到空字符结束
    while循环后的分号没有错误,因为要一直读取数组的内容,直到空字符结束 
    */ 
    while(c[i++]); 
    {
        return i-1; //由于会读取到空字符'\0',所以减一才是真正的字符串长度 
    }
}
int main(){
    int len;
    char c[20];
    char d[100]="caijxlinux";
    gets(c);  //输入hello 
    puts(c);
    len=strlen(c); //使用strlen函数读取字符串长度 
    printf("len=%d\n",len);
    len=mystrlen(c); //使用自定义方法读取字符串长度 
    printf("mystrlen=%d\n",len);
    strcat(c,d); //将d的结果追加到c后,输出结果为hellocaijxlinux 
    puts(c);
    strcpy(d,c);  //将c的内容复制到d,输出结果为hellocaijxlinux 
    puts(d);
    char e[10]="hella";  
    printf("c?e %d\n",strcmp(c,e)); //用c和e作对比,由于o的ascii比a大,所以返回值为1 
    return 0;
}  
注意: 1、strcat函数中目标数组必须大于拼接后的字符串大小,即sizeof(c)>strlen(hellocaijxlinux),否则会造成访问越界,如果无法确定目标数组的大小是否足够,可以考虑使用更安全的函数,比如strncat函数,以避免越界访问。 2、strcpy函数中目标数组一定要大于字符串大小,即sizeof(d)>strlen(c),否则会造成访问越界。 3、读者可以单独使用strcmp函数观察效果,如strcmp("hello","hello"),此时返回值为0,strcmp("hello","hellp"),此时返回值为-1。 附带王道OJ平台5-2参考代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void reverstr(char *str, char *reversed_str) {
    int length = strlen(str);
    int i, j;
    for (i = 0, j = length - 1; i < length; i++, j--) {
        reversed_str[i] = str[j];
    }
    reversed_str[length] = '\0'; // 添加字符串结束符
}

int main() {
    char str[100];
    gets(str);
    char reversed_str[100]; // 声明一个变量用于存放反转后的字符串
    reverstr(str, reversed_str); // 反转字符串 str 并存储到 reversed_str 中
    int result = strcmp(str, reversed_str); // 比较原始字符串和反转后的字符串
    if (result < 0) {
        printf("%d\n", -1);
    } else if (result > 0) {
        printf("%d\n", 1);
    } else {
        printf("%d\n", 0);
    }
    return 0;
}