2 多维数组
2.1 多维数组介绍
下图是一个四行六列的二维数组示意图:
2.2 二维数组的定义
1.方式1: 先定义再初始化(最蠢的做法)
// 定义一个4行6列的二维数组
int a[4][6];
// 进行初始化赋值
a[0][0] = 10
a[0][1] = 20;
a[0][2] = 30;
a[0][3] = 40;
a[0][4] = 50;
a[0][5] = 60;
a[1][0] = 100;
a[1][1] = 200;
……
2. 方式2: 直接定义并初始化
// 定义一个4行6列的二维数组,以为矩阵的形式初始化
int a[4][6] = {
{10, 20, 30, 30, 40, 60},
{100, 200, 300, 400, 500, 600},
{1000, 2000, 3000, 4000, 5000, 6000},
{10000, 20000, 30000, 40000, 50000, 60000}
};
// 定义一个4行6列的二维数组, 会自动匹配到各行各列
int b[4][6] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
// 如果所赋值的数量可以与元素数量对应,第一维的数组长度可以不给出
int b[][6] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
1. 初始化可以不写行但是一定要写列
2. 如果对全部元素都赋初值(即提供全部初始数据),则定义数组时对第一维的长度可以不指定,但第二维的长度不能省。例如
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
与下面定义等价:
int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
2.3 二维数组的访问和遍历
访问二维数组的元素,需要使用两个下标(索引),一个用于访问行(第一维),另一个用于访问列(第二维),我们通常称为行下标(行索引)或列下标(列索引)。遍历二维数组,需要使用双层循环结构
代码示例:
#include <stdio.h>
int main()
{
// 定义一个 3 行 4 列的数组
int map[3][4] = {
{1, 2, 3, 4},
{11, 12, 13, 14},
{21, 22, 23, 24}};
// 计算第一维度的长度
int rows = sizeof(map) / sizeof(map[0]);
// 计算第二维度的长度
int cols = sizeof(map[0]) / sizeof(int);
// 遍历输出每个元素
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
printf("%d\t", map[i][j]); // \t 可以输出得更加整齐
}
printf("\n");
}
// 计算所有元素的和
int sum = 0;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
sum += map[i][j];
}
}
printf("所有元素的和:%d", sum); // 所有元素的和:150
return 0;
}
输出结果:
1 2 3 4
11 12 13 14
21 22 23 24
所有元素的和:150
3.字符数组
3.1字符数组(字符串)介绍
用来存放字符的数组称为字符数组,也可以称为字符串。字符串的输入输出格式占位符是 %s。
字符串结尾,会自动添加一个 \0 作为字符串结束的标志,所以字符数组最后一个元素必须是 \0。
\0 是ASCII码表中的第0个字符,用NUL表示,称为空字符,该字符既不能显示,也不是控制字符,输出该字符不会有任何效果,它在C语言中仅作为字符串的结束标志。
3.2 字符数组(字符串)的定义
- 方式一:最后一个元素设置成 \0
在给某个字符数组赋值时,赋值的元素个数小于字符数组的长度,则会自动在后面加 '\0', 表示字符串结束; 赋值的元素的个数等于该数组的长度(或不指定数组长度),则不会自动添加 '\0'。
#include <stdio.h>
int main()
{
char str1[12] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0'}; // 显式地设值 \0
char str2[4] = {'t', 'o', 'm'}; // 后面自动添加 \0
char str3[] = {'j', 'a', 'c', 'k'}; // 不会自动添加 \0
printf("str1=%s \n", str1);
printf("str2=%s \n", str2);
printf("str3=%s \n", str3); // 由于没有结束标识,会包括相邻内存的数据,直到遇到结束标记
return 0;
}
输出结果:
str1=Hello World
str2=tom
str3=jacktom
2. 方式二:简化写法
#include <stdio.h>
int main()
{
char str1[] = {"I am happy"}; // 后面自动添加 \0
char str2[] = "I am happy"; // 省略{}号,后面自动添加 \0
printf("\n str1=%s", str1);
printf("\n str2=%s", str2);
return 0;
}
输出结果:
str1=I am happy
str2=I am happy
3.3 sizeof跟strlen的区别
strlen用来计算有效字符串的长度,会自动忽略结束字符(’\0’)的长度。sizeof用来计算长度如果用来计算字符串的长度的话会多一个结束字符(’\0’)的长度。
#include <stdio.h>
#include <string.h> // 定义字符串的头文件
int main()
{
// 定义字符串
char greeting[128] = "Hello"; //部分赋值其他部分初始赋值成’\0’
// 计算字符串长度
int len = sizeof greeting / sizeof greeting[0];
int *p = "Hello";
printf("%s \n", greeting);
printf("数组长度(sizeof):%d \n", sizeof(greeting) );
printf("数组长度:%d \n", len);
printf("数组长度(strlen):%d \n", strlen(greeting) );
printf("sizeof:p :%d \n", sizeof(p) ); //相当于sizeof(char *)
printf("strlen):p :%d \n", strlen(p) );
输出结果:
Hello
数组长度(sizeof):128
数组长度:128
数组长度(strlen) :5
Sizeof:p :8;
Strlen:p :5;
上文的sizeof(p)跟sizeof(char *)表达的意思一样,都是表达一个指针所占的几个字节。strlen(p)表达的是字符串的长度。
3.3.几种字符串常用的API
- 输出字符串
- puts()
- printf("%s",p);
- 获取字符串
- scanf("%s",p)
- gets
- char * gets ( char * str );
- 因为本函数可以无限读取,易发生溢 出。如果溢出,多出来的字符将被写入到堆栈中,这就覆盖了堆栈原先的内容,破坏一个或多个不相关变量的值
- 计算长度
- strlen
- 拷贝——【拷贝】
- strcpy
- char *strcpy(char* dest, const char *src);
- (strcpy(拷贝输送,拷贝数据))
- 断言
- assert——【断言】
- 拼接
- strcat
- char *strcat(char *dest, const char *src);
- 把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除*dest原来末尾的“\0”)。要保证*dest足够长,以容纳被复制进来的*src。*src中原有的字符不变。返回指向dest的指针。
- 拼接
- Strcat 【拼接】
- 把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除*dest原来末尾的“\0”)。要保证*dest足够长,以容纳被复制进来的*src。*src中原有的字符不变。返回指向dest的指针.
- 初始化
- strcmp 【初始化】
- int strcmp(const char *s1,const char *s2);
- 若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数
- int strcmp(const char *s1,const char *s2);
- strncmp
- int strncmp ( const char * str1, const char * str2, size_t n )
- 功能是把 str1 和 str2 进行比较,最多比较前 n 个字节,若str1与str2的前n个字符相同,则返回0;若s1大于s2,则返回大于0的值;若s1 小于s2,则返回小于0的值。
- int strncmp ( const char * str1, const char * str2, size_t n )
- strcmp 【初始化】
- 初始化函数
- Memset
- void *memset(void *ptr, int value, size_t num);
- ptr 是指向要填充的内存块的指针。
- value 是要填充的值。
- num 是要填充的字节数。
- 用于将一块内存按指定的值进行填充。
3.4 动态开辟字符串
- malloc
函数原型 void *malloc(size_t size)
含义:C 库函数 void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。
#include <stdio.h>
#include <string.h> // 定义字符串的头文件
#include <stdlib.h>
int main()
{
char *p;
p = (char *)malloc(1); //开辟一个内存空间让p有了具体的内存指向。
*p = ‘c’ ;
printf("%c \n", *p);
//输出结果:c
// 同样也可以开辟多个内存空间
p = (char *)malloc(12); //开辟12个内存空间让p有了具体的内存指向。
strcpy(p, "pengkun");
printf("%c \n", *p);
// 输出结果:penkun
return 0;
}
但是此时第一个malloc断开(也就是这个内存是悬挂的无用的,找不到这个地址了)正常情况下像这个无用的内存要给他free掉(因为malloc在堆上开辟空间,数组,普通的变量是在栈上开辟空间,在函数调用结束之后会清理掉这些数据,回收里面的内存。但是堆只有在程序结束后才释放。如果一直循环的话有可能会耗尽堆上的资源)
2. free
C 库函数 void free(void *ptr) 释放之前调用 calloc、malloc 或 realloc 所分配的内存空间
用来释放,防止内存泄漏;防止悬挂指针(野指针的一种·);
#include <stdio.h>
#include <string.h> // 定义字符串的头文件
#include <stdlib.h>
int main()
{
char *p;
p = (char *)malloc(1); //开辟一个内存空间让p有了具体的内存指向。
*p = ‘c’ ;
printf("%c \n", *p);
//输出结果:c
// 同样也可以开辟多个内存空间
free(p); //清除p的内存
p = (char *)malloc(12); //开辟12个内存空间让p有了具体的内存指向。
strcpy(p, "pengkun");
printf("%c \n", *p);
// 输出结果:penkun
return 0;
}
3. realloc / strcpy
函数原型 void *realloc(void *ptr, size_t size)
用来扩容:
C 库函数 void *realloc(void *ptr, size_t size) 尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小。
strcpy(void ptr,“char p”)
用来拷贝,把变量拷贝到ptr中
#include <stdio.h>
#include <string.h> // 定义字符串的头文件
#include <stdlib.h>
int main()
{
char *p;
p = (char *)malloc(1); //开辟一个内存空间让p有了具体的内存指向。
*p = ‘c’ ;
printf("%c \n", *p);
//输出结果:c
// 同样也可以开辟多个内存空间
free(p); //清除p的内存
p = (char *)malloc(12); //开辟12个内存空间让p有了具体的内存指向。
int len = strlen("pengkun1234567890");
int newLen = len - 12 + 1;
realloc(p, newLen);
strcpy(p, "pengkun1234567890"); //把pengkun1234567890复制到p中
printf("%s \n", *p);
// 输出结果:penkun
return 0;
}
不用的话让指针等于NULL,尽量不要让指针成为野指针【29】(链接下文野指针)
也可以加入判断,如果p == NULL则退出程序,exit(-1);