【C++】C++入门第二课(函数重载 | 引用 | 内联函数 | auto关键字 | 指针空值nullptr)

news2024/11/26 6:21:19

目录

前言

函数重载

概念

重载函数的条件

C++支持重载函数的原理--名字修饰

引用

概念

特性

常引用(const引用)

使用场景

传值,传引用效率比较

引用和指针的区别

内联函数

概念

特性

auto关键字(C++11)

auto简介

auto的使用规则

指针空值nullptr(C++11)

C++98中的指针空值

结语


前言

这里是C++入门的第二课,主要还是补补C语言之前遗留下来的缺陷,在学习完本篇博客的内容之后,就可以进入激动人心的类和对象环节了。看过标题大家应该也知道要讲什么,话不多说,咱们开始今天的内容。

函数重载

自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重
载了。比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”

函数重载,简单来说就是允许定义同名函数,可以通过所传参数的数量和类型来判断运行哪一个函数,接下来咱们看具体定义。

概念

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

注:形参列表(参数 1.类型 2.个数或或 3.类型顺序)不同,都可以是重载的条件。

重载函数的条件

1.重载参数类型不同

#include<iostream>
using namespace std;
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}
double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return left + right;
}

int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 1.2) << endl;
	return 0;
}

代码的形参一份为整型,另一份为浮点型。当调用函数传入的是整型数据时,调用第一份声明形参为整型的代码;如果调用函数传入的是浮点型数据,则调用声明形参为浮点型的代码。 

2.参数个数不同

#include<iostream>
using namespace std;
void f()
{
	cout << "f()" << endl;
}
void f(int a)
{
	cout << "f(int a)" << endl;
}

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

当调用函数不传入参数时,调用无参版的函数;当调用函数传入参数时,调用含参的重载函数。 

3.参数类型顺序不同

#include<iostream>
using namespace std;
void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
	cout << "f(char b, int a)" << endl;
}

int main()
{
	f(1, 'm');
	f('m', 1);
	return 0;
}

运行时会自动匹配最合适的重载函数。 

满足上述三点条件中任意一点重载函数都定义在同一作用域内,就可以构成重载。 

 注:返回值不同不能构成重载,只能通过上述讲的参数不同进行重载。

如果返回值可以重载,会造成巨大的问题,程序无法从调用处区分最终函数运行结束后会产生什么类型的返回值,因此用返回值区分重载本身就是不可行不可靠的。

调用函数查找匹配的时候,编译器自动会匹配最符合的重载函数。

C++支持重载函数的原理--名字修饰

为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接

C++支持函数重载的原理,很大程度上依赖于“名字修饰”(name Mangling)的过程。这种机制使得编译器能够区分同名但参数列表不同的函数。

名字修饰是编译器自动进行的一种处理过程,它将C++源码中的函数名和变量名转换成包含更多信息的唯一标识符。这些信息通常包括函数的参数类型,参数数量等,甚至可能包括所属的类名(对于类成员函数),通过这种方式,每个重载的函数都会被赋予一个独一无二的名字,确保在最后链接的时候能够区分出它们。

C++中允许函数重载,也就是允许一个作用域中存在多个同名函数,只要他们的参数列表不同,在编译成目标代码之后,所有的函数名都能通过修饰出的不同的名字而区分开来,确保了每个函数的调用都能显示的映射到正确的函数体上。名字修饰通过在函数名中编码函数类型等信息,实现了这一点。

名字修饰实例

下面有一组重载函数:

void Fun(int a, int b);
void Fun(double a, double b);

经过编译器的处理修饰,这些函数最后可能被处理为(不同编译器修饰规则不同,具体修饰结果取决于编译器)

  • Fun(int, int) 可能被修饰为 _23Funii_
  • Fun(double, double) 可能被修饰为 _23Fundd_

通过这种修饰方式,尽管这两个函数的名字相同,但在编译器的处理后它们获得了不同的名字,使得编译后的代码能够轻松识别出不同的重载函数。

名字修饰使得C++能够有效的支持重载和模板等功能,虽然这种机制对程序员来说是透明的,但理解其背后的原理对于深入掌握C++很有帮助。同时,这也是C++与C语言的一个重要区别:C语言不支持重载函数,大部分原因在于它没有采用类似的名字修饰机制。

我们来看看在Linux环境下用不同编译器编译相同代码对函数名的处理结果:

采用C语言编译器编译后的结果

在Linux下,采用gcc编译完成后,函数名字的修饰没有发生改变

采用C++编译器编译的结果

在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中
通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修
饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
到这里应该可以感受到,在C++中,编写代码方便的原因是大部分工作C++的编译器都帮你做掉了。这也使得匹配类型变慢,编译速度降低。但代码运行的是指令,这种匹配只会影响一部分编译速度,而不用担心会拖累代码运行的速度

引用

概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间。
比如:一个男孩,在家中被父母叫“儿子”,在学校里被叫“同学”。虽然称呼不同,但是最终的对象还是同一个人。引用就是一种给变量取别名的语法。

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

#include<iostream>
using namespace std;
int main()
{
	int a = 8;
	int& b = a;//定义引用类型
	cout << a << " " << b << endl;
	++a;
	cout << a << " " << b << endl;
	++b;
	cout << a << " " << b << endl;
    cout << &a << " " << &b << endl;
	return 0;
}

要注意的是,引用的类型必须和引用实体同种类型的。

特性

引用有几点特性:

  1. 引用在定义时必须要初始化
  2. 一个变量可以有多个引用
  3. 一旦引用一个实体,不能再引用其他实体(就跟变量不能重定义一样)
void TestRef()
{
	int a = 10;
	// int& ra; 这句语句在引用时未初始化,报错
	int& b = a;
	int& c = a;
	int& d = b;
	// int& d = a; 报错,重定义
}

常引用(const引用)

关于常引用,就是用const修饰的引用,只需要记住,在使用引用的时候,权限可以不变和缩小,但一定不能放大。这里的规则和指针极其相似。

void TestConstRef()
{
	const int a = 10;
	//int& ra = a; 该语句编译时会出错,a为常量
	const int& ra = a;
	// int& b = 10; 该语句编译时会出错,b为常量
	const int& b = 10;
	double d = 12.34;
	//int& rd = d; 该语句编译时会出错,类型不同
	const int& rd = d;//const引用支持不同类型
}

注:创建引用类型前加上const,可以在不同类型之间取别名,第三个案例中的rd里面的值为12。

使用场景

1.做参数

在用C语言中编写函数交换两个变量之间的值时,需要传指针才能实现,这时因为C语言函数传参是传值传递,传过去的只是原数据的拷贝,只有通过传址直接找到存储数据空间的地址,才能从函数内部影响到外部的数据。C++的引用成功的解决了此类问题,也就是常说的传引用。

#include<iostream>
using namespace std;

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}

int main()
{
	int a = 5;
	int b = 8;
	cout << a << " " << b << endl;
	Swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}

引用也是极大的减少了代码中指针的使用比例,也是补了C语言指针难用复杂的一个大坑。

2.做返回值

#include<iostream>
using namespace std;
int& Count()
{
	static int digit = 0;
	digit++;
	return digit;
}
int main()
{
	int& d = Count();
	cout << d << endl;
	Count();
	cout << d << endl;
	d++;
	cout << d << endl;
	return 0;
}

main函数中的变量d和函数内部的digit使用的空间是同一块,当里面digit++的时候外面d的值也会++,得益于Count函数的传引用返回static静态变量的创建(静态变量的生命周期存在于整个程序运行当中),这里传引用返回是可行的。

再来看这样一个案例,下面的代码中想想会输出什么结果?

#include<iostream>
using namespace std;
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;
	return 0;
}

c作为函数中的一个临时变量,在出了Add函数后会自动销毁,保存c数据的那部分空间此时就不存在了,这时候使用传引用返回,接收时就是一块未定义的空间,其性质就跟野指针一样

如果想更深入的理解此部分内容,可以看看我曾写过的一篇文章,里面细致讲解了函数栈帧的创建和销毁:
关于函数栈帧的创建和销毁-CSDN博客

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

传值,传引用效率比较

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

下方TestFun1是传引用,TestFun2是传值:

下方TestFun1是传引用返回,TestFun2是传值返回:

通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大

这种效率差距在传递次数多,拷贝量大的情况下变得尤为明显。

引用和指针的区别

语法概念上引用就是一个别名,没有独立的空间,和其引用实体共用一块空间。
但其在底层上实际是有空间的,因为是按照指针的方式来实现的。

int main()
{
	int a = 10;
	int& ra = a;
	ra = 20;
	int* pa = &a;
	*pa = 20;
	return 0;
}

我们可以来看看上面这段代码的汇编:

会发现,引用和指针的汇编代码竟然一模一样!

引用和指针的不同点:

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

内联函数

概念

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

如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的
调用,其功能和C语言的宏相似。

在用inline修饰Add函数之后,代码就不会通过call调用函数,而是用函数体代码直接做添加。

特性

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
    用函数体替换函数调用
    ,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
    行效率。
  2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
    议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、
    是递归、且频繁调用
    的函数采用inline修饰,否则编译器会忽略inline特性。《C++prime》第五版关于inline的建议:
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
    了,链接就会找不到。

内联是一种用空间换取时间的方式,编译出来的可执行程序可能会变大:

举个例子,Swap函数如果有10行代码:

  • 如果不选择使用inline:则代码量为---> Swap + 调用(call) = 10 + 1000(call) 行指令。
  • 如果选择使用inline:则代码量为---> Swap * Swap指令数 = 10 * 1000 行指令。

故内联适合小函数,如果调用函数代码量大且调用次数多还选择使用内联,会导致代码量爆炸式增长。但是这个问题其实不用我们操心,编译器会自动选择是否将函数内联,你提供的inline对编译器来说只是一个建议,不起决定性作用。

内联函数其实是补C语言中宏的一个坑。由于宏的暴力替换,没有类型安全的检查,同时导致代码可读性差,可维护性差等原因,C++的祖师爷想到了用内联函数这样的方法去解决此问题。

auto关键字(C++11)

随着程序变得越来越复杂,程序中用到的类型也越来越复杂,经常会出现,类型难以拼写或含义不明确导致出错的情况。

#include <string>
#include <map>
int main()
{
	std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange",
	"橙子" },{"pear","梨"} };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

在上面的这一段代码中,std::map<std::string, std::string>::iterator 就是一个类型,但是其类型太长,写起来非常麻烦且及其容易写错。虽然可以使用typedef取别名的方式,像这样:

typedef std::map<std::string, std::string> Map;

但是在实际中还是会遇到,typedef也解决不了的问题。

typedef char* pstring;
int main()
{
	//const pstring p1; 编译失败,p1底层为char* const p1而非const char* p1 
	const pstring* p2; // 编译成功,底层为char * const* p2
	return 0;
}

虽然第二条命令编译成功了,但p2的类型显然不是我们想要的。

在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的
类型。然而有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义(之前版本auto有一个定义,但是基本没人用)。

auto简介

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

#include<iostream>
using namespace std;
int TestAuto()
{
	return 10;
}
int main()
{
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = TestAuto();
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
	return 0;
}

typeid(变量).name()是一个返回变量类型字符串的函数,我们可以借此观察 b c d 用auto声明过后的类型。

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

auto的使用规则

可以

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

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

2.在同一行定义多个变量

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

不可以

1.auto不能作为函数参数

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

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

指针空值nullptr(C++11)

C++98中的指针空值

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

int* p1 = NULL;
int* p2 = 0;

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

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

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

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的
初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器
默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void
*)0。

所以在C++11中,就引入了nullptr指针空值,作为一个新关键字引入到C++体系之中。同时,为了提高代码的健壮性,后续使用指针空值时建议最好使用nullptr

结语

到这里,C++的入门部分总算是结束了。本篇博客讲到了C++相比于C新增的内容:函数重载,可以编写同名函数,但需要参数类型有区分度;讲到了引用,提供一种取别名的方式减少指针的使用,以及与传值相比的效率优越性;内联函数,用空间换时间,但是否内联取决于编译器本身;auto关键字,简化我们声明函数类型的过程,减少错误发生;指针空值nullptr,替换NULL提高程序的健壮性。

感谢大家的支持,博主后续会产出更多有意思的内容!♥

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

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

相关文章

javaWeb项目-家政服务管理系统功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、B/S结构简介 B/S…

OpenHarmony实战:使用宏、std::bind 巧妙实现进出函数日志打印

背景 我们始终渴望了解模块的调用、时序逻辑&#xff0c;每个人都会轻易地想到在函数的入口打印一条进入 enter 相关的日志&#xff0c;在函数的出口打印一条离开 leave 相关的日志。不能有遗漏&#xff0c;我们会复制这条日志到所有关心的函数中&#xff0c;为了表明是哪个模…

网络以太网之(1)基础概念

网络以太网之(1)基础概念 Author: Once Day Date: 2024年4月1日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文档可参考专栏&#xff1a;通信网络技术_Once-Day的…

EI会议相当于国内什么期刊?

EI工程索引收录类型有EI期刊和EI会议&#xff0c;EI会议水平是比较高的&#xff0c;也有的作者喜欢将EI会议与国内刊物作比较&#xff0c;那么EI会议相当于国内什么期刊&#xff1f;这主要看是什么级别的会议&#xff0c;顶尖学术会议相当于国内北大核心期刊。 EI会议应用范围和…

python怎么处理txt

导入文件处理模块 import os 检测路径是否存在&#xff0c;存在则返回True&#xff0c;不存在则返回False os.path.exists("demo.txt") 如果你要创建一个文件并要写入内容 #如果demo.txt文件存在则会覆盖&#xff0c;并且demo.txt文件里面的内容被清空&#xff0c;如…

系统架构图怎么画

画架构图是架构师的一门必修功课。 对于架构图是什么这个问题&#xff0c;我们可以按以下等式进行概括&#xff1a; 架构图 架构的表达 架构在不同抽象角度和不同抽象层次的表达&#xff0c;这是一个自然而然的过程。 不是先有图再有业务流程、系统设计和领域模型等&#…

哈希表(Hash Table) -- 用数组模拟--字符串前缀哈希

本文用于个人算法竞赛学习&#xff0c;仅供参考 目录 一.什么是哈希表 二.哈希函数中的取模映射 三.拉链法&#xff08;数组实现&#xff09; 四.拉链法模板 五.开放寻址法 六.开放寻址法模板 七.字符串前缀哈希 九.字符串前缀哈希 模板 十.题目 一.什么是哈希表 哈希表&…

fork复制进程

1.shell: 在计算机科学中&#xff0c;Shell俗称壳&#xff08;用来区别于核&#xff09;&#xff0c;是指“为使用者提供操作界面”的软件&#xff08;command interpreter&#xff0c;命令解析器&#xff09;。它类似于DOS下的COMMAND.COM和后来的cmd.exe。它接收用户命令&am…

C语言中入门到实战————动态内存管理

目录 前言 一、为什么要有动态内存分配 二、 malloc和free 2.1 malloc 2.2 free 三、calloc和realloc 3.1 calloc 3.2 realloc 四. 常见的动态内存的错误 4.1 对NULL指针的解引用操作 4.2 对动态开辟空间的越界访问 4.3 对非动态开辟内存使用free释放 4.4 使…

2024年【T电梯修理】考试题及T电梯修理考试内容

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【T电梯修理】考试题及T电梯修理考试内容&#xff0c;包含T电梯修理考试题答案和解析及T电梯修理考试内容练习。安全生产模拟考试一点通结合国家T电梯修理考试最新大纲及T电梯修理考试真题汇总&#xff0c;有助…

Collection与数据结构链表与LinkedList(三):链表精选OJ例题(下)

1. 分割链表 OJ链接 class Solution {public ListNode partition(ListNode head, int x) {if(head null){return null;//空链表的情况}ListNode cur head;ListNode formerhead null;ListNode formerend null;ListNode latterhead null;ListNode latterend null;//定义…

计算机网络数据链路层知识总结

物理层知识总结传送门 计算机网络物理层知识点总结-CSDN博客 功能 功能概述 一些基本概念 结点:主机、路由器链路﹔网络中两个结点之间的物理通道&#xff0c;链路的传输介质主要有双绞线、光纤和微波。分为有线链路、无线链路。数据链路︰网络中两个结点之间的逻辑通道&a…

Transformers -- 深入研究 - part 3

公众号:Halo咯咯,欢迎关注~ 前文回顾: Transformers -- 以通俗易懂的方式解释 - Part 1Transformers -- 未知英雄 - Part 2世界正在为人工智能和生成式人工智能而疯狂,特别是 2023 年的 ChatGPT 和大型语言模型。在我们讨论本系列后续部分的技术细节之前,让我们先从它的想…

移植day3

思维导图 重点README文档 1、解压tf-a源码 $> tar xfz tf-a-stm32mp-2.2.r2-r0.tar.gz 2、进入tf-a源码目录 $> cd tf-a-stm32mp-2.2.r2 3、打补丁命令&#xff0c;作用&#xff1a;补丁文件中存放默认的一些配置文件&#xff0c;对于程序员来说&#xff0c;需要将补丁…

【c++初阶】类与对象(下)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

【前端】使用Web Audio API 技术播放音乐

简言 记录下使用web audio播放音乐的方法。 Web Audio API Web Audio API 提供了在 Web 上控制音频的一个非常有效通用的系统&#xff0c;允许开发者来自选音频源&#xff0c;对音频添加特效&#xff0c;使音频可视化&#xff0c;添加空间效果&#xff08;如平移&#xff09…

CUDA从入门到放弃(十三):C++语言扩展 C++ Language Extensions

CUDA从入门到放弃&#xff08;十三&#xff09;&#xff1a;C语言扩展 C Language Extensions 1 Function Execution Space Specifiers 函数执行空间指定符 这些指定符定义函数是在主机还是设备上执行&#xff0c;以及它们是否可以跨平台调用。 1-1 __global__ __global__指…

健身房预约管理系统(源码+文档)

健身房预约管理系统&#xff08;小程序、ios、安卓都可部署&#xff09; 文件包含内容程序简要说明含有功能&#xff1a;项目截图客户端首页我的预约登录教练预约时间我的注册页个人资料课程预约课程预约 管理端订单管理团课管理教练管理分类管理用户管理 文件包含内容 1、搭建…

[数据结构]排序

本篇主要是对常见排序的分类和一些排序的详解 一、插入排序 1.直接插入排序 // 直接插入排序函数 void insertionSort(int arr[], int n) {int i, key, j;for (i 1; i < n; i) {key arr[i]; // 取出待排序的元素j i - 1;// 将大于key的元素向后移动一位while (j > 0…

6.5 Batch Normalization

在训练神经网络时&#xff0c;往往需要标准化&#xff08;normalization&#xff09;输入数据&#xff0c;使得网络的训练更加快速和有效。 然而SGD&#xff08;随机梯度下降&#xff09;等学习算法会在训练中不断改变网络的参数&#xff0c;隐藏层的激活值的分布会因此发生变…