当前位置: 首页 > news >正文

【C语言】C语言期末突击/考研--详解一维数组与字符数组

目录

​一、一维数组

1.数组的定义

2.一维数组在内存中的存储

二、数组访问越界与数组的传递

1.数组的访问越界

2.数组的传递

三、字符数组与scanf读取字符串

1.字符数组的初始化及传递

2.scanf读取字符串

四、gets函数与puts函数,str系列字符串操作函数

1.gets函数与puts函数

2.strlen-strcmp-strcpy函数

五、练习题及解析


一、一维数组

1.数组的定义

    为了存放鞋子,假设你把衣柜最下面的一层分成了10个连续的格子。此时,让他人帮你拿鞋子就会很方便,例如你可直接告诉他拿衣柜最下面一层第二个格子中的鞋子。同样假设现在我们有10个整数存储在内存中,为方便存取,我们可以借助C语言提供的数组,通过一个符号来访问多个元素
    某班学生的学习成绩、一行文字、一个矩阵等数据的特点如下:
    (1) 具有相同的数据类型
    (2) 使用过程中需要保留原始数据。
    C语言为了方便操作这些数据,提供了一种构造数据类型——数组。所谓数组,是指一组具有相同数据类型的数据的有序集合
一维数组的定义格式为:

类型说明符 数组名 [常量表达式]:

例如:

int al10];

定义一个整型数组,数组名为a,它有10个元素。
声明数组时要遵循以下规则:
     (1) 数组名的命名规则和变量名的相同,即遵循标识符命名规则。
     (2) 在定义数组时,需要指定数组中元素的个数,方括号中的常量表达式用来表示元素的个数,即数组长度。
     (3) 常量表达式中可以包含常量和符号常量,但不能包含变量。也就是说,C语言不允许对数组的大小做动态定义,即数组的大小不依赖于程序运行过程中变量的值。


    以下是错误的声明示例(最新的C标准支持,但是最好不要这么写):

int n;

/* 在程序中临时输入数组的大小 */
scanf("%d", &n);
int a[n];

    数组声明的其他常见错误如下:

 /* 数组大小为0没有意义 */

① float a[0];

/* 不能使用圆括号*/
② int b(2)(3)

/* 不能用变量说明数组大小*/
③ int k=3, a[k];

2.一维数组在内存中的存储

    语句int arr[100];定义的一维数组 arr在内存中的存放情况如下图所示,每个元素都是整型元素,占用4字节,数组元素的引用方式是数组名[下标]”,所以访问数组 arr中的元素的方式是 arr[0], arr[1]... arr[99]。注意,没有元素 arr[100],因为数组元素是从0开始编号的。

下面介绍一维数组的初始化方法:

      (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) 可以只给一部分元素赋值。例如:

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

定义a数组有10个元素,但花括号内只提供5个初值,这表示只给前5个元索赋初值,后5个元索的值为0。
     (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};

二、数组访问越界与数组的传递

1.数组的访问越界

下面借助一个数组的实例来掌握数组元素的赋值、访问越界。下例中给出了该例的全部代码。

【例】一维数组的存储及访问越界

#include <stdio.h>
//数组越界
int main(){

        //定义数组时,数组长度必须固定
        int a[5]={1, 2, 3, 4, 5};
        int j=20;
        int i=10;
        a[5]=6;//越界访问
        a[6]=7;//越界访问会造成数据异常
        printf("i=%d\n", i);//i发生改变
        return 0;
}


    下图显示了代码运行情况。如下图所示,在第8行左键打上断点,然后单击“小虫子”按钮,在内存视图依次输入&j、&a、&i 来查看整型变量j、整型数组a、整型变量i的地址,即可看到三个变量的地址,这里就像我们给衣柜的每个格子的编号,第一格、第二格···…一直到柜子的最后一格。操作系统对内存中的每个位置也给予一个编号,对于 Windows 32 位控制台应用程序来说,这个编号的范围是从 0x00 00 00 00 到 OxFF FF FF FF,总计为2的32次方,大小为4G。这些编号称为地址(我们是64 位程序,地址显示的是64位)。

【图】代码运行情况1

     在变量窗口中输入sizeof(a), 可以看到数组a的大小为20字节,计算方法其实就是sizeof(int)*5:数组中有5个整型元素,每个元素的大小为4字节,所以共有20字节。访间元素的顺序是依次从a[0]到 a[4],a[5]=6、a[6]=7 均为访问越界,下图显示了代码运行情况,从中看出,执行到第 1行时,变量i的值被修改了,这就是访问越界的危险性——未对变量i赋值,其值却发生了改变!

【图】代码运行情况2

     数组另一个值得关注的地方是,编译器并不检查程序对数组下标的引用是否在数组的合法范围内。这种不加检查的行为有好处也有坏处,好处是不需要浪费时间对有些已知正确的数组下标进行检查,坏处是这样做将无法检测出无效的下标引用,一个良好的经验法则是:如果下标值是通过那些已知正确的值计算得来的,那么就无须检查;如果下标值是由用户输入的数据产生的,那么在使用它们之前就必须进行检查,以确保它们位于有效范围内。

2.数组的传递

#include <stdio.h>
//一维数组的传递,数组长度无法传递给子函数
//C语言的函数调用方式是值传递
void print(int b[],int len) {int length = sizeof(b);int i;for (i = 0; i < len; i++) {printf("%d\t", b[i]);}b[4] = 20;//在子函数中修改版数组元素printf("\n");
}//数组越界//一维数组的传递
#define N 6
int main(){int a[]={1,3,5,7,9,11};print(a,5);print("a[4]=%d\n",a[4]);//a[4]发生改变return 0;
}

    如下图1所示,在第18行点击向下箭头,进入 print 函数,这时会发现数组b的大小变为8字节,如下图2所示,这是因为一维数组在传递时,其长度是传递不过去的,所以我们通过 len 来传递数组中的元素个数。实际数组名中存储的是数组的首地址,在调用函数传递时,是将数组的首地址给了变量b(其实变量b 是指针类型,具体原理会在指针节讲解),在[b]的方括号中填写任何数字都是没有意义的。这时我们在print 函数内修改元索 b[4]=20,可以看到数组b的起始地址和 main 西数中数组a的起始地址相同,即二者在内存中位于同一位置,当数执行结束时,数组a中的元素 a[4]就得到了修改。

【图1】代码运行情况3

【图1】代码运行情况4

三、字符数组与scanf读取字符串

1.字符数组的初始化及传递

    字符数组的定义方法与前面介绍的一-维数组类似。例如:

char c[10];

    字符数组的初始化可以采用以下方式。
    (1) 对每个字符单独赋值进行初始化。例如:

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';

    (2)对整个数组进行初始化。例如:

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>
void print(char c[]){int i=0;while (c[i]){printf("%c",c[i]);i++;}print("\n");
}//字符数组存储字符串,必须存储结束符“\0”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;
}

    上例中代码的执行结果如下图所示。为什么对数组赋值"hello"却打印出乱码,这是因为printf通过%s打印字符串时,原理是依次输出每个字符,当读到结束符'\0'时,结束打印:


我们通过print函数模拟实现printf 的%s打印效果,当c[i]为'\0'时,其值是0,循环结束,也可以写为c[i]!='\0'。

2.scanf读取字符串

【例】scanf读取字符串

#include <stdio.h>//scanf读取字符串使用%sint main(){char c[10];char d[10];scanf("%s",c);printf("%s\n",c);scanf("%s%s",c,d);printf("c=%s,d=%s\n",c,d);return 0;
}

    scanf通过%s读取字符串,对c和d分别输入"are"和"you" (中间加一个空格),scanf在使用%s读取字符串时,会忽略空格和回车(这一点与%d和%f类似)。
输入顺序及执行结果如下图,

四、gets函数与puts函数,str系列字符串操作函数

1.gets函数与puts函数

    gets函数类似于scanf函数,用于读取标准输入。前面我们已经知道scanf函数在读取字符串时遇到空格就认为读取结束,所以当输人的字符串存在空格时,我们需要使用gets函数进行读取。
gets函数的格式如下:

char *gets(char *str);

    gets函数从STDIN (标准输人)读取字符并把它们加载到str (字符串)中,直到遇到换行符(\n) 。如下例所示,执行后,我们输入"how are you",共11个字符,可以看到gets会读取空格,同时可以看到我们并未给数组进行初始化赋值,但是最后有'\0', 这是因为gets遇到\n后,不会存储\n,而是将其翻译为空字符'\0'。
puts函数类似于printf函数,用于输出标准输出。puts 函数的格式如下:

int puts(char *str);

    函数puts把str (字符串)写人STDOU (标准输出)。puts 会将数组C中存储的"how are you"字符串打印到屏幕上,同时打印换行,相对于printf 函数,puts 只能用于输出字符串,同
时多打印一-个换行符,等价于printf("%s\n”,c)。

#include <stdio.h>//gets一次读取一行int main(){char c[20];gets(c);puts(c);return 0;
}

上例的运行结果如下图:

2.strlen-strcmp-strcpy函数

    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);

    对于传参类型char*,直接放人字符数组的数组名即可。
    接下来我们通过下例来具体学习str系列字符串操作函数,掌握每个函数的内部实现。


【例】str系列字符串操作函数的使用。

#include <stdio.h>
#include <string.h>int mystrlen(char c[]){int i =0;while (c[i++]);return i-1;
}
//统计strlen统计字符串长度int main(){int len;//用于存储字符串的长度char c[20];char d[100]="world";gets(c);puts(c);len=strlen(c);printf("len=%d\n",len);len= mystrlen(c);printf("mystrlen len=%d\n",len);strcat(c,d);strcpy(d,c);//c中的字符串赋值给dputs(d);printf("C?D %d\n",strcmp(c,d));puts(c);return 0;
}

    下图所示为我们输人"hello"后的执行结果,通过strlen函数计算的字符串长度为5,我们自己写的函数就是strlen函数的计算原理,即通过判断结束符来确定字符串的长度,stropy函数用来将字符串中的字符逐个地赋值给目标字符数组,例中我们将c复制给d,就是将c中的每个字符依次赋值给d,也会将结束符赋值给d。注意,目标数组一定要大于字符串大小,即sizeof()>strlen(c).否则会造成访问越界。


    strcmp函数用来比较两个字符串的大小,由于字符数组c中的字符串与d相等,所以这里的返回值为0。如果c中的字符串大于d,那么返回值为1;如果c中的字符串小于d,那么返回值为-1.如何比较两个字符串的大小呢?具体操作是从头开始,比较相同位置字符的ASCII码值,若发现不相等则直接返回,否则接着往后比较。例如,strcmp("hello" ,"how")的返回值是-1。即hello"小于"how", 因为第一一个字符h相等,接着比较第二个位置的字符,e的ASCII码值小于o的,然后返回-1。strcat函数用来将一一个字 符串接到另外一一个字符串的末尾。例中字符数组c中存储的是"hello",我们将d中的"world"与c拼接,最终结果为"hello hhy world".注意,目标数组必须大于拼接后的字符串大小,即sizeof(c)>strlen( "hello hhy world")。

五、练习题及解析

1、数组内两个元素可以存储不同的数据类型
        A正确        B错误

2、int mark[100];我们可以做mark[100]=3; 
        A正确        B错误

3、int a[10]= {0,1,2,3,4};定义a数组有10 个元素,但花括号内只提供5个初值,这表示只给
前5个元素赋初值,后5个元素的值为0。
        A正确        B错误

4、数组int arr[5];我们做arr[5]=20这个操作造成了访问越界
        A正确        B错误

5、访问越界是非常危险的,因为C和C++语言没有针对访问越界进行检测
        A正确        B错误

6、数组传递时,可以把自身长度传递给子函数
        A正确        B错误

7、在子函数中改变数组中某个元素的值,子函数结束后,数组内元素值发生变化
        A正确        B错误
 

8、char c[ 10]= {T,a','m',h','a',p'p'y' }这种初始化方式是常用的方式
        A正确        B错误

9、char c[5]我们可以放5个字符来使用
        A正确        B错误

10、char c[10];scanf("%s",c);读取字符串时会读取到空格和\n
        A正确        B错误

11、gets 一次可以读取一行,能够读取空格
        A正确        B错误

12、puts(c)等 价于print(“%s\n",c)
        A正确        B错误

13、strlen函数用于统计字符串长度,strcpy函数用于将某个字符串复制到字符数组中,
strcmp函数用于比较两个字符串的大小,strcat 函数用于将两个字符串连接到一起
        A正确        B错误

14、如果字符串没有结束符’\0’,也可以使用strlen 正确统计长度
        A正确        B错误

解析:

1、B        解释:数组内的元素必须存储相同的数据类型。

2、B        解释:数组下标从零开始,定义int mark[100],只能方位mark[0]到mark[99]

3、A        解释:初始化元素时,剩余的未赋值元素,会被初始化为零

4、A        解释: int arr[5]数组访问arr[0]到arr[4],5及以后的值都是访问越界

5、A        解释:我们的实例演示了访问越界会改变其他变量的值,因此非常危险

6、B        解释:数组传递时,只是把数组的起始地址传递给了子函数,长度不能传递给子函数,我们需要使用额外的变量,来把长度传递给子函数

7、A        解释:当我们把数组名传递给子函数后,在子函数内就可以访问数组的某个元素,同时可以进行修改

8、B        解释:字符数组的初始化最常用的方式是char c[10]="Iamhappy”这种方式

9、B        解释: char c[5]我们只能放4个字符,第五个字符需要放置结束符\0',否则prinft("%s\n",c);
输出会造成乱码

10、B        解释: scanf("%s",c); 会忽略空格和\n,因此无法读取到空格和\n

11、A        解释:如果要一次读一行,同时需要把空格读到字符数组中,那么就需要用gets 

12、A        解释:如果输出字符串,使用puts更加方便,注意puts只能用于输出字符串,不能输出其
他类型

13、A        解释:这个需要记住

14、B        解释: strlen 是通过结束符'\0'来判断字符串长度的,如果没有结束符,无法统计字符串长度

下一期:

【C语言】C语言期末突击/考研--指针(一篇就够)-CSDN博客

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 罗杰斯的逆势投资:破而后立
  • 如何选择合适的气膜生产厂家—轻空间
  • 动态规划.
  • (四)软件性能测试
  • Python基于Prophet实现时间序列数据趋势周期特征提取项目实战
  • Vulnhub靶机:JANGOW_ 1.0.1
  • Java面试必看!知己知彼才能百战百胜,如何做好面试前的准备?
  • 光线追踪(纹理映射)
  • c语言-EasyX库编写的第一个项目-哪都通快递平台
  • 《Advanced RAG》-07-探索 RAG 中表格数据的处理方案
  • CSS对元素的分类
  • 安装glibc+mysql的权限问题
  • Studying-代码随想录训练营day59| dijkstra(堆优化版)精讲、Bellman_ford 算法精讲
  • Linux网络通信
  • 设计测试用例的具体方法
  • 08.Android之View事件问题
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • exif信息对照
  • iOS | NSProxy
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • Spring框架之我见(三)——IOC、AOP
  • spring学习第二天
  • Vue源码解析(二)Vue的双向绑定讲解及实现
  • 大主子表关联的性能优化方法
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 力扣(LeetCode)965
  • 前端面试总结(at, md)
  • 前嗅ForeSpider采集配置界面介绍
  • 跳前端坑前,先看看这个!!
  • 优化 Vue 项目编译文件大小
  • 原生Ajax
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • # Redis 入门到精通(八)-- 服务器配置-redis.conf配置与高级数据类型
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • %check_box% in rails :coditions={:has_many , :through}
  • (02)Hive SQL编译成MapReduce任务的过程
  • (2015)JS ES6 必知的十个 特性
  • (6) 深入探索Python-Pandas库的核心数据结构:DataFrame全面解析
  • (6)设计一个TimeMap
  • (十一)c52学习之旅-动态数码管
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (四)软件性能测试
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (译)2019年前端性能优化清单 — 下篇
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • (转)为C# Windows服务添加安装程序
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • .net 4.0发布后不能正常显示图片问题
  • .net CHARTING图表控件下载地址
  • .NET/C# 使窗口永不激活(No Activate 永不获得焦点)
  • .NET4.0并行计算技术基础(1)
  • .NET版Word处理控件Aspose.words功能演示:在ASP.NET MVC中创建MS Word编辑器
  • .NET精简框架的“无法找到资源程序集”异常释疑