数组
数组是用来保存一组相同数据类型的数据的
数组是一个构造类型
数组中的每个数据叫做数组的元素,或者叫做数组的成员
数组在内存上需要分配一块连续的空间来存储数据,不管几维数组,成员之间都是连续的
一维数组
格式
存储类型 数据类型 数组名[下标];
存储类型:不写默认就是 auto
数据类型:指的是数组中每个元素的数据类型,可以是基本类型、也可以是构造类型(数组除外)
数组名: 是一个标识符,要符合标识符的命名规范
下标:在定义数组的时候,下标是个常量,用来表示数组的元素的个数
在其他场景下,下标表示访问数组中的第几个元素,既可以是变量、也可以是常量、也可以是一个表达式
例如:
int s[5];
//定义了一个一维数组 数组名叫s 数组中每个元素都是一个 int类型的变量
//数组中共有5个元素
一维数组的性质
#include <stdio.h>
int main(int argc, const char *argv[])
{
//定义了一个一维数组 数组名是s 数组中共有5各元素
//每个元素都是一个int类型的变量
int s[5];
//数组访问元素的方式 数组名[下标]
//注意,下标是从0开始的
//当取出数组的元素操作时 和 操作单个的变量就是一样的了
//给数组元素赋值
s[0] = 100;
s[1] = 200;
s[2] = 300;
s[3] = 400;
s[4] = 500;
//读取数组元素的值
printf("s[0] = %d\n", s[0]);//100
printf("s[1] = %d\n", s[1]);//200
printf("s[2] = %d\n", s[2]);//300
printf("s[3] = %d\n", s[3]);//400
printf("s[4] = %d\n", s[4]);//500
//数组名是一个常量
//s = 1314; //错误的
//s++; //错误的 s = s+1;
//数组的元素在内存上是连续的
//printf %p 可以按照16进制打印地址编号
// &变量名 & 取地址符 可以获取变量的地址
printf("&s[0] = %p\n", &s[0]);//连续的
printf("&s[1] = %p\n", &s[1]);
printf("&s[2] = %p\n", &s[2]);
printf("&s[3] = %p\n", &s[3]);
printf("&s[4] = %p\n", &s[4]);
//数组的长度:元素的个数
//数组的大小:占用的内存空间的大小
//一维数组的大小 = 元素的个数 * 单个元素的大小
printf("sizeof(s) = %ld\n", sizeof(s));//20 == 5*sizeof(int)
//数组的元素是变量 允许被重新赋值
s[0] = 520;
printf("s[0] = %d\n", s[0]);//520
//下标可以是一个变量
int m = 1;
s[m] = 1234;
printf("s[m] = %d\n", s[m]);//1234
//下标可以是一个表达式
s[s[0]-519] = 1314;
printf("s[1] = %d\n", s[1]);//1314
//遍历一维数组 方式1
int i = 0;
for(i = 0; i < 5; i++){
printf("%d ", s[i]);
}
printf("\n");
//遍历一维数组 方式
for(i = 0; i < sizeof(s)/sizeof(s[0]); i++){
printf("%d ", s[i]);
}
printf("\n");
return 0;
}
一维数组的初始化
#include <stdio.h>
int main(int argc, const char *argv[])
{
//如果一维数组没有初始化,里面存放的都是随机值
//int s[5];
//一维数组初始化的方式
//完全初始化
//int s[5] = {10, 20, 30, 40, 50};
//不完全初始化
//这时是从左到右依次初始化 没有初始化的元素 会用0初始化
int s[5] = {10, 20};
//全部初始化成 0
//int s[5] = {0}; //--最常用的用法
//省略下标的初始化
//编译器会根据后面初始化的元素的个数 来自动计算数组的长度
//int s[] = {10, 20, 30};
//数组一旦定义好了就没法整体赋值了,只能一位一位的赋值
//int s2[5];
//s2 = s; //错误的 数组名是常量 不能被赋值
//s2[5] = {1,2,3,4,5};//错误的 相当于给数组的下标为5的那个元素赋值
//越界了
int i = 0;
for(i = 0; i < 5; i++){
printf("%d ", s[i]);
}
printf("\n");
//关于数组访问越界的问题
//注意:数组越界访问的问题 编译器不会检查
//需要程序员自己检查,而且数组越界的问题是很严重的
//越界访问的现象是不可预知的:
// 可能正常执行
// 可能是段错误
// 可能修改了其他变量的数据
//尤其是变量 或者 表达式做为下标使用的使用 要特别注意检查边界
int hqyj[8] = {0};
hqyj[12345] = 1314;//错误的操作
printf("%d\n", hqyj[12345]);//错误的操作
return 0;
}
一维数组练习
1.定义一个长度为10的int类型的一维数组
从终端给数组的元素赋值
找出数组中最大的值,输出最大值。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[10] = {0};
int i = 0;
//循环给数组赋值
for(i = 0; i < 10; i++){
scanf("%d", &s[i]);
}
//遍历一维数组
for(i = 0; i < 10; i++){
printf("%d ", s[i]);
}
putchar(10);
#if 0
//找数组中的最大值
//int max = 0;//这些操作方式 如果选了0为比较的基准
//那么都是负数时就没法得到结果了
//要选取数组中的元素做位比较的基准 才合理
int max = s[0];
for(i = 1; i < 10; i++){
if(s[i] > max){
max = s[i];
}
}
//当循环结束的时候 max 中记录的就是最大值
printf("max = %d\n", max);
#endif
//保存最大值的下标
int max_index = 0;
for(i = 1; i < 10; i++){
if(s[max_index] < s[i]){
max_index = i;
}
}
//当循环结束的时候 max_index 中记录的就是最大值的下标
printf("max_value = %d\n", s[max_index]);
return 0;
}
2.使用一维数组保存斐波那契数列的前20位,并输出。
斐波那契数列 1 1 2 3 5 8 13 21 …
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[20] = {1, 1};
int i = 0;
#if 0
//循环给数组赋值--注意不要出现越界访问
for(i = 2; i < 20; i++){
s[i] = s[i-1] + s[i-2];
}
#endif
//循环给数组赋值--注意不要出现越界访问
for(i = 0; i < 18; i++){
s[i+2] = s[i+1] + s[i];
}
//遍历一维数组
for(i = 0 ; i < 20; i++){
printf("%d ", s[i]);
}
printf("\n");
return 0;
}
冒泡排序
相邻的两个数进行比较,按照要求(升序还是降序)进行交换。
升序:从小到大 降序:从大到小
冒泡排序的时间复杂度是 O(n^2)
实现思路(以升序为例)
第一趟排序:
第1元素和第2个元素进行比较,将较大的值放在第2个位置上,
然后第2个元素和第3个元素进行比较,将较大的放在第3个位置上…依此类推,直到第一趟排序结束,最大的元素就在整组数据的最后一个位置上了
第二趟排序:
第1元素和第2个元素进行比较,将较大的值放在第2个位置上,
然后第2个元素和第3个元素进行比较,将较大的放在第3个位置上…依此类推,直到第二趟排序结束,第二大的数据就在倒数第二个位置上了
依此类推,直到整个数据有序。
冒泡排序的动图:
代码实现
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[10] = {11, 22, 43, 567, 1, 4, 9, 8, 55, 98};
int len = sizeof(s)/sizeof(s[0]);//数组的长度:元素的个数
int temp = 0;
//排序前
int i = 0;
int j = 0;
for(i = 0 ; i < 10; i++){
printf("%d ", s[i]);
}
printf("\n");
#if 0
//一趟排序的过程
for(i = 0; i < len-1; i++){
if(s[i] > s[i+1]){
//交换
temp = s[i];
s[i] = s[i+1];
s[i+1] = temp;
}
}
#endif
//完整的排序
//外层循环控制排序的趟数 此处的 -1 是因为 最后一趟只有一个元素 无需再排序了
for(j = 0; j < len-1; j++){
//内层循环控制一趟排序
for(i = 0; i < len-1-j; i++){
//此处的 -1 是防止越界访问的
//此处的 -j 是因为每趟都可以少比较一个元素
if(s[i] > s[i+1]){//如果是降序 只需要将此处的 > 改成 < 即可
//交换
temp = s[i];
s[i] = s[i+1];
s[i+1] = temp;
}
}
}
//排序后
for(i = 0 ; i < 10; i++){
printf("%d ", s[i]);
}
printf("\n");
return 0;
}
二维数组
二维数组的格式
存储类型 数据类型 数组名[行数][列数];
注意:
二维数组虽然有行号和列号,但是所有的元素在内存上也是连续的,
列数:决定了按行访问元素时的跨度。
例:
int s[3][4];
//定义了一个二维数组 数组名叫s 数组中有3行4列共计12个元素
//每个元素都是一个 int 类型的变量
二维数组的性质
#include <stdio.h>
int main(int argc, const char *argv[])
{
//定义了一个二维数组 数组名叫s 数组中共有3行4列共计12个元素
//每个元素都是一个 int 类型的 变量
int s[3][4];
//二维数组访问元素 数组名[行号][列号]
//注意:行号和列号都是从 0 开始的
//二维数组取出元素后的操作 和 操作单个变量也是一样的
s[0][0] = 520;
s[0][1] = 1314;
s[2][2] = 1234;
printf("s[0][0] = %d\n", s[0][0]);//520
printf("s[0][1] = %d\n", s[0][1]);//1314
printf("s[2][2] = %d\n", s[2][2]);//1234
//二维数组的大小 = 行数*列数*单个元素的大小
printf("sizeof(s) = %ld\n", sizeof(s));//48 == 3 * 4 * sizeof(int)
//二维数组的数组名 也是一个常量
//也不能被赋值 不能++
//int s2[3][4];
//s2 = s; //错误的
//s2++; //错误的
//二维数组的元素再内存上也是连续的
printf("%p\n", &s[0][3]);
printf("%p\n", &s[1][0]);//连续的 一次相差一个 int
printf("%p\n", &s[1][3]);
printf("%p\n", &s[2][0]);//连续的 一次相差一个 int
//遍历二维数组
int i = 0;
int j = 0;
//外层循环控制行数
for(i = 0; i < 3; i++){
//内层循环控制列数
for(j = 0; j < 4; j++){
printf("%d ", s[i][j]);
}
printf("\n");
}
return 0;
}
二维数组的初始化
#include <stdio.h>
int main(int argc, const char *argv[])
{
//如果二维没有初始化 里面也都是随机值
//int s[3][4];
//二维数组的初始化格式
//以行为单位
//完全初始化
//int s[3][4] = {{1,2,3,4},\
{5,6,7,8},\
{9,10,11,12}};
//不完全初始化 没有初始化的位 也是用0初始化
//int s[3][4] = {{1,2},\
{5,6},\
{9}};
//不以行为单位
//完全初始化
//int s[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
//不完全初始化 没有初始化的位 也是用0初始化
//int s[3][4] = {1,2,3,4,5,6};
//全部初始化成0 --常用的用法
//int s[3][4] = {0};
//省略下标的初始化
//行数可以省略 但是列数不能省略 因为列数决定按行操作时的跨度
//如果给定的元素不够整行 也按照整行分配空间
//没有给定的位也是用 0 初始化
int s[][4] = {1,2,3,4,5,6,7,8,9};
printf("sizeof(s) = %ld\n", sizeof(s));//48 说明是按照整行分配的空间
//遍历二维数组
int i = 0;
int j = 0;
for(i = 0; i < 3; i++){
for(j = 0; j < 4; j++){
printf("%d ", s[i][j]);
}
putchar(10);
}
//数组一旦定义好了就不能整体赋值了 只能一位一位的赋值
return 0;
}
二维数组练习题:
1.定义一个3行4列的二维数组,并以行为单位完全初始化
初始化的数据随便指定,找出数组中最大的元素,及最大元素所在的行号、列号,并输出。
#include <stdio.h>
int main(){
int s[3][4] = {{12,23,34,45},{11,22,33,44},{456,1,2,3}};
int max_h = 0;
int max_l = 0;
int i = 0;
int j = 0;
for(i = 0; i < 3; i++){
for(j = 0; j < 4; j++){
if(s[i][j] > s[max_h][max_l]){
max_h = i;
max_l = j;
}
}
}
printf("max_value = %d max_hang = %d max_lie = %d\n",
s[max_h][max_l], max_h, max_l);
return 0;
}
2.使用10*10的二维数组保存杨辉三角的数据 并输出。
#include <stdio.h>
#define N 10
int main(){
int s[N][N] = {0};
s[0][0] = 1;
//循环给数组赋值
int i = 0;
int j = 0;
for(i = 1; i < N; i++){
s[i][0] = 1;//给每行的第一列赋值
//其他列的值循环
for(j = 1; j <= i; j++){
s[i][j] = s[i-1][j] + s[i-1][j-1];
}
}
//输出数组的值
for(i = 0; i < N; i++){
for(j = 0; j <= i; j++){
printf("%-4d", s[i][j]);
}
printf("\n");
}
return 0;
}
字符数组与字符串
#include <stdio.h>
int main(int argc, const char *argv[])
{
//字符数组的使用
char s1[5] = {'h', 'e', 'l', 'l', 'o'};
int i = 0;0.
for(i = 0; i < 5; i++){
printf("%c", s1[i]);
}
printf("\n");
//也可以将字符串保存在字符数组中
//注意:字符串的结尾会有一个隐藏的 '\0' 所以要多分配一个字节来存储
//格式1
char s2[6] = {"world"};
//格式2//--常用
char s3[6] = "12345";
//格式3//--常用
char s4[] = "www.baidu.com";//这种写法 编译器计算长度时会包括 '\0'
printf("sizeof(s4) = %ld\n", sizeof(s4));//14
//如果是字符串 可以使用 %s 处理
printf("s2 = [%s]\n", s2);//world
printf("s3 = [%s]\n", s3);//12345
printf("s4 = [%s]\n", s4);//www.baidu.com
//注意 %s 处理的时候是从给定的首地址开始 一直向后面找
//找到 '\0' 才结束 如果没有 '\0'就一直找 可能会越界访问
char s5[12] = "aabb\0ccdd";
printf("s5 = [%s]\n", s5);//aabb
//ccdd也在数组中 只不过通过 %s 的方式 访问不到了
printf("s5[5] = %c\n", s5[5]);//c
printf("s5[7] = %c\n", s5[7]);//d
//如果不是字符串 就不能使用 %s 处理
//因为不是字符串 就没有 '\0'
//printf("s1 = [%s]\n", s1);//错误的 错误不可预知
//下面的用法是可以的 因为不完全初始化 会用0来初始化那些没有初始化的位
char s6[6] = {'h', 'e', 'l', 'l', 'o'}; // 0 就是 '\0'
printf("s6 = [%s]\n", s6);
return 0;
}
字符处理函数(strlen/strcpy/strcat/strcmp)
strlen
功能:计算字符串的长度,不包括’\0’的
头文件:#include <string.h>
函数原型:
size_t strlen(const char *s);
参数:要计算长度的字符串的首地址
返回值:计算的结果
例:
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char hqyj[32] = "hello";
//可以直接输出
printf("strlen(hqyj) = %ld\n", strlen(hqyj));//5
//也可以用变量保存计算的结果
int len = strlen(hqyj);
printf("len = %d\n", len);//5
//注意 C语言中对字符串的处理 都是到 '\0' 就结束了
char hqyj2[32] = "hello\0wrold";
printf("strlen(hqyj2) = %ld\n", strlen(hqyj2));//5
//注意:如果不是字符串 就不能使用 strlen 计算长度
//因为 strlen会一直往后找 '\0' 错误是不可预知的
char hqyj3[5] = {'h', 'e', 'l', 'l', 'o'};
printf("strlen(hqyj3) = %ld\n", strlen(hqyj3));//5
//弄清楚 sizeof strlen 区别
// 1.sizeof是关键字 strlen 是函数
// 2.sizeof计算的是占用的内存的字节数 只和定义时有关
// strlen计算的字符串中第一个 '\0' 之前的字符的个数
char s[32] = "abcd";
printf("sizeof(s) = %ld\n", sizeof(s));//32
printf("strlen(s) = %ld\n", strlen(s));//4
return 0;
}
自己实现 strlen 函数的功能。
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s1[32] = "hello"; //5
#if 0
int count = 0;
while(s1[count] != '\0'){
count++;
}
#endif
#if 1
int count = 0;
while(s1[count]){
count++;
}
#endif
int count = 0;
while(s1[count++]);
count--;
printf("%d\n", count);
return 0;
}
strcpy
功能:字符串的复制
头文件:#include <string.h>
函数原型:
char *strcpy(char *dest, const char *src);
参数:
src:源字符串
dest:目标字符串 dest指向的缓冲区要保证足够大 否则会越界
返回值:就是dest --一般不关心
例:
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = "beijingnihao";
char s2[32] = "hello";
printf("操作前 s1 = [%s]\n", s1);//beijingnihao
printf("操作前 s2 = [%s]\n", s2);//hello
strcpy(s1, s2);//将s2 拷贝给 s1
printf("操作后 s1 = [%s]\n", s1);//hello
printf("操作后 s2 = [%s]\n", s2);//hello
//将短的字符串 copy 给长的时
//长的字符串后面剩下的部分还在里面
//只不过通过字符串的方式 访问不到了
printf("%c\n", s1[6]);//g
printf("%c\n", s1[7]);//n
printf("%c\n", s1[8]);//i
//注意 要保证 目标字符串足够大 否则越界 错误不可预知
//char s3[3] = {0};
//char s4[32] = "aklhdjkaasdjfh";
//strcpy(s3, s4);
//字符串赋值
char str[32] = {0};
//str = "hello";//错误的
strncpy(str, "hello", 6);//正确的
return 0;
}
自己实现 strcpy 函数的功能。
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = "beijingnihao";
char s2[32] = "hello";
printf("操作前 s1 = [%s]\n", s1);
printf("操作前 s2 = [%s]\n", s2);
//你的操作
int i = 0;
while(s2[i] != '\0'){
s1[i] = s2[i];
i++;
}
//将s2结束的 '\0' 也copy给s1 下面两种写法都可以
//s1[i] = '\0';
s1[i] = s2[i];
printf("操作后 s1 = [%s]\n", s1);
printf("操作后 s2 = [%s]\n", s2);
return 0;
}
strcat
功能:字符串的拼接
头文件:#include <string.h>
函数原型:
char *strcat(char *dest, const char *src);
参数:
src:源字符串
dest:目标字符串 dest指向的缓冲区要保证足够大 否则会越界
返回值:就是dest --一般不关心
例:
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = "beijingnihao";
char s2[32] = "hello";
printf("操作前 s1 = [%s]\n", s1);//beijingnihao
printf("操作前 s2 = [%s]\n", s2);//hello
strcat(s1, s2);//将s2 追加到 s1后面 会覆盖 s1的'\0'
printf("操作后 s1 = [%s]\n", s1);//beijingnihaohello
printf("操作后 s2 = [%s]\n", s2);//hello
//注意 要保证 目标字符串足够大 否则越界 错误不可预知
//char s3[3] = {0};
//char s4[32] = "aklhdjkaasdjfh";
//strcat(s3, s4);
return 0;
}
自己实现 strcat 函数的功能
#include <stdio.h>
int main(){
char s1[32] = "hello";
char s2[32] = "world";
//先找到s1的'\0'的下标
int i = 0;
while(s1[i] != '\0'){
i++;
}
//逐个追加
int j = 0;
while(s2[j] != '\0'){
s1[i] = s2[j];
i++;
j++;
}
//将s2的'\0'也追加给s1
s1[i] = s2[j];
printf("s1 = [%s]\n", s1);//helloworld
printf("s2 = [%s]\n", s2);//world
return 0;
}
strcmp
功能:字符串的比较
头文件:#include <string.h>
函数原型:
int strcmp(const char *s1, const char *s2);
参数:s1 和 s2 (就是要参与比较的两个字符串)
比较方式:
两个字符串逐个的比较每个字符的ascii码,直到出现大小关系就立即返回。两个字符串中第一次出现 ‘\0’ 之前的字符都相等 才认为两个字符串相等
注意:比的不是长度
返回值:
>0 s1>s2
==0 s1==s2
<0 s1<s2
例:
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = {0};
char s2[32] = {0};
gets(s1);
gets(s2);
printf("操作前 s1 = [%s]\n", s1);
printf("操作前 s2 = [%s]\n", s2);
int ret = strcmp(s1, s2);
if(ret > 0){
printf("s1>s2\n");
}else if(ret<0){
printf("s1<s2\n");
}else{
printf("s1==s2\n");
}
//返回值时 s1 和 s2 中第一个不相等的字符 ascii码的差值 是 s1-s2的
//或者是0
printf("ret = %d\n", ret);
//带n的版本 表示只操作前n位
char s3[32] = "hello1234";
char s4[32] = "hello5678";
int ret2 = strncmp(s3, s4, 5);
printf("ret2 = %d\n", ret2);//0
return 0;
}
练习
练习:
1.字符串分割:从buff中解析关键信息。
char buff[256] = "wechat:zhangsan:klrl:1314:2022-10-10:happy new year";
char logo[16];
char sender[16];
char recver[16];
char money[16];
char date[32];
char txt[128];
答案:
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buff[256] = "wechat:zhangsan:klrl:1314:2022-10-10:happy new year";
char logo[16];
char sender[16];
char recver[16];
char money[16];
char date[32];
char txt[128];
int i = 0;
int j = 0;
//解析logo
while(buff[i] != ':'){
logo[j++] = buff[i++];
}
logo[j] = '\0';
//解析 sender
i++;
j = 0;
while(buff[i] != ':'){
sender[j++] = buff[i++];
}
sender[j] = '\0';
//解析 recver
i++;
j = 0;
while(buff[i] != ':'){
recver[j++] = buff[i++];
}
recver[j] = '\0';
//解析 money
i++;
j = 0;
while(buff[i] != ':'){
money[j++] = buff[i++];
}
money[j] = '\0';
//解析 date
i++;
j = 0;
while(buff[i] != ':'){
date[j++] = buff[i++];
}
date[j] = '\0';
//解析 txt
i++;
j = 0;
while(buff[i] != 0){
txt[j++] = buff[i++];
}
txt[j] = '\0';
printf("buff = [%s]\n", buff);
printf("logo = [%s]\n", logo);
printf("sender = [%s]\n", sender);
printf("recver = [%s]\n", recver);
printf("money = [%s]\n", money);
printf("date = [%s]\n", date);
printf("txt = [%s]\n", txt);
return 0;
}
如果字符串解析再涉及bit位的操作 会更复杂些,如:判断某一位是否为1
buff[0]; // 11101010
if( (buff[0] & (1<<5)) != 0){
//说明那一位是1
}
2.自己实现字符串转整型(atoi)
“1314” --> 1314
#include <stdio.h>
#include <stdlib.h>
int main(){
char str[32] = "1314";
int num = 0;
//你的操作
int i = 0;
while(str[i] != '\0'){
num *= 10;
num += (str[i] - '0');
i++;
}
printf("num = %d\n", num);//1314
int num2 = 0;
num2 = atoi(str);
printf("num2 = %d\n", num2);//1314
return 0;
}