第四章 数组

文章介绍了C语言中数组的定义、初始化、赋值以及访问越界问题,强调了指针在传递数组时的作用。同时,详细讲解了字符数组的使用,包括字符串的结束标志、gets和puts函数,以及str系列字符串操作函数的用法。此外,还提供了两个练习题目,帮助读者巩固数组和字符串处理的知识。
摘要由CSDN通过智能技术生成

前言

学习方法

  1. 可以多看几遍视频
  2. 把上课的代码,自己加加注释,在自己写之前,可以画一个流程图
  3. 照着流程图把代码自己实现一遍

不要怀疑自己,不要遇到困难就觉得自己不行,遇到困难就解决困难,编程初学者都是这样一步一步走过来的。


在指针阶段会演示整型、浮点型、字符型传递(传递的含义就是把一个变量传递给对应的子函数。)

一维数组

数组的定义

所谓数组,是指一组具有相同数据类型的数据的有序集合。

数组具有以下特点:

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

一维数组的定义格式为:

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

例如,定义一个整型数组,数组名为a,它有10个元素。

int a[10];

声明数组时要遵循以下规则:

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

	//定义数组就是写一个变量名,后面加上方括号,方括号内写上整型常量
    //定义数组时,数组占据的空间大小就确定下来了
	int a[5] = { 1,2,3,4,5 };
}

使用符号常量,不需要掌握,知道可以这样写即可。

//符号常量
#define N 5
int main() {
	int a[N] = { 1,2,3,4,5 };
}

查看数组内存空间

看任何变量的内存,都是将该变量取地址,拖入内存窗口来看。 只有指针类型才能拖入内存窗口查看。带 * 号的表示是指针类型

在这里插入图片描述

在这里插入图片描述

赋初值

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

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

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

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

或者

int a[5] = {0};

但肯定是第二种写法为好(当赋初值的个数小于数组大小时,则后面的元素会被自动赋值为0)。

数组访问越界

了解一下:微软的编译器设计,不同的变量之间有8个字节的保护空间(Mac和Linux是没有的)。[这并不是标准C规定的]。

在这里插入图片描述

数组访问越界的错误提醒:

Run-Time Check Failure #2 - Stack around the variable ‘a’ was corrupted.

在这里插入图片描述

原理:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

由上面的例子可知,访问越界会造成数据异常。

简单了解说明一下为什么C的执行效率比Java高:C语言中有指针,可以操作内存,因此效率高;而Java中没有指针,内存交给编译器管理(操作系统管理),提高了开发效率,但执行效率降低。

数组传递

自定义的函数必须写在main函数的上面

注意,自己写的其他方法要放在main方法的上面,否则编译不通

实现打印数组里的每一个元素,这里先将 “打印数组元素的方法” 中数组的大小写成固定数值。

//打印数组里的每一个元素
//自己写的print方法必须放在main方法的上面,否则编译不通
void print(int a[5]) {
	for (int i = 0; i < 5; i++)
	{
		printf("a[%d]=%d\n", i, a[i]);
	}
}

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

	print(a);
}

得到输出结果:

在这里插入图片描述

现在想在 “打印数组元素的方法” 中动态的得到数组的大小,进行如下尝试:

//打印数组里的每一个元素
void print(int a[5]) {
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("a[%d]=%d\n", i, a[i]);
	}
}

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

	print(a);
}

而这次的输出结果是:

在这里插入图片描述

通过断点调试发现sizeof(a)=4,而与数组a的大小无关。无论传过来的数组多大,sizeof(a)始终等于4。原因在于数组在传递的时候数组元素是传递不过去的,只能传递数组的起始地址(实际上就是指针)

在这里插入图片描述

正确的写法是这样的,在print方法中新增一个参数表示数组的大小,调用该方法时传入数组的大小。[注:C语言中没有Java中的a.length方法]。

//打印数组里的每一个元素
void print(int a[],int len) {
	for (int i = 0; i < len; i++)
	{
		printf("a[%d]=%d\n", i, a[i]);
	}
}

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

	print(a,5);
}

//以下代码实现也是正确的
/*
//形参和实参可以不一样
void print(int b[],int len) { //b、len是形参
	for (int i = 0; i < len; i++)
	{
		printf("a[%d]=%d\n", i, b[i]);
	}
}

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

	print(a,5);//a是实参
}
*/

在子函数中修改数组元素

在子函数中可以直接修改数组元素,可以读可以写。

//打印数组里的每一个元素
//print是我们自定义的函数,C语言中没有print函数
void print(int b[],int len) {
	for (int i = 0; i < len; i++)
	{
		printf("a[%d]=%d\n", i, b[i]);
	}
	b[4] = 20;//在子函数中修改数组元素

}

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

	print(a,5);//调用函数
	printf("a[4]=%d\n", a[4]);
}

输出结果:

在这里插入图片描述

字符数组

初始化字符数组

字符数组就是专门用来存字符串的(初试和机试)。

初始化字符数组:以后都用第二种初始化字符数组的方式char d[10] = “hello”;

int main() {
	char c[10] = {'h','e','l','l','o'};//这样初始化字符数组太麻烦了

	char d[10] = "hello";//这样初始化字符数组也可以,而且简单

	//给字符数组存一个字符串,就用第二种初始化方式,简单。
	//字符数组就是专门用来存字符串的(初试和机试)
}

在这里插入图片描述

C语言规定字符串的结束标志为’\0’,读到\0就不会再输出了,而系统会对字符串常量自动加一个’\0’,为了保证处理方法一致,一般会认为地在字符数组中添加’\0’,所以字符数组存储的字符串长度必须比字符数组少1字节。例如char c[10]最长存储9个字符,剩余的1个字符用来存储’\0’。

在这里插入图片描述

ASCII码表中\0对应编码是0000 0000 即值是0,\0在内存中存的值就是0.

所以正确的初始化字符数组代码如下:初始化字符数组时,一定要让字符数组的大小比看到的字符串的长度多1

//初始化字符数组时,一定要让字符数组的大小比看到的字符串的长度多1
int main() {
	char c[6] = {'h','e','l','l','o'};//字符数组存储的字符串长度必须比字符数组少1字节
	char d[5] = "how";

	//%s :输出一串字符串
	printf("%s------%s", c, d);//printf的%s,对应后面要写的是字符数组名或者字符串常量

	//printf("%s", "Chinese");//printf的%s,后面写字符串常量的例子
}

向字符数组中读取字符串

scanf中%s不用加&取地址符。[原因在于数组名本身存储的就是数组的起始地址(指针)。当然这里写上&取地址符也不会错,但一般不写].

%s和%d,%f一样都会忽略空格和\n。

scanf读取我们输入的字符串后会自动添加结束符\0。

//向字符数组中读取字符串
int main() {
	char e[20];
	//scanf中%s不用加&取地址符
	scanf("%s", e);//%s和%d,%f一样都会忽略空格和\n。
	printf("%s\n", e);
}

在这里插入图片描述

同时读取多个:

int main() {
	char e[20],f[20];
	scanf("%s%s", e,f);//,当读取多个时%s%s中间不用加空格,因为%s和%d,%f一样都会忽略空格和\n。
	printf("%s---%s\n", e,f);
}

%s、%d、%f都会忽略空格和\n,因此在输入多个时中间不用加空格,而%c不会忽略空格和\n,所以在多个混合输入时有%c出现时要在 %c前面加一个空格。

在这里插入图片描述

如何把wangdao xue408 读到同一个字符数组中(包括那个空格),用scanf实现较为麻烦,因为需要用到正则,不需要掌握。用gets就很容易实现了,gets可以一次读一行,中间有空格也可以读到一个字符数组中。

字符数组的传递

自定义的函数必须写在main函数的上面

/*
在传递整型数组时不光要传递数组名,同时还要传递数组长度。而字符数组在传递时,只需传递数组名即可,
不用传递数组长度。因为字符数组最后一个是 '\0',C语言规定字符串的结束标志为'\0',所以当扫描到'\0'时
就知道应该结束了,可以以此来作为循环结束的判断条件。
*/
//我们把d称为形参
void print(char d[]) {
	int i = 0;
	/*
	由于\0在ASCII码表中对应的编码就是0,所以循环判断条件也可以简写为while (d[i]!=0);
	又C语言中一切非0值都是真,0为假,所以可以再进一步简写为while (d[i]);
	*/
	while (d[i]!='0')
	{
		printf("%c", d[i]);字符数组去输出某一个元素时,用%c。
		i++;
	}
	printf("\n");
}

int main() {
	//初始化字符数组时,一定要让字符数组的大小比看到的字符串的长度多1
	char c[10] = "hello";
	print(c);//我们把c称为实参,调用print函数时,是d=c (本质上就是赋值)
}

在这里插入图片描述

同整型数组一样,同样可以在子函数中修改字符数组元素。如下:

void print(char d[]) {
	int i = 0;
	while (d[i]!='\0')
	{
		printf("%c", d[i]);
		i++;
	}
	printf("\n");

	//在子函数中修改字符数组元素。修改字符数组中的字符串的内容,把首字母变大写。
	d[0] = d[0] - 32;//A:65  a:97   大写字母与小写字母的差是32
}

int main() {
	//初始化字符数组时,一定要让字符数组的大小比看到的字符串的长度多1
	char c[10] = "hello";
	print(c);
	printf("%s\n", c);
}

gets函数

gets函数只能读取字符串

使用scanf读取字符串会遇到一个问题,scanf通过%s读取字符串时,当遇到空格以后,就会匹配结束,这样没办法把一行带有空格的字符串存入到一个字符数组中。这个问题可以用gets解决。

gets函数的格式如下:

//char *str类型是字符指针
char *gets(char *str);

gets函数从标准输入中读取字符并把它们加载到str(字符串)中,直到遇到换行符(\n)或到达EOF。【所以gets可以一次读一行,空格也能读入】

输入“how are you”,共11个字符,可以看到gets会读取空格,同时可以看到我们并未给数组进行初始化赋值,但是最后有’\0’(因为字符串的结束标志是\0,只有遇到\0才会停止输出),这是因为gets遇到\n后不会存储\n,而是将其翻译为空字符’\0’。

在这里插入图片描述

gets读一个字符就将其填到字符数组中,当读到\n时不会将其填到字符数组中,当读到\n就会停止匹配,同时gets会给字符数组填结束符\0,所以可以理解为将\n翻译为了\0。

在这里插入图片描述

字符数组的数组名存的就是字符数组的起始地址,类型就是字符指针。而数组的起始地址就是第一个元素的地址,即有&c[0]==c。如下图:

在这里插入图片描述

好好理解下面这段话

char c[20]; c是一个字符数组,但是编译器给c内部存了一个值,c里面存的值的类型是字符指针。

我们在定义其他变量时如整型变量i、浮点型变量f、字符型变量,我们拿这些变量的值时直接拿这个变量名即可,如直接访问i。但在定义数组时如char c[20],拿数组里的每一个元素时不会再去访问数组名c,而只会去访问c[0]-c[19],这时候编译器就会把数组名c中填一个固定的值。

int main() {
	char c[20];//字符数组的数组名里存的就是字符数组的起始地址,类型就是字符指针
	/*
    不能使用gets的VS,如VS2017请使用下面的操作
    fgets(c,sizeof(c),stdin);
    这些写法与gets(c);是一样的,VS2019可以使用gets。
    */
	gets(c);
    /*
    函数调用是值传递,是把c里存的值传给gets,c里的值类型是字符指针,符合gets函数的格式
    */
	puts(c);
}

puts函数

puts只能输出字符串,printf可以输出所有类型如整型、浮点型、字符型、字符串等。

puts函数的格式:

int puts(char *str);

str系列字符串操作函数

初试中如果不考字符串的操作,则一般用不到str函数;但如果初试中考了字符串的操作,则用到str函数的概率非常高。需要指出的是过去只有一年在初试科目中考察了字符串处理。但str函数在机试中很重要。

str系列字符串操作函数主要包括strlen、strcpy、strcmp、strcat等(可以查询C/C++函数大全)。strlen函数用于统计字符串长度,strcpy函数用于将某个字符串复制到字符数组中,strcmp函数用于比较两个字符串的大小【两个字符串比较,是比较对应字符位置的ASCII码。(从前往后依次逐个对比字符的ASCII码值)。】;strcat函数用于将两个字符串连接到一起。各个函数的具体格式如下所示:[注:当有const修饰表示此处可放字符串常量]

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

size_t就是int类型,当不知道是什么类型时可以用如下操作:

在这里插入图片描述

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

注意使用str系列函数需要使用#include <string.h>头文件

//strlen函数是得到字符数组里字符串的长度。
int main() {
	int a,b;
	char c[20] = "wangdao";
	a = sizeof(c);//使用这种方法得到的是字符数组的长度,输出结果a=20。
	printf("%d\n", a);
	b = strlen(c);//使用这种方法得到的是字符数组里面字符串的长度,输出结果b=7。
	printf("%d\n", b);
}

练习使用str系列函数:

int main() {
	/*
	strlen函数是得到字符数组里字符串的长度,同时字符数组里的结束符\0不计入总长度内
	*/
	char c[20] = "wangdao";
	printf("数组c内字符串的长度=%d\n", strlen(c));

	char d[20];
	strcpy(d, c);//strcpy是把一个字符串复制给另一个字符串,d赋给c
	/*
	strcpy的格式如下:char *strcpy(char *to,const char *from);
	当有const修饰表示此处可放字符串常量
	如放置字符串常量的例子:
	strcpy(d, "study");
	*/
	puts(d);

	/*
	strcmp:比较两个字符串的大小。
	格式:int strcmp(const char *str1,const char *str2);两个地方都有const修饰说明两个地方都能放字符串常量
	str1 > str2 返回值为1;str1 < str2 返回值为-1;str1 = str2 返回值为0
	两个字符串比较,是比较对应字符位置的ASCII码。(从前往后依次逐个对比字符的ASCII码值)。
	如比较"how"与"hello",先对比第一个字符,相等;再对比第二个字符,o的ASCII码大于e的ASCII码,所以how>hello
	*/
	int ret = strcmp("how", "hello");
	printf("两个字符串比较后的结果=%d\n",ret);

	/*
	strcat:拼接两个字符串。格式char *strcat(char *str1,const char *str2);
	strcat实现拼接的原理是将后面的填入前面的字符数组中。因此目标数组要能够容纳拼接后的字符串(还应包括一个结束符)
	*/
	strcat(c, "study");
	puts(c);
}

输出结果:

在这里插入图片描述
补充说明:strcmp:比较两个字符串的大小。格式:int strcmp(const char *str1,const char *str2);两个地方都有const修饰说明两个地方都能放字符串常量。str1 > str2 返回值为1;str1 < str2 返回值为-1;str1 = str2 返回值为0。这是在VS中微软规定返回值是-1、0、1。但是在标准C中并无此规定,在标准C中str1 > str2 返回值为正值;str1 < str2 返回值为负值;str1 = str2 返回值为0。OJ是符合标准C的,所以在OJ中如果使用该函数则返回值是正值,负值,0。

作业练习

练习一

Description:

输入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出现的次数。

int main() {
	int	n;
	scanf("%d", &n);//接下来要输入多少个元素
	int arr[100];
	int i;
	for (i = 0;i < n;i++)
	{
		//scanf里要放地址,所以要加取地址符&。arr[i]表示一个整型数,&arr[i]表示该整型数的地址
		scanf("%d", &arr[i]);//如何往数组里元素读入数据
	}
	int count = 0;
	for (i = 0; i < n; i++)//统计数组元素中2的个数
	{
		if (arr[i] == 2) {
			count++;
		}
	}
	printf("%d\n", count);
	return 0;
}

练习二

Description:读取一个字符串,字符串可能含有空格,将字符串逆转,原来的字符串与逆转后字符串相同,输出0,原字符串小于逆转后字符串输出-1,大于逆转后字符串输出1。例如输入 hello,逆转后的字符串为 olleh,因为hello 小于 olleh,所以输出-1。

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

Output:输出是一个整型数,如果输入的字符串是hello,那么输出的整型数为-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);
	}

[分析]:实现字符串逆转,可以先定义两个大小相等的字符数组,第一个数组用于存放输入的字符串,第二个数组用于存放逆转后的字符串。定义两个字符数组c[100]用于存放输入的字符串,d[100]用来存放逆转后的字符串。假设输入hello,可以通过调用strlen函数获得字符数组c的长度(不包括结束符\0),实现逆转只需将c的最后一个字符放到d的第一个字符,将c的倒数第二个字符放到d的第二个字符…以此类推,直至将c的第一个字符放到d的最后一个字符位置。注意,这里c的最后一个字符不包括结束符\0,同样d的最后一个字符后面也没有结束符\0,因此在逆转后需要在d的最后一个字符后面添加一个结束符\0。注意,这里说的是第几个字符,实际在代码中要用数组下标表示。
在这里插入图片描述

#include <stdio.h>
#include<string.h>//使用strlen需要引入这个宏

//字符串逆转
int main() {
	char c[100], d[100];
	gets(c);
	int i, j;
	for (i = strlen(c) - 1, j = 0; i >= 0; i--, j++)//实现字符串逆转
	{
		d[j] = c[i];
	}
	d[j] = '\0';//在d的最后一个字符后面添加一个结束符\0
	//d[j] = 0;上面那句代码也可以这样写,因为在ASCII码表中结束符\0对应的编码就是0000 0000
	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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值