数组的定义和使用
数组的定义方式:
数据类型 数组名[元素个数] = {值1,值2,值3};
数据类型 数组名[] = {值1,值2,值3....}; 这样写可以不用限定有多少个元素
数组下标:数组名[下标] 数组下标从0开始 到(数组个数-1)
数组在内存中的存储方式: 是连续的一片内存空间 例如 :
int array[3] = {1,2,3};
第一个元素内存地址为0x00 第二个元素内存地址为0x04 0x08这样
数组名不能被赋值,因为数组名是一个地址常量,指向数组首地址的常量, 例如 打印array的地址和打印array[0]的地址,结果是一样的
数组占用内存大小:
sizeof(array);
数组中元素占用内存大小
sizeof(array[0]);
数组中元素的个数就等于
sizeof(array)/sizeof(array[0]);
这样遇到第二种定义数组方式的时候,就可以直接根据数组实际长度来进行遍历元素
数组的初始化
int arr[5] = {1,2,3,4,5}; 遍历打印元素为 1,2,3,4,5
int arr[10] = {1,2,3,4,5}; 遍历打印元素为 1,2,3,4,5,0,0,0,0,0
int arr[10] = {1} 遍历打印元素为 1,0,0,0,0,0,0,0,0,0
int arr[10]; 只声明了内存空间,但是没有赋值, 遍历元素打印为10个乱码
如果上一行加一个 arr[0] = 1; 遍历元素打印为1,9个乱码 这种定义方式一般用作从键盘获取数据赋值给数组元素
定义数组的时候, 数组个数必须是常量或者常量表达式 例如
int a = 10;
int arr[a];
这样写是不对的 数组个数必须使用固定的数,而不能使用变量
定义数组必须预先知道数组大小。如果是动态数组的话,考虑开辟堆空间
数组越界:
C语言中,数据是直接存储在内存中的,如果出现数组越界的情况,默认是不会报错的,他会一直跟着越界的下标去读取下一部分内存的值,但是值可能是乱码
二维数组
二维数组的大小:
行*列*sizeof(数据类型)
二维数组名指向的是二维数组第一行第一列的地址
二维数组第一行指向的也是第一行第一列的地址
示例:
int arr[2][3] = {{1,2,3},{4,5,6}};
printf("%p\n",arr);
printf("%p\n",arr[0]);
printf("%p\n",&arr[0][0]);
三行打印的值都是一样的
二维数组的初始化:
int arr[2][3] = {1,2,3,4,5,6}; 这样写也可以,但是不建议这样写,阅读性不强
int arr[][3] = {1,2,3,4,5,6}; 这样写也可以,虽然不知道有几行,但是知道有几列,根据元素个数,行数是一个可变的值,元素列数不够的,后面补0
如果连3(列数)都不写,就不行了,记住,不管是二维数组还是多维数组,离数组名最近的那个参数是可以省略的,其他的参数不可以省略
int arr[2][3] = {0}; 二维数组的值全部赋值为0
字符数组和字符串
定义字符数组:
char arr[5] = {'h','e','l','l','o'};
定义字符
char c = 'a';
定义字符串
字符串的结束标志为'\0'
char * arr = "hello";
char arr[] = "hello";
char arr[] = {'h'','e','l','l','o','\0'};
如果字符数组结尾是一个\0可以把它看成是一个字符串,反过来说,字符串是字符数组的一个特例,因为他的最后一位是\0
数字0等同于\0,但是不等同于“0”
示例:
char arr[6] = {'h','e','l','l','o'}; 多一位默认补0 打印结果hello
char arr[6] = {'h','e','l','l','o','\0'}; 打印结果hello
char arr[5] = {'h','e','l','l','o'}; 打印结果 出现乱码
通过键盘获取字符串
char ch[10]; 定义字符数组存储字符串
scanf("%s",ch);为什么第二个参数ch不用取地址符呢?因为ch是一个字符数组名,他本身就是一个地址,scanf输入默认以空格分隔
printf("%s",ch);
如果输入helloworld 程序会报错,因为ch定义了10个长度,而且又是以%s接收的,他要预留一个位置存放'\0'
如果输入helloworl 打印正常
如果输入hello wor 会打印hello 因为scanf会将空格认为输入结束
扩展:为了防止输入的字符数大于字符数组长度,可以这样写:scanf("%9s",ch); 这样不管键盘输入多少个字符,只取前9位字符
字符串拼接
char ch1[] = "hello";
char ch2[] = "world";
char ch3[20];
int i = 0;
int j = 0;
while(ch1[i] != '\0'){
ch3[i] = ch1[i];
i++;
}
while(ch2[j] != '\0'){
ch3[i+j] = ch2[j];
j++;
}
ch3[i+j] = 0;
printf("%s",ch3);
字符串的输入输出
-
gets()
从标准输入读入字符,并保存到指定的内存空间,直到出现换行或者读到文件结尾为止char ch[100]; gets(ch); printf("%s",ch);
gets(str)与scanf("%s",str)的区别:
gets(str)允许输入的字符串含有空格 scanf("%s",str)不允许含有空格
注意:由于scanf和gets无法知道字符串s大小,必须遇到换行符或者读到文件结尾为止才接收输入,因此容易导致字符数组越界的情况
scanf通过正则表达式,也可以接收空格scanf("%{^\n}",ch); 正则表达式表示接收非回车符的所有字符
-
fgets()
#include <stdio.h> char *fgets(char *s,int size,FILE *stream); 参数:s 字符串 size 指定最大读取字符串的长度(size-1) stream 文件指针,如果读键盘输入的字符串,固定写为stdin 返回值 成功:读取到的字符串 失败: NULL
该函数表示从stream指定的文件内读入字符,保存到s指定的内存空间,直到出现换行字符,读到文件结尾,或是已读了size-1个字符为止,最后会自动加上‘\0’作为字符串结束
fgets()和scanf()和gets()的区别:
fgets()在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车也作为字符串的一部分,而通过scanf和gets输入字符串的时候不包含结尾的“\n”,但通过fgets结尾多了个“\n”,fgets()函数是安全的,不存在缓冲区溢出的情况
fgets获取字符串少于元素个数会有\n,大于或者等于元素个数的,没有\n
例如:char ch[10]; fgets(ch,sizeof(ch),stdin); printf("%s",ch); 如果从键盘输入的是 hello 结果会输出hello并且光标换到下一行 如果从键盘输入的是 helloworld ,结果会输出helloworl光标在当前行,并不会换到下一行
所以说fgets也是有缺点的,他会把\n当做字符的一部分输入
-
puts()
#include <stdio.h> int puts(const char *s); 功能:标准设备输出字符串,在输出后自动输出一个'\n' 参数:s:字符串首地址 返回值:成功:非负数 失败:-1
puts自带换行,而且内部也是以’\0’作为标识来进行打印的
换行操作最简单的写法:puts(""); -
fputs()
#include <stdio.h> int fputs(const char *s,FILE *stream); 从stream指定的文件内输出字符,如果是控制台输出字符串,固定写为stdout
示例:
char ch[] = "hello world"; fputs(ch,stdout);
fputs()不会自带换行符
字符串的长度
#include <string.h>
size_t strlen(const char *s);
计算指定字符串s的长度,不包含结束符'\0',也就是计算字符数组的真实长度
size_t 无符号整形 使用%d占位符打印就可以
举例:
char ch[100] = "hello world";
printf("数组大小:%d",sizeof(ch)); 100
printf("数组大小:%d",strlen(ch)); 11
char ch[] = "hello world";
printf("数组大小:%d",sizeof(ch)); 12
printf("数组大小:%d",strlen(ch)); 11