C语言学习 五、一维数组与字符数组

5.1一维数组

5.1.1数组的定义

数组特点:

  1. 具有相同的数据类型
  2. 使用过程中需要保存原始数据

C语言为了方便操作这些数据,提供了一种构造数据类型——数组,数组是指一组具有相同数据类型的数据的有序集合。

一维数组的定义格式为

数据类型 数组名[常量表达式];

例如:

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]; //不能用变量说明数组大小

5.2一维数组在内存中的存储

一维数组初始化方法:

(1)在定义数组时对数组元素赋初值,例如:

int a =[10] = {0,1,2,3,4,5,6,7,8,9};

不能写成

int a[10];

a[10] = {0,1,2,3,4,5,6,7,8,9};

(2)可以只给一部分元素赋值,例如:只给前5个元素赋初值,后5个元素值为0

int a =[10] = {0,1,2,3,4};

(3)如果要使一个数组中全部元素的值为0,可以写成:

int a =[10] = {0,0,0,0,0,0,0,0,0,0};

int a[10] = {0}; //更推荐这种写法

(4)在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组的长度(编译器具有自动计算有几个元素的功能)。(考研初试不建议使用,因为改卷老师需要手动数有几个数)

int a[]={1,2,3,4,5};

 5.3数组的访问越界

5.3.1访问越界例子

【访问越界演示】

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5}; //定义数组
    int j = 20;
    int i = 10;
    arr[5] = 6; //访问越界
    arr[6] = 7;
    printf("i=%d\n", i); //i=7 i并没有赋值,但是值却变化了
    return 0;
}

【运行结果】

 5.3.2查看内存视图

在内存视图依次输入&arr,&j,&i 来查看整型数组arr,整型变量j,整型变量i的地址。

 

 

 5.3.3总结
  • 访问越界是很危险的,C语言并没有对访问越界进行检查
  • CLion当中的地址都是8字节64位的,考研当中给的大多数为4个字节,即Windows32位控制台应用程序为0x00 00 00 00 到 0xFF FF FF FF,总计为2的32次方,大小为4G。
  • 数组注意点:编译器并不检查程序对数组下标的引用是否在数组的合法范围内:

好处:不用浪费时间对有些已知正确的数组下标进行检查

坏处:无法检测出无效的下标引用

一个良好的经验法则:

  • 如果下标值是通过那些已知正确的值计算得来则无需检查
  • 如果下标值是由用户输入产生,那么使用它们之前必须进行检查,以确保在有效范围内

5.4数组的传递

【数组传递反例】

#include <stdio.h>

//一维数组传递
//子函数是把一个常用的功能封装起来
//数组名传递到子函数后弱化成指针(8字节),子函数的形参接收到的是数组的起始地址,因此不能把数组的长度传递给子函数
void print(int a[]) {
    //数组传递不需要写数字,因为数组长度传递不过来
    int i;
    for (int i = 0; i < sizeof(a) / sizeof(int); i++) {
        //sizeof(a)/sizeof(int)
        //数组整个的长度/int的长度 即20个字节/int 4个字节 = 5
        printf("%d\n", a[i]);
    }
}

//main函数就是主函数
int main() {
    int a[5] = {1, 2, 3, 4, 5};
    print(a); //调用子函数
    return 0;
}

【运行效果】

 【单步调试】

【正确的数组传递例子】

#include <stdio.h>

//一维数组传递
//子函数是把一个常用的功能封装起来
//数组名传递到子函数后弱化成指针(8字节),子函数的形参接收到的是数组的起始地址,因此不能把数组的长度传递给子函数
void print(int b[], int length) { //多定义一个变量length作为数组的长度
    //数组传递不需要写数字,因为数组长度传递不过来
    int i;
    for (int i = 0; i < length; i++) {
        printf("%3d", b[i]);
    }
    printf("\n");//换行
    b[4] = 10;
}

//main函数就是主函数
int main() {
    int a[5] = {1, 2, 3, 4, 5};
    print(a, 5); //调用子函数 
    printf("b[4]=%d\n",a[4]); //发现b[4]值发生改变
    return 0;
}

【运行效果】

总结:

  • 在调用函数时,一维数组的长度传递不过去,所以通过length来传递数组中的元素个数。
  • 实际数组名中存储的数组的首地址,在调用函数传递时,是将数组的首地址赋给了变量b。
  • 在b[ ]的方括号中填写任何数字是没有意义的,因为不能将数组长度传递给子函数。
  • 此时在print函数内修改元素b[4]=10可以看到数组b的起始地址和main函数中数组a的起始地址相同,即二者在内存中位于同一位置,当函数执行结束时数组a中元素a[4]就得到了修改。

5.5字符数组与scanf读取字符串

5.5.1字符数组初始化及传递

字符数组的定义方法与前面的5.5.1一维数组类似,例如:

char c[10]; //定义一个长度为10的字符数组c

字符数组的初始化方式

(1)对每个字符单独赋值进行初始化

c[0] = '1';c[1] = ' ';c[2] = 'a'; c[3] = 'm';c[4] = ' ';c[5] = 'h';c[6] = 'a'; //初始化速度慢

(2)对整个数组进行初始化

char c[10] = {'1','a','m','h','a','p','y'}; //初始化速度慢

 (3)常用初始化形式:会自动将字符串里的每个字符都放入数组c里面

char c[10] = "Iamhappy"; //编写速度更快

char a[6] = "hello";

【内存视图】

 注意:

  • C语言规定字符串的结束标志是 '\0' ,而系统会对字符串常量自动加一个 '\0' 。
  • 为保证出力方法一致,一般人会人为在字符数组中添加  '\0' ,所以字符数组中存储的字符串长度必须比字符数组少1个字节,例如char c[10]最长存储9个字符,剩余的一个字符用来存储  '\0'

 【字符数组初始化及传递的正确例子

#include <stdio.h>

int main() {
    char c[10] = "Iamhappy";
    printf("%s\n", c);//使用%s来输出一个字符串,直接吧字符数组名放在printf后面
    //%s内部其实也有一个循环,能将字符数组输出
    return 0;
}

【运行效果】

  【字符数组初始化及传递的错误例子

#include <stdio.h>

int main() {
    //若字符串长度超过字符数组长度则会输出乱码
    char c[5] = "hello";
    //正确写法是 char c[6] = "hello";
    //输出字符串乱码时,要去查看字符数组中是否存储了结束符'\0'
    printf("%s\n", c);//使用%s来输出一个字符串,直接吧字符数组名放在printf后面
    return 0;
}

【运行效果】

【内存视图查看】

【总结】

  • 若字符串长度超过字符数组的长度则会输出乱码
  • 输出字符串乱码时,要去查看字符数组中是否存储了结束符'\0'

【自定义函数print模拟printf("%s\n",c);操作】

#include <stdio.h>

//定义一个print函数模拟printf("%s\n", c);操作
void print(char d[]) {
    int i = 0;
    while (d[i]) { //当走到结束符'\0',时循环结束
        printf("%c", d[i]);
        i++;
    }
    printf("\n"); //换行
    d[0] = 'H';//也可以修改字符数组中某一元素的值
}

int main() {
    //若字符串长度超过字符数组长度则会输出乱码
    char c[6] = "hello";
    char d[5] = "how";
    printf("%s\n", c);//使用%s来输出一个字符串,直接吧字符数组名放在printf后面
    print(d); //调用函数print
    printf("%c\n", d[0]); //d[0]的值已经修改
    return 0;
}

【运行效果】

5.5.2scanf读取字符串

scanf读取字符串时用%s

【scanf读取字符串例子】

#include <stdio.h>

int main() {
    char c[10];
    scanf("%s", c);
    //字符数组名c中存储了数组的起始地址,因此这里不用写取地址&c
    //使用scanf读取字符操作时,会自动往数组中填充一个结束符'\0'
    printf("%s\n", c);
    return 0;
}

【运行效果】

【若数组中已提前填充数据】

#include <stdio.h>

int main() {
    char c[10] = {"1234567"}; //若是提前在c中填充一些数据
    scanf("%s", c);
    //字符数组名c中存储了数组的起始地址,因此这里不用写取地址&c
    //使用scanf读取字符操作时,会自动往数组中填充一个结束符'\0'
    printf("%s\n", c);
    return 0;
}

【运行效果】

【单步调试内存视图:scanf语句运行前】

【单步调试内存视图:scanf语句运行后】

【sacnf读取字符串中带有空格】

【若需要读取空格如何操作】

定义两个字符数组,用scanf同时读取两个字符串

#include <stdio.h>

int main() {
    char c[10];
    char d[10];
    scanf("%s%s", c, d);
    //字符数组名c中存储了数组的起始地址,因此这里不用写取地址&c
    //使用scanf读取字符操作时,会自动往数组中填充一个结束符'\0'
    //scanf使用%s会自动忽略空格和回车(与%d和%f类似)
    printf("c=%s,d=%s\n", c, d);
    return 0;
}

【运行效果】

5.6 gets函数与puts函数

5.6.1gets函数
  • gets函数类似于scanf函数,用于读取标准输入
  • scanf函数在读取字符串时遇到空格就认为结束,所以当输入的字符串存在空格时,需要用gets函数进行读取。

gets函数格式如下:

char *gets(char *str);

gets函数从STDIN(标准输入)读取字符并把它们加载到str(字符串)中,直到遇到换行符(\n)。

【gets函数例子】

#include <stdio.h>

int main() {
    char c[20];
    gets(c); //gets中放入字符数组的数组名即可
    //输入“how are you”共11个字符进行测试
    printf("%s\n",c);
    return 0;
}

【运行效果】

【内存视图】

【小结】

  • gets会读取空格,因为gets遇到\n后,不会存储\n,而是将其翻译为空字符'\0'
5.6.2puts函数

puts函数类似于printf函数,用于输出标准输出。

puts函数的格式如下:

int puts(char *str)

  • 函数puts把str(字符串)写入STDOU(标准输出),puts函数会将数组中存储的字符串打印到屏幕上,同时打印换行。
  • 相对于printf函数,puts只能用于输出字符串,同时多打印一个换行符
  • puts等于于printf("%s\n",c); puts内的参数只能是字符数组名

【puts函数例子】

#include <stdio.h>

int main() {
    char c[20];
    gets(c); //gets中放入字符数组的数组名即可
    puts(c);//puts等价于printf("%s\n",c);
    //puts内放的参数只能是字符数组名
    //puts只能输出字符串,同时打印多一个换行符
    //输入“how are you”共11个字符进行测试
    return 0;
}

【运行效果】

5.7str系列字符串操作函数:strlen、strcpy、strcmp、strcat等

  • strlen函数用于统计输入的字符串长度
  • strcpy函数用于将某个字符串复制到字符数组中,会覆盖原数组内容
  • strcmp函数用于比较两个字符串的大小
  • strcat函数用于将两个字符串连接到一起,后一个字符串拼到前一个字符串

#include <string.h>

//需要引用头文件为 #include <string.h>

        size_t strlen(char *str); //参数放字符数组名或字符串常量都可

        char *strcpy(char *to,const *from); //前一个参数位置只能是数组名

        int strcmp(const char *str1,const char *str2); //参数放字符数组名或字符串常量都可

        char *strcat(char *str1,const char *str2); //前一个参数位置只能是数组名

对于传参类型char*,直接放入字符数组的数组名即可。

5.7.1 strlen:用于统计输入的字符串长度

【strlen例子】

#include <stdio.h>
#include <string.h>


int main() {
    int len;
    char c[20];
    char d[100] = "world";
    gets(c);
    puts(c);
    //strlen统计字符串长度时不计入\0结束符
    len = strlen(c);//统计输入的字符串长度
    printf("len=%d\n", len);
    return 0;
}

【运行效果】

【定义函数模拟strlen内部循环机制】

#include <stdio.h>
#include <string.h>

//定义函数模拟strlen内部循环机制
int mystrlen(char c[]) {
    int i = 0;
    while (c[i]) {//遇到\0循环结束,从而得出数组长度
        i++;
    }
    return i;
}

int main() {
    int len;
    char c[20];
    char d[100] = "world";
    gets(c);
    puts(c);
    //strlen统计字符串长度时不计入\0结束符
    len = strlen(c);//统计输入的字符串长度
    printf("len=%d\n", len);
    len = mystrlen(c);
    printf("mylen=%d\n", len);
    return 0;
}

【运行效果】

5.7.2 strcpy:用于将某个字符串复制到字符数组中,会覆盖原数组内容

 【strcpy例子】

#include <stdio.h>
#include <string.h>

int main() {
    int len;
    char c[10];
    char d[100] = "world";
    gets(c);
    puts(c);
    //strlen统计字符串长度时不计入\0结束符
    len = strlen(c);//统计输入的字符串长度
    printf("len=%d\n", len);
    strcpy(c,d);//把d数组中的字符串复制到c数组中,覆盖原数组c
    puts(c);
    return 0;
}

【运行效果】

5.7.3 strcmp例子:用于比较两个字符串的大小

【strcmp例子】

#include <stdio.h>
#include <string.h>

int main() {
    int len;
    char c[10];
    char d[100] = "world";
    gets(c);
    puts(c);
    //strlen统计字符串长度时不计入\0结束符
    len = strlen(c);//统计输入的字符串长度
    printf("len=%d\n", len);
    strcmp(c, d); //比较两个字符串大小,相等返回0,不相等返回1或-1
    printf("c?d=%d", strcmp(c, "how"));//数组c和how比较
    //c大于"how",返回正值+1,相等返回0,c小于"how",返回负值-1
    //字符串比较大小:按单个字母的ascll码比较
    return 0;
}

【运行效果】

5.7.4 strcat例子:用于将两个字符串连接到一起,后一个字符串拼到前一个字符串

【strcat例子】

#include <stdio.h>
#include <string.h>

int main() {
    int len;
    char c[10];
    char d[100] = "world";
    gets(c);
    puts(c);
    //strlen统计字符串长度时不计入\0结束符
    len = strlen(c);//统计输入的字符串长度
    printf("len=%d\n", len);
    strcat(c,d); //把d数组中的字符串拼到c数组的字符串中
    return 0;
}

【运行效果】

【内存视图】

在控制台输入“hello”,回车,查看内存视图

5.8课时作业

  • 课时5作业1

描述:输入N个数(N小于等于100),输出数字2的出现次数。

解题提示:

整型数组读取5个整型数的方法如下:

int a[100];
for(int i = 0;i < 5;i++){
    scanf("%d",&a[i]);
}

Input:输入的格式是两行

第一行输入元素个数,比如5

第二行输入1 2 2 3 2,那么输出结果为3,因为2出现了3次

Output:统计数字2出现的次数

样例:

输入:1 2 2 3 2           输出:3

【课时5作业1代码】

#include <stdio.h>

//课时5作业1
//读取一堆整型数中出现2的次数
int main() {
    int a[100];
    int b;//要输入的元素个数
    int c = 0;//统计2出现的次数
    scanf("%d", &b); //要输入的元素个数
    //printf("\n");//换行
    for (int i = 0; i < b; i++) {
        //scanf读取整型数时会忽略空格
        scanf("%d", &a[i]);//输入b个整型数,循环读物多个整型元素
        if (a[i] == 2) {
            c++;
        }
    }
    printf("%d\n", c);
    return 0;
}

【运行结果】

  • 课时5作业2

描述:读取一个字符串,字符串可能有空格,将字符串逆转,原来的字符串与逆转后字符串相同,输出0,原字符串小于逆转后字符串输出-1,大于逆转后字符串输出1。

例如:输入hello,逆转后字符串为olleh,因为hello小于olleh,所以输出-1.

注意:最后的判断一定要按以下写法,因为strcmp标准C中并不是返回-1和1,而是负值和正值。

int result = strcmp(c,d);
if(result < 0){
    printf("%d\n",-1);
}else if(result > 0){
    printf("%d\n",1);
}else{
    printf("%d\n",0);
}

Input:输入一个字符串,例如hello,输入的字符串也可能是how are you,含有空格的字符串

Output:输出是一个整型数,如果输入的字符串时hello,那么输出的整型数为-1

样例:

输入:                输出:

hello                    -1

cba                      1

aba                      0

【课时5作业2代码:测试逆转输出部分】

#include <stdio.h>
#include <string.h>

//课时5作业2:翻转后比较与原字符串是否相等
//使用增量编写法,不要一气呵成
int main() {
    char c[100];//原字符串
    char d[100] = {0};//翻转后数组,使d中全部元素的值为0
    //初始化数组d的目的是为了d有结束符
    gets(c);
    int i, j;//定义游标
    //strlen(c)为数组c内字符串的长度
    //j=strlen(c)-1 因为数组下标从0开始,最后一个元素的下标比数组的长度少1
    //for循环中的边界写一个即可,i < strlen(c)或 j >= 0
    for (int i = 0, j = strlen(c) - 1; i < strlen(c); i++, j--) {
        d[j] = c[i];//交换
    }
    puts(d);
    return 0;
}

【运行结果】

【课时5作业2代码:完整版】

#include <stdio.h>
#include <string.h>

//课时5作业2:翻转后比较与原字符串是否相等
//使用增量编写法,不要一气呵成
int main() {
    char c[100];//原字符串
    char d[100] = {0};//翻转后数组,使d中全部元素的值为0
    //初始化数组d的目的是为了d有结束符
    gets(c);
    int i, j;//定义游标
    //strlen(c)为数组c内字符串的长度
    //j=strlen(c)-1 因为数组下标从0开始,最后一个元素的下标比数组的长度少1
    //for循环中的边界写一个即可,i < strlen(c)或 j >= 0
    for (int i = 0, j = strlen(c) - 1; i < strlen(c); i++, j--) {
        d[j] = c[i];//交换
    }
    puts(d);
    int result = strcmp(c, d);
    if (result < 0) {
        printf("%d\n", -1);
    } else if (result > 0) {
        printf("%d\n", 1);
    } else {
        printf("%d\n", 0);
    }
    return 0;
}

【运行结果】

  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值