C语言基础08

内容提要

  • 数组

    • 排序算法:冒泡排序

    • 二维数组

    • 字符数组

数组

冒泡排序

  • 排序思想(向前冒泡)

    • 一次只排好一个数,针对n个数,最差情况需要n-1次就可以排好

    • 每次排序假定第一个元素是最大或者最小,用第一个元素后面的元素一一与第一个元素比较,遇到较大或较小的和第一个元素交换,访问完数组的最后一个元素,就排好了一个数

    • 在余下的数中,再次应用第2步的操作,直到只剩下1个数

  • 动图演示:

  • 推理:

    例如:将5,4,3,2,1冒泡排序为1,2,3,4,5

    排序演示:

    • 第0轮:5,4,3,2,1 → 4,3,2,1,5 比较4次 = 数组长度5 - 轮数0 - 1

    • 第1轮:4,3,2,1,5 → 3,2,1,4,5 比较3次 = 数组长度5 - 轮数1 - 1

    • 第2轮:3,2,1,4,5 → 2,1,3,4,5 比较2次 = 数组长度5 - 轮数2 - 1

    • 第3轮:2,1,3,4,5 → 1,2,3,4,5 比较1次 = 数组长度5 - 轮数3 - 1

    总结:

    • 上面案例涉及到5个数的排序,排序了4轮,得到:轮数 = 元素个数(数组长度) - 1,我们可以通过一个外层for循环实现轮数的遍历

    • 案例涉及的每一轮中数列的排序次数,计算规则:次数 = 元素个数 - 轮数 - 1,我们可以通过一个内层for循环实现每一轮次数的遍历

    • 每一次比较过程中,两个数涉及到位置交换,比如a = 3, b = 4,交换ab的数据变为a = 4, b = 3,应该如何实现:

      • 引入一个临时变量temp,将a的值赋值给temp,int temp = a;

      • 将b的值赋值给a,a = b;

      • 将temp的值赋值给a,a = temp;

  • 代码:

    复制代码
     // 创建一个数组,用来存放排序用的数列
     int arr[10];
     ​
     // 定义循环变量和临时变量
     int i,j,temp;
     ​
     printf("请输入10个整数:\n");
     ​
     // 计算数组的长度
     int len = sizeof(arr) / sizeof(arr[0]); // 等价于 sizeof(arr) / sizeof(int);
     ​
     // 通过循环录入
     for (i = 0; i < len; i++) scanf("%d",&arr[i]);
     ​
     printf("\n");
     ​
     // 冒泡排序
     // 外层循环:实现轮数的遍历,轮数 = 数组长度 - 1
     for (i = 0; i < len - 1; i++)
     {
         // 内层循环:实现每一轮的比较次数,比较次数 = 数组长度 - 轮数 - 1
         for (j = 0; j < len - i - 1; j++)
         {
             // 相邻两个数比较后交换位置
             // if (arr[j] < arr[j+1]) // 此时,实现降序排列:从大到小
             if (arr[j] > arr[j+1]) // 此时,实现升序排列:从小到大
             {
                 temp = arr[j];
                 arr[j] = arr[j+1];
                 arr[j+1] = temp;
             }
         }
     }
     ​
     prinf("冒泡排序后的数列:\n");
     for (i = 0; i < len; i++) printf("%3d",arr[i]);
     ​
     printf("\n");
     ​
  • 衍生:

    冒泡排序 → 鸡尾酒排序 → 摇床排序

二维数组

定义

二维数组本质上是一个行列式的组合,也就是说二维数组由行和列两部分组成,属于多维数组。二维数组数据通过行列进行解读

二维数组可被视为一个特殊的一维数组,相当于二维数组又是一个特殊的一维数组,只不过它的元素是一维数组。(数组的元素的类型可以是数组类型)

语法
复制代码
 数据类型 数组名[行数][列数]

行数:外层数组的数组容量

列数:内层数组的数组容量

说明
  • 二维数组在初始化的时候可以省略行数,系统会通过初始化后的数据自动推断行数

  • 二维数组和一维数组一样,也可以部分初始化,未初始化的数据默认用0或者\0(\0对应的ASCII是0)补齐

  • 二维数组在初始化的时候不能省略列数,否则编译报错

举例
复制代码
 int arr[3][3] = {{11,12,13},{21,22,23},{31,32,33}};  // 正确,等价于下面写法
 int arr[][3]  = {{11,12,13},{21,22,23},{31,32,33}};  // 正确,二维数组初始化的时候可以省略行数
 ​
 int arr[3][3] = {{11,12},{21,22},{31}};              // 正确,等价于下面的写法
 int arr[3][3] = {{11,12,0},{21,22,0},{31,0,0}};  
 ​
 int arr[3][3] = {0};                                 // 正确,所有位置使用0补齐
 int arr[3][3] = {};                                  // 正确,所有位置使用0补齐
 int arr[3][3] = {11};                                // 正确,除了第0行第0列使用11填充外,其他位置都使用0补齐
 ​
 int arr[][]   = {{11,12,13},{21,22,23},{31,32,33}};  // 错误,这种写法,编译报错,不能省略列数
 int arr[3][]   = {{11,12,13},{21,22,23},{31,32,33}}; // 错误,这种写法,编译报错,不能省略列数

注意:在C语言中,二维数组在计算机的存储顺序是按行进行的,即第一维的(行)下标变化慢,第二维的(列)下标变化快

内存存储
应用场合

主要是应用于对行列有要求的情况。比如我们现在要存储西安粤嵌所有在班学生的成绩

还有就是字符数组的应用,比如用数组存储学生的姓名

特殊写法
  • 下标可以是整型表达式,如:a[2-1][2*2-1] → a[1][3]

  • 下标可以是已经有值的变量或数组元素,如:a[2*x-1][b[3][1]]

  • 数组元素可以出现在表达式中,如:b[1][2] = a[2][3]/2

注意:使用数组元素的下标应在已定义数组的大小范围内;应注意区别定义数组大小和引用数组元素的区别

初始化
  • 分行给二维数组赋初值

    复制代码
     int arr[3][4] = {{11,12,13,14},{21,22,23,24},{31,32,33,34}};
  • 可将所有数据写在一个花括号内,按照排列顺序对元素赋值

    复制代码
     int arr[3][4] = {11,12,13,14,21,22,23,24,31,32,33,34};
  • 可对部分元素赋初值,其余未赋值部分自动填充数值类型默认值-0 | 字符型默认值-\0

    复制代码
     int arr[3][4] = {{11},{21,22},{31}};
  • 若对全部元素赋初值,自定义数组时可以省略第1维数组的长度,第2维数组的长度必须指明

    复制代码
     int a[][4] = {11,12,13,14,21,22,23,24,31,32,33,34};
  • 在分行赋初值时,也可以省略第1维的长度

    复制代码
     int arr[][4] = {{11,12,13},{0},{0,10}};
案例

案例1:

  • 需求:二维数组的遍历

  • 分析:

    • 二维数组的遍历需要使用到双层for循环,外层循环控制行,内层循环控制列

    • 取数据:arr[行号][列号]

  • 代码:

    复制代码
    // 创建一个二维数组
    int arr[][3] = {{11},{21,22},{31,32,33}};
    
    // 获取行数组容量和列数组容量
    int row = sizeof(arr) / sizeof(arr[0]);
    int col = sizeof(arr[0]) / sizeof(arr[0][0]);
    
    // 遍历数组
    // 外层循环控制行
    for (int i = 0; i < row; i++)
    {
        // 内层循环控制列(也可以在这里计算列的大小)
        // int col = sizeof(arr[i]) / sizeof(arr[i][0]);
        for (int j = 0; j < col; j++)
        {
            // 输出元素
            printf("%-3d",arr[i][j]);
        }
    }
    printf("\n");

案例2:

  • 需求:矩阵的转置

  • 分析:

    • 所谓的转置,就是原本的列变行,行变列

  • 代码:

    复制代码
    #define ROW 2
    #define COL 2
    
    // 定义循环变量
    int i,j;
    
    // 准备2个数组用来存放转置前后的数据
    int arr_before[ROW][COL] = {{11,12,13},{21,22,23}};
    int arr_after[COL][ROW] = {0};
    
    // 计算数组大小
    int arr_before_row = sizeof(arr_before) / sizeof(arr_before[0]);
    int arr_before_col = sizeof(arr_before[0]) / sizeof(arr_before[0][0]);
    
    int arr_after_row = sizeof(arr_after) / sizeof(arr_after[0]);
    int arr_after_col = sizeof(arr_after[0]) / sizeof(arr_after[0][0]);
    
    // 通过循环实现数组转置
    printf("转置前:\n");
    for (i = 0; i < arr_before_row; i++)
    {
        for (j = 0; j < arr_before_col; j++)
        {
            // 打印输出转置前的数据
            printf("%-4d",arr_before[i][j]);
            // 转置
            arr_after[j][i] = arr_before[i][j];
        }
        printf("\n");
    }
    printf("\n");
    
    printf("转置后:\n");
    for (i = 0; i < arr_after_row; i++)
    {
        for (j = 0; j < arr_after_col; j++)
        {
            // 打印输出转置后的数据
            printf("%-4d",arr_after[i][j]);
        }
        printf("\n");
    }
    printf("\n");

运行结果:

课堂练习
  • 需求:求一个3行3列的矩阵对角线上的元素之和

  • 分析:

  • 总结:等行等列的矩阵,转置前后,对角线上的数据相等

字符数组

在C语言中,支持常量字符串,不支持变量字符串,如果想要实现类似的变量字符串,C语言中提供了两种实现方式

  • 字符数组

    复制代码
    char name[] = "哪吒";
  • 字符指针

    复制代码
    char *name = "哪吒";
概念

元素类型为char字符型的数组,字符数组往往是用来存储字符串数据的.需要注意的是,我们C语言中的字符是字节字符

测试题:

复制代码
char a = 'A';  // 正确
char b = '1';  // 正确
char c = 65;   // 正确,这里的65是ASCII码,char的值有两种形式,一种是字符,一种是字符对应的ASCII码
char d = "A";  // 错误,char字符不能用双引号
char e = '王'; // 错误,因为一个中文汉字占两个字节
char f = "王"; // 正确,占三个字节,一个汉字+\n
语法:
复制代码
// 一维数组
char 数组名[数组容量];

// 二维数组
char 数组名[行容量][列容量];

字符数组的语法就是我们前面所学的一维数组和二维数组的语法,只不过数据类型是char而已

注意:

如果我们的char数组初始化的时候,没有完全初始化值的时候,使用'\0'进行填充。这里的'\0'只是起到一个占位或者标识的作用,我们是无法通过printf打印输出到控制台的

比如:

复制代码
char c[8] = {'h','e','l','l','o'};   // 等价于下面写法
char c[8] = {'h','e','l','l','o','\0','\0','\0'}; 
案例

案例1:

  • 需求:输出一个字符序列(I LOVE YOU)

  • 代码:

    复制代码
    // 创建一个数组,用来存储 I LOVE YOU,ASCII中对应空格为:' ',其对应的ASCII值为 32
    char arr[] = {'I',' ','L','O','V','E',32,'Y','O','U'}; 
    
    // 计算数组的长度
    int len = sizeof(arr) / sizeof(arr[0]);
    
    // 通过for循环遍历数组
    for (int i = 0; i < len; i++) printf("%c",arr[i]);
        
    printf("\n");

案例2:

  • 需求:输出一个用字符*组成的空菱形图案

  • 代码:

    复制代码
    // 创建一个二维数组,存放空菱形
    char arr[5][5] = {
        {' ',' ','*',' ',' '},
        {' ','*',' ','*',' '},
        {'*',' ',' ',' ','*'},
        {' ','*',' ','*',' '},
        {' ',' ','*',' ',' '}
    };
    // 计算行数和列数
    int row = sizeof(arr) / sizeof(arr[0]);
    int col = sizeof(arr[0]) / sizeof(arr[0][0]);
    // 遍历数组
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            printf("%c",arr[i][j]);
        }
        printf("\n");  // 当一行所有列数据输出完毕,需要换行
    }
    printf("\n");

注意:

①如果定义时,不初始化,元素值不确定(针对定义在函数中的数组)

复制代码
char arr1[2];   // 此时这个值是随机的,不固定的
char arr2[5] = {'a','b','c'};  // 此时处于不完全初始化,未初始化的元素使用\0进行填充

②如果提供的字符个数大于数组长度,则按照语法错误处理(会报警告,但是能编译通过);如果字符个数小于数组长度,后面的元素自动补充\0

复制代码
char arr1[2] = {'h','e','e'}; // 编译能通过,但是会报警告;不建议写
char arr2[3] = {'a'};         // 正确,未初始化的元素使用\0填充

③如果提供的字符个数与数组长度相同,可以省略数组长度,系统会自动确定元素个数,适合字符较多时

复制代码
char arr1[] == {'b','u'};    // 正确,根据初始化元素,由系统自动计算元素个数
字符串结束标志
说明
  • C语言规定,字符串以字符\0作为结束标志

  • 编译系统对字符串常量自动加一个\0作为结束标志。比如"hello"实际上的存储{'h','e','l','l','o','\0'}

  • 程序中往往通过判断\0来检测字符串是否结束

  • \0的ASCII码是0,不是一个可显示可输出的字符,是"空操作符",它什么都不做,不会增加有效字符,仅仅用作一个工程判别的标志或者在字符数组中占位

    复制代码
    char a[] = {'h','i'};       // hi
    char a[] = {'h','i','\0'};  // hi
    char c[] = "hello";         // 实际的存储形式为 hello\0
字符数组的多样表示

我们的char数组可以以数组的形式一个一个输出每个字符;也可以以字符串的形式整体进行输出

案例:

复制代码
// 字符串的第1种表示:
char s1[] = {'h','e','l','l','o',' ','w','o','r','l','d','\0'};
// 字符串的第2种表示:
char s2[] = {"hello world"};  // ""包裹的字符串自带\0
// 字符串的第3种表示:
char s3[] = "hello world";


// 字符串输出第1种方式:
// 计算数组的长度
int len = sizeof(s3) / sizeof(s3[0]);

for (int i = 0; i < len; i++)
{
    // 过滤\0
    if (s1[i] == '\0' || s2[i] == '\0' || s3[i] == '\0') continue;
    
    printf("%c,%c,%c\n",s1[i],s2[i],s3[i]);
}
printf("\n");


// 字符串输出第2种方式:
printf("%s,%s,%s\n",s1,s2,s3);

printf("\n");

注意:

  • 字符串的长度与字符数组的长度不一定相同

    复制代码
    char c[] = {'h','\0','i','\0'};
    // c作为数组,长度为:4
    // c作为字符串,长度为:1
  • 利用字符串常量可以对字符数组进行初始化,但不能用字符串常量对字符数组赋值

    复制代码
    // 正确演示:利用字符串常量给字符数组初始化
    char arr1[6] = "hello";
    
    // 错误演示:用字符串常量给字符数组赋值
    char arr2[6];
    arr2 = "hello";
字符串的基础操作

在用格式化说明符%s进行输入输出时,其输入输出项均为数组名,但在输入时,相邻两个字符串之间要用空格分隔,系统将自动在字符串后加上\0,在输出时,遇到结束符\0作为输出结束标志

对于字符串的操作,我们需要使用到一些系统提供的API函数

字符串输入

scanf

语法:

复制代码
scanf("%s",数组名);

注意:数组名对应的数组只能是char类型

案例:

复制代码
// 创建一个字符数组,用来存储姓名
char name[20];

printf("请输入您的名字:\n");
scanf("%s",name); // 数组本身没有空间,它的内存空间其实就是其元素空间,C语言规定数组名指向的就是首地址
printf("您的姓名是%s\n",name);

注意:采用scanf进行字符串输入,要求字符串中不能有空格,否则字符串遇到空格就会结束

fgets

语法:

复制代码
fgets(数组名,数组容量,stdin);

功能:

从键盘录入一个字符串常量到字符数组,返回字符数组的地址(首地址,默认返回的地址,一般用12位16进制数表示)

说明:

采用fgets进行字符串输入,可获取所有输入的字符串,包含\n,在实际的字符串处理时,我们可能需要手动处理\n

案例:

复制代码
// 创建一个字符数组,用来存储姓名
char name[20];
// 计算数组的大小
int len = sizeof(name) / sizeof(name[0]);

printf("请输入您的名字:\n");
fgets(name,len,stdin); // 数组本身没有空间,它的内存空间其实就是其元素空间,C语言规定数组名指向的就是首地址

printf("您的名字是%s\n",name);

注意:

①如果输入的字符串不包括空格或换行,可以使用scanf或者fgets

②如果输入的字符串包括空格或换行,只能使用fgets

gets 危险的

语法:

复制代码
gets(数组名);

功能:

从键盘录入一个字符串常量到字符数组,返回字符数组的地址(首地址,默认返回的地址,一般用12位16进制数表示)

说明:

采用gets进行字符串输入,可获取所有输入的字符串,包含\n,在实际的字符串处理时,我们可能需要手动处理\n

案例:

复制代码
// 创建一个字符数组,用来存储姓名
char name[20];
// 计算数组的大小
int len = sizeof(name) / sizeof(name[0]);

printf("请输入您的名字:\n");
gets(name); // 数组本身没有空间,它的内存空间其实就是其元素空间,C语言规定数组名指向的就是首地址

printf("您的名字是%s\n",name);
字符串输出

printf

语法:

复制代码
printf("%s",数组名);

案例:

复制代码
// 创建一个字符数组,用来存储姓名
char name[20];

printf("请输入您的名字:\n");
scanf("%s",name); // 数组本身没有空间,它的内存空间其实就是其元素空间,C语言规定数组名指向的就是首地址

printf("您的名字是%s\n",name);

fputs

语法:

复制代码
fputs(数组名,stdout);

功能:

输出一个字符串

说明:

字符串可以包含转义字符(可以识别转义字符)

案例:

复制代码
char arr[] = "hi lucy\teat\n";

// 第1种输出
printf("%s\n",arr);

// 第2种输出
fputs(arr,stdout);

puts

语法:

复制代码
puts(数组名);

功能:

输出一个字符串

说明:

字符串可以包含转义字符(可以识别转义字符)

案例:

复制代码
char name[20];

printf("请输入您的名字:\n");

// gets、fgets和scanf只能多选一
gets(name);

// 输出
puts(name); // 标准的输出
字符串转数值【扩展】
  • strtol

    复制代码
     long strtol(const char *str, char **endptr, int base);

    将字符串转换为长整型数

    参数说明:

    • str:指向要转换的字符串的指针

    • endptr:一个指向字符指针的指针。如果提供了这个参数,并且转换成功,endptr将被设置为指向第一个为转换字符的指针。如果endptrNULL,则不使用它

    • base:用于指定转换的基数。它可以是 2 到 36 之间的值,或者是特殊值 0。如果base是 0,则函数会根据字符串的前缀(如 "0x" 或 "0X" 表示十六进制,"0" 表示八进制,否则默认为十进制)来自动确定基数。

  • strtoul

    复制代码
    unsigned long strtoul(const char *str, char **endptr, int base);

    将字符串转换为无符号长整型数

  • strtod

    复制代码
    double strtod(const char *str, char **endptr);

    将字符串转换为双精度浮点数

  • atol

    复制代码
    long atol(const char *str);

    将字符串转换为长整型数(不推荐使用,建议使用strtol

  • atof

    复制代码
    double atof(const char *str);

    将字符串转换为双精度浮点数(不推荐使用,建议使用strtod

案例:

复制代码
printf("%lo,%ld,%lx\n",strtol("12",NULL,8),strtol("12",NULL,10),strtol("12",NULL,16));
printf("%lo,%ld,%lx\n",strtol("012",NULL,0),strtol("12",NULL,10),strtol("0x12",NULL,0));

int a = 10;

printf("%p,%lx\n",&a,&a);

main()
{
   int a=2,b=-1,c=2;
   if(a<b)
       if(b<0) c=0;
   else c+=1;
   printf("%d\n",c);
}

关于单字符的输入:

  • scanf("%c",.....);

  • getchar();

关于单字符的输出:

  • printf("%c",变量);

  • putchar();

相关推荐
jndingxin10 分钟前
OpenCV图像拼接(2)基于羽化(feathering)技术的图像融合算法拼接类cv::detail::FeatherBlender
人工智能·opencv·算法
寂空_35 分钟前
【算法笔记】图论基础(一):建图、存图、树和图的遍历、拓扑排序、最小生成树
笔记·算法·图论
前端不能无1 小时前
从零实现React核心架构:虚拟DOM、Fiber与协调算法
算法·react.js
沐墨专攻技术1 小时前
函数递归(C语言版)
c语言·开发语言
Absolute clown maste1 小时前
第十六届蓝桥杯康复训练--6
数据结构·c++·算法·蓝桥杯·动态规划
Joey_friends1 小时前
【第14届蓝桥杯C/C++B组省赛】01串的熵
c语言·开发语言·算法·蓝桥杯
老马啸西风2 小时前
SOFABoot-05-依赖管理
算法·spring·微服务·云原生·中间件·springboot·sofa
烂蜻蜓2 小时前
深度剖析 C 语言存储类:掌控变量与函数的存储奥秘
java·c语言·算法
浅安的邂逅2 小时前
Linux shell脚本-概述、语法定义、自定义变量、环境变量、预设变量、变量的特殊用法(转义字符、单双引号、大小括号)的验证
linux·c语言·bash·shell脚本
秋凉 づᐇ2 小时前
数据结构——最短路径BFS算法
数据结构·算法·宽度优先