【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.数组的传递

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 函数内修改元索 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'。

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

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打印效果,当c[i]为'\0'时,其值是0,循环结束,也可以写为c[i]!='\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 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博客

相关推荐
晨曦_子画9 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
Black_Friend17 分钟前
关于在VS中使用Qt不同版本报错的问题
开发语言·qt
希言JY41 分钟前
C字符串 | 字符串处理函数 | 使用 | 原理 | 实现
c语言·开发语言
残月只会敲键盘41 分钟前
php代码审计--常见函数整理
开发语言·php
xianwu54342 分钟前
反向代理模块
linux·开发语言·网络·git
午言若43 分钟前
C语言比较两个字符串是否相同
c语言
ktkiko111 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
y5236481 小时前
Javascript监控元素样式变化
开发语言·javascript·ecmascript
IT技术分享社区2 小时前
C#实战:使用腾讯云识别服务轻松提取火车票信息
开发语言·c#·云计算·腾讯云·共识算法
极客代码2 小时前
【Python TensorFlow】入门到精通
开发语言·人工智能·python·深度学习·tensorflow