四.数组
https://beryl-licorice-3a8.notion.site/1b5130fd6d1a4bc78d153dbbd81c6514?pvs=25 作者原创地点,可能会看的更明白
1.数组的概念
数组是一组相同类型元素的集合,从这个概念出发我们就可以发现两个有价值的信息:
- 数组存放的是1个或者多个数据,但是数组元素个数不能为零
- 数组中存放的多个数据,类型是相同的
数组分为一维数组和多维数组,多维数组比较常见的是二维数组
#include<stdio.h>
#include<memory.h> //声明头文件。
// 什么是数组?
// 数组如何赋值/初始化?
//使用内存赋值的方法 使得数组传递元素。
// 需要声明函数 memcpy() 和头文件memory.h
int main_11()
{
int arr1[10];//等价于 int arr1[10] = {};
int arr2[10] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr1, arr2, sizeof(arr2)); //(内存指向的数组;原始数组;复制的内存字节);
for (int i = 0; i < 10; i++) {
printf("%d\n", arr1[i]);
}
return 0;}
2.一维数组的创建和初始化
2.1数组创建
基本语法:
type arr_name[常量值];//指定元素类型和数目大小;也可以指定数组名
2.2数组的初始化
int fate [] = {}; 基本结构
完全初始化: int fate [10] = {1,2,3,4,5,6,7,8,9,10,};
不完全初始化:int fate[10] = {1,2,3,}//剩下的元素为0,默认为0
完全初始化括号内的数目大小指定可以省略掉。编译器根据初始化的内容确定初始化的数组大小。
2.3数组的类型
首先要明确,数组的类型不是元素的类型
int arr[4]={};
数组的类型是 int [4] 不是int
3.一维数组的运用
一维数组可以存放数据,存放数据的目的是对数据的操作,那么又该如何使用呢?
3.1数组下标
C语言规定数组有下表从0开始,n个数组元素对应的下标就是0~n-1。为了存储数组中的元素,会向内存申请连续的空间,每个空间以下标区别,通过访问下标来访问数组元素。Eg:a[5]={1,2,3,4,5]
数组下标 | a[0] | a[1] | a[2] | a[3] | a[4] |
---|---|---|---|---|---|
对应的元素 | 1 | 2 | 3 | 4 | 5 |
3.2数组元素的打印-遍历数组
这里我们就需要用到循环的知识了。
int arr[10] = {1,2,3,4,5,6};
for(int i =0;i<10;i++){
printf("%d", i);}//这样就会打印出1 2 3 4 5 6 0 0 0 0!
3.3数组的输入
同样我们需要使用循环的知识,这是因为数组不可直接打印,也不可以直接全部访问。
int arr[10] = {0};
for(int i = 0; i <10; i++ ){
scanf("%d ",&arr[i]);}
4.一维数组在内存中的存储
我们最好了解一下数组在内存中的存储。我们用%p
打印地址,用&
取地址。
int arr[10] = { 0 };
for (int i = 0; i < 10; i++) {
printf("&arr[%d]=%p\n", i, &arr[i]);}
00CFF840 | 0 |
---|---|
00CFF844 | 1 |
00CFF848 | 2 |
00CFF84C | 3 |
00CFF850 | 4 |
00CFF854 | 7 |
00CFF848 | 8 |
计算机处理的数据,都要加载到内存中处理
内存会被划分为一个个的内存单元-1个字节
然后给每个内存单元都编上号–通过编号就可以找到内存单元
如左图所示,编号为0~9 我们称之为地址 地址则与指针关系密切
我们通过&获得地址,地址打印出来为16进制
我们发现地址是连续递增的
5.sizeof计算数组中的元素个数
sizeof
计算的是数组的总大小,单位是字节。如果要计算元素的数量,需要 sizeof(arr) / sizeof(int)
.也就是数组总大小/数组元素类型的大小
这启示了我们:我们可以在索引数组的时候:
for(i=0;i<sizeof(arr)/sizeof(int); i++)//用sizeof来计算需要索引的数组数目
6.二维数组的创建
6.1二维数组的概念
数组的元素都是内置类型的,如果我们把一维数组作为数组的元素,这时候就是二维数组,二维数组作为数组元素的数组被称为三维数组,二维数组以上被称为多维数组。
int a[3][5] = {
{1,2,3,4,5},{1,2,3,4,5},{1,2,3,4,5},
};
int i, j;
for (i = 0; i < 3; i++) {
for (j = 0; j < 5; j++)
printf("%d", a[i][j]);
}
return 0;
}
//二维数组的遍历、赋值
for (i = 0; i < 3; i++) {
for (j = 0; j < 5; j++) {
a[i][j] = i * j;}
6.2二维数组的创建
我们是怎么定义二维数组的呢?语法如下:
type arr_name [行常量值][列常量值];//我知道这很像矩阵Matrix,线性代数很重要
例:double arr_1 [3][5]; //创建了一个三行五列的二维数组
7.二维数组的初始化
在创建变量和数组的时候 - 给定初始值就是初始化
二维数组的初始化比较复杂,我们结合实例分析:
7.1二维数组的完全初始化
int main()
{
int a[3][5] = {
{1,2,3,4,5},{1,2,3,4,5},{1,2,3,4,5},//中间的大括号甚至可以省略
}; // 因为二维数组从行开始依次录入元素
7.2二维数组的不完全初始化
//二维数组的不完全初始化
int arr[3][5] = { 0,1,2,3 };
依次初始化每一行,剩余元素为0
7.3**********************************************************************二维数组按照行进行初始化
这里我不得不承认,{}
是非常有必要且非常有用的
//二维数组按行进行初始化
int arr[3][5] = { {1,2},{3,4},{5,6} };
每一个内置的{}表明这是第几行的元素
8.二维数组的运用
8.1二维数组的下标与访问
同样,二维数组的行和列都是从0开始的,因为要从0开始访问。比如int a [3] [5];
行 \ 列 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
0 | a [0][0] | a [0][1] | a [0][2] | a [0][3] | a [0][4] |
1 | a [1][0] | a [1][1] | a [1][2] | a [1][3] | a [1][4] |
2 | a [2][0] | a [2][1] | a [2][2] | a [2][3] | a [2][4] |
我要找一个元素:a[0][0] 我们就找到了第一行第一列的元素
8.2二维数组的数据输入和打印 首先定义 int a[3][5];
int i, j;
for (i = 0; i < 3; i++) {
for (j = 0; j < 5; j++)
printf("%d", a[i][j]);}
int i, j;
for (i = 0; i < 3; i++) {
for (j = 0; j < 5; j++)
scanf("%d", &a[i][j]);}
我们最好先行再列 按照行优先的方式访问数组(即先行后列)可能更符合内存的存储方式,从而获得更好的缓存局部性,这可能会导致更快的访问速度。
9.二维数组的内存存储
二维数组在内存中也是连续存放的!二维数组是一个一维数组的数组,存放一维数组的数组
也就是说:二维数组的每一个元素都是一个一维数组。arr[]就是二维数组的存储数组。
二维数组的每一个元素也都是连续存储的;且按行连续存储。
10.C99之后-引入“变长数组“,可以使用变量来指定数组的大小
我无比欣喜地接受它,又无比心灰的认识到VS不支持!但是oj网站一般是支持的。
int main(){
int n = 0;
scanf("%d",&n);
int arr[n];
int main(){
int n = 10;
int arr[n];
如果硬上必报错
11.数组练习
11.1练习一:多个字符从两端移动,向中间汇聚
-
开始之前,我们需要补充一些字符数组的知识
-
定义字符数组:
在 C 语言中,你可以定义一个字符数组来存储一个字符串。例如:char name[5];
-
初始化字符数组:
你可以在声明字符数组的时候就对其进行初始化。例如:char name[] = "John"; // 这会自动为字符串 "John" 分配足够的空间,包括末尾的 '\\0'。
-
空字符:
C 语言中的字符串是通过一个空字符 ‘\0’ 结束的。这意味着如果你有一个长度为 4 的字符串 “John”,在内存中的实际长度是 5,因为它包含一个末尾的 ‘\0’。\0
是字符0,与常量区分。 -
访问字符数组:
你可以使用数组索引来访问字符数组中的每一个字符。例如:char firstLetter = name[0]; // 这会得到 'J'
-
字符串函数:
C 标准库<string.h>
提供了很多有用的函数来操作字符串,例如strcpy()
,strlen()
, 等等。
-
字符串会用到的两个特殊函数
1.strlen()
在 C 语言中,字符串是以字符数组的形式存储的,并以
'\0'
空字符作为结束标志。strlen()
函数就是通过查找这个结束标志来计算字符串的长度的。下面是一个使用
strlen()
函数计算字符数组长度的例子:#include <stdio.h> #include <string.h> int main() { char message[20] = "Hello, World!"; printf("The length of the message is: %zu\\n", strlen(message)); return 0;}
这段代码会输出:
The length of the message is: 13
需要注意的是,
strlen()
只返回到'\0'
字符为止的字符数,它不计算'\0'
本身。所以,尽管message
的数组长度是 20,但实际存储的字符串 “Hello, World!” 的长度只有 13。当使用
strlen()
时,要确保传递的字符数组确实包含了结束标志'\0'
,否则strlen()
可能会读取超出数组边界的内存,从而导致未定义的行为。2.strcpy()
strcpy()
是 C 语言<string.h>
库中的一个函数,用于将一个字符串复制到另一个字符串。函数原型:
char *strcpy(char *dest, const char *src);
参数:
dest
:目标字符串的指针。这是您要复制到的字符串。src
:源字符串的指针。这是您要从中复制的字符串。
返回值:
- 返回指向
dest
的指针。
功能:
strcpy()
函数会将src
字符串(包括末尾的 ‘\0’)复制到dest
字符串。
注意事项:
- 确保
dest
有足够的空间来存储src
的内容。否则,超出dest
边界的写入可能会导致未定义的行为。 src
和dest
不应该重叠,因为结果是未定义的。
示例:
#include <stdio.h> #include <string.h> int main() { char source[] = "Hello, World!"; char destination[20]; strcpy(destination, source); printf("Source: %s\\n", source); printf("Destination: %s\\n", destination); return 0;}
输出:
Source: Hello, World! Destination: Hello, World!
在上述示例中,
source
字符串被复制到destination
字符串。两者现在都包含相同的内容。总的来说,
strcpy()
是一个非常有用的函数,但使用时需要小心,确保目标字符串有足够的空间来容纳源字符串,以避免潜在的缓冲区溢出问题。
- 注意事项:
- 当你为字符数组分配空间时,确保考虑到末尾的 ‘\0’。
- 不要超出字符数组的边界。这可能会导致未定义的行为。
- 打印字符数组可以用
printf(”%s”,数组名);
这告诉我们字符数组和字符串有某种联系
- 与指针的关系:
字符指针经常被用来指向字符串。例如,char *str = "Hello";
在这里,str
是一个指针,指向字符串 “Hello” 的第一个字符。
-
确定两个字符串,使其输出动画的效果:b的元素从两边逐渐取代a的元素
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<Windows.h> //Sleep
#include<stdlib.h> //system
int main()
{
char a[] = "*****************";
char b[] = "welcome to leshan";
int i = 0;
int length = sizeof(a) / sizeof(a[0])-1;
int j = length-1;
printf("%c\n", b[length]);
while(i<=j) {
a[i] = b[i];
a[j] = b[j];
j--;
i++;
for (int k = 0; k <length ; k++) {//对与字符数组也可以使用
//printf("%s",a); 进行打印
printf("%c", a[k]);
}
Sleep(500);//使用Sleep函数进行休眠处理
system("cls");//清楚控制台屏幕 system是库函数
// printf("\n");
}
printf("%s", a);
return 0;
}
11.2练习二:二分查找的运用
二分查找就是折半查找,目的是简化运算量。
比如我们想再一个递增数列中寻找某个值:
#include<stdio.h>
int main()
{
int a[] = { 1,2,3,4,5,6,7,8,9,10 };
int left = 0;
int right = sizeof(a) / sizeof(a[1]) - 1;
int k = 0;
scanf("%d", &k);
while (left <= right) {
int middle = (left + right) / 2;
if (a[middle] == k) {
printf("%d", middle);
break;
}
else if (a[middle] > k) right = middle-1 ;
else left = middle+1 ;
}
if (left > right) printf("我找不到");
return 0;
}