C 数组
C语言数组属于构造数据类型。 一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。 因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。
C语言数组属于构造数据类型。 一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。 因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。
数组的定义
在C语言中,定义数组需要遵循特定的语法,一般形式如下:
dataType arrayName[length];
其中, dataType 是每个元素的类型,比如 int、float、char 等; arrayName 是数组的名字,它是一个自定义的标识符,符合标识符的命名规则;
ATTENTION 方括号
[ ]中的 length 是数组的长度,也即数组包含的元素的个数。
数组下标(Index)
arrayName[index]
arrayName 为数组名称,index 为下标
- 在C语言中,数组的下标 从 0 开始
通过下标,可以对数组进行访问、赋值等操作
ATTENTION 访问数组元素时,下标的取值范围为 0 ≤ index < length,过大或过小都会越界,导致数组溢出,发生不可预测的情况。 相关内容将在C语言数组的越界和溢出(计划补充)中重点讨论
数组的初始化
定义数组、给数组赋值 E.g.
int num[5] = {20, 345, 700, 22, -94};
数组元素的值由{ }包围,各个值之间以,分隔
初始化过程中需要注意的是
-
可以只给部分元素赋值。当
{ }中值的个数少于元素个数时,只给前面部分的元素赋值。未被赋值的元素自动初始化为 0。- 对于short、int、long,就是整数 0;
- 对于char,就是字符 ‘\0’;
- 对于float、double,就是小数 0.0。
-
只能给元素逐个赋值,不能给数组整体赋值。 例如给 10 个元素全部赋值为 1,不能写作
int a[10] = 1; -
如果给全部元素赋值,那么在定义数组时可以不指明数组长度,编译器会根据元素的个数自动推断出数组长度。 例如:
int a[] = {1, 2, 3, 4, 5};等价于int a[5] = {1, 2, 3, 4, 5};
数组内存是连续的
| a[0] | a[1] | a[2] | a[3] |
|---|---|---|---|
| 连续的内存为指针操作(通过指针来访问数组元素)和内存处理(整块内存的复制、写入等)提供了便利,这使得数组可以作为缓存(临时存储数据的一块内存)使用。 |
二维数组
一般形式:
dataType arrayName[length1][length2];
dataType 为每个元素的类型,arrayName 为数组名,length1 为第一维下标的长度,length2 为第二维下标的长度
省略
二维数组只能省略第一维的内容
字符数组和字符串
简介
在C语言中,用来存放字符的数组称为字符数组
EXAMPLE
char a[10]; //一维字符数组 char b[5][10]; //二维字符数组 char c[20]={'c', ' ', 'p', 'r', 'o', 'g', 'r', 'a','m'}; // 给部分数组元素赋值 char d[]={'c', ' ', 'p', 'r', 'o', 'g', 'r', 'a', 'm' }; //对全体元素赋值时可以省去长度
字符数组实际上是一系列字符的集合,也就是字符串(String)。 C语言中并没有专门的字符串类型(比如 string 类型),通常就用一个字符数组来存放一个字符串。
C语言规定,可以将字符串直接赋值给字符数组
EXAMPLE
char str[30] = {"c.biancheng.net"}; char str[30] = "c.biancheng.net"; //这种形式更加简洁,实际开发中常用
数组第 1 个元素为'c',第 2 个元素为'.',第 3 个元素为'b',后面的元素以此类推。
为了方便,你也可以不指定数组长度,
EXAMPLE
char str[] = {"c.biancheng.net"}; char str[] = "c.biancheng.net"; //这种形式更加简洁,实际开发中常用
给字符数组赋值时,我们通常使用这种写法
ATTENTION 这里需要留意一个坑,字符数组只有在定义时才能将整个字符串一次性地赋值给它,一旦定义完了,就只能一个字符一个字符地赋值了
EXAMPLE
char str[7]; str = "abc123"; //错误 //正确 str[0] = 'a'; str[1] = 'b'; str[2] = 'c'; str[3] = '1'; str[4] = '2'; str[5] = '3';
字符串结束标志 ⚠
字符串是一系列连续的字符的组合,要想在内存中定位一个字符串,除了要知道它的开头,还要知道它的结尾。
ATTENTION
- 在C语言中,字符串总是以
'\0'作为结尾,所以'\0'也被称为字符串结束标志,或者字符串结束符。
'\0'是 ASCII 码表中的第 1 个字符,英文称为 NUL,中文称为“空字符”。该字符既不能显示,也没有控制功能,输出该字符不会有任何效果,它在C语言中唯一的作用就是作为字符串结束标志。
C语言在处理字符串时,会从前往后逐个扫描字符,一旦遇到'\0'就认为到达了字符串的末尾,就结束处理。'\0'至关重要,没有'\0'就意味着永远也到达不了字符串的结尾。
由" "包围的字符串会自动在末尾添加'\0'。例如,"abc123"从表面看起来只包含了 6 个字符,其实不然,C语言会在最后隐式地添加一个'\0',这个过程是在后台默默地进行的,所以我们感受不到。
下图演示了"C program"在内存中的存储情形:
![]()
需要注意的是,逐个字符地给数组赋值并不会自动添加'\0',例如:
char str[] = {'a', 'b', 'c'};
数组 str 的长度为 3,而不是 4,因为最后没有'\0'。
当用字符数组存储字符串时,要特别注意
'\0',要为'\0'留个位置; 这意味着,字符数组的长度至少要比字符串的长度大 1。
EXAMPLE
char str[7] = "abc123";
"abc123"看起来只包含了 6 个字符,我们却将 str 的长度定义为 7,就是为了能够容纳最后的'\0'。如果将 str 的长度定义为 6,它就无法容纳'\0'了。
BUG 当字符串长度大于数组长度时,有些较老或者不严格的编译器并不会报错,甚至连警告都没有,这就为以后的错误埋下了伏笔,要多多注意。
根据 ASCII 码表,字符'\0'的编码值就是 0。
更加专业的做法是将数组的所有元素都初始化为“零”值,这样才能够从根本上避免问题。
#include <stdio.h>
int main(){
char str[30] = {0}; //将所有元素都初始化为 0,或者说 '\0'
char c;
int i;
for(c=65,i=0; c<=90; c++,i++){
str[i] = c;
}
printf("%s\n", str);
return 0;
}
printf() 输出字符串时,会从第 1 个元素开始往后检索,直到遇见'\0'才停止,然后把'\0'前面的字符全部输出,这就是 printf() 输出字符串的原理。
字符串长度
定义:指字符串包含的字符数量,不包括最后的结束符'\0'
e.g. "abc"的长度是 3,不是 4
在C语言中,我们使用string.h头文件中的 strlen() 函数来求字符串的长度,它的用法为:
length = strlen(strname);
strname 是字符串的名字,或者字符数组的名字;length 是一个变量,用来接收 strlen() 求得的字符串长度,它是一个整数。
字符串的输入和输出
字符串的输出
在C语言中,有两个函数可以在控制台(显示器)上输出字符串,它们分别是:
- puts():输出字符串并自动换行,该函数只能输出字符串。
- printf():通过格式控制符
%s输出字符串,不能自动换行。除了字符串,printf() 还能输出其他类型的数据。
#include <stdio.h>
int main(){
char str[] = "http://c.biancheng.net";
printf("%s\n", str); //通过字符串名字输出
printf("%s\n", "http://c.biancheng.net"); //直接输出
puts(str); //通过字符串名字输出
puts("http://c.biancheng.net"); //直接输出
return 0;
}
/*运行结果
http://c.biancheng.net
http://c.biancheng.net
http://c.biancheng.net
http://c.biancheng.net
*/
ATTENTION 输出字符串时只需要给出名字,不能带后边的
[ ],例如,下面的两种写法都是错误的:printf("%s\n", str[]); puts(str[10]);
字符串的输入
在C语言中,有两个函数可以让用户从键盘上输入字符串,它们分别是:
- scanf():通过格式控制符
%s输入字符串。除了字符串,scanf() 还能输入其他类型的数据。 - gets():直接输入字符串,并且只能输入字符串。
ATTENTION
gets函数的行为与scanf不同。它会读取缓冲区中的所有字符(包括空格),直到遇到换行符\n为止。它会消耗掉换行符,并在字符串末尾加上终止符\0。
但是,scanf() 和 gets() 是有区别的:
- scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以无法读取含有空格的字符串。
- gets() 认为空格也是字符串的一部分,只有遇到回车键时才认为字符串输入结束,所以,不管输入了多少个空格,只要不按下回车键,对 gets() 来说就是一个完整的字符串。换句话说,gets() 用来读取一整行字符串。
EXAMPLE
#include <stdio.h> int main(){ char str1[30] = {0}; char str2[30] = {0}; char str3[30] = {0}; //gets() 用法 printf("Input a string: "); gets(str1); //scanf() 用法 printf("Input a string: "); scanf("%s", str2); scanf("%s", str3); printf("\nstr1: %s\n", str1); printf("str2: %s\n", str2); printf("str3: %s\n", str3); return 0; }ATTENTION scanf() 在读取数据时需要的是数据的地址,这一点是恒定不变的,所以对于 int、char、float 等类型的变量都要在前边添加
&以获取它们的地址。但是在本段代码中,我们只给出了字符串的名字,却没有在前边添加&,这是为什么呢?因为字符串名字或者数组名字在使用的过程中一般都会转换为地址,所以再添加&就是多此一举,甚至会导致错误了。
REMEMBER 就目前学到的知识而言,int、char、float 等类型的变量用于 scanf() 时都要在前面添加
&,而数组或者字符串用于 scanf() 时不用添加&,它们本身就会转换为地址。
C语言 字符串处理函数
string.h是一个专门用来处理字符串的头文件,它包含了很多字符串处理函数
strlen函数––测字符串长度的函数
一般形式
strlen(字符串)
用途:测试字符串长度 e.g. “China” 字符串长度为5
字符串连接函数strcat()
strcat 是 string catenate 的缩写,意思是把两个字符串拼接在一起,语法格式为:
strcat(arrayName1, arrayName2);
arrayName1、arrayName2 为需要拼接的字符串。
strcat() 将把 arrayName2 连接到 arrayName1 后面,并删除原来 arrayName1 最后的结束标志'\0'。
这意味着,arrayName1 必须足够长,要能够同时容纳 arrayName1 和 arrayName2,否则会越界(超出范围)
strcat() 的返回值为 arrayName1 的地址。
字符串复制函数 strcpy()
strcpy 是 string copy 的缩写,意思是字符串复制,也即将字符串从一个地方复制到另外一个地方,语法格式为:
strcpy(arrayName1, arrayName2);
strcpy() 会把 arrayName2 中的字符串拷贝到 arrayName1 中,字符串结束标志'\0'也一同拷贝。
EXAMPLE
#include <stdio.h> #include <string.h> int main(){ char str1[50] = "C语言教程"; char str2[50] = "https://c.biancheng.net/c/"; strcpy(str1, str2); printf("str1: %s\n", str1); return 0; } //运行结果:str1: https://c.biancheng.net/c/str2 复制到 str1 后,str1 中原来的内容就被覆盖了 另外,strcpy() 要求 arrayName1 要有足够的长度,否则不能全部装入所拷贝的字符串
字符串比较函数 strcmp()
strcmp 是 string compare 的缩写,意思是字符串比较,语法格式为:
strcmp(arrayName1, arrayName2);
arrayName1 和 arrayName2 是需要比较的两个字符串。
字符本身没有大小之分,strcmp() 以各个字符对应的 ASCII 码值进行比较。strcmp() 从两个字符串的第 1 个字符开始比较,如果它们相等,就继续比较下一个字符,直到遇见不同的字符,或者到字符串的末尾。
返回值:
- 若 arrayName1 和 arrayName2 相同,则返回 0;
- 若 arrayName1 大于 arrayName2,则返回大于 0 的值;
- 若 arrayName1 小于 arrayName2,则返回小于 0 的值。
strlwr函数––转换为小写的函数
一般形式
strlwr(字符串)
作用:将字符串中大写字母换成小写字母
strupr函数––转换为大写的函数
一般形式
strupr (字符串)
作用:将字符串中小写字母换成大写字母
关于查找和排序
学完了数组,有两项内容大家可以深入研究了,分别是查找(Search)和排序(Sort),它们在实际开发中都经常使用,比如:
- 给你 10 个打乱顺序的整数,要能够按照从小到大或者从大到小的顺序输出;
- 给定一个字符串 str1,以及一个子串 str2,要能够判断 str2 是否在 str1 中。
查找和排序算法(计划补充)
- 冒泡排序算法
- 插入排序算法
- 选择排序算法
- 希尔排序算法
- 归并排序算法
- 快速排序算法
- 二分查找(折半查找)算法
- 插值查找算法
- 哈希查找算法