《C语言高级》(二)------ 函数与指针 篇

news2024/9/23 7:33:25

目录

一、函数

1.1、创建和使用函数

1.2、全局变量与局部变量

1.3、函数的参数和返回

1.4、递归调用

1.5、斐波那契数列解法其三

1.6、汉诺塔

1.7、快速排序算法

二、指针

        2.1、初识指针

        2.2、指针与数组

        2.3、多级指针

        2.4、指针数组与数组指针

        2.5、指针函数与函数指针


一、函数

其实函数我们在一开始就在使用了:

int main() { //这是定义函数
    ...
}

我们程序的入口点是main函数,我们只需要将我们的程序代码编写到主函数中就可以运行了,不过这个函数只是由我们来定义,而不是我们自己来调用。当然,除了主函数之外,我们一直在使用的printf也是一个函数,不过这个函数是标准库中已经实现好了的,现在是我们在调用这个函数:

printf("hello world"); //直接通过 函数名称(参数...)的形式调用函数

那么,函数的具体定义是什么呢?

函数是完成特定任务的独立程序代码单元


1.1、创建和使用函数

首先我们来看看如何创建一个函数,其实创建一个函数是很简单的,格式如下:

返回值类型 函数名称([函数参数...]);

其中函数名称也是有要求的,并不是所有的字符都可以用作函数名称,它的命名规则与变量的命名规则基本一致,所有这里就不一一列出了。

函数不仅仅需要完成我们的任务,可能某些函数还需要告诉我们结果,比如我们之前学习的getchar函数,这个函数实际上返回了一个int值作为结果(也就是我们输入的字符)我们同样可以将函数返回的结果赋值给变量或是参与运算等等。

当然如果我们的函数只需要完成任务,不需要告诉我们结果,返回值类型可以写成void表示为空

#include <stdio.h>
void test(void) {
    printf("hi");
}

int main(){
 test();

}

1.2、全局变量与局部变量

现在我们已经了解了如何创建和调用函数,在继续学习后续内容之前,我们需要先认识一下全局变量和局部变量这两个概念

我们首先来看看局部变量,实际上我们之前使用的都是局部变量,比如

int main() {
    int i = 10;
}

局部变量只会在其作用域中生效:

int main() {
    int i = 10;
}

void test() {
    i += 10;
}

可以看到在其他函数中,无法使用main函数中的变量,因为局部变量的作用域是有限的,比如位于某个函数内部的变量,那么它的作用域就是整个函数内部,而在其他位置均无法访问。


1.3、函数的参数和返回

我们的函数可以接受参数来完成任务,比如我们现在想要实现用一个函数计算两个数的和并输出到控制台。这种情况我们就需要将我们需要进行加法计算的两个数,告诉函数,这样函数才能对这两个数求和,那么怎么才能告诉函数呢?我们可以通过设定参数。

#include <stdio.h>
void add(int x, int y) {
    int z;
    z = x + y;
    printf("%d", z);
}
int main() {
    add(5, 4);
}

如果我们修改形式参数的值,外面的实参值会跟着发生修改吗

# include <stdio.h>

void swap(int a, int b){
    int mid;
    mid = a;
    a = b;
    b = mid;
}

int main() {
    int a=10, b=20;
    swap(a, b);
    printf("a=%d,b=%d", a, b);

}

通过结果发现,虽然调用了函数对a和b的值进行交换,但缺并没有什么用

函数的形参实际上就是函数内的局部变量,它的作用域仅仅是这个函数,而我们外面传入的实参,仅仅只是将值赋值给了函数内的形参而已,并且外部的变量跟函数内部的变量作用域都不同,这里交换的仅仅是函数内部的两个形参变量值,跟外部作实参的变量没有任何关系。

 如何在每次调用函数时保留变量的值呢,我们可以使用静态变量:

# include <stdio.h>

void swap(){
    static int count = 0;
    count++;
    printf("Appied:%d", count);

}

int main() {
    swap();
    swap();

}

我们接着来看函数的返回值,并不是所有的函数都是执行完毕就结束了的,可能某些时候我们需要函数告诉我们执行的结果如何,这时我们就需要用到返回值了,比如现在我们希望实现一个函数计算a+b的值:

#include <stdio.h>
int add(int x, int y) {
    int z;
    z = x + y;
    return z;

}
int main() {
    int z = add(5, 4);
    printf("%d", z);
}

1.4、递归调用

我们的函数除了在其他地方被调用之外,也可以自己调用自己(递归)

#include <stdio.h>
void fun() {
    printf("hi");
    fun();
}
int main() {
    fun();
}

我们可以尝试运行一下上面的程序,会发现程序直接无限打印hi,这是因为函数在自己调用自己,不断地重复进入到这个函数,理论情况下,它将永远都不会结束,而是无限地执行这个函数的内容。

比如现在要求求某个数的阶乘

#include <stdio.h>
int fun(int n) {
    if(n == 1) return 1;
    return fun(n-1) * n;
}
int main() {
    printf("%d", fun(5));
}

1.5、斐波那契数列解法其三

前面我们介绍了函数递归调用,现在来看一个具体的实例,还是以斐波那契数列为例。

既然每个数都是前两个数之和,那么我们是否也可以通过递归的形式不断划分进行计算呢,我们依然可以借鉴之前动态规划的思想,通过划分子问题,分而治之来完成计算。

#include <stdio.h>
int F(int n) {
    if(n == 1 || n == 2) return 1;
    return F(n-1) + F(n-2);
}
int main() {
    printf("%d", F(7));
}

1.6、汉诺塔

什么是汉诺塔?

汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始

按照大小顺序重新放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

这三根柱子我们依次命名为A、B、C,现在请你设计一个C语言程序,计算N阶(n片圆盘)汉诺塔移动操作的每一步。

我们可以对每一步进行推理

当汉诺塔只有1阶的情况下:直接把A上的圆盘移动到C

当汉诺塔只有2阶的情况下:我们的最终目标还是需要将A柱最下面的圆盘丢到C,不过现在多了圆盘,我们得先把这个圆盘给处理了,所以我们得把这上面的1个圆盘丢到B上去,这样才能把A最下面的圆盘丢给C。然后再把B上面的1个圆盘丢到C上去

当汉诺塔只有3阶的情况下:我们的最终目标还是需要将A柱最下面的圆盘丢到C,不过现在多了圆盘,我们得先把这个圆盘给处理了,所以我们得把这上面的2个圆盘丢到B上去,这样才能把A最下面的圆盘丢给C。然后再把B上面的2个圆盘丢到C上

#include <stdio.h>

void move(char start, char end, int n){
    printf("第%d个圆盘:%c --> %c\n", n, start, end);
}

void hanoi(char a, char b, char c, int n){
    if (n==1){
        move(a, c, n);
    } else {
        hanoi(a,c,b,n-1);
        move(a,c,n);
        hanoi(b, a, c, n-1);
    }
}

int main(){
    hanoi('A', 'B', 'C', 10);
}

1.7、快速排序算法

有一个数组:

int arr[] = {4, 3, 8, 2, 1, 7, 5, 6, 9, 0};

        现在请你设计一个C语言程序,对数组按照从小到大的顺序进行排序。这里我们使用冒泡排序的进阶版本--“快速排序”来完成,它的核心思想是分而治之,每一轮排序都会选出一个基准,一轮排序完成后,所以比基准小的数一定在左边,比基准大的数一定在右边,在分别通过同样的方法对左右两边的数组进行排序,不断划分,最后完成整个数组的排序。它的效率比冒泡排序的双重for循环有所提升。

#include <stdio.h>
//arr是数组,left是起始下标,right是结束下标
void quickSort(int arr[], int left, int right){
    if(left >= right) return;

    int base = arr[left], l = left, r = right;
    while(l < r) {
        while (l < r && arr[r] >= base) r--;
        arr[l] = arr[r];
        while (l < r && arr[l] <= base) l++;
        arr[r] = arr[l];
    }
    arr[r] = base;
    quickSort(arr, left, r-1);
    quickSort(arr, r+1, right);
}

int main() {
    int arr[] = {4, 3, 8, 2, 1, 7, 5, 6, 9, 0};
    quickSort(arr, 0, 9);
    for (int i = 0; i < 10; ++i) {
        printf("%d", arr[i]);
    }
}

二、指针

        2.1、初识指针

        我们知道,程序中使用的变量实际上都在内存中创建的,每个变量都会被保存在内存的某一个位置上(具体在哪个位置是由系统分配的),就像我们最终会在这个世界上的某个角落安家一样,所有的变量在对应的内存位置上都有一个地址(地址是独一无二的),而我们可以通过这个地址寻找到这个变量本体,比如int占据4字节,因此int类型变量的地址就是这4个字节的起始地址,后面32个bit位全部是用于存放此变量的值的。

我们来看看如何创建一个指针变量用于保存变量的内存地址

#include <stdio.h>

int main(){
    int a = 10;
    //指针类型需要与变量的类型相同,且后面需要添加一个*符号(注意这里不是乘法运算)表示是对于类型的指针
    int *p = &a;
    printf("a在内存中的地址为:%p", p);  //地址用%p表示
}

 拿到指针之后,我们可以很轻松的获取指针所指地址上的值:

#include <stdio.h>

int main(){
    int a = 10;
    //指针类型需要与变量的类型相同,且后面需要添加一个*符号(注意这里不是乘法运算)表示是对于类型的指针
    int *p = &a;
    printf("a在内存中的地址为:%p,值为:%d", p, *p);  //地址用%p表示
}

 注意这里访问指针所指向地址的值时,是根据类型来获取的,比如int类型占据4个字节,那么就读取地址后面4个字节的内容作为一个int值,如果指针是char类型的,那么就只读取地址后面1个字节作为char类型的值。


实际上拿到一个变量的地址后,我们完全不需要再使用这个变量,而是可以通过它的指针来对其进行各种修改。因此,现在我们想要实现两个变量的值进行交换的函数就很简单了

#include <stdio.h>

void swap(int *a, int *b){
    int mid = *a;
    *a = *b;
    *b = mid;

}
int main(){
    int a = 10, b = 20;
    swap(&a, &b);
    printf("a=%d, b=%d", a,b);
    
}

 当然,和变量一样,要是不给指针变量赋初始值的话,就不知道指的哪里了,因为指针变量也是变量,存放的其他变量的地址值也在内存中保存,如果不给初始值,那么存放别人地址的这块内存可能在其他地方使用过,这样就不知道初始值是多少了,所以一定要给个初始值或者将其设定为NULL,表示空指针,不指向任何内容。

下面我们来看看const标记指针

#include <stdio.h>

int main() {
    int a = 9, b = 10;
    const int * p = &a;
    *p = 20;  //这里直接报错,因为被const标记的指针,所指地址上的值不允许发生修改
    p = &b;  //但是指针指向的地址是可以发生改变的    

}

我们再来看另一种情况

#include <stdio.h>

int main() {
    int a = 9, b = 10;
     int * const p = &a; //const关键字被放在了类型后面
    *p = 20;  //允许修改地址上的值
    p = &b;  //但是不允许修改指针存储的地址值,其实就是反过来了。  

}

  


    2.2、指针与数组

前面我们介绍了指针的基本使用,我们来回顾一个问题,为什么数组可以以原身在函数之间进行传递呢?先说结论,数组表示法实际上是在变相地使用指针,你甚至可以将其理解为数组变量其实就是一个指针变量,它存放的就是数组中第一个元素的起始地址。

#include <stdio.h>
int main(){
    char str[] = "hello world";
    char *p = str;
    printf("%c", *p[0]);

}

        数组在内存中是一块连续的空间,所以为什么声明数组一定要明确类型和大小,因为这一块连续的内存空间生成后就固定了。

        而我们的数组变量实际上存放的就是首元素的地址,而实际上我们之前一直使用的都是数组表示法来操作数组,这样可以很方便地让我们对内存中的各个元素值进行操作

#include <stdio.h>
int main(){
    char str[] = "hello world";
    printf("%c", str[0]);

}

而我们知道实际上str表示的就是数组的首地址,所以我们完全可以将其赋值给一个指针变量,因为指针变量也是存放的地址:

char str[] = "hello world";
char *p = str;

 而使用指针后,实际上我们可以使用另一种表示法来操作数组,这种表示法叫做指针表示法

#include <stdio.h>
int main(){
    char str[] = "hello world";
    char *p = str;
    printf("第一个元素为:%c,第二个元素为:%c", *p, *(p+1));

}

比如我们现在需要表示数组中的第二个元素:

· 数组表示法:str[1]

· 指针表示法:*(p+1)

虽然写法不同,但是他们表示的意义是完全相同的,都代表了数组中的第二个元素,其中指针表示法使用了 p+1 的形式表示第二个元素,这里的 +1 操作并不是让地址 +1,而是让地址 + 一倍的对应的类型大小,也就是说地址后移一个char的长度,所以正好指向了第二个元素,然后通过 * 取到对应的值

 *p   //数组的第一个元素

p  //数组的一个元素的地址

p == str  //为真,都是数组首元素地址

*str  //因为str就是首元素的地址,所以这里对地址加*就代表第一个元素,使用的是指针表示法

&str[0]  //这里得到的实际上还是首元素的地址

*(p+1)  //代表第二个元素

p + 1  //第二个元素的内存地址

*p + 1  //注意*的优先级比+要高,所以这里代表的是首元素的值 + 1, 得到字符'K'

指针也可以进行自增和自减操作,比如:

#include <stdio.h>
int main(){
    char str[] = "hello world";
    char *p = str;
    p++;  //自增后相当于指针指向了第二个元素的地址
    printf("%c", *p);  //这里打印的就是第二个元素的值

}

   


    2.3、多级指针

        我们知道,实际上指针本身也是一个变量,它存放的是目标的地址,但是它本身作为一个变量,它也要将地址信息保存到内存中。我们还可以继续创建一个指向指针变量地址的指针,甚至可以创建更多级(比如指向指针的指针的指针)

int main(){
    int a = 20;
    int * p = &a; //指向普通变量的指针
    int ** pp = &p; //二级指针
    int *** ppp = &pp; // 三级指针
}

 


      2.4、指针数组与数组指针

前面我们了解了指针的一些基本操作,包括它与数组的一些关系。我们接着来看指针数组和数组指针,这两词语看着就容易搞混,不过哪个词在后面就是哪个,我们先来看指针数组,虽然名字很像数组指针,但是它本质上是一个数组,不过这个数组是用于存放指针的数组

int main(){
    int a, b, c;
    int * arr[3] = {&a, &b, &c};
}

 因为这个数组中全都是指针,比如现在我们想要访问数组中第一个指针指向的地址:

#include <stdio.h>
int main(){
    int a, b, c;
    int * arr[3] = {&a, &b, &c};

    *arr[0] = 999;  //[]运算符优先级更高,所以这里先通过[0]取出地址,然后再使用*将值赋到对应的地址上
    printf("%d", a);
}

数组指针,可以看到指针在后,说明本质是一个指针,不过这个指针比较特殊,它是一个指向数组的指针(注意它的目标是整个数组,和我们之前认识的指针不同,之前认识的指针是指向某种类型变量的指针)

int * p;   //指向int类型的指针
int (*p)[3];   //数组指针,这里需要将*p括起来,因为[]的优先级更高
int arr[3] = {111, 222, 333};
int (*p)[3] = &arr;   //直接对整个数组再取一次地址
#include <stdio.h>
int main(){
    int arr[3] = {111, 222, 333};
    int (*p)[3] = &arr;   //直接对整个数组再取一次地址

    printf("%d, %d, %d", *(*p + 0), *(*p + 1), *(*p + 2));
}

· p代表整个数组的地址

· *p表示所指向数组中首元素的地址

· *p+i 表示所指向数组中第 i 个(0开始)元素的地址(实际上这里的 *p 就是指向首元素的指针)

· *(*p + i)就是取对应地址上的值

      


  2.5、指针函数与函数指针

我们的函数可以返回一个指针类型的结果,这种函数我们就称为指针函数

#include <stdio.h>
//函数返回值类型是int *指针类型的
int * test(int * a){
    return a;
}

int main(){
    int a = 10;
    int * p = test(&a);  //使用指针去接受函数的返回值
    printf("%d", *p);
    printf("%d", *test(&a));  //当然也可以直接把间接运算符在函数调用前面表示直接对返回的地址取地址上的值
}

函数指针,实际上指针除了指向一个变量之外,也可以指向一个函数,当然函数指针本身还是一个指针,所以依然是用变量表示,但是它代表的是一个函数的地址(编译时系统会为函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址)

#include <stdio.h>
//函数返回值类型是int *指针类型的
int sum(int a, int b){
    return a + b;
}

int main(){
    // 类型(*指针变量名称)(函数参数....) 注意一定要把*和指针变量名称括起来,不然优先级不够
    int (*p)(int, int) = sum;
    int result = p(1, 2);
    printf("%d", result);
}

有了函数指针,我们就可以编写函数回调了(所谓回调就是让别人去调用我们提供的函数,而不是我们主动来调别人的函数),比如现在我们定义了一个函数,不过这个函数需要参数通过一个处理的逻辑才能正常运行:

#include <stdio.h>
int sum(int (*p)(int, int), int a, int b){
    return p(a, b);
}
//函数返回值类型是int *指针类型的
int sumImpl(int a, int b){
    return a + b;
}

int main(){
    // 类型(*指针变量名称)(函数参数....) 注意一定要把*和指针变量名称括起来,不然优先级不够
    int (*p)(int, int) = sumImpl;
    printf("%d", sum(p, 10, 20));
}

     

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/181783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud入门实战(五)-集成Ribbon

一、Ribbon简介 Spring Cloud Ribbon是Netflix发布的开源项目&#xff0c;主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时&#xff0c;重试等。简单地说&#xff0c;就是在配置文件中列出Load Balancer(简称LB)后面所有…

python爬虫学习笔记-mongodb安装基本介绍pymongo使用

MongoDB数据存储 MongoDB是一个非关系型数据库(NoSQL). 非常适合超大数据集的存储, 由 C 语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系…

Spring Cloud_OpenFeign服务接口调用

目录一、概述1.OpenFeign是什么2.能干嘛二、OpenFeign使用步骤1.接口注解2.新建Module3.POM4.YML5.主启动类6.业务类7.测试8.小总结三、OpenFeign超时控制1.超时设置&#xff0c;故意设置超时演示出错情况2.是什么3.YML中需要开启OpenFeign客户端超时控制四、OpenFeign日志打印…

论文投稿指南——中文核心期刊推荐(水路运输)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

Json的语法及使用

Json的语法及使用前言一、Json是什么&#xff1f;二、Json语法三、Json示例前言 在数据传输时用到Json格式&#xff0c;在此稍作记录。 一、Json是什么&#xff1f; JSON 指的是 JavaScript 对象表示法&#xff08;JavaScript Object Notation&#xff09;JSON 是轻量级的文本…

零入门容器云网络实战-4->ip地址分类尝试

本篇文章主要用于收集、整理、总结关于IP地址相关知识点。 1、IP地址分类&#xff1f; IPv4地址是指IPv4协议使用的32位地址。 为了便于对IP地址进行管理&#xff0c; 根据IPv4地址的第一个字节&#xff0c;IPv4地址可以分为以下五类。 A类&#xff1a;0~127 B类&#xff1…

【Linux】Linux自动化构建工具make/makefile

文章目录&#x1f3aa; Linux自动化构建工具make/makefile&#x1f680; 1.Makefile文件格式⭐1.1 简单makefile例子⭐1.2 概述⭐1.3 目标(target)⭐1.4 前置条件(prerequisites)⭐1.5 命令(commands)&#x1f680; 2.Makefile文件语法⭐2.1 注释⭐2.2 回声⭐2.3 通配符⭐2.4 变…

4-异步:非阻塞IO

4-异步&#xff1a;非阻塞I_O 简介 什么是Node.js的非阻塞I/O I/O 即 Input/Output. 也就是一个系统的输入和输出阻塞I/O和非阻塞I/O的却别就在于系统接收输入再到输出期间&#xff0c;能不能接收其他输入 对于这两句话的理解&#xff0c;可以把系统比作服务员&#xff0c;…

React 实现自动上报 PV/Click 的埋点 Hooks

自定义 hooks 是基于 React Hooks 的一个拓展&#xff0c;我们可以根据业务需求制定满足业务需要的组合 hooks&#xff0c;更注重的是逻辑单元。怎样把一段逻辑封装起来&#xff0c;做到复用&#xff0c;这才是自定义 hooks 的初衷。 自定义 hooks 也可以说是 React Hooks 的聚…

【并发编程十三】c++原子操作

【并发编程十三】c原子操作一、改动序列1、改动序列2、预测执行二、原子操作及其类别1、原子操作2、非原子操作3、原子类型三、标准原子类型1、标准原子类型的两种实现方式2、原子操作的用途3、原子操作的宏四、操作std:atomic_flag1、简介2、使用说明3、使用std:atomic_flag实…

Kettle(16):Kitchen作业执行引擎

在Linux中对Kettle做Linux配置(和Windows相同,添加驱动jar包) 上传以后需要重启。 1 在Windows中开发作业 2 配置Start组件 3 配置转换组件 修改Kettle&

【HBase——陌陌海量存储案例】1.案例介绍与HBase表结构设计(上)

前言 本系列接【HBase入门】系列文章后实战案例的学习。 学习目标 能够掌握HBase表结构设计&#xff08;表设计、ROWKEY设计、预分区&#xff09; 能够安装部署Apache Phoenix 能够掌握Phoenix的基本操作 能够掌握使用Phoenix建立二级索引提升性能 能够基于Phoenix JDBC API编…

Ubuntu安装ROS(每个步骤图文详细)

Ubuntu安装ROS&#xff08;每个步骤图文详细&#xff09;前言&#xff08;推荐安装&#xff09;ROS对应的Ubuntu的版本换源安装ROS一、添加ROS软件源二、添加密钥三、安装ROS-melodic四、初始化rosdep可能出现的问题&#xff1a;一 、 **sudo: rosdep&#xff1a;找不到命令**二…

算法_位运算x(-x)和x(x-1)

最近在跟着y总学算法。 今天学到了两个很经典的位运算&#xff0c;x&(-x)和x&(x-1)&#xff1a; x&(-x)&#xff1a;保留二进制下最后出现的1的位置&#xff0c;其余位置置0&#xff08;即一个数中最大的2的n次幂的因数 x&(x-1)&#xff1a;消除二进制下最后…

SpringCloud整合Zookeeper代替Eureka

目录 一、注册中心Zookeeper 二、服务提供者 三、服务消费者 一、注册中心Zookeeper zookeeper是一个分布式协调工具&#xff0c;可以实现注册中心功能 关闭Linux服务器防火墙后启动zookeeper服务器 zookeeper服务器取代Eureka服务器&#xff0c;zk作为服务注册中心 Lin…

ORA-600 kcbzpbuf_1故障恢复----惜分飞

数据库启动报错ORA-03113SQL> startup;ORACLE instance started. Total System GlobalArea 5.1310E10 bytesFixed Size 2265224 bytesVariable Size 1.8119E10 bytesDatabaseBuffers 3.3152E10 bytesRedo Buffers 36069376 bytesDatabasemounted. ORA-03113: end-of-file on…

二叉树的层序遍历

二叉树的层序遍历 层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。 需要借用一个辅助数据结构即队列来实现&#xff0c;队列先进先出&#xff0c;符合一层一层遍历的逻辑&#xff0c;而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。 而这种层序遍历方式就是…

Java:基于XML的Spring使用

基于XML的Spring使用一、Spring IOC 底层实现1.1 BeanFactory与ApplicationContexet1.2 图解IOC类的结构二、 Spring依赖注入数值问题【重点】2.1 字面量数值2.2 CDATA区2.3 外部已声明bean及级联属性赋值2.4 内部bean2.5 集合三、 Spring依赖注入方式【基于XML】3.1 set注入3.…

白炽灯护眼还是LED护眼?盘点专业护眼的LED护眼灯

目前大多数家庭都会购买台灯使用&#xff0c;选择白炽灯还是LED灯呢&#xff1f;建议是LED灯更护眼。白炽灯缺点&#xff1a;耗电、发光效率低、温度过高不安全。白炽灯优点&#xff1a;体积小、显色能力好。LED灯缺点:价格较高、显色能力比白炽灯弱一些。LED灯优点&#xff1a…

JDBC(powernode CD2206)详尽版(内含教学视频、源代码、SQL文件)

JDBC&#xff08;powernode CD2206&#xff09;详尽版&#xff08;内含教学视频、源代码、SQL文件&#xff09; 包含&#xff1a;教学视频、源代码&#xff08;与博客同步&#xff09;、SQL文件 下载链接地址&#xff1a; https://download.csdn.net/download/weixin_4641135…