摘要:
  本文主要是记录C语言笔记——数组与字符串。

一、数组

  数组是构造数据类型之一,是具有一定顺序关系的若干个变量集合,组成数组的各个变量称为数组的元素,数组中各元素的数据类型要求相同,用数组名下标确定。数组可以是一维的,也可以是多维的。

1.一维数组

1.1一维数组定义

  • 概念

  一维数组是指只有一个下标的数组。它在计算机的内存中是连续存储的。

  • 一维数组声明
1
storage_type data_type array_name[ array_size ];
storage_type 存储类型(可以说明也可以不说明)
data_type 任意有效的 C 数据类型(必须说明)
array_name 数组名称(必须说明)
array_size 数组长度(有些情况可以省略)

例如:

1
2
3
4
float m[12];  /* 定义一个长度为 12 的浮点型数组 */
char ch[9]; /* 定义一个长度为 9 的字符型数组 */
int a[1]; /* 定义一个长度为 9 的整型数组 */
int b[] = {0, 1, 2}; /* 定义了一个长度为3,且进行了初始化的整形数组 */

【注意】

  (1)数组必须先声明才能进行使用和赋值。

  (2)C语言对数组不作越界检查,使用时要注意。

  (3)用变量定义数组维数时,该变量必须提前告知。例如int i = 6;int a[i];

  (4)sizeof(array_name)可以获取整个一维数组所占据的字节数(等于元素个数x元素类型所占字节数)。

1.2一维数组元素访问

  • 访问格式
1
array_name[index]  /* 数组名[元素索引(也可以说是下标)] 索引从0开始 */

例如:

1
2
3
4
5
6
7
8
int a[10];

printf(“%d”, a); /* (错误) 这种访问方式是错误的 */

for(j=0;j<10;j++)
{
printf(“%d\t”, a[j]); /* (正确) 这种情况才是正确的访问方式 */
}

【注意】

  (1)数组必须定义之后才能够访问。

  (2)数组的元素只能逐个访问,不能一次性访问整个数组。

  (3)下标可以是整型表达式或者常量。

  • 遍历一维数组
1
2
3
4
5
6
7
8
int a[10];
int i, n;

n = sizeof(a) / sizeof(int);

/* 通过for循环来逐个访问 */
for(j = 0; j < n; j++)
printf(“%d\t”, a[j]); /* 逐个打印数组元素 */

1.3一维数组元素初始化

1
2
int a[6] = {0, 1, 2, 3, 4, 5}; /* 在声明数组时直接进行初始化 */
int b[] = {0, 1, 2}; /* 定义了一个长度为3,且进行了初始化的整形数组 */

【注意】

  (1)数组不初始化,其元素值为随机数

  (2)对static数组元素不赋初值,系统会自动赋以0

  (3)只给部分数组元素赋初值,剩余元素自动赋值为0

【说明】

数组声明形式 等价写法
static int a[3]; static int a[3]; a[0] = 0; a[1] = 0; a[2] = 0;
int a[3] = {1, 2}; int a[3]; a[0] = 1; a[1] = 2; a[2] = 0;
int a[] = {1, 2, 3} int a[3]; a[0] = 1; a[1] = 2; a[2] = 3;
/* 编译系统根据初值个数确定数组维数 */

1.4一维数组的存储空间

  数组是一个整体,它的内存是连续的,相邻元素地址相差 data_type所占字节长度。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(int argc, const char *argv[])
{
int a[3] = {1, 2, 3};
int i;
for(i = 0; i < 3; i++)
{
printf("&a[%d] = %p, a[%d] = %d\n",i, &a[i], i,a[i]);
}
return 0;
}
image-20220128110245590

  a[3]在内存中的结构如下图所示,a[3]数组为int类型,在64位平台下占4个字节,所以相邻元素之间地址相差4

image-20220128111357350

1.5一维数组的数组名

  一维数组的数组名代表了该数组的起始地址,可以通过该数名推出各个元素的地址,并通过指针(后边说,先提一下)进行访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main(int argc, const char *argv[])
{
int a[3] = {1, 2, 3};
int i;
printf("a = %p\n",a);
for(i = 0; i < 3; i++)
{
printf("a + %d = %p, &a[%d] = %p, a[%d] = %d\n",i, a + i, i, &a[i], i,a[i]);
}
return 0;
}
image-20220128112243966

【注意】数组名不可以进行自加自减运算。

2.二维数组

2.1二维数组声明

1
数据类型 数组名[ 常量表达式1 ][ 常量表达式2 ];

例如:

1
int a[2][3]; /* 定义了一个2行3列的二维数组 */

  表示定义了一个2行3列的二维数组,元素共有2x3=6个(元素个数 = 行数 x 列数)。

【注意】

  (1)声明时列数不能省略,**行数可以省略(定义时进行初始化,编译器可以判别出行数时)**。

  (2)sizeof(数组名)可以获取整个二维数组所占据的字节数(等于元素个数x元素类型所占字节数)。

  (3)2)sizeof(行数组名)可以获取二维数组某行所占据的字节数(等于该行元素个数x元素类型所占字节数)。

2.2二维数组访问

  • 访问格式
1
array_name[row_index][col_index]  /* 数组名[行索引][列索引] 行索引和列索引都是从0开始*/
storage_type 存储类型(可以说明也可以不说明)
data_type 任意有效的 C 数据类型(必须说明)
p_name 指针变量名
1
2
3
4
5
6
7
8
9
int a[2][3];
int i, j;
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
printf(“%d\t”, a[i][j]); /* (正确) 这种情况才是正确的访问方式 */
}
}
  • 遍历二维数组
1
2
3
4
5
6
7
8
9
10
int a[2][3];
int i, j;
/* 通过for循环进行逐个元素的访问 */
for(i=0;i<2;i++) /* 行循环 */
{
for(j=0;j<3;j++) /* 列循环 */
{
printf(“%d\t”, a[i][j]);
}
}

2.3二维数组元素初始化

1
2
3
int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; /* 分行赋值 */
int a[2][3] = {1, 2, 3, 4, 5, 6}; /* 按数组排列顺序进行赋值 */
int a[2][3] = {{1}, {4, 5}; /* 对部分元素赋初值 */

【注意】

  (1)与一维数组相同,数组不初始化,其元素值为随机;对static数组元素不赋初值,系统会自动赋以0值;只给部分数组元素赋初值,剩余元素自动赋值为0

  (2)当采用分行赋值的方法声明二维数组时,二维数组的行数可以省略,行数会由编译器自己进行计算。

【说明】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* 声明形式 1 (两种写法等价) */
static int a[2][3];
static int a[2][3]; a[0][0] = 0; a[0][1] = 0; a[0][2] = 0; a[1][0] = 0; a[1][1] = 0; a[1][2] = 0;

/* 声明形式 2 (两种写法等价) */
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
int a[2][3]; a[0][0] = 1; a[0][1] = 2; a[0][2] = 3; a[1][0] = 4; a[1][1] = 5; a[1][2] = 6;

/* 声明形式 3 (两种写法等价) */
int a[2][3] = {{1}, {4, 5}};
int a[2][3]; a[0][0] = 1; a[0][1] = 0; a[0][2] = 0; a[1][0] = 4; a[1][1] = 5; a[1][2] = 0;

/* 声明形式 4 (两种写法等价) */
int a[ ][3] = {{1, 2, 3}, {4, 5, 6}};
int a[2][3]; a[0][0] = 1; a[0][1] = 2; a[0][2] = 3; a[1][0] = 4; a[1][1] = 5; a[1][2] = 6;

/* 声明形式 5 (两种写法等价) */
int a[ ][3] = {{}, {4, 5}};
int a[2][3]; a[0][0] = 0; a[0][1] = 0; a[0][2] = 0; a[1][0] = 4; a[1][1] = 5; a[1][2] = 0;

/* 声明形式 6 (两种写法等价) */
int a[2][3] = {1, 2, 3, 4, 5, 6};
int a[2][3]; a[0][0] = 0; a[0][1] = 0; a[0][2] = 0; a[1][0] = 4; a[1][1] = 5; a[1][2] = 0;/* 编译器自己计算赋值,不过会有警告*/

/* 声明形式 7 (两种写法等价) */
int a[ ][3] = {1, 2, 3, 4, 5, 6};
int a[2][3]; a[0][0] = 0; a[0][1] = 0; a[0][2] = 0; a[1][0] = 4; a[1][1] = 5; a[1][2] = 0;/* 编译器自己计算赋值,不过会有警告*/

2.4二维数组的存储空间

  二维数组在内存中是一维的,存储时行序优先。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a[][3] = {{1, 2, 3}, {4, 5, 6}};
int i, j;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
{
printf("&a[%d][%d] = %p, a[%d][%d] = %d\n",i, j, &a[i][j],i, j, a[i][j]);
}
}
return 0;
}
image-20220128124741125

  a[2][3]在内存中的结构如下图所示,a[2][3]数组为int类型,在64位平台下占4个字节,所以相邻元素之间地址相差4

image-20220128125248664

2.5二维数组的数组名

  与一维数组一样,二维数组的数组名也代表了该二维数组的起始地址。

  二维数组的另一种理解方式:二维数组可以看做是由多个元素组成的一维数组,而每个元素又是一个数组,从而合起来构成了二维数组。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main(int argc, const char *argv[])
{

int a[][3] = {{1, 2, 3}, {4, 5, 6}};
int i, j;
printf("a = %p\n", a);
for(i = 0; i < 2; i++)
{
printf("a + %d = %p, a[%d] = %p\n",i,a +i, i, a[i]);
for(j = 0; j < 3; j++)
{
printf("&a[%d][%d] = %p, a[%d][%d] = %d\n",i, j, &a[i][j],i, j, a[i][j]);
}
}
return 0;
}

image-20220128161750817

经过打印地址发现:

  (1)a为整个二维数组的数组名,代表了整个二维数组的起始地址。

  (2)a[0], a[1]也分别代表了第一行和第二行起始数据的地址。

image-20220128161709639

经过分析和验证,可以得到:一个二维数组,按行可以分为多个一维数组,以int a[2][3]为例,该2行3列的二维数组就可以理解为2个元素组成,每个元素都是一个一维数组组成,每个一维数组的数组名就是二维数组名加上第一个下标,即a[0], a[1]

行名(代表了地址) 每行元素
a a[0] &a[0][0] a[0][0] a[0][1] a[0][2]
a + 1 a[1] &a[1][0] a[1][0] a[1][1] a[1][2]

【注意】数组名不可以进行自加自减运算,自然这里的行名就相当于每一行元素的数组名,也不可以进行自加自减运算。

二、字符串与字符数组

1.字符串

1.1字符串定义

  字符串是一系列连续的字符的组合,要想在内存中定位一个字符串,除了要知道它的开头,还要知道它的结尾。找到字符串的开头很容易,知道它的名字(字符数组名或者字符串名)就可以,但是结尾怎么办呢?在C语言中,字符串总是以'\0'作为结尾,所以'\0'也被称为字符串结束标志,或者字符串结束符。由" "包围的字符串会自动在末尾添加'\0'。例如,"abc123"从表面看起来只包含了6个字符,其实不然,C语言会在最后隐式地添加一个'\0'

  '\0'ASCII 码表中的第 0 个字符,英文称为 NULL,中文称为“空字符”。该字符既不能显示,也没有控制功能,输出该字符不会有任何效果,它在C语言唯一的作用就是作为字符串结束标志。

1.2字符串长度

  所谓字符串长度,就是字符串包含了多少个字符(**不包括最后的结束符'\0'**)。

例如:abc123字符串在内存中占据的空间是6 + 1 = 7个字符的空间,但是这个字符串的长度为6

2.字符数组

2.1字符数组定义

  元素的数据类型为字符类型的数组称为字符数组,字符数组实际上是一系列字符的集合,例如:

1
2
char a[10];     /* 定义一个长度为10的一维字符数组 */
char b[2][10]; /* 定义一个2行3列的二维字符数组 */

2.2字符数组初始化

  • 一维字符数组初始化
1
2
3
4
5
/* 一维字符数组初始化 */
char a[7] = {'h', 'e', 'l', 'l', 'o'}; /* 逐个字符赋值 */
char a[] = {'h', 'e', 'l', 'l', 'o'}; /* 省略长度,逐个字符赋值 */
char a[] = {"world!"}; /* 用字符串常量赋值 */
char a[] = "world!"; /* 用字符串常量赋值 */

【注意】

  (1)给字符数组赋值时,通常将字符串一次性地赋值(可以指明数组长度,也可以不指明),而不是一个字符一个字符地赋值。

  (2)字符数组只有在定义时才能将整个字符串一次性地赋值给它,一旦定义完了,就只能一个字符一个字符地赋值了。

  (3)字符数组在未赋初值时,内容也是随机的。

  (4)当使用字符串给字符数组赋值,且未给出长度时,创建的字符数组真实长度为字符串长度 + 1,原因就在于用字符串进行赋值时,字符串结束处自带一个字符串结束标志:\0。所以这也就意味着,我们若是要指定字符数组的大小,一定要留够字符串长度 + 1的长度,这样字符串才更加完整,也会比较严谨。

【说明】

字符数组声明形式 等价写法
char a[3] = {'h', 'e', 'l'}; char a[3]; a[0] = 'h'; a[1] = 'h'; a[2] = 'l';
char a[3] = {'h'}; char a[3]; a[0] = 'h'; a[1] = '\0'; a[2] = '\0';
char a[3] = {"hel"}; char a[3]; a[0] = 'h'; a[1] = 'e'; a[2] = 'l';
/* 这里若是长度定义为4就会更加合理 */
char a[] = {"hel"}; char a[4]; a[0] = 'h'; a[1] = 'e'; a[2] = 'l';a[3] = '\0';
  • 二维字符数组初始化
1
char fruit[][7]={"Apple", "Orange"};

效果如下表

行名 每行元素
fruit[0] 'A' 'p' 'p' 'l' 'e' '\0' '\0'
fruit[1] 'O' 'r' 'a' 'r' 'g' 'e' '\0'

2.3字符数组访问

  与一般的数组一样,访问元素时只能一个逐个字符访问,但是若是只输出的话要注意赋值方式的不同,输出时也会不一样。

  • 逐个字符访问输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>

int main(int argc, const char *argv[])
{

char a[6] = { 'h', '\0', 'e', 'l', 'l','o'}; /* 逐个赋值,中间带有 '\0' */
char b[] = "h\0ello"; /* 字符串常量赋值,中间带有 '\0' */
char c[] = "hello"; /* 字符串常量赋值,中间没带有 '\0' */
int i, n;

printf("---------Character by character---------\n");
n = sizeof(a)/sizeof(char);
printf("\nlength(a)=%d\t",n);
for(i = 0; i < n; i++)
printf("%c",a[i]);

n = sizeof(b)/sizeof(char);
printf("\nlength(b)=%d\t",n);
for(i = 0; i < n; i++)
printf("%c",a[i]);

n = sizeof(c)/sizeof(char);
printf("\nlength(c)=%d\t",n);
for(i = 0; i < n; i++)
printf("%c",a[i]);

printf("\n------------------end------------------\n");

return 0;
}
image-20220128185210049

注意数组b[]打印是有问题的。

  • 以字符串的形式访问输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

int main(int argc, const char *argv[])
{

char a[6] = { 'h', 'e', 'l', '\0', 'l','o'}; /* 逐个赋值,中间带有 '\0' */
char b[6] = { 'h', 'e', 'l', 'l','o', '!'}; /* 逐个赋值,整个字符数组不带 '\0' */
char c[] = "h\0ello"; /* 字符串常量赋值,中间带有 '\0' */
char d[] = "hello"; /* 字符串常量赋值,中间没带有 '\0' */
int n;

printf("---------Character by character---------\n");
n = sizeof(a)/sizeof(char);
printf("length(a)=%d, a[6]=%s\n", n, a);

n = sizeof(b)/sizeof(char);
printf("length(b)=%d, b[]=%s\n", n, b);

n = sizeof(c)/sizeof(char);
printf("length(c)=%d,c[]=%s\n", n, c);

n = sizeof(d)/sizeof(char);
printf("length(d)=%d,c[]=%s\n", n, d);
printf("\n------------------end------------------\n");

return 0;
}

image-20220128185825245

注意数组b[]打印是有问题的。

【总结】

  (1)注意字符数组中的\0,当输出时,该字符会对输出结果造成影响。

  (2)注意在字符数组逐个赋值时,若没有字符串结束标志\0,那么尽量不要使用printf函数的%s格式进行输出,就如上图中的b[]数组

3字符串处理函数

  在使用字符串处理函数时,要加上头文件<string.h>

1
2
3
4
5
6
7
8
9
10
11
/* 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。 */
size_t strlen(const char *str)

/* 把 src 所指向的字符串复制到 dest */
char *strcpy(char *dest, const char *src)

/* 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。 */
char *strcat(char *dest, const char *src)

/* 把 str1 所指向的字符串和 str2 所指向的字符串进行比较。 */
int strcmp(const char *str1, const char *str2)