【C++修炼之路】C++入门(中)—— 函数重载和引用

news2024/12/26 3:42:21

👑作者主页:@安 度 因
🏠学习社区:StackFrame
📖专栏链接:C++修炼之路

文章目录

  • 一、前言
  • 二、函数重载
    • 1、重载规则
    • 2、函数名修饰规则
  • 三、引用
    • 1、区分
    • 2、本质
    • 3、特性
    • 4、应用
      • a、做参数
      • b、做返回值
    • 5、效率比较
    • 6、常引用
    • 7、指针和引用区别
  • 四、结语

如果无聊的话,就来逛逛 我的博客栈 吧! 🌹

一、前言

小伙伴们好,我是 a n d u i n anduin anduin . 今天我们继续讲解 C++ 入门的知识,内容主要为两大块:函数重载和引用 ,这两块在 C++ 中可谓是重量级选手,特别是引用,学完使用会很舒适。虽然引用的点很多,但是没关系, a n d u i n anduin anduin 对它全方面进行了讲解。

话不多说,我们开始学习吧!

二、函数重载

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

对于c语言是不允许重名函数的存在的,当函数名字相同时,就会报错。但是对于 c++ 可以。

1、重载规则

当函数重载条件满足如下三条时,则可以构成函数重载:

  1. 参数类型不同
  2. 参数个数不同
  3. 参数类型顺序不同

对于 c++ ,同名函数是允许存在的:

// 参数类型不同
int add(int left, int right)
{
	return left + right;
}

int add(double left, double right)
{
	return left + right;
}

// 参数个数不同
int add(double left, double right, int mid)
{
	return left + right;
}

// 参数类型顺序不同
int add(int left, char right)
{
	return left + right;
}

int add(char right, int left)
{
	return left + right;
}

函数重载需要在同一个命名空间。

对于相同类型的数据,顺序不同,不构成函数重载,因为编译器无法识别:

int add(int right, int left)
{
	return left + right;
}

int add(int left, int right)
{
    return left + right;
}

因为函数重载是根据类型识别的。

2、函数名修饰规则

对于函数重载后的函数,执行会不会变慢?不会,因为不是在运行时匹配,而是在编译时。

编译时如何进行识别?

对于编译而言,调用函数处会变成 call + add(地址) 的形式,然后通过汇编指令完成调用。

那么调用函数时,如何找到这个函数?

对于C语言来说,就是依靠函数名去找函数的,如果函数名相同,则会冲突,因为不知道找哪个.

证明修饰规则:

image-20230128181759027

对于C++来说,不同平台就有不同的修饰规则,对于 vs 上比较复杂,暂且不谈;这里我们讲 linux 上的:

int add(int left, int right)
{
	return left + right;
}

对于这个函数,就会被修饰为 _Z3addii

  • _Z 是前缀
  • 3 是函数名长度
  • ii 代表参数类型的首字母

当编译时,就拿修饰以后的函数的名字去找,找到了就可以调用了。所以只要参数类型,个数,顺序不同均可以满足,因为此刻修饰后的函数名是可以被区分的。

从这里也可以看出为什么参数类型相同但是顺序不同不可以构成重载:因为识别不了。

Linux 下修饰规则(重要)格式: _ Z + 函数名称长度 + 函数名 + 类型首字母 \_Z+函数名称长度+函数名+类型首字母 _Z+函数名称长度+函数名+类型首字母

证明:

code:

#include <iostream>

using namespace std;

int add(int left, int right) {}

int main()
{
	return 0;
}

编译自定义名称为 mytest :

image-20230128182829828

使用 objdump -S exeName 查看修饰规则,exeName 为可执行程序名称:

image-20230128183043943

发现名字是符合修饰规则的。

修改代码,再次验证:

#include <iostream>

using namespace std;

int add(int left, int right) {}

int adddd(int left, double right) {}

char subb(double* left, int right) {} // 验证指针

int main()
{
	return 0;
}

对于指针参数,则会在参数类型首字母前加上大写P修饰,P代表point,表示它是个指针

image-20230128184359089

对于相同名字的函数,函数重载就根据参数的类型,顺序,个数,以这些为基准,来区别不同的函数。

而根据上面的验证,我们也知道为什么 返回值不同参数类型相同但顺序不同 为什么不能构成函数重载的原因:

因为对于 参数类型相同但顺序不同,形成的后缀还是一样的 ,并不能区分该调用哪个函数;而对于返回值不同的其他都相同的函数来说,则是因为分不清调用哪个函数,不仅仅是因为函数返回值不在修饰规则内。

比如 int add() 和 double add() ,在函数调用时,我该调用哪个?编译器在这时候就错乱了,根本上是语法层面的问题。

Windows 下修饰规则(简单了解)

image-20230128182025880

去除函数定义,主函数调用后报错:

image-20230128181924616

三、引用

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

别名,又可以说是外号,代称,比如水浒传里几乎是别名最多的地方。李逵,在家称为"铁牛",江湖上人称"黑旋风"。铁牛和黑旋风就是李逵的别名。

1、区分

& 就是引用,但是& 这个操作符和取地址 & 操作符是重叠的。

所以它们需要不同的场景规范:

&b 单独存在时,这时就代表取地址,为取出变量的地址。

但是如果这样:

int main()
{
	int a = 10;
	int& b = a; // 引用

	int* p = &b; // 取地址

	return 0;
}

& 位于类型和变量名之间时,为引用。

2、本质

调试查看一下 a 和 b 的关系:

image-20230128200705864

我们发现a和b不仅值相等,连地址也是相同的。而这就说明,b 就是 a ,在语法层面上,这里 b 并不是开辟的新空间,而是对原来的 a 取了一个新名称,叫做 b

就好比李逵被叫做黑旋风一样,李逵还是李逵,黑旋风也是它;而 a 就是 a ,但是 b 也是 a

image-20230128201339079

而如果这时候对 a 或 b 任意一个修改,那么 a 和 b 都会发生修改。

image-20230128201712339

3、特性

引用有三个注意点。

1) 引用必须在定义时初始化

image-20230128203648434

引用是取别名,所以在定义的时候必须明确是谁的别名。

2)一个变量可以有多个引用

就和李逵一样,他可以叫黑旋风也可以叫铁牛,这都是它。

所以一个变量也可以有多个别名。

image-20230128203843816

而对于一个起过别名的变量,对它的别名取别名也是可以的。

就好比说有人可能知道李逵也叫铁牛,并不知道他真实姓名,但是他觉得李逵很黑,于是叫他黑旋风,这也没问题,因为这里描述的都是一个人,同理,描述的也是同一个变量。

image-20230128204130467

而从根本上看,就可以这么理解:

image-20230128204221383

本质上还是一个变量。

但是别名不能和正式名字冲突,就比如取过别名,就不能定义和别名重命的变量,即使它们的类型不同。

image-20230128205100610

但是这里的报错信息并不准确,实际上为命名冲突。

3)引用一旦引用一个实体,就不能引用其他实体

int main()
{
	int a = 10;
	int& b = a;

	int c = 20;
	b = c;

	return 0;
}

对于下一组代码,有什么含义?

  1. 让 b 变成 c 的别名?
  2. 还是把 c 赋值给 b ?

这里的代码意思是第二个含义,就是赋值,我们调试看看:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ewhy1pGV-1675308403233)(https://anduin.oss-cn-nanjing.aliyuncs.com/%E5%BC%95%E7%94%A81.gif)]

调试我们也可以看到,我们只是把 c 的值赋值给了 b ,b 的地址还是没变的 ,并且 a 的值也改变了。

这就说明引用一旦引用一个实体,就不能引用其他实体,引用是不会发生改变的

因为它们是完全独立的两个变量,仅有的关联也只是值相等,改变 b 并不能影响 c ,但是此时 b 是 a 的别名,所以改变 b 会影响 a 。

图:

image-20230128211658314

但是对于指针,则是截然不同的:

int main()
{
	int a = 10;
	int c = 20;

	int* p = &a;
	p = &c;

	return 0;
}

对于指针来说,指针可以时刻修改:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9KSddSMf-1675308403233)(https://anduin.oss-cn-nanjing.aliyuncs.com/%E5%BC%95%E7%94%A82.gif)]
p原本指向 a ,现在指向 c .

但是引用也有局限性,因为引用之后的变量是不可修改引用的,比如链表,节点是要不断更替迭代的,所以还需要指针配合,C++才可以写出一个链表

4、应用

a、做参数

我们知道实参的改变不影响实参,所以这种写法并不能改变值,因为此刻是 传值调用

image-20230128212733541

按照之前 c 的写法,我们使用 传址调用 ,用指针修改:

image-20230128212838245

但是学习引用之后,完全可以用引用修改

image-20230128213028237

x 和 y 分别是 a 和 b 的引用,对 x 和 y 进行修改,就是对 a 和 b 进行修改,所以值也被修改成功了。

调试看一下:

image-20230128213641052

它们的地址是完全相同的。而这里这里既不是传值调用,也不是传址调用,而是 传引用调用

思考:上面三个函数是否构成函数重载? 构成,但无法调用

根据函数名修饰规则,传值和传引用的是不一样的,比如会加上 R 做区分

但是不能同时调用传值和传引用,因为有歧义,因为 调用不明确 ,编译器并不知道调用哪个:

image-20230128214924534

引用解决二级指针生涩难懂的问题

讲单链表时,我们写的由于是没有头结点的链表,所以修改时,需要二级指针,对于指针概念不清晰的小伙伴们可能比较难理解。

但是学了引用,就可以解决这个问题:

结构定义:

typedef struct SListNode
{
	int data;
	struct SListNode* next;
}SLTNode;

原代码:

void SListPushFront(SLTNode** pphead, SLTDateType x)
{
	SLTNode* newnode = BuyListNode(x);
	newnode->next = *pphead; 
	*pphead = newnode;
}

// 调用
SLTNode* pilst = NULL;
SListPushFront(&plist);

修改后:

void SListPushFront(SLTNode*& pphead, SLTDateType x) // 改
{
	SLTNode* newnode = BuyListNode(x);
	newnode->next = *pphead; 
	*pphead = newnode;
}

// 调用
SLTNode* pilst = NULL;
SListPushFront(plist); // 改

修改之后的代码里的二级指针被替换成了引用。

而这里的意思就是给一级指针取了一个别名,传过来的是plist,而plist 是一个一级指针,所以会出现 * ,而这里就相当于 pphead 是 plist 的别名。而这里修改 pphead ,也就可以对 plist 完成修改。

但是有时候也会这么写

结构改造:

typedef struct SListNode
{
	int data;
	struct SListNode* next;
}SLTNode, *PSLTNode;

这里的意思就是将 struct SListNode* 类型重命名为 PSLTNode

代码:

void SListPushFront(PSLTNode& pphead, SLTDateType x) // 改
{
	PSLTNode newnode = BuyListNode(x);
	newnode->next = pphead; 
	pphead = newnode;
}

// 调用 
PSLTNode plist = NULL;
SListPushFront(plist);

在 typedef 之后,PSLTNode 就是结构体指针,所以传参过去,只需要在形参那边用引用接收,随后进行操作,就可以达成目的。

而形参的改变影响实参的参数叫做输出型参数,对于输出型参数,使用引用十分舒适

如果了解引用,那么这一部分是相当好理解的,一些数据结构教科书上也是这么写的,但是如果不懂引用,甚至会觉得比二级指针还难以理解。

在我们学习了引用之后,之后也可以这么写代码,更加方便。

b、做返回值

要搞清楚这一块,我们先进行一些铺垫。

int add(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int ret = add(1, 2);
	cout << ret << endl;

	return 0;
}

这里看似很简单,就是把add函数计算结束的结果返回,但是这里包含了 传值返回

若从栈帧角度看,会先创建 main 函数的栈帧,里面就会有 call 指令,开始调用 add 函数。而 add 函数也会形成栈帧,而栈帧中也有两块小空间,用来接受参数,分别为 a 和 b,而里面的 c 则用来计算结果并返回。

image-20230128224420670

而对于传值返回,返回的并不是 c ,而是返回的是 c 的拷贝。而这其中会有一个临时变量,返回的是临时变量(见函数栈帧)

如果返回的是 c 的话,由于 add 的函数栈帧已经销毁了,就会产生很多奇怪的问题。c 能不能取到都是未知,而这时都是非法访问,因为空间已经被归还给系统了,所以必定是c拷贝后的数据被返回。

但是临时变量在哪?

  • 如果 c 比较小(4/8 byte),一般是寄存器充当临时变量,例如eax
  • 如果 c 比较大,临时变量放在调用 add 函数的栈帧中,

最后将临时变量中的值赋值给ret

图:

image-20230128225254172

所有的传值返回都会生成一个拷贝

便于理解,看一下汇编:

image-20230128225920666

看第四句话,这里是说,把 eax 中的值,拷贝到 ret 中。

而再函数调用返回时:

image-20230128230035833

这里是将 c 的值放到 eax 中的。

这也就印证了返回时,是以临时拷贝形式返回的,由于返回值是 int ,所以是直接用的 eax 寄存器。

而不论这个函数结束后,返回的那个值会不会被销毁,都会创建临时变量返回,例如这段代码

int c()
{
	static int n = 0;
    n++;
    return n;
}

int main()
{
	int ret = c();
	cout << ret << endl;

	return 0;
}

对于该函数,编译器仍然是创建临时变量返回;因为编译器不会对其进行特殊处理。

看一下汇编:

image-20230202090124151

仍然是放到 eax 寄存器中返回的。

埋个伏笔:你觉不觉的这个临时变量创建的很冤枉,明明这块空间一直存在,我却依然创建临时变量返回了?能不能帮它洗刷冤屈。


如果我改成引用返回会发生什么情况吗?

int& add(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int ret = add(1, 2);
	cout << ret << endl;

	return 0;
}

引用返回就是不生成临时变量,直接返回 c 的引用。而这里产生的问题就是 非法访问

造成的问题

  1. 存在非法访问,因为 add 的返回值是 c 的引用,所以 add 栈帧销毁后,会访问 c 位置空间,而这是读操作,不一定检查出来,但是本质是错的。
  2. 如果 add 函数栈帧销毁,空间被清理,那么取 c 值时取到的就是随机值,取决于编译器的决策。

ps:虽然vs销毁栈帧没有清理空间数据,但是会二次覆盖

来看个有意思的:

image-20230128231841269

例如这里,当调用 add 函数之后,返回 c 的引用,接收返回值是用的ret相当于是 c 的引用,这时由于没有清理栈帧数据,所以打印3;

但是第二次调用,重新建立栈帧,由于栈帧大小相同,第二次建立栈帧可能还是在原位置,之前空间的数据被覆盖,继续运算,但是此时,ret 那块空间的值就被修改了,而这时没有接收返回值,但是原先的那块 c 的值被修改,所以打印出来 ret 是 30 。

所以使用引用返回时,一旦返回后,返回值的空间被修改,那么都可能会造成错误,使用要小心!

引用返回有一个原则:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

它俩的区别就是一个生成拷贝,一个不生成拷贝。

而这时 static 修饰的静态变量不委屈了

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

因为 static 修饰的变量在静态区,出了作用域也存在,这时就可以引用返回。

我们可以理解引用返回也有一个返回值,但是这个返回值的类型是 int& ,中间并不产生拷贝,因为返回的是别名。这就相当于返回的就是它本身。

有时引用返回可以发挥出意想不到的结果

#include <cassert>
#define N 10

typedef struct Array
{
	int a[N];
	int size;
}AY;

int& PostAt(AY& ay, int i)
{
	assert(i < N);

	return ay.a[i];
}

int main()
{
	AY ay;
	PostAt(ay, 1);	
    // 修改返回值
	for (int i = 0; i < N; i++)
	{
		PostAt(ay, i) = i * 3;
	}
	
	for (int i = 0; i < N; i++)
	{
		cout << PostAt(ay, i) << ' ';
	}

	return 0;
}

由于PostAt 的形参 ay 为 main 中 局部变量 ay的别名,所以 ay 一直存在;这时可以使用引用返回。

引用返回 减少了值拷贝 ,不比将其拷贝到临时变量中返回;并且由于是引用返回,我们也可以 修改返回对象

image-20230202094500414

总结提炼:如果出了作用域,返回变量(静态,全局,上一层栈帧,malloc等)仍然存在,则可以使用引用返回

5、效率比较

值和引用的作为返回值类型的性能比较:

#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();

	return 0;
}

由于传值返回要拷贝,所以当拷贝量大,次数多时,比较耗费时间;而传引用返回就不会,因为返回的就是别名。

image-20230129092445760

对于返回函数作用域还在的情况,引用返回优先。

引用传参和传值传参效率比较

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

还是引用快,因为引用减少拷贝次数:

image-20230129100552326

总结:引用的作用主要体现在传参和传返回值

  1. 引用传参和传返回值,在有些场景下可以提高性能(大对象 and 深拷贝对象 – 之后会讲)。
  2. 引用传参和传返回值,在对于输出型参数和输出型返回值很舒服。说人话就是形参改变,实参也改变 or 返回对象(返回值改变)。

6、常引用

const 修饰的是常变量,不可修改。

image-20230129101402496

a 本身都不能修改,b 为 a 的引用,那么 b 也不可以修改,这样就没意义了。a 是只读,但是引用 b 具有 可读可写 的权利,该情况为 权限放大 ,所以错误了。

这时,只要加 const 修饰 b ,让 b 的权限也只有只读,使得 权限不变 ,就没问题了:

image-20230129101640052

而如果原先变量可读可写,但是别名用 const 修饰,也是可以的,这种情况为 权限缩小

image-20230129101753498

对于函数的返回值来说,也不能权限放大,例如:

int c()
{
	static int n = 0;
    n++;
    return n;
}

int main()
{
    int& ret = c(); // error
    
    return 0;
}

这样也是不行的,因为返回方式为 传值返回 ,返回的是临时变量,具有 常性 ,是不可改的;而引用放大了权限,所以是错误的;这时加 const 修饰就没问题:const int& ret = c(1, 2)

那么这种情况为什么不可以?

image-20230202110312938

而这样就可以了?

image-20230202110348444

因为类型转换会产生临时变量

对于类型转换来说,在转换的过程中会产生一个个临时变量,例如 double d = i,把i转换后的值放到临时变量中,把临时变量给接收的值d

而临时变量具有常性,不可修改,引用就加了写权限,就错了,因为 权限被放大了

提炼:对于引用,引用后的变量所具权限可以缩小或不变,但是不能放大(指针也适用这个说法)

作用

在一些场景下,假设 x 是一个大对象,或者是深拷贝对象,那一般都会用引用传参,减少拷贝,如果函数中不改变 x ,尽量用 const 引用传参。

image-20230129102412361

这样可以防止 x 被修改 ,而对于 const int& x 也可以接受权限对等或缩小的对象,甚至为常量

image-20230129102604491

结论

  • const type& 可以接收各种类型的对象(变量、常量、隐式转换)。对于输出型参数用引用,否则用 const type&,更加安全。

7、指针和引用区别

从语法概念上来说,引用是没有开辟空间的,而指针是开辟了空间的,但是从底层实现上来说,则又不一样:

int main()
{
	int a = 10;
	
	int& ra = a;
	ra = 20;

	int* pa = &a;
	*pa = 20;
	return 0;
}

汇编:

image-20230202111422435

lea 是取地址:我们发现无论引用和指针,都会取地址,且这些过程和指针一样。

其实从汇编上,引用其实是开空间的,并且实现方式和指针一样,引用其实也是用指针实现的

区别汇总:

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

7, 8, 9点说明指针使用起来更复杂一些,更容易出错;而引用则相对简单。

四、结语

到这里,本篇文章就到此结束了。

今天的内容就两块,其实讲讲还是很多的,特别是引用,要点很多,但是用起来那是相当的舒服。所以小伙伴们,赶快用起来吧!

如果觉得 a n d u i n anduin anduin 写的不错的话,可以点赞 + 收藏 + 评论支持一下哦!

那么我们下期见!

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

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

相关文章

windows安装cnpm

文章目录1 cnpm简介2 cnpm 安装步骤1 cnpm简介 npm下载速度比较慢&#xff0c;可以通过cnpm下载node包 2 cnpm 安装步骤 找到nodejs的安装路径&#xff1a; 使用nvm安装和管理node 直接安装node的方式 直接通过安装包安装node的方式&#xff0c;node会被安装在某个实际目录下…

docker pull nginx

取最新版的 Nginx 镜像 docker pull nginx:latest 查看本地镜像 使用以下命令来查看是否已安装了 nginx&#xff1a; $ docker images 运行容器 安装完成后&#xff0c;我们可以使用以下命令来运行 nginx 容器&#xff1a; $ docker run --name nginx-door -p 8080:80 -…

VS code的使用指南

VS code的使用指南 VS code的安装与下载 VS Code的安装地址的 在下面选择版本的位置进行安装&#xff08;一般选择Stable进行安装&#xff0c;这个软件是跨系统的安装与设置相关的内容信息&#xff09;。 插件安装 直接点击这个位置,选择自己需要的相应的插件 这些都是常用的…

软件工程(四)——结构化设计、模块独立性、面向对象设计、软件测试与维护

目录 一、界面设计 二、结构化设计 1.概要设计 2模块独立性 三、面向对象设计 1.面向对象设计的五大基本原则(SOLID)和其他5个原则 2.设计模式 三、软件测试与维护 1.白盒测试和黑盒测试 2.测试的阶段 3.软件维护阶段 四、系统演化策略 一、界面设计 人机界面设计&…

50条必背JAVA知识点(一)

1.编写&#xff1a;编写的Java代码保存在以“.java”结尾的源文件中。 2.编译&#xff1a;使用javac.exe命令编译java源文件&#xff0c;生成字节码文件。格式&#xff1a;javac 源文件名.java 3.运行&#xff1a;使用java.exe命令解释运行字节码文件。格式&#xff1a;java …

深入浅出PyTorch-PyTorch的主要组成模块

目录1.基本配置2.数据读入3.模型构建3.1神经网络的构造3.2神经网络中常见的层常见网络层的构造常见的网络层3.3模型示例卷积神经网络&#xff08;LeNet&#xff09;深度卷积神经网络&#xff08;AlexNet&#xff09;4.模型初始化5.损失函数6.训练和评估深度学习和机器学习在流程…

python小游戏——塔防小游戏代码开源

♥️作者&#xff1a;小刘在这里 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽放&#xff0c;愿所有的美好&#…

TCP/IP网络编程——优雅的断开套接字的连接

完整版文章请参考&#xff1a; TCP/IP网络编程完整版文章 文章目录第 7 章 优雅的断开套接字的连接7.1 基于 TCP 的半关闭7.1.1 单方面断开连接带来的问题7.1.2 套接字和流&#xff08;Stream&#xff09;7.1.3 针对优雅断开的 shutdown 函数7.1.4 为何要半关闭7.1.5 基于半关闭…

AST入门与反混淆初体验

文章目录1.什么是AST&#xff1f;2. AST反混淆的目的3. babel库安装4. 直观的理解AST5.如何用AST解混淆&#xff1f;思路是什么&#xff1f;6. babel库的学习7. AST反混淆初体验-常量折叠1.什么是AST&#xff1f; ​ 在计算机科学中&#xff0c;抽象语法树&#xff08;Abstrac…

【服务器数据恢复】raid5中3块磁盘先后掉线的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某单位同友存储设备&#xff0c;该存储有大于5台的虚拟机&#xff0c;其中有3台linux系统虚拟机存储重要数据。存储设备中组建的raid5由于未知原因崩溃导致存储无法启动。 存储结构&#xff1a; 服务器数据恢复过程&#xff1a; 1、对…

C语言进阶——字符函数和字符串函数(上)

目录 一、前言 二、正文 1.求字符串长度 ♥strlen 2.长度不受限制的字符串函数 ♥strcpy ♥strcat ♥strcmp 三、结语 一、前言 一日不见&#xff0c;如隔三秋&#xff1b;几日不见&#xff0c;甚是想念。猜想小伙伴们在平常进行有关字符的练习时遇到有关字符的操作却无从下手…

GEC6818 移植 rtl8723bu wifi驱动

文章目录1. 配置内核2、RTL8723BU 模块驱动编译2.1 下载解压2.2 配置编译3. openssl 移植3.1 下载解压3.2 配置3.3 编译安装4. libnl 移植4.1 下载解压4.2 进入源码目录并配置4.3 编译安装5. wpa_supplicant 移植5.1 解压源码5.2 配置5.3 make编译6. 启动wifi网卡6.1 配置WiFi连…

Python调用Go语言编译的动态链接库(CGO)【待续】

CGO C 语言作为一个通用语言&#xff0c;很多库会选择提供一个 C 兼容的 API&#xff0c;然后用其他不同的编程语言实现。Go 语言通过自带的一个叫 CGO 的工具来支持 C 语言函数调用&#xff0c;同时我们可以用 Go 语言导出 C 动态库接口给其它语言使用。 hello world程序 p…

【定时同步系列11】各种开环定时同步算法与MCRB性能对比的MATLAB仿真

重要声明:为防止爬虫和盗版贩卖,文章中的核心代码和数据集可凭【CSDN订阅截图或公z号付费截图】私信免费领取,一律不认其他渠道付费截图! 引言 开环定时同步包括内插控制、定时误差估计等环节,其中内插控制在之前的博客里有详细的描述,请翻阅之前的博客! 【定时同步系…

Python编程技巧分享:6 个必知必会高效 Python

编写更好的Python 代码需要遵循Python 社区制定的最佳实践和指南。遵守这些标准可以使您的代码更具可读性、可维护性和效率。 本文将展示一些技巧&#xff0c;帮助您编写更好的 Python 代码 遵循 PEP 8 风格指南 PEP 8 是 Python 代码的官方风格指南。它提供了一组用于格式化…

Maven parent多项目打包找不到reversion变量问题

项目结构&#xff1a;packagetest&#xff1a;顶级父级c1&#xff1a;子项目&#xff08;web项目&#xff09;c2&#xff1a;子项目(jar包)c1依赖c2的jar包。在父级maven中deploy成功&#xff0c;package也成功&#xff0c;私服上有都有包了。但是在c1上package的时候&#xff…

二十、操纵管道

本章将讨论如何在应用程序中使用多种方式操作管道。本章的部分内容都是很底层的&#xff0c;所以在开始阅读之前&#xff0c;请确保你需要一些编程知识&#xff0c;并对GStreamer有很好的理解。 这里将讨论的主题包括如何从应用程序向管道中插入数据&#xff0c;如何从管道中读…

电商项目之Mailgun邮件退信率升高

文章目录1 项目背景2 前言3 采取的措施4 排查的思路5 原因6 技术实现方案1 项目背景 运维人员进行日常巡检发现Mailgun邮件代发渠道的退信率不寻常&#xff0c;在某个时间段会飙高。这会有2个影响&#xff1a; &#xff08;1&#xff09;针对运维的角度&#xff0c;Mailgun官方…

3.SpringBoot配置

一、IOC的好处在对象依赖关系复杂的对象中&#xff0c;只需要考虑“我自己”依赖什么&#xff0c;减少程序员思考负担方便的进行依赖概念&#xff08;接口、抽象类&#xff09;的实现类对象的替换。二、什么是SpringBoot?1.SprigBoot是Spring的一个子工程&#xff0c;目标就是…

JS逆向技巧汇总---给普通爬虫学习者的吐血建议

爬虫实战的JS逆向&#xff0c;就像是做侦探。很多时候&#xff0c;我们要尝试不同方式和手段寻找线索&#xff0c;不能放过蛛丝马迹&#xff0c;通过仔细观察和比较&#xff0c;然后顺藤摸瓜&#xff0c;找到加密入口。再调试JS代码的时候&#xff0c;需要保持清晰的目标和方向…