数组适合处理有大量相同数据类型的数据
一、数组 //数组必须先定义,后引用
1.1 一维数组的定义
表现形式:
数据类型 数组名 [常量表达式]; //常量表达式中可以包括常量和符号常量,不能包含变量
例如:int array [10]; 定义了一个叫array的一维数组,从array[0]-array[9]一共有10个元素,每个元素都是int型的。
1.1.1 数组的初始化
a. 未初始化的数组元素值是什么?
- 静态数组和全局数组没初始化时,系统会自动将其初始化为0值;
- 其它的比如动态数组等,会初始化为随机值
b. 一维数组初始化的方法:
说明:
(1)array [0] = 10; 或者array = 10;// 给数组中第一个元素赋值为10, 数组名表示数组首地址。
(2)int array[5] = {6, 8, 8, 10, 9}; //给array数组内的5个元素分别赋值为6, 8, 8, 10, 9。在给了数组的全部元素后,
"[ ]"内的常量表达式可忽略。如:int array[ ] = {6, 8, 8, 10, 9};
(3)也可以只给一部分元素赋值。如:int array[5] = {6, 8}; //表示int array[5] = {6, 8, 0, 0, 0};
int main()//一维数组输出实数和字符串
{
int i;
int array[10] = {5, 4, 8, 12, 20}; //实数数组中不够的元素用0补齐,并输出显示
for(i = 0; i < 10; i++) //显示一维实数数组一般用for循环,来显示数组的全部元素
{
printf("array[%d] = %d\n",i, array[i]);
}
char b[10] = "abc"; //字符数组数组长度大于实际字符串长度时,只输出到遇0(‘\0’)结束。
printf("{%s}\n",b);
return 0;
}
运行结果:
1.1.2 更高效的数组初始化和赋值方法
该方法需要包含相应的头文件:
#include <string.h>
更高效的数组初始化方法:
memset(a, 0, sizeof(a)); //将数组a赋值为0
更高效的数组赋值方法:
memcpy(b, a, sizeof(a)); //将数组a赋值给数组b
1.2 一维数组元素的引用
数组元素的表示形式为:数组名 [下标]; //下标可以是整型常量或者整型表达式
如:a[0] = a[5] + a[7] - a[2 * 3];
注:定义数组时用到的“数组名【常量表达式】”和引用数组元素时用到的“数组名【下标】”的区别,例如:
(1)int a[10]; //定义数组长度为10
(2)t = a[6]; //引用a数组中序号为6的元素。此时6不代表数组长度。
下面是一个排序算法的例子:
#include <iostream>
using namespace std;
int main()
{
int array[5] = { 6, 7, 5, 2, 4 };
int i;
int j, a;
for (j = 0; j < 4; j++)
{
for (i = 0; i < 4 - j; i++)
{
if (array[i] > array[i + 1]) #小数在前,大数在后
{
a = array[i];
array[i] = array[i + 1];
array[i + 1] = a;
}
}
}
cout << "the sorted numbers:\n" << endl;
for (i = 0; i < 5; i++)
{
cout << "array[" << i << "] = " << array[i] << endl;
}
system("pause");
return 0;
}
结果:
1.3 数组在内存的存储方式
数组在内存中就是一段连续的内存空间。
二、二维数组
2.1 二维数组定义
表现形式:
数据类型 数组名[常量表达式][常量表达式];
eg:float a[3][4],b[5][10]; //定义a为3*4(3行4列)的数组,b为5*10(5行10列)的数组。注意不能写成:float a[3,4],b[5,10];这3个元素为a[0]、a[1]、a[2],每个元素又是一个包含4个元素的一维数组,如下图:
可以把a[0],a[1],a[2]看作是3个一维数组的名字。
上面定义的二维数组可以理解为定义了3个一维数组,即相当于:
float a[0][4],a[1][4],a[2][4]; //此处可以把a[0]、a[1]、a[2]看作一维数组名。
注: 二维数组可被看作是一个特殊的一维数组:它的元素又是一个一维数组。C语言中,二维数组中元素排列的顺序是按行存放的,即在内存中先顺序存放第一行的元素,再存放第二行的元素。及从左到右先a00->a01->a02->a03->a10...->a23.
2.2 二维数组的引用
表现形式:数组名[下标][下标];
eg:a[2][3]; //下标可以是整型表达式,如a[2 - 1][2 * 2 - 1]。
数组元素可以出现在表达式中,也可以被赋值,例如: b[1][2] = a[2][3] / 2;
2.3 二维数组的初始化
(1)分行给二维数组赋初值。
int a[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
或者
int a[ ][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; //二维数组行参数可不写,但列参数必须要写
(2)也可以将所有数据写在一个花括号里
int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; //不如第一种方法好
(3)对部分元素赋初值
int a[3][4] = {{1},{2},{3}};这样三行的第一个元素分别为1,2,3.其余值为0。
或者 int a[][4] = {{0,0,3}, {}, {1,10}};
2.4 数组越界:
行数组越界会有错误明显提示,列数组越界不会提示。由于数组在内存中是按行顺序存储,对于越界的列数组,会按顺序显示下一行某元素的值。比如对于数组a[2][3] = {{1,2,3},{4,6,8}},其a[2][5] = 6,而6实际上是a[1][1]的值
三、数组做函数参数
变量做函数参数时,是进行值传递的,即将实参变量的值直接传给函数的形参,这样形参改变时,不会影响实参的值。数组做函数参数时,由于数组中变量较多,所以将数组的首地址传给函数进行计算最简单,即地址传递。数组的首地址即不带下标的数组名。地址传递时,实参会随形参的变化而改变。
3.1 向函数传递一维数组:
- 传递数组的首地址,形参数组和实参数组共享同一段内存
- 通常不指定数组的长度,用另一个形参来指定数组的大小
#include <stdio.h>
void Readscore(int score[], int n)
{
printf("input scores:\n");
for (int i = 0; i < n; i++)
{
scanf_s("%d", &score[i], 4);
}
}
int main()
{
int score[10] = { 0 };
int n,aver;
printf("The number of students: ");
scanf_s("%d", &n, 4);
Readscore(score, n);
for (int i = 0; i < n; i++)
{
printf("score[%d] = %d\n", i, score[i]);
}
system("pause");
return 0;
}
3.2 向函数传递二维数组
可省略数组第一维的长度,不能省略第二维的长度
四、字符串
c语言中没有专门的数据类型来表示字符串类型,一般用字符数组或者字符指针来处理字符串。
4.1 字符数组
4.1.1 定义
- 每个元素都是字符类型的数组
- 注:数组的最后一个元素必须是‘\0’才表示字符串
4.1.2 字符数组的初始化
char array[100] = {'a', 'b', 'c', 'd', '\0'}; //100表示字符串的长度。
等价于
char array[100] = "abcd"; //后面没赋值的元素在这里不显示。
注:字符数组中字符串的有效字符串小于字符串长度([]中的数)时,多的长度不显示,不像整型那样要用0补齐。
还可以
char array[] = "hello"; //有效长度为6,多了一个结束标志‘\0’.
若定义一个字符串数组的时候,没写数组的长度/维数,那c语言编译器会自动根据字符串的长度自动填写。
4)char array[20] = {0};//定义一个空数组。
int main9()
{
int num = 0; //字符串中字符的数量
int len = 0; //表示字符串的长度
char buf[100] = "你"; //计算该字符串长度,即字节数量
while(buf[len++]);
len--; //在while循环过程中len被多计了一次,所以要减掉
printf("The length of buf:%d\n",len); //测一个字的长度,linux中一个汉字占3个字节,一个字母1个字节
char buf1[100] = "你好世界abf"; //计算该字符串有几个字符
while(buf1[len])
{
if(buf1[len] < 0)
len += 2;
len++;
num++;
}
printf("num = %d\n",num);
return 0;
}
4.2 字符指针
(1)若字符指针指向一个字符串常量:
可修改字符指针变量指向的字符串常量,但不能改字符串常量的值
(2)若字符指针指向一个字符数组,则可以改字符数组保存的内容,但数组名不能改
即
4.3 字符数组的输入输出
字符串和字符串结束标志:
c语言中,是将字符串作为字符数组来处理的。通常人们只关心字符串的有效长度,而不是字符串长度。所以c语言中规定了一个“字符串结束标志”:'\0'。其表示字符串结束,其前面的字符组成字符串输出。
4.3.1 字符数组的输入输出
字符数组输入输出的两种方式,
scanf();
printf();
和
gets();
puts();
4.3.1.1 第一种方式:输入输出函数
1)按字符逐个输入/输出。用格式符“%c”输入或输出一个字符,见下面代码第二种方式。
2)将整个字符串一次性输入/输出。用“%s”格式符。见下面代码第一种方式。
char b[10] = "abc"; //整个字符串一次输出
printf("{%s}\n",b); //注意用%s格式符输出字符串时,printf函数中的输出项是字符数组名b,而不是数组元素名b[i]
char b1[100] = {'I', ' ', 'a', 'm', ' ', 'a', ' ', 'b', 'o', 'y', '!'}; //逐个字符输入输出
for(i = 0; i < 12; i++)
{
printf("%c",b1[i]);
}
printf("\n");
- 如果利用一个scanf函数输入多个字符串,在输入时以空格分隔,以回车键作为输入完成的标志,但回车键本身并不作为字符串的一部分。例如:
char str1, str2, str3;
scanf("%s%s%s",str1, str2, str3); //注:scanf函数中的输入项如果是字符数组名,则不要再加地址符&
用键盘输入:
How are you?回车
显示:
How are you?
- scanf函数会将回车、空格都认为是字符串输入结束标志。
比如:键盘输入:hello word!
显示的是:hello
空格后面的内容都没了
要解决这种问题需要用到gets()函数
4.3.1.2 第二种方式:字符串处理函数
a、 gets(字符数组);//输入字符串,类似于scanf();
注意:
1) gets函数认为回车是输入结束标志,空格不是。所以用gets函数就可以实现输入带空格的字符串
2)gets和scanf一样有缓冲区溢出的问题。
3)gets不能用"%s"或者"%d"之类的字符转义,只接受字符串的输入。即用gets函数输入的信息都是字符串类型,你输入4,也是一个char类型的。
4)gets和puts函数只能输入一个字符串,
gets(str);
不能写成
gets(str1, str2);
5)上面gets(str);的结果如下:
从键盘输入:It is a computer回车
输出显示:It is a computer
fgets(字符数组);
1)gets函数不检查预留缓冲区是否能够容纳用户实际输入的数据,多出来的字符会导致内存溢出。
2)fgets函数改进了这个问题,它会将缓冲区溢出的字节去掉。调用fgets函数的时候,只要保证第二个参数小于数组实际的大小,就可以避免缓冲区溢出。
3)缺点:fgets函数是为读取文件设计的,所以读取键盘时没有gets那么方便
char s[100] = {0};
fgets(s, sizeof(s), stdin); //固定格式。第一个参数是char的数组名,第二个参数是数组的大小。单位:字节。第三个参数stdin代表标准输入的意思
4)第二个参数sizeof(s)表示数组的大小,还可以用具体数值表示,但一旦数组大小改变,fgets也要改,所以用sizeof(s)更方便。
b、puts(字符数组名)//将字符串输出到终端,类似于printf。
puts函数自动会在输出完成之后打印一个'\n'出来。
#include <stdio.h>
int main(void) //puts/fgets输入/输出字符串
{
char a[100] = {0};
fgets(a, sizeof(a), stdin);
puts(a);
return 0;
}
结果:
输入上面的“hello world”字符串,下面用puts函数输出结果。
注:gets和puts函数只用于输入输出字符串
4.4 跟字符串处理有关的函数
4.4.1 字符串追加
strcat(字符数组1,字符数组2);
连接两个字符数组中的字符串,把字符串2接到字符串1的后面,结果放在字符数组1中。所以数组1的长度一定要留的足够大。
int main()
{
char str1[50] = "hello";
char str2[] = " world!";
printf("%s\n",strcat(str1, str2)); //合并两个字符串,将后一个字符串放到第一个字符串后面
return 0;
}
4.4.2 字符串有限追加
strncat(字符数组1,字符数组2, 追加数量);
“追加数量”:在合并的时候可以限制追加多少个字符
#include <stdio.h>
#include <string.h>
int main2() //strncat合并时可限制追加多少字符
{
char a[20] = "hello world";
char str2[50] = "!1234567894579595";
printf("%s\n",strncat(a, str2, 5)); //合并时可限制追加多少字符
return 0;
}
4.4.3 求字符串的实际长度
strlen(字符数组名);
使用这个库函数要先加
#include <string.h>
int main2()
{
char a[100] = "hello world";
int len = strlen(a);
printf("len =%d\n",len); //求字符串的实际长度
printf("%d\n",sizeof(a));//求整个字符串数组的长度
return 0;
}
第二个100是用sizeof函数求出来的。
4.4.4 随机数产生函数rand和srand
#include “stdlib.h” //引用头文件
rand();//是随机数产生器,每次调用产生的随机数是一样的。
若调用rand之前先调用srand才能出现任意随机数。只要保证每次调用srand函数的时候,参数的值是不同的,那么rand函数就一定会产生不同的随机数。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
time_t tm = time(NULL);//得到系统时间
printf("tm =%u\n",tm);
srand(tm);//随机数种子发生器
int i = 0;
for(i = 0; i < 10; i++)//控制产生的随机数的数量
{
int value = rand();//随机数产生器
printf("%d\n",value);
}
return 0;
}