11.字符数组
11.1 字符数组定义与初始化
字符数组是数组的元素类型为字符型
的数组。特殊之处在于它是数组元素为字符的数组。其定义与初始化的一般形式和注意事项与之前讲解的一般数组类似,只是其中的类型说明符是char。当然,并不是说类型说明符只能是char,也可以是long、int等,但是由于char型只占用一个字节的大小,使用long型和int型来定义字符数组会造成资源的浪费,因此一般选择使用char型来定义字符数组。
//字符数组定义 - 这些都是一维数组,因此初始化的方式和一维数组是一样的 char chars1[10]; int chars2[10]; long chars3[10];
我们来比较一下占用的空间大小:
#include<stdio.h> int main() { /* 这里使用的一维数组初始化方式: 对数组全部元素赋值,但不指定长度 */ char arr[] = { 'g','i','r','l' }; /* 这里使用的一维数组初始化方式: 直接对数组中的全部元素赋值 */ int brr[4] = { 'g','i','r','l' }; /* 比较一下数组内存大小: char类型占一个字节,int类型占4个字节 因此sizeof(arr)的值是 1 * 4 = 4 因此sizeof(brr)的值是 4 * 4 = 16 */ printf("char类型数组占用内存:%lld\n" ,sizeof(arr)); printf("int 类型数组占用内存:%lld" ,sizeof(brr)); return 0; }
在上面的代码中定义了不同类型的字符数组来存放相同的字符,可以看出,它们占用的内存大小相差很大,int型字符数组所占用内存大小是char型数组占用内存大小的4倍。从这点可以看出,选用char型作为字符数组类型避免了内存空间的浪费。
11.2 未完全初始化
#include<stdio.h> int main() { int i; char arr[20] = { 'm','y',' ','g','i','r','l' }; // sizeof(arr)/sizeof(arr[0] 的值为数组的长度 - 20 for (i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) { printf("%c", arr[i]); } return 0; }
运行结果为“my girl”,其中有一些空字符。看看上面代码中定义的arr数组,其数组长度为20,而初始化的字符元素的个数为7,初始化的字符元素个数小于数组长度,编译器在编译过程中将后面没有初始化的数组元素赋值为‘\0’,这也正是打印输出中含有空字符的原因。
⚠️ 注意这个空字符不是空格符号。而是指的什么也不输出打印。
‘\0’代表ASCII码为0的字符,从ASCII码表中可以查到,ASCII码为0的字符不是一个可以显示的字符,而是一个“空操作符”,即它什么也不做。
#include<stdio.h> int main() { int i; char arr[20] = { 'm','y',' ','g','i','r','l' }; // sizeof(arr)/sizeof(arr[0] 的值为数组的长度 - 20 for (i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) { printf("%c", arr[i]); } printf("我爱过你"); return 0; }
我们看下上述代码对数组字符的打印次数:
#include<stdio.h> int main() { int i; char arr[20] = { 'm','y',' ','g','i','r','l' }; // sizeof(arr)/sizeof(arr[0] 的值为数组的长度 - 20 for (i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) { printf("打印第%d个字符:%c\n", i+1,arr[i]); } return 0; }
但很明显,我们并不需要打印那些为’\0’的字符。因此,在打印的时候也可以将数组中的元素‘\0’视为数组结束的标志,例如:
#include<stdio.h> int main() { int i; char arr[20] = { 'm','y',' ','g','i','r','l' }; for (i = 0; arr[i]!='\0'; i++) { printf("打印第%d个字符:%c\n", i+1,arr[i]); } return 0; }
11.3 字符串常量的方式来对一维字符数组进行初始化
在C语言中没有专门的数据类型来定义 字符串 ,因此我们是将字符串作为字符数组来处理的,字符串中的字符是逐个存放到数组元素中的。
11.3.1 不指定长度初始化
在对一维字符数组进行定义和初始化的过程中,可以不指定其长度:
#include<stdio.h> int main() { int i; /* 这里使用的一维数组初始化方式: 对数组全部元素赋值,但不指定长度, 但注意的是这是数组的长度不是3,而是4, 因为我们在将字符串存入数组的时候会默认在末尾加上一个 \0 字符 即以这种初始化方式对字符串进行存储会以 \0 作为数组的结束标志 */ char arr[] = "abc"; // sizeof(arr) / sizeof(arr[0] 结果是 4 for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("打印第%d个字符:%c\n", i + 1, arr[i]); } return 0; }
为了测定字符串的实际长度,C语言规定了一个“字符串结束标志”,以字符‘\0’作为结束标志。
我们可以进行一个对比:
#include<stdio.h> int main() { /* 这里使用的一维数组初始化方式: 对数组全部元素赋值,但不指定长度 */ char arr[] = { 'g','i','r','l' }; /* 1.char brr[] = { "girl" }; 2.char brr[] = "girl"; 加不加括号都可以,一样的 */ char brr[] = { "girl" }; /* 比较一下数组内存大小: 1. sizeof(arr)的值是 1 * 4 = 4 2.在将 "girl" 赋值给 brr 时会将这四个字母因此存入数组, 同时在末尾还会追加一个 \0 字符,作为该数组结束的标志 因此 sizeof(brr)的值是 1 * (4 + 1) = 5 */ printf("arr 数组占用内存:%lld\n", sizeof(arr)); printf("brr 数组占用内存:%lld", sizeof(brr)); return 0; }
💬 采用这两种方式得到的数组长度并不相同,在采用字符串常量对字符数组进行初始化的过程中,在内存中进行存储时会自动在字符串的后面添加一个结束符‘\0’,所以得到的字符数组长度是字符串常量的长度加1;而采用字符常量列表的方式对字符数组进行初始化就不会在最后添加一个结束符,所以利用这种方式定义的字符数组的长度就是字符常量列表中字符的个数。
11.3.2 完全初始化
#include<stdio.h> int main() { int i; /* 数组的长度是4, 由于规定了arr数组的长度为4,并且字符串常量的长度也是4, 因此数组就没有空间在字符串最后追加 \0 了 */ char arr[4] = "girl"; // sizeof(arr) / sizeof(arr[0] 结果是 4 for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("打印第%d个字符:%c\n", i + 1, arr[i]); } return 0; }
11.3.3 未完全初始化
#include<stdio.h> int main() { int i; /* 数组的长度是10, 由于规定了arr数组的长度为10,而字符串常量的长度也是4, 因此数组其它部分都为 \0 */ char arr[10] = "girl"; // sizeof(arr) / sizeof(arr[0] 结果是 10 for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("打印第%d个字符:%c\n", i + 1, arr[i]); } return 0; }
11.4 C语言字符串处理函数
这些函数都是来自与 <stdio.h> 标准输入输出库,因此在使用这些函数的时候必须引入该库。
11.4.1 puts函数 - 输出字符串的函数
📍 将一个字符串输出到终端。基本语法:
puts(字符数组) • 1
📝 演示:
#include<stdio.h> int main() { char arr[] = "my girl , i love you"; puts(arr); return 0; }
11.4.2 gets函数 - 输入字符串的函数
📍 从终端输入一个字符串到字符数组,并且得到一个函数值。基本语法:
gets(字符数组)
📝 演示:
#include<stdio.h> int main() { //必须声明长度 char arr[20]; //键盘输入 printf("输入:"); gets(arr); //输出 printf("输出:"); puts(arr); return 0; }
⚠️ 关于使用 gets() 函数需要注意:使用 gets() 时,系统会将最后“敲”的换行符从缓冲区中取出来,然后丢弃,所以缓冲区中不会遗留换行符。这就意味着,如果前面使用过 gets(),而后面又要从键盘给字符变量赋值的话就不需要吸收回车清空缓冲区了,因为缓冲区的回车已经被 gets() 取出来扔掉了。
11.4.3 strcat函数 - 字符串连接函数
📍 把两个字符数组中的字符串连接起来,把字符串2接到字符串1的后面,结果放在字符数组1中,函数调用后得到一个函数值——字符数组1的地址。基本语法:
strcat(字符数组1,字符数组2)
📝 演示:
#include<stdio.h> int main() { //设置arr的长度为40,保证追加时有足够空间 char arr[40] = "my girl "; char brr[] = "i love you"; //将brr的字符追加到arr数组中 strcat(arr, brr); puts(arr); return 0; }
11.4.4 strcpy - 字符串复制函数
📍 将字符数组1拷贝到字符数组2中。基本语法:
strcpy(字符数组1,字符串2)
📝 演示:
#include<stdio.h> int main() { char arr[20] = "i love you"; char brr[] = "hate"; //清空arr的内容,再将brr的内容逐一拷贝到arr strcpy(arr, brr); puts(arr); return 0; }
11.4.5 strcmp函数 - 字符串比较函数
📍 比较字符串1和字符串2,将两个字符串自左向右逐个字符相比,直到出现不同的字符或遇到“\0”为止。如果全部字符相同,则认为两个字符串相等:若出现不相同的字符,则以第1对不相同的字符的比较结果为准。基本语法:
strcmp(字符串1,字符串2)
📝 演示:
#include<stdio.h> int main() { char arr[] = "abc"; char brr[] = "aaa"; //两个数组的第二个字符一个为b,一个为a, //由于b的ASCII码值大于 a,因此此时结束比较, //对于 strcmp(字符串1,字符串2), //如果是 字符串1 大,则返回数字 1 //如果是 字符串2 大,则返回数字 -1 //如果完全相等,返回 0 printf("%d\n\n",strcmp(arr, brr)); printf("%d\n\n", strcmp(brr, arr)); printf("%d\n\n", strcmp(arr, "abc")); return 0; }
11.4.6 strlen函数 - 测字符串长度的函数
📍 测试字符串长度的函数。函数的值为字符串中的实际(有效)长度,即在遇到 \0 以前的字符个数。基本语法:
strlen(字符数组)
📝 演示:
#include<stdio.h> int main() { char arr[20] = "love"; //有效长度,在 \0 之前的内容 printf("数组有效长度:%d\n\n", strlen(arr)); //数组的内存大小 printf("数组内存大小:%lld", sizeof(arr)); return 0; }
11.4.7 strupr函数 - 全部转换为大写的函数
📍 将字符串中小写字母换成大写字母。基本语法:
strupr(字符串)
📝 演示:
#include<stdio.h> int main() { char arr[20] = "Love"; strupr(arr); puts(arr); return 0; }
11.4.8 strlwr函数 - 转换为小写的函数
📍 将字符串中小写字母换成小写字母。基本语法:
strlwr(字符串)
📝 演示:
#include<stdio.h> int main() { char arr[20] = "LovE"; strlwr(arr); puts(arr); return 0; }
11.5 VS中利用scanf_s函数输入字符串时出错问题解决
在vs中直接使用 scanf_s 输入函数会报错:
scanf_s函数还需加一个参数 :length参数,限定字符串的长度。若超过length参数将无法输入。
#include<stdio.h> int main() { char arr[5]; printf("输入:"); // %s 是字符数组的格式占位符 // sizeof(arr)是告诉编译器应当在缓冲区预留 sizeof(arr) 即5个字节大小的空间输入到arr // 同时由于这里会默认以 \0 作为字符串的结果,因此我们最多可以输入四个字符,否则报错 scanf_s("%s", arr, sizeof(arr)); printf("\n输出:"); puts(arr); return 0; }
11.6 vs中用scanf输入
在代码第一行加入预处理指令:#define _CRT_SECURE_NO_WARNINGS 1
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { char arr[5]; printf("输入:"); scanf("%s", arr); printf("\n输出:"); puts(arr); return 0; }
11.7 gets() 函数 与 scanf/scanf_s 函数的选择
gets() 函数不仅比 scanf 简洁,而且,就算输入的字符串中有空格也可以直接输入,不用像 scanf 那样要定义多个字符数组。我们来演示一下这个问题:
📝 使用 gets():
#include<stdio.h> int main() { char arr[20]; printf("输入:"); gets(arr); printf("\n输出:"); puts(arr); return 0; }
📝 使用 scanf或者scanf_s:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { char arr[20]; printf("输入:"); scanf("%s", arr); printf("\n输出:"); puts(arr); return 0; }
12.数组总结
12.1 数组的定义
元素类型相同,大小相等。
12.2 数组的特点
- 在内存中,数组是一块连续的区域。
- 数组需要预留空间,在使用前要先申请占内存的大小,可能会浪费内存空间。
- 插入数据和删除数据效率低,插入数据时,这个位置后面的数据在内存中都要向后移。
- 随机读取效率很高。因为数组是连续的,知道每一个数据的内存地址,可以直接找到给定地址的数据。
- 并且不利于扩展,数组定义的空间不够时要重新定义数组。
12.3 数组的优缺点
12.3.1 数组的优点
- 随机访问性强
- 查找速度快
12.3.2 数组的缺点
- 插入和删除效率低
- 可能浪费内存
- 内存空间要求高,必须有足够的连续内存空间。
- 数组大小固定,不能动态拓展
13.其他说明
愿你善待自己年轻的皮囊,也愿你拥有不会腐朽的有趣灵魂。我们每个人,不管是否肤白貌美,腰缠万贯,都值得有个人来好好爱你。所以你要加油,等到他或她来的时候,抱紧ta,告诉ta说,等了你好久好久,来了,就别再走了。❤️