目录
一、一维数组
1.数组概念
数组使用来管理一组相同数据类型的数据的。
数组是一个构造类型。
数组中的每个相同的数据类型的数据称为数组的元素、或者叫数组的成员。
数组的成员在内存上一定是连续的,不管是几维数组,都是连续的。
2. 一维数组的概念
所谓的一维数组,就是下标只有一个的数组,他在内存上是连续的。
3. 定义一维数组
存储类型 数据类型 数组名[下标];
存储类型:先不用管,后面讲,不写的默认就是 auto
数据类型:既可以是基本类型,也可以是构造类型(数组除外)
数组名:是一个标识符,要符合标识符命名规范
下标:在定义数组时,下标只能是一个常量,用来表示数组的长度(元素的个数)
在定义数组之后,使用下标时,既可以是常量、也可以是变量,
也可以是表达式,表示取数组的第几个元素
例:
int s[10]; //定义了一个数组,数组名为s,数组中最多能存储10个元素
每个元素都是一个 int 类型。
4. 一维数组的性质
#include <stdio.h>
int main(int argc, const char *argv[])
{
//定义了一个名为s 长度为5 每个元素都是int 的数组
int s[5];
//访问数组成员的方式
//数组名[下标] 方式来访问
//下标是 从 0 开始的,长度为5的数组最大的下标应该是4
//取到s某个成员后 int 类型的变量怎么用 这个成员就怎么用
s[0] = 10;
s[1] = 20;
s[2] = 30;
s[3] = 40;
s[4] = 50;
printf("s[0] = %d\n", s[0]);//10
printf("s[1] = %d\n", s[1]);//20
printf("s[2] = %d\n", s[2]);//30
printf("s[3] = %d\n", s[3]);//40
printf("s[4] = %d\n", s[4]);//50
//数组在定义之后就不能整体赋值了
//只能一位一位的赋值
//s = 10,20,30,40,50; //错误的
//一维数组的成员在内存上是连续的
// & 取变量的地址
// %p 可以输出地址
// 下面的地址输出的是连续的
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]);
//一维的大小 = sizeof(成员类型) * 成员的个数
printf("sizeof(s[0]) = %ld\n", sizeof(s[0]));//4
printf("sizeof(s) = %ld\n", sizeof(s));//20
printf("--------------------------------\n");
//使用循环给数组成员赋值
int i = 0;
for(i = 0; i < 5; i++){
s[i] = (i+1)*100;
}
//一维数组的遍历
for(i = 0; i < 5; i++){
printf("s[%d] = %d\n", i, s[i]);
}
printf("--------------------------------\n");
for(i = 0; i < sizeof(s)/sizeof(s[0]); i++){
printf("s[%d] = %d\n", i, s[i]);
}
return 0;
}
5. 一维数组的初始化
#include <stdio.h>
int main(int argc, const char *argv[])
{
//方式1:完全初始化
//short hqyj[5] = {10, 20, 30, 40, 50};//10 20 30 40 50
//方式2:不完全初始化
//按下标是小到大初始化,没有初始化的位,会初始化成 0
//short hqyj[5] = {10, 20};//10 20 0 0 0
//方式3:所有成员都初始化成 0
//----这种方式在开发中比较常用
//short hqyj[5] = {0};// 0 0 0 0 0
//方式4:省略下标的方式初始化
//这种方式 编译器会根据后面元素的个数 自动给数组分配空间
//short hqyj[] = {10, 20, 30, 40, 50, 60};
//printf("%ld\n",sizeof(hqyj));//12 = sizeof(short) * 6
//数组如果没有初始化,里面存的是随机值
//short hqyj[5];
//数组一旦定义完成了 就不能整体赋值了
//short hqyj[5];
//hqyj[4] = {10, 20, 30, 40};//错误的用法
//!!!!!!!!!!!!!!!
//数组名是一个地址常量,里面存的就是数组的首地址,也就是首元素的地址
short hqyj[5];
printf("&hqyj[0] = %p hqyj = %p\n", &hqyj[0], hqyj);
//既然是常量 就不能被赋值 也不能进行 hqyj++ 操作
//hqyj++;//错误的
short hqyj2[5] = {1, 2, 3, 4, 5};
//hqyj = hqyj2;//错误的
int i = 0;
for(i = 0; i < 5; i++){
printf("%d ", hqyj[i]);
}
printf("\n");
//数组越界访问的问题,编译器不会做检查,需要程序员自己检查
//所以使用数组时,一定要注意,下标不要越界
//一旦越界了,出现的错误是不可预知的
printf("------------------------\n");
hqyj[5] = 100;//已经使用了非法内存,错误已经不可预知了,千万不要这样使用
printf("hqyj[5] = %d\n", hqyj[5]);
return 0;
}
练习:
1.定义整型数组,长度为10,在终端给数组赋值
赋值后,找出数组最大的元素,及最大元素的下标,并输出。
#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]);
}
#if 0
//如果找最大值,注意 要使用这一组数据中的某一个来开始
int max = s[0];
for(i = 1; i < 10; i++){
if(s[i] > max){
max = s[i];
}
}
#endif
int max_index = 0;
for(i = 1; i < 10; i++){
if(s[i] > s[max_index]){
max_index = i;
}
}
printf("最大值 %d, 最大值的下标 %d\n", s[max_index], max_index);
return 0;
}
2. 斐波那契数列
1 1 2 3 5 8 13 21 。。。。。
定义一个 int 数组,长度为20,保存 斐波那契数列的前20位,并输出。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int fb[20] = {1,1};
int i = 0;
#if 0
for(i = 2; i < 20; i++){
fb[i] = fb[i-1] + fb[i-2];
}
#endif
for(i = 0; i < 18; i++){
fb[i+2] = fb[i+1] + fb[i];
}
for(i = 0; i < 20; i++){
printf("%d ",fb[i]);
}
putchar(10);
return 0;
}
二、二维数组
1. 概念
所谓的二维数组,就是有两个下标的数组。
2. 定义二维数组的格式
存储类型 数据类型 数组名[行数][列数];
如 int s[3][4]; //定义了一个二维数组,有3行 4 列 共计12个元素,每个元素都是一个 int类型
注意:虽然二维数组有行号和列号,但是二维数组的成员在内存上也是连续的。
其中列宽,决定了数组按行偏移时的跨度。
二维数组访问元素:
数组名[行号][列号]; //行号和列号都是从0开始的
s[0][0] s[0][1] s[0][2] s[0][3]
s[1][0] s[1][1] s[1][2] s[1][3]
s[2][0] s[2][1] s[2][2] s[2][3]
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
//定义了一个二维数组 有2行 3列 共计6个元素 每个元素都是一个 int 类型
int s[2][3];
//访问二维数组 使用 数组名[行号][列号]
s[0][0] = 10;
s[0][1] = 20;
s[0][2] = 30;
s[1][0] = 40;
s[1][1] = 50;
s[1][2] = 60;
printf("s[0][0] = %d\n", s[0][0]);//10
printf("s[1][1] = %d\n", s[1][1]);//50
//二维数组的数组名也时常量 不可以被赋值 也不可以 ++
//s++; //错误的
//二维数组的大小 = 行数 * 列数 * sizeof(元素类型)
printf("sizeof(s) = %ld\n", sizeof(s));// 2 * 3 * sizeof(int) = 24
//二维数组在内存上也时连续的
int i = 0;
int j = 0;
for(i = 0; i < 2; i++){
for(j = 0; j < 3; j++){
printf("%p ", &s[i][j]);
}
printf("\n");
}
printf("--------------------------------------\n");
//二维数组的遍历
//外层循环控制行数
for(i = 0; i < 2; i++){
//内层循环控制列数
for(j = 0; j < 3; j++){
printf("%d ", s[i][j]);
}
printf("\n");
}
return 0;
}
3 .二维数组的初始化
#include <stdio.h>
int main(int argc, const char *argv[])
{
//二维数组如果没有初始化,里面也是 随机值
//int s[3][4];
//二维数组初始化的方式
//方式1:以行为单位 完全初始化
/*
int s[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
*/
//方式2:以行为单位 不完全初始化
//----没有初始化的位 默认用0初始化
/*
int s[3][4] = {{1,2},
{5},
{9,10,11}};
*/
//方式3:不以行为单位 完全初始化
//int s[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
//方式4:不以行为单位 不完全初始化
//----没有初始化的位 默认用0初始化
//int s[3][4] = {1,2,3,4,5};
//方式5:全部初始化成0
//int s[3][4] = {0};
//方式6:省略下标的初始化方式
//列宽是不能省略的 行号是可以省略的
//并且不足一行的,也会自动根据列数分配一整行,不够的位用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]);
}
printf("\n");
}
return 0;
}
练习:
1.定义个 3行 4列的二维数组,并完全初始化,
输出数组中最大的元素,及最大元素的行号 列号。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[3][4] = {1,2,3,4,5,6,100,8,9,10,11,12};
int i = 0;
int j = 0;
int max_row = 0;//最大值的行号
int max_clum = 0;//最大值的列号
for(i = 0; i < 3; i++){
for(j = 0; j < 4; j++){
if(s[i][j] > s[max_row][max_clum]){
max_row = i;
max_clum = j;
}
}
}
//当循环结束时,max_row 里面记录的就是最大值的行号
//max_clum 里面记录的就是最大值的列号
printf("max_value = %d max_row = %d max_clum = %d\n",\
s[max_row][max_clum], max_row, max_clum);
return 0;
}
2.使用二维数组保存 杨辉三角 ,并输出
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
答案:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[10][10] = {0};
s[0][0] = 1;
int i = 0;
int j = 0;
for(i = 1; i < 10; i++){
s[i][0] = 1;
for(j = 1; j < 10; j++){
s[i][j] = s[i-1][j]+s[i-1][j-1];
}
}
for(i = 0; i < 10; i++){
for(j = 0; j <= i; j++){
printf("%-4d", s[i][j]);
}
putchar(10);
}
return 0;
}
三、字符数组和字符串
#include <stdio.h>
int main(int argc, const char *argv[])
{
//可以使用字符数组来保存一组字符
char s1[5] = {'A','B','C','D','E'};
//一维数组的遍历
int i = 0;
for(i = 0; i < 5; i++){
printf("%c", s1[i]);
}
putchar(10);
//可以将字符串保存在字符数组里
char s2[32] = {"www.hqyj.com"};
printf("s2 = %s\n", s2);
char s3[32] = "www.hqyj.com";//两种写法都可以 一般是用下面这种
printf("s3 = %s\n", s3);
printf("sizeof(s3) = %ld\n", sizeof(s3));//32
//字符串结尾都会有一个隐藏的 '\0' 来标识字符串结束
//所以自己定义数组保存字符串时 要给 '\0' 留一个字节的空间
char s4[] = "helloworld";
printf("sizeof(s4) = %ld\n", sizeof(s4));//11
//C语言处理字符串 遇到 '\0' 表示结束
char s5[32] = "hello\0world";
printf("sizeof(s5) = %ld\n", sizeof(s5));//32
printf("s5 = %s\n", s5);//hello
printf("s5[6] = %c\n", s5[6]);//w 说明\0后面的 world 也存到数组里了
//只不过printf输出时 遇到 \0 就结束了
//如果时字符数组的话 不要使用 %s 输出
printf("s1 = %s\n", s1);//因为s1中没有 \0 所以printf会一直向后找
//直到遇到 \0 结束
//数组访问会越界,出现的错误 不可预知
//如果非要用%s输出 需要在结尾加个 \0
// '\0' 0 '0' 三个零是不一样的
// '\0' 0 一样的 ascii码都是 0
// '0' ascii码是 48
char s6[6] = {'A','B','C','D','E','\0'};
printf("s6 = %s\n", s6);
return 0;
}
四、字符串处理函数
1.strlen()
功能:
计算字符串的长度,不包括 '\0'
头文件:
#include
函数原型:
size_t strlen(const char *s); //size_t 是 unsigned int 的别名
参数:
要计算长度的字符串的首地址
返回值:
字符串的长度
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = "hello world";
int ret = strlen(s1);//如果需要用到结果 可以使用变量保存
printf("sizeof(s1) = %ld\n", sizeof(s1));//32
printf("strlen(s1) = %ld\n", strlen(s1));//11
printf("ret = %d\n", ret);//11
//如果是字符数组 要慎用 strlen计算长度
//因为 strlen 也时遇到 '\0' 才结束的
char s2[5] = {'A','B','C','D','E'};
printf("strlen(s2) = %ld\n", strlen(s2));//结果不可预知
return 0;
}
练习:
自己写代码实现 strlen 的功能。
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s[32] = "hello world";
int i = 0;
//while(s[i]){ //这样写也可以
while(s[i] != '\0'){
i++;
}
printf("mystrlen(s) = %d\n", i);
return 0;
}
2.strcpy
功能:
将src 拷贝到 dest 里面
头文件:
#include
函数原型:
char *strcpy(char *dest, const char *src);
参数:
dest : 目的字符串
src : 源字符串
返回值:
目的字符串
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = "hello world";
char s2[32] = "hqyj";
printf("s1 = %s\n", s1);//hello world
printf("s2 = %s\n", s2);//hqyj
//将s1的字符串拷贝到 s2 中
//注意:s2 要足够大 否则可能出现 访问越界
strcpy(s2, s1);
printf("-----------------------------\n");
printf("s1 = %s\n", s1);//hello world
printf("s2 = %s\n", s2);//hello world
char s3[32] = "kjasshdjkfka";
char s4[32] = "abcd";
strcpy(s3, s4);//s4只会覆盖s3的前5位 分别替换成 abcd \0
//s3 后面的内容还在里面,只是处理不到了
printf("s3 = %s\n", s3);//abcd
printf("s4 = %s\n", s4);//abcd
printf("s3[4] = %d [%c]\n", s3[4], s3[4]);//0
printf("s3[5] = %d [%c]\n", s3[5], s3[5]);//h
return 0;
}
练习:
自己写代码实现 strcpy 的功能。
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s1[32] = "hqyj";
char s2[32] = "hello world";
printf("s1 = %s\n", s1);
printf("s2 = %s\n", s2);
printf("----------------------------\n");
int i = 0;
//只要 s2 没遇到 '\0' 就将字符复制给 s1
while(s2[i] != '\0'){
s1[i] = s2[i];
i++;
}
s1[i] = s2[i];//将 s2 的 '\0' 也拷贝给 s1 !!!!
printf("s1 = %s\n", s1);
printf("s2 = %s\n", s2);
return 0;
}
3. strcat
功能:
将src 追加到 dest 后面,会覆盖dest的 '\0'
头文件:
#include
函数原型:
char *strcat(char *dest, const char *src);
参数:
dest : 目的字符串
src : 源字符串
返回值:
目的字符串
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = "hello world";
char s2[32] = "1234";
printf("s1 = %s\n", s1);
printf("s2 = %s\n", s2);
//注意:s1 要足够大 否则可能出现访问越界
strcat(s1, s2);
printf("-----------------------------\n");
printf("s1 = %s\n", s1);//hello world1234
printf("s2 = %s\n", s2);//1234
return 0;
}
练习:
自己写代码实现 strcat 的功能。
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s1[32] = "hello world";
char s2[32] = "1234";
int i = 0;
int j = 0;
//先找 s1 的 '\0' 的下标
while(s1[i]){
i++;
}
//从s1 的 '\0' 开始覆盖
while(s2[j]){
s1[i+j] = s2[j];
j++;
}
//将s2 的 '\0' 也追加给 s1
s1[i+j] = s2[j];
printf("s1 = %s\n", s1);
return 0;
}
4.strcmp
功能:
比较两个字符串的大小,
比较的是第一个不相等的字符的 ascii 码
注意:不是比较长度!!!
头文件:
#include
函数原型:
int strcmp(const char *s1, const char *s2);
参数:
要参与比较的两个字符串
返回值:
>0 s1>s2
==0 s1==s2
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = "helloakjdshfjkahsdkjfh";
char s2[32] = "helzo";
//strcmp 会一位一位的比较两个字符串对应字符的ascii码
//直到出现大小关系,就立即返回
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");
}
//返回的实际是两个字符串中第一个不相等的字符的 ascii码的差值(s1-s2)
printf("%d\n", ret);
printf("%d\n", 'l'-'z');
return 0;
}
练习:
自己写代码实现 strcmp 的功能。
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s1[32] = "hello";
char s2[32] = "hello";
int i = 0;
int ret = 0;
while(s1[i]!='\0' && s2[i]!='\0'){
ret = s1[i] - s2[i];
if(ret != 0){
break;
}
i++;
}
//上面的循环如果结束了,有两种情况
//一种是 有字符串已经处理到 '\0'
//另一种是提前 break 了
//循环结束后 重新获取一下当前第i位的差值
ret = s1[i] - s2[i];
if(ret > 0){
printf("s1>s2\n");
}else if(ret < 0){
printf("s1<s2\n");
}else{
printf("s1==s2\n");
}
return 0;
}
带 n 版本的函数
strncpy 表示将src的前n位 拷贝给 dest
strncat 表示将src的前n位 追加给 dest
strncmp 表示之比较两个字符串的 前 n 位
五、冒泡排序法
1.基本思想:
相邻的两个元素之间进行比较,按照要求进行交换。
2.详细步骤:(以升序为例 从小到大)
第一趟排序:
将第一个元素和第二个元素进行比较,将较大放在后面,
然后第二个元素和第三个元素进行比较,将较大的放在后面,
依次类推,直到第一趟排序结束,最大的元素就在最后一位了
第二趟排序:
将第一个元素和第二个元素进行比较,将较大放在后面,
然后第二个元素和第三个元素进行比较,将较大的放在后面,
依次类推,直到第二趟排序结束,第二大的元素就在倒数第二位了
依次类推,直到最后一趟排序完成,整个序列就有序了。
冒泡排序动图:
3.代码实现:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[10] = {12,34,5,3,6,45,67,98,55,1};
int i = 0;
int j = 0;
int temp = 0;
int len = sizeof(s)/sizeof(s[0]);
//排序前
for(i = 0; i < 10; i++){
printf("%d ",s[i]);
}
putchar(10);
#if 0
//第一趟排序
//因为时 第i和第i+1位比的 所以 i 只需要能访问到倒数第二个元素就行了
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
#if 0
//外层循环控制比较的趟数
//当只剩下最后一个元素时,就不用再比了 所以 可以少比一趟
for(j = 0; j < len-1; j++){
//内层循环用来找每趟的最大值
//因为每趟都能确定一个最大值,所以每趟都可以少比一个元素 所以 -j
for(i = 0; i < len-1-j; i++){
if(s[i] > s[i+1]){ //如果想降序排序,只需将此处的 > 改成 < 即可
temp = s[i];
s[i] = s[i+1];
s[i+1] = temp;
}
}
}
#endif
//更优化的写法:有些场景 数据不一定需要比到最后
//才能确定有序,可能比到中途就已经有序了,如下面的数据
//1 2 3 4 5 6 8 7 10 9
//这时,对已经有序的数据再进行比较 就是多余的操作了
//优化方式:使用标志位确定数据是否已经有序,代码优化如下:
int flag = 0;//用于判断是否已经有序的标志位 0 有序 1 无序
for(j = 0; j < len-1; j++){
flag = 0;//每趟开始时 我们都认为是已经有序了
for(i = 0; i < len-1-j; i++){
if(s[i] > s[i+1]){
temp = s[i];
s[i] = s[i+1];
s[i+1] = temp;
flag = 1;//如果发生交换,说明我们认为的有序是错的 将flag 置1
}
}
//如果没有发生交换,flag仍然为0 就说明数据已经有序了
if(0 == flag){
break;
}
}
//排序后
for(i = 0; i < 10; i++){
printf("%d ",s[i]);
}
putchar(10);
return 0;
}