【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 an;

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

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

① float a0;

/* 不能使用圆括号*/

② int b(2)(3);

/* 不能用变量说明数组大小*/

③ int k=3, ak;

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

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

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

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

int a10={0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

不能写成:

int a10; a10={0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

(2) 可以只给一部分元素赋值。例如:

int a10={0, 1, 2, 3, 4};

定义a数组有10个元素,但花括号内只提供5个初值,这表示只给前5个元索赋初值,后5个元索的值为0。

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

int a10 ={0, 0, 0, 0,0, 0, 0, 0, 0, 0};


int a10={0};

(4)在对全部数组元索赋初值时,由于数据的个数已经确定,因此可以不指定数组的长度。例如:

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

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

1.数组的访问越界

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

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

#include <stdio.h>

//数组越界
int main(){

//定义数组时,数组长度必须固定

int a5={1, 2, 3, 4, 5};

int j=20;

int i=10;

a5=6;//越界访问

a6=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字节。访间元素的顺序是依次从a0到 a4,a5=6、a6=7 均为访问越界,下图显示了代码运行情况,从中看出,执行到第 1行时,变量i的值被修改了,这就是访问越界的危险性------未对变量i赋值,其值却发生了改变!

【图】代码运行情况2

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

2.数组的传递

cs 复制代码
#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 函数内修改元索 b4=20,可以看到数组b的起始地址和 main 西数中数组a的起始地址相同,即二者在内存中位于同一位置,当数执行结束时,数组a中的元素 a4就得到了修改。

【图1】代码运行情况3

【图1】代码运行情况4

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

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

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

char c10;

字符数组的初始化可以采用以下方式。

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

c0='I';c1=' ';c2='a';c3='m';c4=' ';c5='h';c6='a';c7='p';c8='p';c9='y';

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

char c10={'I','a','m',h',a','p','p;y'};

但工作中一般不用以上两种初始化方式,因为字符数组一般用来存取字符串。通常采用的初始化方式是char c10= "hello"。因为C语言规定字符串的结束标志为'\0',而系统会对字符串常量自动加一一个"\0',为了保证处理方法一-致,- -般会人为地在字符数组中添加'\0',所以字符数组存储的字符串长度必须比字符数组少1字节。例如,char c10最长存储9个字符,剩余的1个字符用来存储'\0'。

【例】字符数组初始化及传递

cpp 复制代码
#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打印效果,当ci为'\0'时,其值是0,循环结束,也可以写为ci!='\0'。

2.scanf读取字符串

【例】scanf读取字符串

cpp 复制代码
#include <stdio.h>

//scanf读取字符串使用%s

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\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)。

cpp 复制代码
#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系列字符串操作函数的使用。

cpp 复制代码
#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中的字符串赋值给d
  puts(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 mark100;我们可以做mark100=3;

A正确 B错误

3、int a10= {0,1,2,3,4};定义a数组有10 个元素,但花括号内只提供5个初值,这表示只给

前5个元素赋初值,后5个元素的值为0。

A正确 B错误

4、数组int arr5;我们做arr5=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 c5我们可以放5个字符来使用

A正确 B错误

10、char c10;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 mark100,只能方位mark0到mark99

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

4、A 解释: int arr5数组访问arr0到arr4,5及以后的值都是访问越界

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

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

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

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

9、B 解释: char c5我们只能放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博客

相关推荐
Scott9999HH16 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社17 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海17 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖18 小时前
JDK 26 新特性详解
java·开发语言
马优晨18 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区20 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大20 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai21 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜21 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa21 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio