C++基础知识梳理<2>(引用、内联函数、auto关键字) [入门级】

news2024/11/28 2:29:43

目录

一、引用

1. 引用概念

2. 引用特性

2.1 引用在定义时必须初始化

2.2 一个变量可以有多个引用

2.3 引用一旦引用一个实体,再不能引用其他实体 

3. 常引用 

3.1 取别名的规则

3.2 权限放大error

3.3 权限不变 

3.4 权限缩小 

4. 引用原理与拓展

4.1 如何给常量取别名?

4.2 临时变量具有常性 

4.3 权限控制的用处

5. 引用的使用场景

5.1 做参数 

5.2 做返回值

6. 引用的使用场景

二、内联函数 

1. 概念

2. 特性 

3. 经典面试题 

三、auto关键字(C++11)

1. auto简介

2. auto的使用细则 

3. auto不能推导的场景 

4. auto的实际应用价值

5. 基于范围的for循环(C++11) 

5.1 范围for的语法

5.2 范围for的使用条件 

四、指针空值nullptr(C++11)


一、引用

1. 引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

  • 类型& 引用变量名(对象名) = 引用实体。我们看下面代码:
int main()
{
	int a = 10;
	int& b = a;
	int& c = a;
	int& d = b;
}

上述代码中定义一个变量a,a这块空间占据4个字节,接下来我们又给它取了二个名字叫做b和c,然后又给b取了一个名字叫做d,也就是说a,b,c,d同时可以访问且修改变量a的这块空间,并且a,b,c,d的地址都是一样的。

2. 引用特性

引用具有三大特性:

  • 引用在定义时必须初始化。
  • 一个变量可以有多个引用。
  • 引用一旦引用一个实体,再不能引用其它实体。

接下来我们将其特性进行一一讲解。

2.1 引用在定义时必须初始化

 假如我们写出如下的引用会发生什么呢?

int& d;

我们可以看到已经发生错误,所以引用在定义的时候必须进行初始化。 

2.2 一个变量可以有多个引用

比如我有个变量a,你可以给其取个别名b,也可以取个别名c,甚至给别名c再取别名d都可以,并且这些别名和a的地址均是一样的,我改变其中一个,其它的也会随之改变。

2.3 引用一旦引用一个实体,再不能引用其他实体 

在这段代码中,我们已经给a取别名b,随后把e的值赋给b,这里可不是对e取别名了,通过编译即可看出来,b的地址同引用的a的地址,而不同于e的地址。

3. 常引用 

3.1 取别名的规则

我们在取别名的时候不是在所有的情况下都是可以随便取的,要在一定的范围内。对原引用变量,权限只能缩小,不能放大。

3.2 权限放大error

我们都知道在C语言中有个const,而在C++的引用这一块也是有const引用的,假如我们现在有个被const修饰过的变量X,现在我们想对X取别名,我们还能用下面的方式吗??

这个就是典型的权限放大,变量X被const修饰只可以进行读,不能进行修改。而此时我们对X引用成y,并且是int型的,此时y是可读可写的,不满足X的只读条件。

那怎样才能对x进行引用呢?我们只需要确保权限不变即可。见下文: 

3.3 权限不变 

想要使权限不变,我们只需要对x引用的同时加上const修饰,让变量y也只是只读。

//权限不变
const int x = 0;
const int& y = x;

那如果变量没有加const修饰,但是在引用时加上const可以吗??这就是权限缩小。

3.4 权限缩小 

//权限缩小
	int c = 10;
	const int& d = c;

这里变量C是可读可写的,我们创建引用变量d进行const修饰,那么d就只为可读,权限缩小,不报错。

4. 引用原理与拓展

4.1 如何给常量取别名?

我们可以用下面方法给常量取别名吗?

int& c=20; //err

对于常量我们不可以直接进行取别名,我们需要加上const修饰才可以。

const int& c=20; //right

4.2 临时变量具有常性 

我们看如下代码:

double d = 2.2;
int& e = d;

编译一下看看:

很明显e不能成为d的别名。但是如果我们加上const,发现它竟然不会出错!!

 该如何解释上述现象呢??这就需要我们先回顾一下C语言的类型转换。C++本身就是建立在C语言之上,C语言在相似类型是允许隐式类型转换的。大的数据类型给小的会发生截断,小的给大的会提升。我们看如下代码:

 这里的丢失数据其实就是会丢失精度。

⭐原理:

这里把d赋值给f并不是直接赋值的,会先把d的整数部分取出来赋值给一个临时变量,该临时变量大小为4个字节,随后再把这个临时变量赋给f

  • 临时变量具有常性,就像被const修饰了一样,不能被修改。

 谈到这里我想大家应该就可以理解为什么下面的这段代码需要加上const才能编译通过:

double d = 2.2;
const int& e = d;

这里e引用的是临时变量,临时变量具有常性,不能直接引用否则就是放大了权限,加上const才能保证其权限不变。

  • 既然是这样,那为何下面这段代码在赋值的时候不加上const呢??
double d = 2.2;
int f = d;
  • 上述加const是在引用的基础上加的,如果不加const那么就是放大权限。而在此段代码,对f的改变并不会影响到临时变量。普通的变量不存在权限放大或者缩小。
  • 那么下列代码中的e还是对d的引用吗? 
double d=2.2
const int& e=d;

当然不是,此时的e是对临时变量的引用,是临时变量的别名我们可以通过编译来验证:

我们可以看到变量e和d的地址都不一样。 

4.3 权限控制的用处

这里简单的提一下,例如下面的传参问题。

如若函数写成普通的引用,那么很多参数可能会传不过去:

观察上面代码我们发现只有a能够正常传过去,后面的均传不过去,因为后面传的参数均涉及权限放大。 但如果我们在函数的形参上加const修饰呢?

加上const修饰后编译器就不会报错了。 

5. 引用的使用场景

引用的使用场景分为两个:

  • 做参数
  • 做返回值

接下来我们将会详细讲解一下。

5.1 做参数 

比如我们现在想写一个Swap函数,以前是用指针传参:

//指针版
void Swap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

int main()
{
	int a = 10;
	int b = 5;
	Swap(&a, &b);
	return 0;
}

而现在,我们可以巧用引用来完成Swap函数

//引用版
void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

//支持函数重载
void Swap(double& x, double& y)
{
	double tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	//交换整数
	int a = 0, b = 1;
	Swap(a, b);
	//交换浮点数
	double c = 1.1, d = 2.2;
	Swap(c, d);
	return 0;
}
  • 在输出型参数里面用引用非常的方便:
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    //……
}

我们可以将上述代码改为下面的代码:

int* preorderTraversal(struct TreeNode* root, int& returnSize) {
    //……
}
int main()
{
    preorderTraversal(tree, size);
}

加上引用在调用函数时省去了写&,更加方便理解,同时也减少了对指针的使用。

用引用做参数的好处如下:

  • 输出型参数
  • 减少拷贝,提高效率

5.2 做返回值

 我们先看下面一段代码:

输出结果为1、2、3。 

  • 这里可能有人会提问为什么不是1、1、1呢?注意这里使用了静态区的变量只会初始化一次也就是说我static int n = 0这行代码在编译时只有第一次会跳到这,其余两次均不会走这一行代码,你每次进去的n都是同一个n。

⭐传值返回:

int Count()
{
	int n = 0;
	n++;
	return n;
}

int main()
{
	int ret = Count();
	return 0;
}

在传值返回的过程中会产生一个临时变量(类型为int),如果这个临时变量小会用寄存器进行替代,如果大就不会用寄存器替代。

具体返回的过程中是先把函数的n拷贝给临时变量,再把临时变量拷贝给ret。

  •  main函数里有个变量ret,汇编时会call一个指令跳到函数Count,Count里有一个变量n。这里不能把n直接传给ret,因为函数调用完后函数的栈帧就销毁了,这里会产生一个临时变量保存n的值,再把n的值传给ret。

我们如何证明这个过程中产生了临时变量呢?

我们只需要加一个引用。

这里很明显发生了编译错误。 这里ret之所以出错就是因为其引用的是临时变量,临时变量具有常性,只读不可修改,直接引用则会出现上文所述的权限放大问题。 所以这就很巧合的验证了此函数调用中途会产生临时变量。

想要解决此类问题,我们只需要使其保持权限不变即可,即加上const修饰:

 ⭐传引用返回:

我们对上述代码进行微调:

int& Count()
{
	int n = 0;
	n++;
	return n;
}

int main()
{
	int ret = Count();
	return 0;
}

这里加上了引用&后,中间也会产生一个临时变量,只是这个临时变量的类型是int&我们把这个临时变量假定为tmp那么此时tmp就是n的别名,再把tmp赋值给ret。这个过程不就是直接把n赋给ret吗这里区分于传值返回的核心就在于传引用的返回就是n的别名

如何证明传引用返回的是n的别名?

我们还可以通过打印法来验证:

这里的ret和n的地址都是一样的,这也就意味着ret其实就是n的别名。 综上,传值返回和传引用返回的区别如下:

  • 传值返回:会有一个拷贝
  • 传引用返回:没有这个拷贝了,返回的直接就是返回变量的别名 

这里又存在一个问题:上述代码有没有其他错误??

我传引用返回后,ret就是n的别名,出了函数出了这个作用域变量n就销毁。但空间的销毁并不代表空间不在了。空间的归还就好比你退房,虽然你退房了,但是这个房间还是在的,只是说使用权不是你的了。但是假说你在不小心的情况下留了一把钥匙,你依旧是可以进入这个房间,不过你这个行为是非法的。这个例子也就足矣说明了上述的代码是有问题的。是一个间接的非法访问。

我们看下面代码和运行结果:

这里第一次打印ret的值为1,打印完后函数栈帧销毁,此时ret里面存储的是随机值。 

  • 那如果我们非要引用返回,该怎样做呢?

加上static即可:

int& Count()
{
	static int n = 0;
	n++;
	cout << "&n: " << &n << endl;
	return n;
}
int main()
{
	int& ret = Count();
	cout << ret << endl;
	cout << "&ret: " << &ret << endl;
	cout << ret << endl;
	return 0;
}

加上了static后n就被放入了静态区,出了作用域不会被销毁。

⚠: 如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。否则就可能会出越界问题。

看下面代码:

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;  //7
	return 0;
}

此段代码的执行结果ret的值为7,首先我Add(1,2)。调用完后返回C的别名给ret,调用完后Add栈帧销毁,当我第二次调用函数c的值就被修改成7。

正常情况下我们应该加上static:

 

 加上static后这里ret的值就是3了,因为加上了static初始化只有一次。此时c在静态区了,销毁栈帧它还在。

  • 我们再演示一下其被覆盖的情形:

正常情况:

不加static发生覆盖:

  ⭐传值、传引用效率比较:

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

  • 作为参数比较
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
	TestRefAndValue();
}

  • 作为返回值比较
#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
	TestReturnByRefOrValue();
}

6. 引用的使用场景

引用和指针的不同点:

  • 引用概念上定义一个变量的别名,指针存储一个变量地址
  • 引用在定义时必须初始化,指针没有要求
  • 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  • 没有NULL引用,但有NULL指针
  • 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  • 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  • 有多级指针,但是没有多级引用
  • 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  • 引用比指针使用起来相对更安全

⭐:其实在底层实现上,引用是按照指针方式来实现的。下面我们来看下引用和指针的汇编代码对比:

二、内联函数 

1. 概念

inline修饰的函数叫做内联函数编译时C++编译器会在调用内联函数的地方展开没有函数压栈的开销,内联函数提升程序运行的效率。

我们以Add函数为例:

int Add(int x, int y)
{
	int z = x + y;
	return z;
}

上述的Add函数如若被频繁调用,则在效率上会存在一定的损失。假如我们调用10次,那么就需要建立十次栈帧。建立栈帧又要保存寄存器,压参数等一系列操作都会造成效率损失。

  • 在我们先前学过的C语言中就给出了解决方案:
#define ADD(x,y) ((x)+(y))
  • 既然用宏能够解决,那C++为何要引出inline?

使用宏确实可以帮助我们避免调用栈帧而造成效率损失,但是宏的写法上欠妥,我们需要注意结尾不能加分号,要注意优先级带来的问题而频繁加括等等一系列问题C++为了填补宏书写规则麻烦的坑,引出了内敛函数的概念(inline)。内敛函数的特点:

 1.解决宏函数晦涩难懂,容易写错的问题

 2.解决宏不支持调试,不支持类型安全的检查等问题

  • 内敛函数(inline)如何使用?

我们只需要在函数前面加上inline即可:

inline int Add(int x, int y)
{
	int z = x + y;
	return z;
}

此时我们再调用函数就不会再建立栈帧了,函数直接会在调用的地方展开。

  • inline的好处如下:

  1.debug支持调试

  2.不容易写错,就是普通函数的写法

2. 特性 

  1. inline是一种以空间换时间的做法省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
  2. inline对于编译器而言只是一个建议编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

3. 经典面试题 

  • 问题1:宏的优缺点

优点:

  1. 增强代码的复用性
  2. 提高性能

缺点:

  1. 不方便调试宏(因为预编译阶段进行了替换)
  2. 导致代码可读性差,可维护性差,容易误用
  3. 没有类型安全的检查
  • 问题2:C++有那些技术替代宏
  1. 常量定义,换用const
  2. 函数定义,换用内联函数

三、auto关键字(C++11)

1. auto简介

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它,大家可思考下为什么?

C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

简单来说:先前定义变量要在变量前指定类型,使用auto可以不指定类型,让右边赋的值进行推导,如示例:

int a = 10;
auto b = a;
auto c = 'a';

这里a的类型是整型,那么就自动推出b的类型为int,而'a'为char类型,自然c就是char类型。

  • 补充:

这里补充一个知识点:typeid().name。它是专门用来输出一个变量的类型,返回的是一个字符串。

  • 代码演示:
int TestAuto()
{
	return 10;
}
int main()
{
	const int a = 10;
	auto b = a;
	auto m = &a;
	auto c = 'a';
	auto d = TestAuto();
	cout << typeid(b).name() << endl; // int
	cout << typeid(m).name() << endl; // int const *
	cout << typeid(c).name() << endl; // char 
	cout << typeid(d).name() << endl; // int
	//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
	return 0;
}
  • 结果如下:

  • 注意: 

使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

2. auto的使用细则 

  • auto与指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有任何的区别,但用auto声明引用类型时则必须加&

int main()
{
	int x = 10;
	auto a = &x;
	auto* b = &x;
	auto& c = x;
	cout << typeid(a).name() << endl; // int*
	cout << typeid(b).name() << endl; // int*
	cout << typeid(c).name() << endl; // int
	*a = 20;
	*b = 30;
	c = 40;
	return 0;
}

  •  在同一行定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

void TestAuto()
{
	auto a = 1, b = 2;
	auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

3. auto不能推导的场景 

  • auto不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
  • auto不能做返回值
auto Test()
{
	return 10; // err
}
  • auto不能直接用来声明数组
void TestAuto()
{
	int a[] = { 1,2,3 };
	auto b[] = { 4,5,6 }; //err 错误
}
  • 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法。
  • auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。

4. auto的实际应用价值

  • 类型很长时,懒得写,我们可以让其自动推导

以后在我们学到容器的时候,我们会写出这样的代码:

#include<map>
#include<string>
int main()
{
	std::map<std::string, std::string>dict;
	dict["sort"] = "排序";
	dict["string"] = "字符串";
//auto意义之一:类型很长时,懒得写,可以让它自动推导
	std::map<std::string, std::string>::iterator it = dict.begin();
	auto it = dict.begin();
	return 0;
}

我们使用auto就可以简化前面定义过长类型的代码,使其自动判断类型

  • 基于范围的for循环

基于范围的for循环我们单独来讲,看下文。

5. 基于范围的for循环(C++11) 

5.1 范围for的语法

我们平常写打印一串数组中的数据的时候我们可以这样写:

void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(array) / sizeof(int); ++i)
		array[i] *= 2;
	for (int i = 0; i < sizeof(array) / sizeof(int); ++i)
		cout << array[i] << " "; // 2 4 6 8 10
}

对于一个有范围的集合而言,我们可以利用基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

因此在C++中我们可以这样写:

void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(array) / sizeof(int); ++i)
		array[i] *= 2;
	for (auto e : array)
		cout << e << " "; // 2 4 6 8 10
}

此段代码就是范围for,它可以自动遍历,会依次取数组中的数据赋值给e,自动判断结束

  • 可如果现在我想对数组进行修改,使数组中的每一个数字除以2,我们该怎么做呢?是如下这样吗?

我们可以看到上述方法是错误的,其并没有起到修改的作用。我们注意看范围for的规则:依次取数组中的数据赋值给e这也就说明e是数组中每个值的拷贝,e的改变并不会影响到数组 此时就需要我们用到引用了,当我们给其取别名时,e的修改就会影响到原数组。

void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(array) / sizeof(int); ++i)
		array[i] *= 2;
	for (auto e : array)
		cout << e << " "; // 2 4 6 8 10
	cout << endl;
	for (auto& e : array)
		e /= 2;
	for (auto e : array)
		cout << e << ' ';
}
  • 补充:

1、范围for里的auto也可以写成int,不过最好还是写成auto,毕竟auto可以自动推出数组的类型嘛,不用auto还要自己手动设置。把e改成其它的变量也是可以的,不强求。

2、与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

5.2 范围for的使用条件 

  • for循环迭代的范围必须时确定的

对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

注意:以下代码就有问题,因为for的范围不确定

void TestFor(int array[])
{
	for (auto& e : array)
		cout << e << endl;
}

用范围for必须是数组名,C语言有规定参数传递的过程中不能是数组,这里的形参是指针,自然不能用范围for的规则了。

  • 迭代的对象要实现++和==的操作。(关于迭代器这个问题,以后会讲)

四、指针空值nullptr(C++11)

在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

void TestPtr()
{
	int* p1 = NULL;
	int* p2 = 0;
	// ……
}

但是在C++中,我们推荐这样写:

int* p3=nullptr;

前者中,NULL和0在C++其实是等价的,都不规范。NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

如果没有定义宏,如果在cplusplus里,NULL被定义成0。可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

  • 注意:
  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

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

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

相关文章

RestTemplate.exchange各种用法(包括泛型等 --全)

文章目录前言1. Get请求1.1 返回基本类型1.2 返回自定义对象类型1.3 返回List\<T> 类型1.4 返回Map\<K,V> 类型1.5 返回自定义泛型类型2.Post请求2.1 传headerbody返回对象类型2.2 传headerbody返回自定义泛型类型3. 异常情况处理4. RestTemplate配置Bean最后前言 …

学习笔记之Vue基础学习——持更

Vue学习一、Vue简介1.1 什么是Vue&#xff1f;1.2 Vue的特点1.3 Vue官网使用1.4 搭建Vue开发环境1.5 Hello小案例总结案例&#xff1a;二、模板语法2.1 两大类型三、数据绑定3.1 两种方式四、el和data的两种写法4.1 el的两种写法4.2 data的两种写法五、MVVM模型5.1 什么是MVVM模…

目标检测(5)—— YOLO系列V1

一、YOLO系列V1 经典的one-stage方法&#xff0c;You Only Look Once将检测问题转化成回归问题&#xff0c;一个CNN搞定可以对视频进行实时监测 YOLO系列的速度更快&#xff0c;我们检测的物体很简单&#xff0c;进行取舍&#xff0c;舍弃了一些精度。 V1核心思想 现在要预测…

Windows与Linux行尾换行符引发Git的一系列惨案

1 前言 最近在使用 Git 提交代码的时候&#xff0c;老是碰到一段看起来 “没有任何改动” 的代码&#xff0c;被 diff 检测出异常&#xff0c;很是苦恼&#xff0c;特别是项目紧急的时候&#xff0c;不敢用 VSCode 编辑了&#xff0c;只能用 vim 进行少量代码的修改、上库。 …

传统Spring项目的创建和使用xml文件来保存对象和取对象

传统Spring项目的创建和使用xml文件来保存对象和取对象## 传统Spring项目的创建 一、创建一个maven项目&#xff08;maven项目无需使用模板&#xff09; 二、导入Spring依赖&#xff08;Spring Context依赖和Spring Beans依赖&#xff09; 可以从maven仓库获取&#xff0c;也…

Java 中代码优化的 30 个小技巧(中)

11 位运算效率更高 如果你读过 JDK 的源码&#xff0c;比如 ThreadLocal、HashMap 等类&#xff0c;你就会发现&#xff0c;它们的底层都用了位运算。 为什么开发 JDK 的大神们&#xff0c;都喜欢用位运算&#xff1f; 答&#xff1a;因为位运算的效率更高。 在 ThreadLoca…

数码相机raw照片编辑Capture One Pro中文

怎么编辑数码相机拍摄的raw格式的照片&#xff1f;Capture One Pro 22是一款专业、强大、易于使用的图像编辑软件&#xff0c;与主流相机型号兼容&#xff0c;直接导入照片进行编辑操作&#xff0c;包括佳能、尼康、索尼、富士等。将所有必备工具和高端性能融于一体、使您在一套…

riscv引导程序及仿真记录

1.riscv基本的寄存器列表 这里只关注32个通用寄存器x0-x31 2.引导程序代码 # 1 "iriscvboot.casm" # 1 "<built-in>" # 1 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<command-line>&qu…

【Linux】进程间通信

文章目录1.进程间通信基础2.管道2.1匿名管道2.1.1匿名管道的原理2.2匿名管道的特点2.3匿名管道函数2.3.1用例2.3.2实现ps -ajx | grep bash指令2.4匿名管道的特点2.5管道的大小2.6管道的生命周期2.7进程池3.命名管道FIFO3.1命名管道的接口3.2命名管道和匿名管道的区别3.3用FIFO…

大数据面试重点之kafka(七)

大数据面试重点之kafka(七) Kafka的分区器、拦截器、序列化器&#xff1f; 问过的一些公司&#xff1a;ebay 参考答案&#xff1a; Kafka中&#xff0c;先执行拦截器对消息进行相应的定制化操作&#xff0c;然后执行序列化器将消息序列化&#xff0c;最后执行分 区器选择对应分…

python:基础知识

环境&#xff1a; window11python 3.10.6vscodejavascript、c/c/java/c#基础&#xff08;与这些语言对比&#xff09; 注释 一、数据类型 基础六大数据类型&#xff0c;可以使用 type()查看&#xff0c;如下图&#xff1a; 1.1 数字&#xff08;Number&#xff09; 支持 整…

联邦学习--记录

简介 联邦学习&#xff08;Federated Learning&#xff09;是一种新兴的人工智能基础技术&#xff0c;其设计目标是在保障大数据交换时的信息安全、保护终端数据和个人数据隐私、保证合法合规的前提下&#xff0c;在多参与方或多计算结点之间开展高效率的机器学习。其中&#…

【机器学习大杀器】Stacking堆叠模型-English

1. Introduction The stacking model is very common in Kaglle competitions. Why? 【机器学习大杀器】Stacking堆叠模型&#xff08;English&#xff09; 1. Introduction 2. Model 3: Stacking model 2.1 description of the algorithms: 2.2 interpretation of the es…

浅谈Vue中 ref、reactive、toRef、toRefs、$refs 的用法

&#x1f4ad;&#x1f4ad; ✨&#xff1a; 浅谈ref、reactive、toRef、toRefs、$refs   &#x1f49f;&#xff1a;东非不开森的主页   &#x1f49c;: 技术需沉淀&#xff0c;不要浮躁&#x1f49c;&#x1f49c;   &#x1f338;: 如有错误或不足之处&#xff0c;希望可…

Redhat(3)-Bash-Shell-正则表达式

1.bash脚本 2.bash变量、别名、算术扩展 3.控制语句 4.正则表达式 1.bash脚本 #!/bin/bash#this is basic bash script<< BLOCK This is the basic bash script BLOKC: This is the basic bash script echo "hello world!" 双引号、单引号只有在变量时才有区…

健身房信息管理系统/健身房管理系统

21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存储达到…

VCS 工具学习笔记(1)

目录 引言 平台说明 关于VCS 能力 Verilog 仿真事件队列 准备 VCS工作介绍 工作步骤 支持 工作机理 编译命令格式 编译选项 示例 仿真命令格式 仿真选项 示例 库调用 -y 总结 实践 设计文件 仿真文件 编译 仿真 关于增量编译 日志文件记录 编译仿真接续进…

链接脚本和可执行文件

几个重要的概念 摘取自知乎内容&#xff1a; 链接器与链接脚本 - 知乎 linker 链接器 链接器(linker) 是一个程序&#xff0c;这个程序主要的作用就是将目标文件(包括用到的标准库函数目标文件)的代码段、数据段以及符号表等内容搜集起来并按照 ELF或者EXE 等格式组合成一个…

【C++学习】string的使用

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《C学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; string的使用&#x1f640;模板&#x1f639;函数模板&#x1f639;类模板&#x1f640;string模板简…

【菜鸡读论文】Former-DFER: Dynamic Facial Expression Recognition Transformer

Former-DFER: Dynamic Facial Expression Recognition Transformer 哈喽&#xff0c;大家好呀&#xff01;本菜鸡又来读论文啦&#xff01;先来个酷炫小叮当作为我们的开场&#xff01; 粉红爱心泡泡有没有击中你的少女心&#xff01;看到这么可爱的小叮当陪我们一起读论文&am…