(数组p2以及对递归的补充) C语言从入门到入土(入门篇)(一)

简介: 1. 一维数组的创建和初始化 2. 一维数组的使用 3. 一维数组在内存中的存储 4. 二维数组的创建和初始化 5. 二维数组的使用 6. 二维数组在内存中的存储

//下面两个用例我们后面单独拿出来讲哈!

数组的应用实例1:三子棋

数组的应用实例2:扫雷游戏

这两个会专门写在后面


3. 数组越界

数组的下标是有范围限制的。

数组的下规定是从 0 开始的,如果数组有 n 个元素,最后一个元素的下标就是 n-1 。

所以数组的下标如果小于 0 ,或者大于 n-1 ,就是数组越界访问了,超出了数组合法空间的访问。

C 语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,

所以程序员写代码时,最好自己做越界的检查。

#include <stdio.h>
int main ()
{
int arr [ 10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };
    int i = 0 ;
    for ( i = 0 ; i <= 10 ; i ++ )
  {
        printf ( "%d\n" , arr [ i ]); // 当 i 等于 10 的时候,越界访问了
  }
return 0 ;
}

二维数组的行和列也可能存在越界。


42.png43.png

4. 数组作为函数参数


往往我们在写代码的时候,会将数组作为参数传个函数,比如:我要实现一个冒泡排序(这里要讲算法思想函数

将一个整形数组排序。

那我们将会这样使用该函数:


4.1 冒泡排序函数的错误设计


冒泡排序思路:

对于冒泡排序的大体思路,就是第一个元素去和后面所有的元素去比较大小比如 1 5 3 7 4  这几个数一开始1和5必(我们实现升序),然后再和3比然后往后比(到4)发现,都比1大,1就说明排序好了,然后5再和3比,发现比他大,就交换变成 1 3 5 7 4,然后再3和7比,再和4比,我们比的时候只在同一个位置比,然后后面谁小我们就把谁换过来,一直到最后一个数我们就发现假设有n个数,我们就要进行n-1趟  每一趟比较的次数就是 n-1 再 -i(因为前面的数已经排好了所以减去)

// 方法 1 :
#include <stdio.h>
void bubble_sort ( int arr [])
{
int sz = sizeof ( arr ) / sizeof ( arr [ 0 ]); // 这样对吗?
    int i = 0 ;
for ( i = 0 ; i < sz - 1 ; i ++ )
  {
        int j = 0 ;
        for ( j = 0 ; j < sz - i - 1 ; j ++ )
      {
            if ( arr [ j ] > arr [ j + 1 ])
          {
                int tmp = arr [ j ];
                arr [ j ] = arr [ j + 1 ];
                arr [ j + 1 ] = tmp ;
          }
      }
  }
}
int main ()
{
    int arr [] = { 3 , 1 , 7 , 5 , 8 , 9 , 0 , 2 , 4 , 6 };
    bubble_sort ( arr ); // 是否可以正常排序?
    for ( i = 0 ; i < sizeof ( arr ) / sizeof ( arr [ 0 ]); i ++ )
  {
        printf ( "%d " , arr [ i ]);
  }
    return 0 ;
}

方法 1 ,出问题,那我们找一下问题,调试之后可以看到 bubble_sort 函数内部的 sz ,是 1 。

难道数组作为函数参数的时候,不是把整个数组的传递过去?


44.png45.png

4.2 数组名是什么?


#include <stdio.h>
int main ()
{
    int arr [ 10 ] = { 1 , 2 , 3 , 4 , 5 };
printf ( "%p\n" , arr );
    printf ( "%p\n" , & arr [ 0 ]);
    printf ( "%d\n" , * arr );
    // 输出结果
    return 0 ;
}
结论:
数组名是数组首元素的地址。(有两个例外)
如果数组名是首元素地址,那么:
int arr [ 10 ] = { 0 };
printf ( "%d\n" , sizeof ( arr ));

为什么输出的结果是: 40 ?

补充:

1. sizeof( 数组名 ) ,计算整个数组的大小, sizeof 内部单独放一个数组名,数组名表示整个数组。

2. & 数组名,取出的是数组的地址。 & 数组名,数组名表示整个数组。

除此 1,2 两种情况之外,所有的数组名都表示数组首元素的地址。


4.3 冒泡排序函数的正确设计


当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。

所以即使在函数参数部分写成数组的形式: int arr[] 表示的依然是一个指针: int *arr 。

那么,函数内部的 sizeof(arr) 结果是 4 。

如果 方法 1 错了,该怎么设计?

// 方法 2
void bubble_sort ( int arr [], int sz ) // 参数接收数组元素个数
{
// 代码同上面函数
}
int main ()
{
    int arr [] = { 3 , 1 , 7 , 5 , 8 , 9 , 0 , 2 , 4 , 6 };
    int sz = sizeof ( arr ) / sizeof ( arr [ 0 ]);
    bubble_sort ( arr , sz ); // 是否可以正常排序?
    for ( i = 0 ; i < sz ; i ++ )
  {
        printf ( "%d " , arr [ i ]);
  }
    return 0 ;
}


对于递归的补充:

//这是作者自己在查的时候发现了一位我认为讲的很细的博主 帅地 写的一篇文章,但是可能不太适合小白看,因为我自己也看了,是讲了很多内容的,所以我就把递归内容拷贝过来了,后面有链接哈,他也出了很多递归的题,大家可以看一下哈!下面是拷贝过来的哈:


递归的三大要素

第一要素:明确你这个函数想要干什么

对于递归,我觉得很重要的一个事就是,这个函数的功能是什么,他要完成什么样的一件事,而这个,是完全由你自己来定义的。也就是说,我们先不管函数里面的代码什么,而是要先明白,你这个函数是要用来干什么。


例如,我定义了一个函数


// 算 n 的阶乘(假设n不为0)

int f(int n){

 

}

这个函数的功能是算 n 的阶乘。好了,我们已经定义了一个函数,并且定义了它的功能是什么,接下来我们看第二要素。


第二要素:寻找递归结束条件

所谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出递归的结束条件,不然的话,会一直调用自己,进入无底洞。也就是说,我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么。


例如,上面那个例子,当 n = 1 时,那你应该能够直接知道 f(n) 是啥吧?此时,f(1) = 1。完善我们函数内部的代码,把第二要素加进代码里面,如下

// 算 n 的阶乘(假设n不为0)
int f(int n){
    if(n == 1){
        return 1;
    }
}


有人可能会说,当 n = 2 时,那我们可以直接知道 f(n) 等于多少啊,那我可以把 n = 2 作为递归的结束条件吗?


当然可以,只要你觉得参数是什么时,你能够直接知道函数的结果,那么你就可以把这个参数作为结束的条件,所以下面这段代码也是可以的。


// 算 n 的阶乘(假设n>=2)
int f(int n){
    if(n == 2){
        return 2;
    }
}

注意我代码里面写的注释,假设 n >= 2,因为如果 n = 1时,会被漏掉,当 n <= 2时,f(n) = n,所以为了更加严谨,我们可以写成这样:


// 算 n 的阶乘(假设n不为0)
int f(int n){
    if(n <= 2){
        return n;
    }
}
相关文章
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
63 16
|
1月前
|
存储 编译器 C语言
【C语言程序设计——入门】C语言入门与基础语法(头歌实践教学平台习题)【合集】
本文档介绍了C语言环境配置和编程任务,主要内容包括: - **C语言环境配置**:详细讲解了在Windows系统上配置C语言开发环境的步骤。 - **第1关:程序改错**:包含任务描述、相关知识(如头文件引用、基本语法规则)、编程要求、测试说明及通关代码。 - **第2关:scanf函数**:涉及`scanf`和`printf`函数的格式与使用方法,提供编程要求、测试说明及通关代码。 文档结构清晰,涵盖从环境搭建到具体编程任务的完整流程,适合初学者学习和实践。
50 4
|
1月前
|
C语言
【C语言程序设计——入门】基本数据类型与表达式(头歌实践教学平台习题)【合集】
这份文档详细介绍了编程任务的多个关卡,涵盖C语言的基础知识和应用。主要内容包括: 1. **目录**:列出所有关卡,如`print函数操作`、`转义字符使用`、`数的向上取整`等。 2. **各关卡的任务描述**:明确每关的具体编程任务,例如使用`printf`函数输出特定字符串、实现向上取整功能等。 3. **相关知识**:提供完成任务所需的背景知识,如格式化输出、算术运算符、关系运算符等。 4. **编程要求**:给出具体的代码编写提示。 5. **测试说明**:包含预期输入输出,帮助验证程序正确性。 6. 文档通过逐步引导学习者掌握C语言的基本语法和常用函数,适合初学者练习编程技能。
45 1
|
2月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
124 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
2月前
|
传感器 算法 安全
【C语言】两个数组比较详解
比较两个数组在C语言中有多种实现方法,选择合适的方法取决于具体的应用场景和性能要求。从逐元素比较到使用`memcmp`函数,再到指针优化,每种方法都有其优点和适用范围。在嵌入式系统中,考虑性能和资源限制尤为重要。通过合理选择和优化,可以有效提高程序的运行效率和可靠性。
186 6
|
3月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
99 5
|
3月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
3月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
6月前
|
机器学习/深度学习 C语言
【C语言篇】递归详细介绍(基础概念习题及汉诺塔等进阶问题)
要保持最小的步数,每一次汉诺塔问题(无论是最初还是递归过程中的),如果此时初始柱盘子数为偶数,我们第一步是把最上面的盘子移动到中转柱,如果为奇数,我们第一步则是将其移动到目标柱。
145 0
【C语言篇】递归详细介绍(基础概念习题及汉诺塔等进阶问题)
|
9月前
|
C语言
C语言递归问题【青蛙跳台阶】和【汉诺塔】
C语言递归问题【青蛙跳台阶】和【汉诺塔】