《C++入门篇》——弥补C不足

文章目录

    • 前言
      • 一.命名空间
      • 二.缺省参数
      • 三.函数重载
      • 四.引用
        • 4.1引用做参数
        • 4.2引用做返回值
      • 五.内联函数
      • 六.小语法
        • 6.1auto
        • 6.2范围for
        • 6.3空指针

前言

C++是业内一门久负盛名的计算机语言,从C语言发展起来的它,不仅支持C语言的语法,还新添加了面向对象、泛型等特性,以及祖师爷本贾尼博士补充C语言的不足。

这一篇我们先来讲讲C++对C语言不足部分的补充。

一.命名空间

在这里插入图片描述

Cpp能很好的支持C,所以在Cpp文件中写C语言程序是没问题的。

从这段代码里,我们好像看不出什么问题,唯一觉得奇怪的是,为什么整型变量要取名为rand这么奇怪。我们接着往下看:

在这里插入图片描述

将变量rand放到全局中,报错信息给出rand重定义,我们想起C语言库里定义有一个叫rand函数与我们定义的rand变量命名冲突了,于是报出了这个语法错误。

那么上面为什么放在局部中没有报错呢?这是因为局部和全局都有时,局部优先!

到这里读者可能会说,在日常写代码中,自己写的又不一定会和自己命名冲突,那么在一个大工程里,数十几个程序员的代码合并到一起,会不会冲突?

在这里插入图片描述

使用命名空间,将其隔离起来,这样就不冲突了,打印rand时,找的是库里的rand函数。

在这里插入图片描述

使用域作用限定符::,可以让编译器在找rand时,先找域作用限定符指定的域里先去找。

#include <stdio.h>
namespace name
{
    //可以嵌套使用,如果一个命名空间内也有命名冲突,可以再隔离。
    namespace name1
    {
    	int a = 0;
    }
    namespace name2
    {
        int a = 1;
    }
    
    int Add(int x, int y)
    {
        return x+y;
    }
    
    struct Node
    {
        int val;
        struct Node* next;
    };
}

int main()
{
    printf("%d\n", name::name1::a);//到name里找name1,name1里找a
    printf("%p\n", name::Add);
    struct name::Node node = {0};//::要加在Node前面
    return 0;
}

关于namespace关键字,基本的用法和作用讲完了,还有一个较为重要分文件写声明和定义,接着往下看:

//Stack.h文件
#include <assert.h>
namespace sjr
{
    typedef struct Stack
	{
    	int* a;
    	int top;
    	int capacity;
	}Stack;

	void StackInit(Stack* ps);
	void StackPush(Stack* ps, int x);
}


//Stack.cpp文件
#include "Stack.h"
namespace sjr
{
    void StackInit(Stack* ps)
    {
    	assert(ps);
        ps->a = NULL;
        ps->top = 0;
        ps->capacity = 0;
    }
    void StackPush(Stack* ps, int x)
    {
        //...
    }
}

将两个不同文件的声明和定义使用同名的命名空间包起来,就可以实现声明和定义分离的同时都在命名空间内。

编译器会将不同文件的同名命名空间合并在一起,一个文件里有同名的命名空间也会合并,只是我们一般不会这么写。

总结:命名空间是用来弥补C语言命名冲突的不足。有如何创建命名空间、命名空间里的变量、函数、类型都可以正常创建、可以嵌套、声明和定义分离使用同一个命名空间,编译器会合并它们。

域作用限定符是一种指定命名空间里找的方法,以上面栈为例子,我们试着创建栈并插入几个数据:

#include "Stack.h"

/*int main
{
	name::Stack st;
	name::StackInit(&st);
	name::StackPush(&st, 1);
	name::StackPush(&st, 2);
	name::StackPush(&st, 3);
	name::StackPush(&st, 4);
	//每次使用都需要指定,日常练习这样完全没必要
}*/

//展开命名空间
using namespace name;
int main
{
	Stack st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
}

展开命名空间就可以直接使用里面的变量、函数等,但是这和头文件的展开不同,它只是将隔离拆除了,编译器会到展开的命名空间里面去找。如果展开命名空间里的定义与全局的有命名冲突,这样还是会报错!

在项目中全展开不是很好的做法,还有一种指定展开的方法:

//第一个C++代码:cout是输出,使用流插入<<,自动识别类型;endl是换行 end line;
#include <iostream>

using namespace std;//展开std(std是C++官方库的命名空间)
int main()
{	//cout 和 endl都在命名空间里
	cout << "hello world"<< endl;
	return 0;
}
#include <iostream>
//为了cout和endl展开整个库太坑了
using std::cout;
using std::endl;
int main()
{
    int a = 0;
	cout << "hello world"<< endl;
    
    //cin也在std里,没有指定展开,需要加std::
    std::cin >> a;//cin是输入,使用流提取>>,自动识别类型。 
	return 0;
}

总结:编译器默认不会到命名空间里找,这是解决命名冲突的基础。使用::可以单次到指定空间里找、还有using namespace std;展开命名空间,此时里面定义的所有东西都将暴露出来、而using std::cout;则只暴露std里面的cout。

二.缺省参数

在这里插入图片描述

在形参的后面加上一个值,就是缺省参数。Func函数里的a就是缺省参数,在调用Func函数时,如果没有传参,缺省值10将会默认赋值给a打印,否则按实际传递的值打印。

接下来我们看缺省参数更多的知识:

在这里插入图片描述

在这里插入图片描述

以上是全缺省的细节,也就是缺省值要从左往右给

在这里插入图片描述

半缺省遵循缺省参数从右往左给。

这是因为,如果不遵循这个规则,对于传参有很大的歧义。比如Fun2(10);,此时这个10是传给a还是传给b是不确定的;不支持跳跃传参Fun2(,10);,总之对于半缺省,遵循以上规则。

缺省有什么用?请看以下代码:

//Stack.h文件
#include <assert.h>
namespace sjr
{
    typedef struct Stack
	{
    	int* a;
    	int top;
    	int capacity;
	}Stack;
	//声明和定义分离,缺省写在声明里!
	void StackInit(Stack* ps, int n = 4);
	void StackPush(Stack* ps, int x);
}


//Stack.cpp文件
#include "Stack.h"
namespace sjr
{
    void StackInit(Stack* ps, int n)//不写缺省,但要写对应类型的形参
    {
    	assert(ps);
        ps->a = (int*)malloc(sizeof(int)*n);
        ps->top = 0;
        ps->capacity = 0;
    }
    void StackPush(Stack* ps, int x)
    {
        //...
    }
}

//Test.cpp文件
#include "Stack.h"
using namespace sjr;
int main()
{
    //不知道要多少空间
    Stack st1;
    StackInit(&st1);//默认开辟4个整型空间
    
    //知道要多少空间
    Stack st2;
    StackInit(&st2, 100);//此时我们显示传多少,就开多大
    //使逻辑清晰且知道需要多大空间时减少扩容消耗。
    return 0;
}

函数声明、定义分离,缺省参数只写在声明里,不能声明定义同时写缺省,这是为了避免声明和定义缺省值不一。

比如在声明里的缺省值是10,在定义里的缺省值是20这种情况。

为什么是在声明里给缺省参数,而不是定义里给:这是因为有定义的地方一定包括声明,有声明的地方不一定有定义。

比如Test.cpp包含头文件Stack.h,如果声明里没有缺省参数,那么到StackInit这个函数的时候,编译器并不知道有没有缺省参数存在。

总结一句话:函数声明定义分离,缺省参数写在声明里。

三.函数重载

函数重载指的是函数名相同、参数类型不同、个数不同、顺序不同的函数。

在这里插入图片描述

同为Add函数名的有两个,从C语言的角度看,main函数里调用的都是同一个函数,这是因为C语言不支持函数重载(仅根据函数名区分函数)。

C++区分函数不仅仅看函数名,还看函数的参数类型、个数、顺序等。C++中以上两个Add函数构成重载,main函数里两个int实参调用int加法,两个double实参调用double加法。

但是如果我们调用Add(1,2.2);时,隐式转换将产生调用歧义(即使构成函数重载,也要注意细节)。接下来我们看缺省参数属于什么类型:

在这里插入图片描述

Func(int a = 0)这个函数参数虽然是缺省参数,但是它的类型依旧是int,也就是所它们两个函数不能构成重载,并且传参调用有二义性。

函数构成重载的条件,类型和个数不同都好理解,顺序不同是什么意思呢?

在这里插入图片描述

​ 总结:函数重载是C++支持的一种不同于C语言的特性,不同函数的函数名可以相同,但参数的类型、个数、顺序需要有不同的。此外函数返回值不参与构成函数重载的评判(调用时用不到),因为调用时无法根据参数列表确定调用哪个重载函数。

四.引用

引用是给已经存在的变量取一个别名,也就是说,一块空间可以用多个名称表示。

在这里插入图片描述

c是a的别名,可以继续给a起别名,也可以给a的别名(c)起别名,都指向同一块空间(a)。并且引用必须初始化不能更改指向。以上是引用的基本使用方法。

那么引用的作用是什么,如果仅使用这个功能是没有意思的,接下来我们看引用的应用场景:

4.1引用做参数
#include <iostream>

void Swap(int* left, int* right)
{
    int tmp = *left;
    *left = *right;
    *right = tmp;
}
//left是a的别名,right是b的别名,在函数里的改变会影响外面的a,b
void Swap(int& left, int& right)//int& left 也是int类型
{
    int tmp = left;
    left = right;
    right = left;
}
int main()
{
    int a = 10;
    int b = 20;
    //函数重载
    Swap(&a, &b);
    Swap(a, b);
    return 0;
}

使用引用做参数,形式看起来比较容易,而且熟悉之后,理解起来也很容易。

我们再讲讲之前单链表需要传递二级指针的理解问题,对比使用引用和指针的区别更进一步体会引用做参数带来形式上的简便。

typedef struct SListNode
{
	int val;
	struct SListNode* next;
}SLNode,*PSLNode

//简写
void SListPushBack(SLNode** pphead, int x)
{
	if(*pphead == NULL)
	{
		*pphead = newnode;//要把plist从空指针改成指向第一个结点
	}
	else
	{
		//找尾
		tail->next = newnode;// 
	}
}

int main()
{
	SLNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
}

在尾插时需要判断plist是否为空,为空则要改变SLNode*类型的变量,假如形参部分写着SLNode* phead,那就只是plist的一份拷贝,phead的改变不会影响plist,所以要传二级指针。

那么学了引用,怎么用引用传参呢?请看下面代码:

typedef struct SListNode
{
	int val;
	struct SListNode* next;
}SLNode,*PSLNode


void SListPushBack(SLNode*& phead, int x)
{
	if(phead == NULL)
	{
		phead = newnode;//phead是SLNode*类型 是plist的别名,改变phead就会改变plist
	}
	else
	{
		//找尾
		tail->next = newnode;// 
	}
}

int main()
{
	SLNode* plist = NULL;
	SListPushBack(plist, 1);
	SListPushBack(plist, 2);
	SListPushBack(plist, 3);
	SListPushBack(plist, 4);
}

甚至使用上结点指针类型重名命的PSLNode创建变量,让不是很懂引用的初学者糊涂。

这以上是引用做参数的应用场景,使用指针也可以做到,引用和指针在做参数的时候都可以提高效率,只是指针用起来形式更复杂一点。

接下来说引用做返回值的应用场景:

4.2引用做返回值
int Count()
{
    int n = 0;
    n++;
    
    return n; 
}
int main()
{
	int ret = Count();
	return 0;
}

不知道读者有没有思考过,局部变量在出了作用域后生命周期结束。对于上面Count函数里的局部变量n,return n;的时候,Count函数调用结束。

如果返回n的话,那就相当于是访问一个被释放了的空间。其实了解过函数与栈帧的读者知道,n在结束生命周期前拷贝给一个寄存器(由于n变量较小),这个寄存器代替n作为Count的返回值赋值给ret。

也就是说传值返回会进行拷贝,和传值传参一个道理(传值传参会生成一个临时拷贝)。

上述代码使用传值返回是对的,如果使用传引用返回会是怎么样的呢?

int& Count()
{
    int n = 0;
    n++;
    
    return n; //传引用返回,传n的别名返回
}
int main()
{
	int ret = Count();//n的别名赋值给ret,相当于把n赋值给ret,因为n的别名指的也是n
	return 0;
}

前面讲过,在return n;的时候,n就被释放掉了。于是返回n赋值给ret的应该是随机值

如果操作系统还没清理n变量这块空间,那么仍有可能保留着1,否则会被刷成随机值,并且根据不同编译器可能还有所不同。

在这里插入图片描述

由于这种随机性,编译器会报出警告。因此返回会销毁的变量时,不采取传引用返回。(注意:会销毁的变量使用引用返回是错误的程序)

接着再进一步看使用引用接收引用返回(会销毁的变量)会如何。

在这里插入图片描述

ret是n的别名,相当于ret指向n那块释放了的空间。那么第一次打印取决于n的空间有没有被清理、取决于是什么编译器,因此是随机值。第二次打印的时候,我们看到那块区域已经被清理了。

在这里插入图片描述

这里执行了Add(3, 4);后,ret打印出来的值就变成了7。这是由于栈空间复用的原因,同一个函数或结构相似的函数连续调用,上一次的栈帧销毁后,立刻为下一次相同函数调用做准备,局部变量的地址不变。

因此ret指向的z的那块空间被第二次的Add函数调用改成7,但是打印出来的也是随机值,具体取决编译器和操作系统,VS2019打印的是7。

以上都是使用引用返回不恰当场景导致的结果,真正使用引用返回的场景是返回不会销毁的。比如malloc在堆上的对象、静态变量等等。

那么引用做返回值的价值是什么:提高效率和可以修改返回值。指针也可以做到,但形式复杂。当然引用还有指针更适合使用的场景,入门篇先不讲。

int main()
{
	//权限平移
	const int a = 10;
	const int& b = a;
	//权限缩小
	int c = 20;
	const int& d = c;
	//引用可以是常量
	const int& e = 10;

	//权限放大
	const int f = 5;
	int& g = f;//error

	c = f//c是int类型,f是const int会不会有问题?
	return 0;
}

引用和指针一样存在权限缩放的问题,权限可以平移、可以缩小,不能放大。

f赋值给c是没问题的,它是值拷贝(不存在权限问题),c和f不属于同一块空间,改变c不影响f。

在这里插入图片描述

引用和指针的区别:引用是别名,不开空间,指针存储变量的地址;引用必须初始化,并且不能更改引用对象,指针可以不初始化,也可以更改指向;引用没有空引用,指针有空指针;

五.内联函数

内联函数的关键字是inline,这是用来替代宏函数的。使用内联函数可以使代码量少的函数在调用处展开,避免栈帧创建和销毁的损耗。

宏函数的缺点是:写法复杂(括号较多);宏在预处理阶段就进行替换了,不能调试;没有类型检查;

而使用内联函数避免了宏的缺点,写法就是正常写函数一样,只需在函数返回类型前加inline就变为内联函数,可以进行调试,也有类型检查。

在这里插入图片描述

这里即使Add函数很短,调试进入反汇编还是选择调用,而不是像宏一样展开的原因是,Debug版本下默认内联函数是不会展开的,要把属性修改一下:

在这里插入图片描述

在这里插入图片描述

设置完成后,我们再调试起来看看效果:

在这里插入图片描述

虽说是展开,但不是把Add函数里的代码全部放到调用处,而是编译器实现和函数逻辑一样的指令。

注意内联函数在调用处选不选择展开取决于编译器,不是加了inline关键词的函数就会展开,编译器只会展开代码量少的函数。

这是因为如果有程序员给长代码函数、循环函数、递归函数加上内联,并且编译器无条件展开则会导致需要执行的指令变得非常多,生成的可执行程序文件特别大。

内联函数还有一个特别的点:

//Func.h
#include <iostream>
using namespace std;

inline void Func(int a = 10);

//Func.cpp
#include "Func.h"

void Func(int a)
{
	cout << a << endl;
}

//Test.cpp
#include "Func.h"

int main()
{
	Func();
	return 0;
}

在这里插入图片描述

出现了链接错误,这是因为内联函数在编译时,Func.cpp文件包含头文件得知Func函数是内联函数后,就没有把函数的地址放进符号表,因为在链接的时候,Test.cpp文件找不到Func函数的地址。

所以当内联函数声明和定义分离时,使用只能在定义的那个文件里使用。

正确的使用内联函数的方法是,将内联函数完整的实现放在头文件中,这样函数就可以在调用的地方直接展开,而不用在链接时候找地址。

六.小语法

6.1auto

auto是C++一个用来自动推导类型的关键字,它的用途是对长类型的省略写法。

在这里插入图片描述

对于指针的写法可以写auto* d = c;,而对于引用我们只能显示写,以上只是说明atuo的用法,对于这种短类型,不是auto的真正使用场景。

注意:auto不能做参数类型,也不能做函数返回类型以及不能用来创建数组。

6.2范围for

对于数组的遍历,C语言使用求数组下标依次遍历,C++使用一个更为简便的语法:

在这里插入图片描述

for(auto e: 数组)这个语法中e是一个和数组元素类型一样的临时变量,将数组里的值依次取出赋值给e,自动判断结束。习惯使用auto当e的类型,让其自动推导类型。

当e作为数组里每个元素的别名时,对其进行修改会影响数组里的元素。

由于冒号后面加的是数组,因此:

#include <iostream>

void Func(int arr[])
{
    for(auto e : arr)//error,arr是首元素地址不是数组
    {
        //...
    }
}
int main()
{
    int arr[3] = {1,2,3};
    Func(arr);
    return 0;
}
6.3空指针

在C++程序中,使用nullptr当做空指针,C语言的NULL有点错误,因此C++委员会后来补上这个坑,引入nullptr这个关键词,实质是void*。

在这里插入图片描述

好了,以上就是C++入门篇,希望读者有所收获!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL之视图索引

学生表&#xff1a;Student (Sno, Sname, Ssex , Sage, Sdept) 学号&#xff0c;姓名&#xff0c;性别&#xff0c;年龄&#xff0c;所在系 Sno为主键 课程表&#xff1a;Course (Cno, Cname,) 课程号&#xff0c;课程名 Cno为主键 学生选课表&#xff1a;SC (Sno, Cno, Score)…

华为设备NAT的配置

实现内网外网地址转换 静态转换 AR1&#xff1a; sys int g0/0/0 ip add 192.168.10.254 24 int g0/0/1 ip add 22.33.44.55 24 //静态转换 nat static global 22.33.44.56 inside 192.168.10.1 动态转换 最多有两台主机同时访问外网 AR1&#xff1a; sys int g0/0/0 ip add…

C语言之【函数】篇章以及例题分析

文章目录 前言一、函数是什么&#xff1f;二、C语言中函数的分类1、库函数2、自定义函数 三、函数的参数1、实际参数&#xff08;实参&#xff09;2、形式参数&#xff08;形参&#xff09; 四、函数的调用1、传值调用2、传址调用3、专项练习3.1 素数判断3.2 闰年判断3.3 二分查…

【OpenCV学习笔记17】- 平滑图像

这是对于 OpenCV 官方文档中 图像处理 的学习笔记。学习笔记中会记录官方给出的例子&#xff0c;也会给出自己根据官方的例子完成的更改代码&#xff0c;同样彩蛋的实现也会结合多个知识点一起实现一些小功能&#xff0c;来帮助我们对学会的知识点进行结合应用。 如果有喜欢我笔…

【数据结构】栈的远房亲戚——队列

队列的基本概念 前言一、队列的定义二、队列的重要术语三、队列的基本操作四、数据结构的三要素4.1 线性表的三要素4.2 栈的三要素4.3 队列的三要素 结语 前言 大家好&#xff0c;很高兴又和大家见面啦&#xff01;&#xff01;&#xff01; 在经过前面内容的介绍&#xff0c;…

“Oops,Account deactivated” 账号被停用,如何解封?

“Oops&#xff0c;Account deactivated” &#xff0c;当看到这个报错的时候&#xff0c;说明账号被停用&#xff0c;封了。 为什么被封 出现这种情况&#xff0c;多是因为违规使用账号&#xff0c;比如&#xff0c;批量注册多个账号或者违规使用账号&#xff0c;白嫖官方Api…

代码随想录算法训练营第三十六天 | 435.无重叠区间、763.划分字母区间、56.合并区间

435.无重叠区间 题目链接&#xff1a;435.无重叠区间 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 文章讲解/视频讲解&#xff1a;https://programmercarl.com/0435.%E6%9…

Java毕业设计-基于jsp+servlet的家用电器购物商城管理系统-第87期

获取源码资料&#xff0c;请移步从戎源码网&#xff1a;从戎源码网_专业的计算机毕业设计网站 项目介绍 基于jspservlet的家用电器购物商城管理系统&#xff1a;前端 jsp、jquery、layui&#xff0c;后端 servlet、jdbc&#xff0c;角色分为管理员、用户&#xff1b;集成商品…

AI制作《流浪地球3》高清宣传片

AI制作《流浪地球3》高清宣传片 星辰大海&#xff0c;再次启航&#xff0c;人类的冒险&#xff0c;永无止境。The vast expanse of stars and oceans, setting sail once again. Human adventure knows no bounds. 当家园变得遥不可及&#xff0c;我们唯有勇往直前。With our …

电脑端网络记事本哪个安全稳定?

随着互联网科技的飞速发展&#xff0c;越来越多的上班族发现在电脑上使用网络记事本的重要性。这种网络记事本不仅便于记录工作内容&#xff0c;而且自动将数据上传到云端进行备份&#xff0c;让用户不再为数据丢失而担忧。让我们来看看上班族使用网络记事本的好处。 在日常工…

阿里云服务器地域如何选择?哪个地域价格优惠一些?

阿里云服务器地域和可用区怎么选择&#xff1f;地域是指云服务器所在物理数据中心的位置&#xff0c;地域选择就近选择&#xff0c;访客距离地域所在城市越近网络延迟越低&#xff0c;速度就越快&#xff1b;可用区是指同一个地域下&#xff0c;网络和电力相互独立的区域&#…

一文了解GeoTrust SSL证书

在当今互联网的高度连接世界中&#xff0c;确保网站安全性至关重要。SSL证书是保护网站和用户数据的关键组成部分。GeoTrust证书在SSL证书市场上享有盛誉&#xff0c;被许多网站所有者和企业所信赖。JoySSL将深入探讨GeoTrust证书的特点&#xff0c;帮助大家了解该品牌并做出更…

Spring中动态注册和销毁对象

1. 使用说明 通常我们项目中想要往spring容器中注入一个bean可以在项目初始化的时候结合Bean注解实现。但是该方法适合项目初始化时候使用&#xff0c;如果后续想要继续注入对象则无可奈何。本文主要描述一种在后续往spring容器注入bean的方法。 2. 实现 2.1 说明 2.1.1 注册…

Page268~270 11.3.4 wxWidgets项目配置

项目w28_gui的项目配置&#xff1a; 一&#xff0c;编译选项&#xff0c; -pipe -mthreads [[if (GetCompilerFactory().GetCompilerVersionString(_T("gcc")) > _T("4.8.0")) print(_T("-Wno-unused-local-typedefs"));]] 1, -pipe&#…

spark dateformat源码排错

背景 有一个任务 yyyy写成了YYYY&#xff0c;导致年份不对触发告警 select from_unixtime(unix_timestamp(),YYYY-MM-dd HH:mm:ss) 第一时间用spark dateformat搜索下看看官网&#xff0c;发现spark 官网也没有描述YYYY的信息 Datetime patterns - Spark 3.5.0 Documentati…

【计算机组成与体系结构Ⅱ】Cache性能分析(实验)

实验6&#xff1a;Cache性能分析 一、实验目的 1&#xff1a;加深对 Cache 的基本概念、基本组织结构以及基本工作原理的理解。 2&#xff1a;掌握 Cache 容量、相联度、块大小对 Cache 性能的影响。 3&#xff1a;掌握降低 Cache 不命中率的各种方法以及这些方法对提高 Ca…

Springboot智慧校园电子班牌统一管理平台源码

借助AIoT智能物联、云计算技术打造智慧绿色校园&#xff0c;助力实现校园教务管理、教师管理、学籍管理、考勤、信息发布、班级文明建设、校园风采、家校互通等场景功能&#xff0c;打造安全、便捷、绿色的智慧校园。 前后端分离架构 1、使用springbootvue2 2、数据库&#xff…

Day31 46全排列 47全排列II 回溯去重tips 51N皇后 37解数独

46 全排列 给定一个 没有重复 数字的序列&#xff0c;返回其所有可能的全排列。 示例: 输入: [1,2,3]输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ] 排列问题与组合问题的不同之处就在于&#xff0c;没有startIndex&#xff0c;同时需要设置一个used数组…

剩余电流继电器装在哪里?电工必备知识

可实时监测和显示TN-S、TT系统配电线路的剩余电流&#xff1b; 每只剩余电流监测仪最多可监测16个回路的剩余电流&#xff0c;剩余电流监测范围为1mA-30A&#xff1b; 每路剩余电流监测均可设置报警值&#xff0c;报警值的设置范围为5mA-30A。每路剩余电流监测可设置为超值…

Docker(一)简介和基本概念

一、简介 本章将带领你进入 Docker 的世界。 什么是 Docker&#xff1f; 用它会带来什么样的好处&#xff1f; 好吧&#xff0c;让我们带着问题开始这神奇之旅。 1.什么是 Docker Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目&…