C++入门——引用|内联函数|auto关键字|基于范围的for循环|指针空值

news2024/11/18 3:52:34

前言

C++入门专栏是为了补充C的不足,并为后面学习类和对象打基础。在前面我们已经讲解了命名空间、输入输出、缺省参数、重载函数等,今天我们将完结C++的入门。

下面开始我们的学习吧!

一、引用

1、引用是什么呢?为什么C++添加了引用?

(1)引用的概念: 引用是给已存在变量取了一个别名,不是重新定义一个新变量。编译器不会为引用变量开辟内存空间,它和它引用的变量一起用同一块内存空间。(就如西游记中孙悟空,也叫齐天大圣,你不管叫他那个名字,都是代表一个人。)

(2) 在C语言中,我们要改变一个变量的值可以取一个变量的地址通过解引用来改变,但是这是一种间接的玩法。总结:简单的说就是指针是间接的,而引用相当于它的别名还是它自己,更直接,更方便了。 所以以后我们大多都是使用引用了。下面让我们详细了解引用的魅力!

2、引用的语法

引用的语法:

类型& 引用变量名(对象名) = 引用实体。

tip:

(1)引用的理解:

①C++觉得添加太多新符号不太好,所以就会在某些地方共用一些符号。例如这里的‘&’。

②‘&’这个符号用法的区分:‘&’在变量名之前,代表取地址;‘&’在类型之后代表引用(取别名)。

③引用在语法逻辑上不会开辟新空间,它和它引用的实体共用一块内存空间。

④引用就是取别名,不管怎样还是共用一块内存空间。

⑤代码示例:

//引用的理解:
#include<iostream>

using namespace std;

int main()
{
	int a = 10;
	//b是a的引用(别名)
	int& b = a;

	//打印a与b的地址
	cout << &a << endl;
	cout << &b << endl;
	return 0;
}

运行结果:
在这里插入图片描述

(2)引用的特性:

①引用在定义的时候必须初始化(就如你给谁取别名,肯定是有一个明确的指向,是给谁取的)。

②一个变量可以有多个别名(就像孙悟空就有多个别名)。

③引用一旦引用了一个实体,就不能再引用其他实体了(从这里就能看出C++的革命是不彻底的,引用并没有将指针完全替代)。

④代码示例:

//引用的特性:
#include<iostream>

using namespace std;

int main()
{
	int a = 10;
	//①必须初始化;
	//int& b;//编译报错,引用必须初始化

	//②一个变量可以有多个引用
	int& b = a;//b是a的引用(别名)
	int& c = b;//c是b的引用(别名的别名也是可以的)

	//③引用一旦引用了一个实体,就不能在引用其他实体了
	int x = 9;
	//int& b = x;//编译报错,"b"重定义,多次初始化
	b = x;//注意这里b不是x的引用,而是x赋值给b,b仍是a的别名。
	return 0;
}

(3)常引用——引用的权限

①引用过程中,权限可以平移或者缩小,但是不可以放大。

②算术转化:如果操作符的操作数类型不一致,会发生类型转化,只有类型一致,才能进行运算。

③类型转化:生成一个临时变量,临时变量具有常性,即临时变量不可改变,是常变量。图示:

在这里插入图片描述

④为什么类型转化会生成临时变量——因为变量a类型不会改变,所以需要生成一个中间变量来进行类型转化。

⑤当引用的实体是一个常变量的时候,我们就要使用常引用,因为权限不可以放大。

⑥常引用的应用:常引用做参数——如果函数中只是使用参数,不改变参数的值,建议使用常引用。

⑦代码示例:

//常引用——引用的权限
#include<iostream>

using namespace std;

int main()
{
	//1、引用过程中,权限可以平移或者缩小
	int a = 10;
	int& b = a;//权限平移,a/b可读可写
	//如a能++,b也可以
	b++;
	a++;
	const int& c = a;//权限的缩小,a可读可写c可读不可写
	//a能++,c不可以
	a++;
	//c++;//编译报错,c不能改变

	//2、引用过程中,权限不可以放大
	double x = 9;
	//int& y = x;//因为类型不一致,x生成const int的临时变量可读不可写,而y可读可写,引用权限不可以放大,所以报错。
	const int& y = x;//权限平移

	return 0;
}

3、引用的应用

(1)引用做参数

引用做参数的意义:

①做输出型参数:形参的改变要影响实参。

代码示例:交换两个整数

#include<iostream>

using namespace std;
//使用引用做输出参数交换两个整数
void Swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}

int main()
{
	int a = 3;
	int b = 5;
	//调用函数交换a b
	Swap(a, b);
	cout << "交换后:a=" << a << " b=" << b << endl;
	return 0;
}

tip:在C语言的时候,我们交换两个整数,要使形参的改变影响实参,我们用的指针来实现,现在C++里面我们可以使用引用实现。

②引用做参数,减少拷贝提高了效率。(特别是对于大对象和深拷贝类对象)

代码示例:

//2.调高效率,建议大对象/深拷贝对象使用引用做参数

#include<iostream>
#include<time.h>

using namespace std;

//定义一个大结构体
struct A
{
	int a[10000];
};

//定义函数:以值作为函数参数
void Func1(A a){}
//定义函数:以引用作为函数参数
void Func2(A& a){}

//定义函数:分别计算两个函数运行结束后的时间
void TestRefAndValue()
{
	//struct A a;//C中定义结构体类型变量的方式,Cpp也可以这样(兼容C)
	A a;//Cpp中定义类对象的方式,因为在Cpp中struct升级为类了

	//以值作为函数参数
	size_t begin1 = clock();//开始时间
	for (size_t i = 0; i < 10000; ++i)
	{
		Func1(a);
	}//调用10000次Func1函数
	size_t end1 = clock();//结束时间

	//以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
	{
		Func2(a);
	}
	size_t end2 = clock();

	//分别输出两个函数运行结束后的时间
	cout << "Func1(A)_time:" << end1 - begin1 << endl;
	cout << "Func1(A&)_time:" << end2 - begin2 << endl;
}

int main()
{
	//调用函数TestRefAndValue分别计算以值为参数和以引用为参数的运行时间
	TestRefAndValue();
	return 0;
}

运行结果:

在这里插入图片描述

(2)引用做返回值

①引用做返回值的第一个意义:减少拷贝提高效率。

代码示例:

//①减少拷贝提高效率
#include<iostream>
#include<time.h>

using namespace std;

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();
	return 0;
}

运行结果:

在这里插入图片描述

②为什么会减少拷贝提高效率呢?

答案是:函数的返回类型是传值返回,就会将返回值拷贝到一个临时变量中,最后再拷贝回主调函数。当函数的返回类型是引用返回时,就不会生成临时变量通过拷贝返回,而是直接返回返回值的别名。图示:

在这里插入图片描述

③局部变量与静态变量传引用返回。

代码示例1:局部变量传引用返回

//局部变量传引用返回
#include<iostream>

using namespace std;

int& Count()
{
	int n = 0;//局部变量
	n++;
	//……
	return n;
}

int main()
{
	//ret也是n的别名
	int& ret = Count();
	//调用完Count直接输出ret
	cout << ret << endl;
	printf("sss\n");
	//调用完printf,再次输出ret
	cout << ret << endl;
	return 0;
}

运行结果:

在这里插入图片描述

为什么ret的值不一样呢——因为局部变量随栈帧销毁而销毁。如果Count函数结束,栈帧不清理,那么ret的结果侥幸正确;如果Count函数结束,栈帧清理,那么ret的结果是随机值。图示:

在这里插入图片描述

代码示例2:静态变量传引用返回

//静态变量传引用返回
#include<iostream>

using namespace std;

int& Count()
{
	static int n = 0;//静态变量
	n++;
	//……
	return n;
}

int main()
{
	//ret也是n的别名
	int& ret = Count();
	//调用完Count直接输出ret
	cout << ret << endl;
	printf("sss\n");
	//调用完printf,再次输出ret
	cout << ret << endl;
	return 0;
}

运行结果:

在这里插入图片描述

为什么这里ret的值一样呢——静态变量存储在静态区,不随栈帧的销毁而销毁。

tip:谨慎用引用做返回值,出了函数作用域,对象不在了,就不能用引用返回,还在就可以用引用返回。

④引用做返回值的第二个意义:获取返回值与修改返回值。

代码示例:静态顺序表获取pos位置值与修改pos位置值

//代码示例:静态顺序表获取pos位置值与修改pos位置值
#include<iostream>
#include<assert.h>

using namespace std;

//定义静态顺序表类型
struct SeqList
{
	int a[100];//顺序表大小
	int size;//有效数据个数
};

//C实现——获取pos位置值
int SLGet(SeqList* ps, int pos)
{
	//断言pos位置是否合理
	assert(pos >= 0 && pos < 100);
	//返回pos位置值
	return ps->a[pos];
}

//C实现——修改pos位置值
void SLModify(SeqList* ps, int pos, int x)
{
	//断言pos位置是否合理
	assert(pos >= 0 && pos < 100);
	//修改pos位置值
	ps->a[pos] = x;
}

//C++使用引用做返回值实现——修改&获取pos位置值
int& SLAt(SeqList& ps, int pos)
{
	//断言pos位置是否合理
	assert(pos >= 0 && pos < 100);
	//返回pos位置的别名
	return ps.a[pos];
}

int main()
{
	SeqList s;
	//调用C的实现,来获取与修改pos位置
	SLModify(&s, 1, 2);
	cout << SLGet(&s, 1) << endl;
	//调用C++的实现,来获取与修改pos位置
	SLAt(s, 0) = 1;//修改
	cout << SLAt(s, 0) << endl;//获取
	return 0;
}

总结

1、引用做参数:①做输出型参数;②减少拷贝提高效率;③基本任何场景都可以用引用做参数。

2、引用做返回值:①减少拷贝提高效率;②可以读写返回值;③谨慎用引用做返回值,出了函数作用域,对象不在了,就不能用引用返回,还在就可以用引用返回。

4、引用与指针的区别

(1)语法层面: 引用不开空间,是对实体取别名;指针开空间,是存储实体地址。

代码示例:

//引用与指针的区别
#include<iostream>

using namespace std;

int main()
{
	int a = 11;

	//引用在语法层面:不开空间,是对a的别名
	int& ra = a;
	ra = 13;

	//指针在语法层面:开空间,存储a的地址
	int* pa = &a;
	*pa = 14;
	return 0;
}

F10调试观察:

在这里插入图片描述

(2)底层层面: 从底层汇编指令实现的角度看,引用是类似指针的方式实现的。即在底层实现上引用实际是开空间的。

汇编代码图示:

在这里插入图片描述

(3)引用与指针不同点总结:

①在语法层面:引用不开空间是一个实体的别名,指针开空间,存储实体的地址。

②引用在定义时必须初始化,指针没有要求。

③引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。(即引用不可以修改指向,指针可以修改指向。)

④没有NULL引用,但有NULL指针。

⑤在sizeof中含义不同:引用结果为引用类型的大小,但是指针始终是地址空间所占字节个数。

⑥引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。

⑦有多级指针,但没有多级引用。

⑧访问实体方式不同,指针需要显示解引用,引用编译器自己处理。

⑨引用比指针使用起来相对更安全。(如上面第四点。)

二、内联函数

1、回顾宏函数

我们都知道调用函数需要建立栈帧是有消耗的,所以对于一些代码少且频繁调用的函数,在C语言我们使用了宏函数优化。

(1)代码示例:两数相加的宏

错误形式1:

#define Add(x,y) x+y

解读:Add(10,20) * 20,宏预编译进行替换为10 + 20 * 20,我们发现因为操作符优先级的问题,不是先加后乘。

错误形式2:

#define Add(x,y) (x+y)

解读:Add(1 | 2 , 1 & 2),宏替换后为:(1 | 2 + 1 & 2),位操作符的优先级低于算术操作符,所以错误。

正确形式:

#define Add(x,y) ((x) + (y))

tip:宏参数的求值是在所有周围表达式的上下环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多写括号。(可以替换看一看。)

(2)宏的优缺点:

优点: 不需要建立栈帧,提高效率。

缺点: 因为宏在预编译阶段进行了替换,所以①不方便调试;②代码可读性差,可维护性差,容易出错;③没有类型的检查等等。

(3)宏函数有这么多缺点,我们C++祖师爷就看不下去了,所以就有了inline内联函数。

2、内联函数

(1)概念: 以inline修饰的函数叫做内联函数,编译时C++编译器会在调用函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

代码示例:

//内联函数
#include<iostream>
using namespace std;

//inline修饰的函数
inline int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int ret = 0;
	ret = Add(1, 2);
}

tip: ①内联函数弥补了宏的缺点,继承了宏的优点。内联函数可读性高,可调试,不复杂等等。②在默认的debug模式下,inline不会起作用,否则不方便调试。

(2)内联这么好能不能都写成内联呢?

答案是: 不可以,因为inline的特性不支持所有函数都写成内联。

(3)内联函数的特性:

①inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺点:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。例如Func编译后是50行指令,如果Func不是inline,调用10000次Func合计指令为:10000+50(调用即call Func(地址),跳转到Func。);如果Func是inline,调用10000次Func合计指令为:10000*50(假设inline只是单纯展开,实际不是)。指令越多,目标文件越大。

inline对于编译器而言只是一个建议,最终是否成为inline,由编译器决定。一般来说,内联机制用于优化规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、流程直接(不递归)、频繁调用的函数。例如递归函数加了inline也会被编译器否决。

inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

代码示例:

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

inline void f(int i);
//F.cpp
#include"F.h"

void f(int i)
{
	cout << i << endl;
}
//Test.cpp
#include"F.h"

int main()
{
	f(10);
	return 0;
}

//链接错误:Test.obj:LNK2019:无法解析的外部符号 "void __cdecl f(int)" (? f@@YAXH@Z),函数 _main 中引用了该符号	

tip:不建议声明与定义分开,所以inline直接在.h文件中定义实现。

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

1、auto简介

C++11之前: 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量(限定变量的作用域及生命周期),但是没有人去使用,因为局部变量默认是auto修饰的。如下:

int a = 10;//自动存储类型
auto int b = 10;//自动存储类型

这样的话,auto没有用了。

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

简单来说,在C++11中auto可以根据右边的表达式自动推导变量的类型

代码示例:

#include<iostream>
using namespace std;

int main()
{
	auto a = 13;
	auto b = 0.14;

	//打印类型
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	return 0;
}

运行结果:

在这里插入图片描述

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

为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法。

2、auto的应用场景

随着程序越来越复杂,程序中用到的类型也越来越复杂。类型复杂,我们不仅容易写错还难于拼写。所以这个时候使用auto来帮我们自动推导变量的类型,就非常方便了。

代码示例:

#include<iostream>
#include<map>

int main()
{
	std::map<std::string, std::string> dict;

	//std::map<std::string, std::string>::iterator it = dict.begin();
	//等价于
	auto it = dict.begin();
	return 0;
}

tip:auto在实际中最常见的优势用法就是跟C++11提供的新式for循环,还有lambda表达式等进行配合使用。

3、auto的使用细则

(1)auto与指针和引用结合使用

代码示例:

#include<iostream>
using namespace std;

int main()
{
	int x = 10;
	auto a = &x;
	auto* b = &x;//auto*指定必须是指针类型
	auto& c = x;

	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;

	return 0;
}

运行结果:

在这里插入图片描述

tip:使用auto声明指针时,用auto和auto没有任何区别(auto指定必须是指针),但用auto声明引用类型时必须加&。

(2)auto在同一行定义多个变量

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

代码示例:

int main()
{
	auto a = 1, b = 2;
	auto c = 3, d = 13.14;//编译失败,因为c和d的初始化表达式类型不同
	return 0;
}

在这里插入图片描述

4、auto不能推导的场景

(1)auto不能作为函数的参数

void TestAuto(auto a){}

此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导。

(2)auto不能直接用来声明数组

void TestAuto()
{
	auto a[] = {1, 2};
}

编译失败,auto不能声明数组,因为auto类型不能出现在顶级数组类型中。

四、基于范围的for循环(C++11)

1、范围for的语法

在以前(C++98),如果要遍历一个数组,我们是按照下面的方式实现:

#include<iostream>
using namespace std;

int main()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	//通过下标访问
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		arr[i] *= 2;
	}
	//通过指针访问
	for (int* p = arr; p < arr + sizeof(arr) / sizeof(arr[0]); p++)
	{
		cout << *p << endl;
	}
	return 0;
}

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还容易犯错。因此C++11中引入了基于范围的for循环

范围for的语法格式如下:

for(auto e : array)

for循环后的括号由冒号“:”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

现在我们使用范围for来遍历数组,修改数组:

#include<iostream>
using namespace std;

int main()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	//范围for与引用结合:修改数组
	for (auto& e : arr)
	{
		e *= 2;
	}
	//范围for:打印数组
	for (auto e : arr)
	{
		cout << e << endl;
	}
	return 0;
}

总结:

①使用范围for遍历数组与以前相比,用起来非常方便,不易出错。所以范围for是一个语法糖。

②适用于数组,依次取数组中的数据赋值给变量e,自动迭代,自动判断结束。

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

2、范围for的使用条件

(1)for循环迭代的范围必须是确定的

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

错误演示:以下代码中for的范围是不确定的

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

解读:数组传参,实际只能接收数组的首元素地址,这里我们并不知道数组的范围,所以报错。

(2)迭代的对象要实现++和==的操作。(后期讲解,大家先知道即可)

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

1、C++98中的指针空值

在良好的C/C++编程习惯中,声明一个变量时最好给变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。

如果一个指针没有合法的指向,我们就需要将其置为空。

代码演示:

int main()
{
	int* p1 = NULL;
	int* p2 = 0;
	return 0;
}

为什么0也可以将指针置为空呢?

答案是:NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

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

可以看到,NULL可能别被定义为字面常量0,或者被定义为无类型指针(void)的常量。

不论采用哪种定义,在使用空值的指针时,都不可避免的遇到一些麻烦,如下:

#include<iostream>
using namespace std;

//参数类型是整形
void f(int)
{
	cout << "f(int)" << endl;
}
//参数类型是整形指针
void f(int*)
{
	cout << "f(int*)" << endl;
}

int main()
{
	//调用f函数,观察
	f(0);
	f(NULL);
	f((int*)NULL);
	return 0;
}

运行结果:

在这里插入图片描述

解读:

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

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

③形参因为我们只是观察参数匹配规则,所以可以只写形参类型。

NULL这样太尴尬,所以C++11引入了nullptr

2、nullptr

先看一段代码示例:

#include<iostream>
using namespace std;

//参数类型是整形
void f(int)
{
	cout << "f(int)" << endl;
}
//参数类型是整形指针
void f(int*)
{
	cout << "f(int*)" << endl;
}

int main()
{
	//调用f函数,观察
	f(NULL);
	f(nullptr);
	return 0;
}

运行结果:

在这里插入图片描述

总结:

①为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

②在使用nullptr表示空值时,不需要包含头文件,因为nullptr是C++11作为关键字引入的。

③在C++11中,sizeof(nullptr)与sizeof((void*)0)所占字节数相同。

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

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

相关文章

数据结构---HashMap和HashSet

HashMap和HashSet都是存储在哈希桶之中&#xff0c;我们可以先了解一些哈希桶是什么。 像这样&#xff0c;一个数组数组的每个节点带着一个链表&#xff0c;数据就存放在链表结点当中。哈希桶插入/删除/查找节点的时间复杂度是O(1) map代表存入一个key值&#xff0c;一个val值…

原型与原型链

一、原型&#xff1a;prototype 1.什么是原型&#xff1f; javascript常被描述为一种基于原型的语言&#xff08;每个对象都拥有一个原型对象&#xff09; 当访问一个对象的属性时&#xff0c;它不仅在该对象上寻找&#xff0c;还会寻找该对象的原型&#xff0c;以及该对象原…

【Java】人工智能交互智慧导诊系统源码

随着人工智能技术的快速发展&#xff0c;语音识别与自然语言理解技术的成熟应用&#xff0c;基于人工智能的智慧导诊导医逐渐出现在患者的生活视角中&#xff0c;智能导诊系统应用到医院就医场景中&#xff0c;为患者提供导诊、信息查询等服务&#xff0c;符合智慧医院建设的需…

Java利用反射和读取xml实现迷你容器

由于需要框架能实现多态&#xff0c;达到控制反转解耦。所以容器还是需要的&#xff0c;容器的存在可以简化对象获取工作&#xff0c;但是容器也不是万能的。合理使用即可&#xff0c;Spring对我来说太庞大了&#xff0c;用不着&#xff0c;为此给框架写一个迷你版容器。 容器…

Netty的高性能基石ByteBuf

前言 ​ NIO中缓冲区是数据传输的基础&#xff0c;JDK通过ByteBuffer实现&#xff0c;Netty框架中并未采用JDK原生的ByteBuffer,而是构造了ByteBuf。 ​ Netty中的ByteBuf对ByteBuffer做了大量的优化&#xff0c;比如说内存池&#xff0c;零拷贝&#xff0c;引用计数&#xf…

QFileDialog 文件对话框

文章目录 1、简介2、公共类型3、属性4、functions1、访问属性相关 function2、静态公共成员1、 通过对话框获取用户选择的文件路径&#xff1a;QFileDialog::getOpenFileName2、 通过对话框获取用户选择的文件夹路径&#xff1a;QFileDialog::getExistingDirectory 3、Public F…

家庭资产配置

不同家庭的资产配置 理财就是理人生 为人生的每件事&#xff0c;准备好相应的钱 生存的事 生活费 假设我们今年30岁&#xff0c;则至60岁期间所需的日常生活开支为&#xff1a; 4000元/月X 12月X30年144万 养老的事 养老费 吃饭居住娱乐其他开销60至80岁期间所需的养老…

用Wokwi仿真ESP-IDF项目

陈拓 2023/10/21-2023/10/21 1. 概述 Wokwi是一个在线的电子电路仿真器。你可以使用它来仿真Arduino、ESP32、STM32和许多其他流行的电路板、元器件以及传感器&#xff0c;免去使用开发板。 Wokwi提供基于浏览器的界面&#xff0c;您可以通过这种简单直观的方式快速开发一个…

农产品农货经营小程序商城的作用是什么

农产品行业涵盖的产品很多&#xff0c;以小麦、稻子、玉米、高粱等为主&#xff0c;还有粮油、果蔬、畜牧等产品。 自建技术团队&#xff0c;耗时耗力&#xff0c;培养成本较高&#xff0c;销售渠道单一、等客上门、产品无法高效宣传及促进用户购买&#xff0c;营销力不足&…

会声会影2023官方破解版激活码

随着短视频、vlog等媒体形式的兴起&#xff0c;视频剪辑已经成为了热门技能。甚至有人说&#xff0c;不会修图可以&#xff0c;但不能不会剪视频。实际上&#xff0c;随着各种智能软件的发展&#xff0c;视频剪辑已经变得越来越简单。功能最全的2023新版&#xff0c;全新视差转…

JavaWeb学生管理系统(详细源码+解析)

​ 很多人大学的第一个小项目就是使用JavaWeb实现了一个学生管理系统或者是图书管理系统。在没有项目经验的情况下&#xff0c;前后端都需要自己去完成&#xff0c;还是要耗费不少时间精力的。本次我就分享一下我在大学期间完成的第一个小项目&#xff1a;学生管理系统。采用的…

2023年【司钻(钻井)】及司钻(钻井)作业模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 司钻&#xff08;钻井&#xff09;是安全生产模拟考试一点通生成的&#xff0c;司钻&#xff08;钻井&#xff09;证模拟考试题库是根据司钻&#xff08;钻井&#xff09;最新版教材汇编出司钻&#xff08;钻井&#…

Tuxera NTFS2023破解版苹果电脑磁盘读写工具

当您获得一台新 Mac 时&#xff0c;它只能读取 Windows NTFS 格式的 USB 驱动器。要将文件添加、保存或写入您的 Mac&#xff0c;您需要一个附加的 NTFS 驱动程序。Tuxera 的 Microsoft NTFS for Mac 是一款易于使用的软件&#xff0c;可以在 Mac 上打开、编辑、复制、移动或删…

性能优化:JIT即时编译与AOT提前编译

优质博文&#xff1a;IT-BLOG-CN 一、简介 JIT与AOT的区别&#xff1a; 两种不同的编译方式&#xff0c;主要区别在于是否处于运行时进行编译。 JIT:Just-in-time动态(即时)编译&#xff0c;边运行边编译&#xff1a;在程序运行时&#xff0c;根据算法计算出热点代码&#xf…

CVE-2019-1388 UAC提权实战

1.查看用户权限&#xff1a;guest来宾权限 2.右键-以管理员身份运行&#xff1a; 3.这个时候会弹出UAC&#xff0c;不用管它&#xff0c;点击&#xff1a;显示详细信息 4.然后点击蓝色字体&#xff1a;显示有关此发布者的证书信息 5.来到证书信息这里&#xff0c;点击颁发着…

线程是如何在 6 种状态之间转换的?

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 今天我们主要学习线程是如何在 6 种状态之间转换的。 线程的 6 种状态 就像生物从出生到长大、最终死亡的过程一样&#xff0c;线程也有自己的生命周期&#xff0c;在 Java 中线程的生命周期中一共有 6 种状态。 …

揭开 Amazon Bedrock 的神秘面纱 | 基础篇

在 2023 年 4 月&#xff0c;亚马逊云科技曾宣布将 Amazon Bedrock 纳入使用生成式人工智能进行构建的新工具集。Amazon Bedrock 是一项完全托管的服务&#xff0c;提供各种来自领先 AI 公司&#xff08;包括 AI21 Labs、Anthropic、Cohere、Stability AI 和 Amazon 等&#xf…

Python学习第一天-安装Python

文章目录 前言一、下载Python二、执行安装程序三、命令行验证总结 前言 以下榜单来自于TIOBE编程语言流行指数 不多说了&#xff0c;Python天下第一 一、下载Python 从官网下载Python安装程序 二、执行安装程序 找到python-3.12.0-amd64.exe执行&#xff0c;选择Install …

基于Python3的Scapy构造DNS报文

一&#xff1a;DNS协议 DNS&#xff08;Domain Name System&#xff09;协议是计算机网络中的一种基础协议&#xff0c;它用于将域名&#xff08;如www.baidu.com&#xff09;转换为IP地址&#xff08;如192.168.0.1&#xff09;&#xff0c;从而实现计算机之间的通信。 DNS 分…

【Unity地编细节】为什么Unity笔刷在地形上面刷不出来

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…