文章目录
1.一维数组的定义
数组不属于C语言的基本数据类型。属于构造类型。
借助C语言提供的数组,可以通过一个符号来访问多个元素。
一个长度为n数组的索引从0开始,到n-1
。
类型说明符 数组名 [常量表达式]
①数组名的命名规则与变量名相同。
②定义数组时需要指定数组中元素的个数。即数组长度。
③常量表达式中可以包含常量和符号常量,但不能包含变量。(C99之前的标准不能包含变量,C99后的标准支持变量了。因此为保证考试时的正确性,最好不要写变量。)
常见的数组定义方式有以下几种情况:
①指定长度,类型,所以元素相等。
如定义a是一个长度为10,数据类型为整型的数组,数组的元素全部为0。
②指定长度,类型,以及全部元素。
如定义b是一个长度为5,数据类型为整型的数组,数组元素依次为1,3,5,7,9。
③指定长度,类型,只初始化前几个值。
定义c是一个长度为10,数据类型为整型的数组,只初始化前五个元素值为1,3,5,7,9。
④不指定长度,对全部元素进行赋值。
定义d数据类型为整型的数组,初始化元素值为1,2,3,4,5,6,7,8,9,10。
int a[10]={0};
int b[5]={1,3,5,7,9};
int c[10]={1,2,3,4,5};
int d[]={1,2,3,4,5,6,7,8,9,10}
数组的初始化不能先声明,后定义。这样分成两步来写是编译不通的。
2. 数组的访问越界 Access out of bounds
这是一个很重要的知识点。
数组的访问越界不是技巧,而是编程中的灾难。编程工作中通常要避免数组的访问越界。不然可能导致混乱。
定义一个长度为5的数组a,我们可以通过赋值表达式对数组中的元素值进行修改。
但是若尝试修改a[5],a[6],a[7],则可能会修改到程序中定义的其他变量,从而造成混乱。
代码示例如下:
#include <stdio.h>
int main() {
int a[5]={1,2,3,4,5};
int b=100;
int c=200;
a[5]=6;
a[6]=7;
a[7]=8;
printf("%d %d",b,c);
return 0;
}
输出结果
8 7
变量c的值被访问越界操作有200修改成了7。变量b的值被访问越界操作有100修改成了8。
C语言对访问越界没有做检查,所以高效,虽然危险。其他的一些语言如python对访问越界有检查,不能修改范围之外的值,虽然安全性更高,但是程序也因此更慢。
一般情况下访问越界的错误是不会直接犯的。一种常见的出现访问越界错误的情况是,比如for
循环的边界没有控制好。
3.遍历输出数组元素
下边提供了一段使用for循环依次输出数组中的元素的代码示例:
#include <stdio.h>
int main(){
int a[5]={1,2,3,4,5};
int i;
for(i=0;i<5;i++)
{
printf("%d\n", a[i]);
}
return 0;
}
程序输出结果如下:
4.数组的传递
当数组被传递给子函数的时候,子函数接收到的是数组的初始地址,而不是数组这个变量。因此数组的长度是无法传递给子函数的。如果子函数需要使用到数组的长度,则需要将其作为一个参数传入。
这导致的另一个细节后果就是,不可以在子函数中给传递进来的数组使用sizeof
运算,不然会得到指针的固定大小:8。但是在子函数外可以。
#include <stdio.h>
void print(int a[], int lenght){
int i;
for(i=0;i<lenght;i++)
{
printf("%d\n", a[i]);
}
}
int main(){
int a[5]={1,2,3,4,5};
print(a,5);
return 0;
}
程序输出结果如下:
5.可以在子函数中修改数组
在子函数中修改数组元素,是可行的。子函数外的数组会因此而被修改。这也是数组的一个重要的特点。
#include <stdio.h>
void print(int a[], int lenght){
int i;
for(i=0;i<lenght;i++)
{
printf("%d\n", a[i]);
}
a[3]=100; //在子函数中修改数组元素
}
int main(){
int a[5]={1,2,3,4,5};
print(a,5);
printf("%d\n", a[3]);//打印修改后的元素
return 0;
}
程序输出结果如下:
6.字符数组
6.1 字符数组的定义
字符数组即一个字符串,可以整体输出,不需要借助for循环。
因为字符串通常是以空字符(‘\0’)结尾的。这个空字符也被称为字符串的“结束符”。
考虑了结束符后,3个字符组成的字符串实际大小为4个字节,元素数量为4;
四个字符组成的字符串实际大小为5个字节,元素数量为5。
所以字符数组存储的字符串长度必须比字符数组少 1 字节。最后一个字符存储的是’\0’。
关于字符数组的定义有以下几种情况:
①
char c1[6]={'h','e','l','l','o'};
依次指定每个元素以实现对字符数组的定义。这种写法相对繁琐。
当执行char c1[6]={'h','e','l','l','o'};
时,程序会自动在字符数组末尾处追加一个结束符\0
。带上结束符共有6个字符元素。
定义字符数组的时候不能写char c1[5]
。如果写char c1[5]
,则程序不会自己访问越界地将c1[6]定义为’\0’。即得到的字符数组没有结束符。没有结束符的后果是,打印这个字符串时,可能 会在输出了原字符串后还继续输出乱码,直到遇到存储中的第一个结束符’\0’为止。
②
也可以以直接将一个字符串常量赋值给字符数组。同上,也是不能写成char c2[5]="hello"
。不然后果同上。
char c2[6]="hello";
③
此外,定义字符数组时,填入的字符数组长度大于等于元素数量即可。而不必一定要等于。
当大于指定元素个数时,多于的部分都用结束符填充。
如:
char c3[10]="hello";
printf("%d\n", sizeof(c3));
printf("%s\n", c3);
输出结果:
10
hello
这样定义出的字符串虽然只有五个字符,但是字符串所占的内存大小依然为10个字节。
6.2 字符串的输出
定义一个print
函数,来模拟print("%s",s)
输出字符串。
#include <stdio.h>
void print(char d[])
{
int i=0;
while (d[i])
{
printf("%c",d[i]);
i++;
}
printf("\n");
}
int main() {
char d[10]="hello";
print(d);
return 0;
}
6.3scanf 读取字符串
下边一段代码,先定义了一个长度为10的字符串c,然后使用scanf
函数将输入值以字符串的形式赋值给c。
最后再以字符串的形式打印c。
这段程序首先定义了长度为10的字符串"123456789"。(该字符串最后一个字节是结束符’\0’)
然后输入"hello"赋值给字符串c。c的前五个字节被替换为"hello",第六个字节被自动替换为"hello"后边自动追加的结束符’]0’。
再打印该字符串c,打印结果仅显示"hello"。
但是打印c的所占内存大小,结果显示仍然为10,占10个字节。
此外需要注意,在scanf("%s",c);
中,c
前是不需要加&来取地址的。因为其本身就表示地址。
#include <stdio.h>
int main() {
char c[10]="123456789";
scanf("%s",c);
printf("%s\n", c);
return 0;
}
输入:
hello
程序输出结果:
hello
10
7.gets 与 puts
scanf函数不能识别字符串中的空格,当输入的字符串中存在空格时,则会被理解为缓冲区内出现了结束符。scanf会仅读取结束符前的一部分字符。
定义多个变量来接收多个空格隔开的字符串是一种针对此问题的解决方案。
除此之外还可以使用gets
函数来解决。
gets
和puts
,也是常用的用于读取标准输入和进行标准输出的函数。
gets
和puts
引用的头文件同scanf和printf,也是<stdio.h>
。
gets
函数与scanf
的区别是,gets函数仅针对字符串的输入与读取。且scanf函数在缓冲区读取输入的字符串时,仅读取到第一次出现结束符’\0’为止。而gets函数在缓冲区读取输入的字符串时,读取到第一次出现换行符‘\n’为止。即完整地读取一行。且在读取完毕后,会在最后边追加结束符’\0’。
puts
函数的效果相当于,printf(“%s\n”,c)。只能用于输出字符串,且在输出的时候自动加了换行符’\n’。
下边提供一段代码示例,输入hello, world!
,输出hello, world!
。(逗号与w
之间有一个空格)
#include <stdio.h>
int main() {
char a[20];
gets(a);
puts(a);
}
程序输出结果如下:
hello, world!
8.str系列字符串操作函数
str 系列字符串操作函数主要包括 strlen、strcat、strcpy、strcmp 等。这里主要介绍这几个。
使用str系列字符串操作函数需要引用的头文件是<string.h>
。
8.1 strlen统计字符长度
strlen
可以用于统计字符串能够打印出的字符长度。不同于sizeof
运算出的所占存储空间。对char a[10]="hello"
,sizeof(a)
的值为10,但是strlen(a)
的值为5,即"hello"中的五个字符。strlen不统计结束符’\0’。
#include <stdio.h>
#include <string.h>
int main() {
char a[10]="hello";
printf("%d\n",strlen(a));
printf("%d\n",sizeof(a));
}
程序输出结果如下:
5
10
下边通过自定义一个my_strlen
函数来实现strlen
的功能,以加强对strlen
的理解。
#include <stdio.h>
int my_strlen(char c[]){
int i=0;
while(c[i])
{
i++;
}
return i;
}
int main() {
char c[20]="hello, world!";
printf("%d\n", my_strlen(c));
return 0;
}
程序输出结果如下:
13
8.2 strcat 字符串拼接
通过strcat(a,b)
,可以将一个字符串(b)拼接到另一个字符串变量(a)上边。
其中b
可以是一个字符串变量,也可以是一个常量。但是a
必须是一个变量。
此外,假设拼接后的字符为“xxxxx”,使用strcat
还需满足,sizeof(a)>strlen(“xxxxx”)。即拼接前变量a所占空间的大小要大于拼接后的结果所占空间的大小,不认会发生越界访问。
示例如下:
#include <stdio.h>
#include <string.h>
int main() {
char a[20]="hello,";
char b[20]="world!";
strcat(a,b);
printf("%s\n", a);
return 0;
}
程序输出结果如下:
hello,world!
8.3 strcpy 字符串复制
通过strcpy(a,b)
,可以将一个字符串(b)的值复制给另一个字符串变量(a)。
其中b
可以是一个字符串变量,也可以是一个常量。但是a
必须是一个变量。
且,使用strcpy
应满足:sizeof(a)>strlen(b)。
示例如下,将b
复制给a
:
#include <stdio.h>
#include <string.h>
int main() {
char a[20]="hello,";
char b[20]="world!";
strcpy(a,b);
printf("%s\n", a);
return 0;
}
程序输出结果如下:
world!
8.4 strcmp 字符串比较
strcmp
比较的是字符串的ASCII码的大小。若两个字符串完全相同则会返回0;
以strcmp (a,b)
为例,若a<b若小于,则返回-1
。若a>b,则返回1
。若a=b
则返回0。
#include <stdio.h>
#include <string.h>
int main() {
printf("%d\n", strcmp("hello","how"));
printf("%d\n", strcmp("how","hello"));
printf("%d\n", strcmp("hello","hello"));
return 0;
}
程序输出结果如下:
-1
1
0
hello
与how
的第一个字母都是h
,是相同的。所以从第二个字母,e
与o
开始比较。
第一次返回-1是因为e的ASCII值小于o的;
第二次返回11是因为o的ASCII值大于e的。
第三次两字符相等,返回0.