c/c++开发,无可避免的函数参数实践

news2024/9/22 23:18:52

一、函数参数表

        函数由函数名以及一组操作数类型唯一地表示。函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号分隔。每一个函数都有一个相关联的返回类型。

int setval(int val)
{
    //函数体
};

        这里,定义了一个名为 setval的函数,该函数返回一个 int 型值,并带有一个 int型形参。这个形参就类似于一个局部变量,为函数提供了已命名的局部存储空间,它是在函数的形参表中定义的,并由调用函数时传递函数的实参初始化。形参和实参都是函数参数,只是出现时机不一样:

        1)形参:在函数定义()中出现的参数,形参就是拷贝实参的值,随着函数的调用才产生,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放而销毁。
        2)实参:在函数调用()中出现的参数(外界实际存在的值),实参可以是常量、变量或表达式,无论实参是何种类型的量,在进行函数调用时,都必须具有确定的值,以便把这些值传送给形参。

        函数形参表可以为空,没有任何形参的函数可以用空形参表或含有单个关键字 void 的形参表来表示,但建议采用第二种方式。

void dosomething();        //空参数表,可行
void dosomething(void);    //建议

        形参表由一系列用逗号分隔的参数类型和(可选的)参数名组成。如果两个参数具有相同的类型,则其类型必须重复声明。参数表中不能出现同名的参数,参数名是可选的,但在函数定义中,通常所有参数都要命名,参数必须在命名后才能使用,否则在函数实现内毫无意义。

int setval(int val_a,int val_b);    //正确
int setval(int val_a,val_b);        //错误
int setval(int val_a,int val_a);    //错误
//无参数名也能通过但参数不能使用,毫无意义
int setval(int)
{
	return 0;
};

        在函数调用时,对于每一个实参,其类型都必须与对应的形参类型相同,或具有可被转换为该形参类型的类型。编译器在编译时会检查函数的实参。

int setval(int val_a,int val_b)
{
    return val_a+val_b;
};

int a = 10;
int b = 11;

setval(a,b);      //
setval(a,12);     //编译器检查识别为正确参数类型
setval(a,(int)&b);//强制转换了参数类型
setval(a,12.3);   //编译器自动转换了参数类型,基础类型向下转换,等于setval(a,12)
setval(a);        //参数太少
setval(a,b,10);   //参数太多
setval(a,&b);     //参数类型不对(int*)

        程序运行过程中,在调用函数时,会重新创建该函数所有的形参,若形参具有非引用类型,则复制实参的值,若形参为引用类型,它就只是实参的别名。

二、非引用形参

        非引用类型的参数在函数调用时,通过复制对应的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。

int changeVal(int val)
{
    val+=5;            //该语句仅限于局部参数,不会改变实参本身
    return val;
};

int val_arg = 10;
int val_ret = 0;
val_ret=changeVal(val_arg);    
printf("%d,%d\n",val_arg,val_ret);    //10,15

        非引用形参表示对应实参的局部副本。对这类形参的修改仅仅改变了局部副本的值。一旦函数执行结束,这些局部变量就会被释放。

        指针作为形参时,和普通变量一样,该指针形参本身与其他非引用类型的形参一样,也仅作用于局部副本。如果函数调用时,将某指针赋给形参,在函数使用的该实参指针的值改变,对外部的指针本身没影响。

void set_ptr(int *pval)
{
    pval = NULL;
};

int val = 10;
printf("%0X\n",&val);    //61FF0C,本人编辑环境下
set_ptr(&val);
printf("%0X\n",&val);    //61FF0C,可见函数只是改变了局部副本,对本身无影响

        当然指针作为形参,其指向内容是可以被改变的,因为形参指针虽然是局部副本,但其指向内容和外部指针本身指向内容是一致的。

void set_ptr(int *pval)
{
	*pval = 100;            //注意先赋值哦
    pval = (int*)0X61FF10;  //0X61FF0C+0X04,注意根据上一步得到的地址做偏移赋值哦
};

int val = 10;
printf("%0X,%d\n",&val,val);    //61FF0C,10
set_ptr(&val);
printf("%0X,%d\n",&val,val);    //61FF0C,100

        那么如果我们实际项目中为了防止只想函数内局部使用指针,但不想改变指针指向的值呢,答案就是通过const关键字来限制。

void only_set_ptr(const int *pval)
{
	*pval = 100;            //error,不能给指针指向内容赋值
    pval = (int*)0X61FF10;  //OK,0X61FF0C+0X04
};

        既然谈到const,再延展开了,看看其对基础类型非引用形参的影响,如下文。

int changeVal(int val)
{
    val+=5;
    return val;
};

int const_Val(const int val)
{
    //val += 10;	//error
    return val+5;
};

const int val_const = 10;
//编译器会做转换,const ->常规,构建一个副本const int _val_const,并用val_const的值在初始化时赋值给_val_const
changeVal(val_const);
int val = 10;      
//编译器会做转换,常规 ->const,即函数调用时,构建一个副本const int _val,并用val的值在初始化时赋值给_val      
const_Val(val);       

        因为,const 对象的标准初始化规则是定义时必须初始化。因此,函数调用时,非引用类型的参数初始化复制了实参初始化式的值,实参仍然是以副本的形式传递,无论是const 形参还是非 const 形参,都是复制实参的副本而已,这就是非引用形参特性。

三、引用形参

        设想当需要以大型对象作为实参传递时(非引用形参)。对实际的应用而言,复制对象所付出的时间和存储空间代价如何规避,这就要用到引用形参了。对于大型对象、容器、数组等作为参数的情况,或者实参无法复制时,又或者对于需要修改实参时,这些都可以通过引用形参得到解决。

void setVal_realy(int &val)
{
    val+=10;    //生效咯
};

int val_realy = 10;
printf("val_realy=%d\n",val_realy );    //val_realy=10
setVal_realy(val_realy);
printf("val_realy=%d\n",val_realy );    //val_realy=20
//setVal_realy(10);        //error,不能作为左值
int val_realy_b = 20;
//setVal_realy(val_realy + val_realy_b );    //error,同样不能作为左值

        与前面所述不同,这次通过&直接关联到其所绑定的真实值,而并非这些对象的副本。定义引用形参时,必须用与该引用绑定的对象初始化该引用,每次调用函数,引用形参被创建并与相应实参关联起来,操作引用参数就相当于操作实参本身。虽然前讲述过,通过传递指针这个非引用形参也能实现对实参的间接访问,但使用引用形参则更安全和更自然。

        若既想作为引用形参,又想防止其在函数内部被改变赋值,就用const修饰吧。将不应不修改参数定义为 const 引用,限制函数的对参数的使用,降低异常风险。

void setVal_realy(const int &val)
{
    //val+=10;    //不生效咯
    int in_val = val;
};
//又或
int get_substr_pos(const std::string &str,const char &c)
{
    int ret = 0;
    while(ret != str.size() && str[ret ] != c)
	{
		++ret;
	} 
    return ret;
};

in val = 10;
setVal_const(val);
setVal_const(10);           //若const不限制,函数内赋值呢 
get_substr_pos("abcd",'c');

        上述例子不好体现其价值,若是向函数传递大型对象时,需使用引用形参,这就大不同了。虽然复制实参对于内置数据类型的对象或者规模较小的类类型对象来说没有什么问题,但是对于大部分的类类型或者大型数组,它的效率(通常)太低了;使用引用形参,只想函数可以直接访问实参对象,而无须复制它或改变它。

class A
{
public:
    A(){
        memset(data,0,10000);
    };
private:
    int data[10000];
};

void do_Something(const A &val)
{
    //your code
};

        对于那些大型数据,传递引用要不传递实参获得更高的效率。例如,现需要打印一个map容器的内容,通过引用形参或非引用形参都能实现,但如果从效率来看,还是建议采用引用形参吧。

//void print_map(std::map<int,int> maps) //OK,比较与下一句的区别
void print_map(std::map<int,int> &maps)
{
	std::map<int,int>::iterator it = maps.begin();
	while (it != maps.end())
	{
		printf("key=%d,val=%d\n",it->first,it->second);
		it++;
	}
}

//
std::map<int,int> maps_;
maps_[4] = 2;
maps_[1] = 3;
maps_[2] = 4;
print_map(maps_);

       针对容器对象作为形参,有时还可以引用 Iterator(迭代器),它是一种Cursor(游标)模式,提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。由iterator提供的方法访问聚合对象中的各个元素,达到引用形参的效果。

void print_map(std::map<int,int>::iterator begin,std::map<int,int>::iterator end)
{
	std::map<int,int>::iterator it = begin;
	while (it != end)
	{
		printf("key=%d,val=%d\n",it->first,it->second);
		it++;
	}
}

std::map<int,int> maps_;
maps_[4] = 2;
maps_[1] = 3;
maps_[2] = 4;
print_map(maps_.begin(),maps_.end());

 四、数组参数-特例说明

         前面我们提到,有些对象是不允许复制的,例如数组,组不能复制,所以数组名在作为参数时,会被编译器转换,数组名会自动转化为指向其第一个元素的指针。但是仅传递了首元素的指针,长度就是一个不可忽略的痛点了,毕竟数组需要知道长度才好处理信息,最好能显式传递数组大小。

void print_group(int g[]);
void print_group(int g[10]);
//以上编译器都能通过, 但建议采用下列方式,就像我们main函数一样int main(int argc, char* argv[])
void print_group(int g[],int size);

        数组以普通的非引用类型传递,此时数组会悄悄地转换为指针。在传递数组时,形参复制的是指向数组第一个元素的指针的值,而不是数组元素本身。函数操纵的是指针的副本,因此不会修改实参指针的值,但可通过该指针改变它所指向的数组元素的值来修改数组元素本身。

//等同于指针类型
void print_group(int *g);
void print_group(int *g,int size);

        不需要修改数组形参的元素时,函数应该将形参定义为指向 const 对象的指针

//等同于指针类型
void print_group(const int *g);
void print_group(const int *g,int size);

        如果数组想通过引用传递给函数呢,就需要指定传递数组的引用本身,这样编译器不会将数组实参转化为指针。

void print_group(int g[],int size)
{
    for(int i=0; i<size; i++)
    {
        printf("%d ",g[i]);
    };
    printf("\n");	
};

void print_group(int (&g)[10])
{
    for(int i=0; i<10; i++)
    {
        printf("%d ",g[i]);
    };
    printf("\n");
};

int gval[9] = {1,2,3,4,5,6,7,8,9};
print_group(gval,9);
print_group(gval);		//error
	
int gval10[10] = {0,1,2,3,4,5,6,7,8,9};
print_group(gval10,9)   //OK
print_group(gval10);    //OK

        函数二只严格地接受含有 10 个 int 型数值的数组,这限制了哪些数组可以传递。&arr 两边的圆括号是必需的,因为下标操作符具有更高的优先级。

        对于多维数组作为参数来说,多维数组的元素本身就是数组。除了第一维以外的所有维的长度都是元素类型的一部分,必须明确指定,即:

void print_mgroup(int gs[][2],int First_dimension_size);
void print_mgroup(int (*gs)[2],int First_dimension_size);
//三维
void print_mgroup(int gs[][2][3],int First_dimension_size);
void print_mgroup(int (*gs)[2][3],int First_dimension_size);

五、函数指针作为参数-特例说明

        函数指针也是指针,只是一般的指针指向变量、对象、数组等,函数指针指向的是函数入口地址,其作为参数时,和普通指针作为参数是类似的。

int myFunc(int val)
{
	return val+10;
}

void print_funcRVal(int (*pfunc)(int val))
{
	printf("%d \n",pfunc(10));
}

//
int (*pf)(int val) = &myFunc;
print_funcRVal(pf);    //输出20

        当然,函数指针参数有事会很复杂,函数指针通过typedef重定义一下,增加代码阅读性。

typedef int (*pfunc)(int val);
pfunc pf_ = &myFunc;
print_funcRVal(pf);

六、不确定形参-特例说明

        c++中printf系列函数都是不确定形参的典型应用代表:

int printf(const char *format, ...);

        其省略符形参就是不确定形参,实际是使用了 varargs 实现的,例如。

void out_print(const char* lpFormat, ...)
{
	va_list args;
	char szBuffer[256] = {0};
	va_start(args, lpFormat);
	vsnprintf(szBuffer, sizeof(szBuffer), lpFormat, args);
	va_end(args); 
	std::cout << szBuffer << std::endl;
}

out_print("hello %s","world");    //输出:hello world

        在无法列举出传递给函数的所有实参的类型和数目时,可以使用省略符形参。省略符暂停了类型检查机制。它们的出现告知编译器,当调用函数时,可以有 0 或多个实参,而实参的类型未知。注意,省略符号只能放置在最后面。

void out_print( ...);                         //OK
void out_print(const char* lpFormat, ...);    //OK
void out_print(const char* lpFormat, int val, ...);    //OK

void out_print(const char* lpFormat, ..., int val);    //error
void out_print( ..., const char* lpFormat);            //error

        大部分带有省略符形参的函数都利用显式声明的参数中的一些信息,来获取函数调用中提供的其他可选实参的类型和数目。因此向第一种形式的函数声明是不建议的。

七、本文涉及源码

        构建test.cpp源文件,编译命令g++ test.cpp -o test.exe测试如下:

         test.cpp源文件

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <iostream>
#include <map>

//可行但参数无意义
int setval01(int)
{
	return 0;
};

int setval(int val_a,int val_b)
{
    return val_a+val_b;
};

int changeVal(int val)
{
    val+=5;
    return val;
};

int const_Val(const int val)
{
	//val += 10;	//error
    return val+5;
};
//
void set_ptr(int *pval)
{
	*pval = 100;			//注意先赋值哦
    pval = (int*)0X61FF10;  //0X61FF0C+0X04
};

void only_set_ptr(const int *pval)
{
	//*pval = 100;            //error,不能给指针指向内容赋值
    pval = (int*)0X61FF10;  //OK,0X61FF0C+0X04
};
//
void setVal_realy(int &val)
{
    val+=10;    //生效咯
};

void setVal_const(const int &val)
{
    //val+=10;    //不生效咯
	int in_val = val;
};

int get_substr_pos(const std::string &str,const char &c)
{
    int ret = 0;
    while(ret != str.size() && str[ret ] != c)
	{
		++ret;
	} 
    return ret;
};

class A
{
public:
    A(){
		memset(data,0,10000);
	};
private:
    int data[10000];
};

void do_Something(const A &val)
{
    //your code
};

//void print_map(std::map<int,int> maps) //OK,比较与下一句的区别
void print_map(std::map<int,int> &maps)
{
	std::map<int,int>::iterator it = maps.begin();
	while (it != maps.end())
	{
		printf("key=%d,val=%d\n",it->first,it->second);
		it++;
	}
}

void print_map(std::map<int,int>::iterator begin,std::map<int,int>::iterator end)
{
	std::map<int,int>::iterator it = begin;
	while (it != end)
	{
		printf("key=%d,val=%d\n",it->first,it->second);
		it++;
	}
}

void print_group(int g[],int size)
{
    for(int i=0; i<size; i++)
    {
        printf("%d ",g[i]);
    };
    printf("\n");	
};

void print_group(int (&g)[10])
{
    for(int i=0; i<10; i++)
    {
        printf("%d ",g[i]);
    };
    printf("\n");
};
//void print_mgroup(int gs[][2],int row_size)
void print_mgroup(int (*gs)[2],int row_size)
{
    for(int i=0; i<row_size; i++)
    {
		for(int j=0; j<2; j++)
			printf("%d ",gs[i][j]);
    };
    printf("\n");	
};

//void print_m_group(int gs[][2][3],int First_dimension_size);
//void print_m_group(int (*gs)[2][3],int First_dimension_size);

int myFunc(int val)
{
	return val+10;
}

void print_funcRVal(int (*pfunc)(int val))
{
	printf("%d \n",pfunc(10));
}
#ifdef WIN32
#define vsnprintf _vsnprintf
#endif
void out_print(const char* lpFormat, ...)
{
	va_list args;
	char szBuffer[256] = {0};
	va_start(args, lpFormat);
	vsnprintf(szBuffer, sizeof(szBuffer), lpFormat, args);
	va_end(args); 
	std::cout << szBuffer << std::endl;
}

int main(int argc, char* argv[])
{
	int a = 10;
	int b = 11;

	setval(a,b);
	setval(a,12);     //
	setval(a,(int)&b);//转换类型
	setval(a,12.0);   //参数类型不对
	//setval(a);        //参数太少
	//setval(a,b,10);   //参数太多
	
	int val_arg = 10;
	int val_ret = 0;
	val_ret=changeVal(val_arg);
	printf("%d,%d\n",val_arg,val_ret);

	int val = 10;
	printf("%0X,%d\n",&val,val);    //61FF0C
	set_ptr(&val);
	printf("%0X,%d\n",&val,val);    //61FF0C
	
	const int val_const = 10;
	changeVal(val_const);
	const_Val(val);
	//
	int val_realy = 10;
	printf("val_realy=%d\n",val_realy );    //val_realy=10
	setVal_realy(val_realy);
	printf("val_realy=%d\n",val_realy );    //val_realy=20
	setVal_const(10);
	get_substr_pos("abcd",'c');
	//
	std::map<int,int> maps_;
	maps_[4] = 2;
	maps_[1] = 3;
	maps_[2] = 4;
	print_map(maps_);
	print_map(maps_.begin(),maps_.end());
	//
	int gval[9] = {1,2,3,4,5,6,7,8,9};
	print_group(gval,9);
	//print_group(gval);		//error
	int gval10[10] = {0,1,2,3,4,5,6,7,8,9};
	print_group(gval10,9);   //OK
	print_group(gval10);    //OK
	//
	int gs[2][2] = {{1,2},{3,4}};
	print_mgroup(gs,2);   //OK
	//
	int (*pf)(int val) = &myFunc;
	print_funcRVal(pf);
	typedef int (*pfunc)(int val);
	pfunc pf_ = &myFunc;
	print_funcRVal(pf);
	//
	out_print("hello %s","world");
	return 0;
}

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

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

相关文章

Vulkan教程(14): Graphics pipeline之Fixed functions(固定管线功能)

Vulkan官方英文原文&#xff1a;https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Fixed_functions对应的Vulkan技术规格说明书版本&#xff1a; Vulkan 1.3.2The older graphics APIs provided default state for most of the stages of the graphic…

个人如何获得免费的VMware Fusion Player?在macOS上虚拟化系统

不管是开发还是测试&#xff0c;有时候都需要虚拟机。比如虚拟化一台Linux&#xff0c;部署Web服务进行服务器仿真操作。亦或者&#xff0c;macOS上虚拟化Windows&#xff0c;进行Windows环境模拟。 VMware这家公司&#xff0c;大家应该都比较熟悉。旗下的VMware Workstation在…

LabVIEW NI网络设备在MAX中不显示或未识别

LabVIEW NI网络设备在MAX中不显示或未识别有一个NI设备通过网络连接到主机。发生以下情况之一&#xff1a;尝试在Measurement&#xff06;AutomationExplorer&#xff08;MAX&#xff09;中配置设备。设备未显示在“远程系统”下。NIMAX中未检测到CompactRIO&#xff08;cRIO&a…

2D图像处理:2D Shape_Base_Matching_缩放_旋转_ICP_显示ROI

文章目录 调试结果参考调试说明问题0:并行运行问题问题1:模板+Mask大小问题问题2:组合缩放和旋转问题3:可以直接将计算边缘的代码删除问题4:如何在原始图像上显示匹配到的ROI问题5:计算的原始旋转角度不需要判断,直接可以在ICP中使用问题6:绘制坐标轴问题7:绘制ROI调试…

图像优化篇

目录&#xff08;1&#xff09;矢量图&#xff08;2&#xff09;位图 2.1 分辨率2&#xff0c;图像格式格式选择建议&#xff1a;&#xff08;1&#xff09;矢量图 被定义为一个对象&#xff0c;包括颜色&#xff0c;大小&#xff0c;形状&#xff0c;以及屏幕位置等属性&…

Netty实现Http服务器案例

功能&#xff1a;Netty服务器在6668端口监听&#xff0c;浏览器发出请求"http://localhost:6668"服务器可以恢复消息给浏览器&#xff1a;“hello&#xff0c;我是服务器”&#xff0c;并对特定请求资源进行过滤目的&#xff1a;Netty可以做服务器端开发&#xff0c;…

家政服务小程序实战教程07-轮播图组件

小程序中首页一般显示轮播图的功能&#xff0c;点击轮播图会跳转到具体的一篇文章或者是产品&#xff0c;本篇我们就介绍一下轮播图功能的开发 01 设计数据源 我们轮播图组件需要两个字段&#xff0c;一个是展示的图片&#xff0c;一个是跳转页面传入的参数。打开数据源&…

JAVA集合专题5 ——ArrayDeque + BlockingQueue

目录ArrayDeque的特点BlockingQueue什么是BlockingQueue?什么叫阻塞队列?阻塞队列的应用场景是什么?BlockingQueue的阻塞方法是什么?BlockingQueue的四类方法codecode2ArrayDeque的特点 ArrayDeque是Deque接口子实现ArrayDeque数据结构可以表示为: 队列、双端队列、栈Arra…

【MFC】工具条(16)

创建工具条的基本步骤是&#xff1a; 1.创建工具条资源。 2.构建一个CToolBar对象。 3.调用CToolBar::Create函数创建工具条窗口。 4.调用CToolBar::LoadToolBar载入工具条资源。 使用工具条 打开资源视图&#xff0c;可视化创建或者修改工具条&#xff1a; 其中ID项一般与菜…

【计组】硬盘--《深入浅出计算机组成原理》(十二)

目录 一、机械硬盘 二、SSD硬盘 &#xff08;一&#xff09;SSD硬盘的读写原理 1、SLC、MLC、TLC 和 QLC 2、P/E 擦写问题 &#xff08;二&#xff09;SSD 读写的生命周期 &#xff08;三&#xff09;磨损均衡、TRIM 和写入放大效应 1、FTL 和磨损均衡 2、TRIM 指令的…

vueday01-脚手架安装详细

一、vue脚手架安装命令npm i -g vue/cli 或 yarn global add vue/cli安装上面的工具&#xff0c;安装后运行 vue --version &#xff0c;如果看到版本号&#xff0c;说明安装成功或 vue -V工具安装好之后&#xff0c;就可以安装带有webpack配置的vue项目了。创建项目之前&#…

用队列实现栈VS用栈实现队列

之前我们就讲过队列&#xff0c;栈的基础知识&#xff0c;笔者之前有过详细的介绍&#xff0c;感兴趣的可以根据笔者的个人主页进行查找&#xff1a;https://blog.csdn.net/weixin_64308540/?typelately225. 用队列实现栈请你仅使用两个队列实现一个后入先出&#xff08;LIFO&…

加入bing体验chatGPT大军中来吧,它来了!

1 第一步&#xff1a;加入候选名单 1、首先需要加入候选名单 https://www.microsoft.com/zh-cn/edge?formMA13FJ 2、下载最新的Edge浏览器、androd、iOS都有试用版本&#xff08;可以看到iOS加护当前已满&#xff09; 这里我下载的是dev版本&#xff0c;Canary版本由于是…

50. Pow(x, n)

50. Pow(x, n) 一、题目描述&#xff1a; 实现 pow(x, n) &#xff0c;即计算 x 的整数 n 次幂函数&#xff08;即&#xff0c;xn &#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;1024.00000 示例 2&#xff1a; 输入&#xff1a;x 2…

决策树分类算法(一)(信息熵,信息增益,基尼指数计算)

目录一、信息量二、信息熵三、信息增益四、基尼指数五、代码&#xff1a;信息熵&#xff0c;信息增益&#xff0c;基尼指数计算&#xff08;splitInfo.py&#xff09;例子&#xff1a; 一、信息量 : I(x)log⁡21p−log⁡2pI(x)\log_{2}{\frac{1}{p}}-\log_{2}{p}I(x)log2​p1…

傻白探索Chiplet,Modular Routing Design for Chiplet-based Systems(十一)

阅读了Modular Routing Design for Chiplet-based Systems这篇论文&#xff0c;是关于多chiplet通信的&#xff0c;个人感觉核心贡献在于实现了 deadlock-freedom in multi-chiplet system&#xff0c;而不仅仅是考虑单个intra-chiplet的局部NoC可以通信&#xff0c;具体的一些…

如何判断两架飞机在汇聚飞行?(如何计算两架飞机的航向夹角?)内含程序源码

ok&#xff0c;在开始一切之前&#xff0c;让我先猜一猜&#xff0c;你是不是想百度“二维平面下如何计算两个移动物体的航向夹角&#xff1f;”如果是&#xff0c;那就请继续往下看。 首先&#xff0c;我们要明确一个概念&#xff1a;航向角≠航向夹角&#xff01;&#xff0…

微信小程序图书馆座位预约管理系统

开发工具&#xff1a;IDEA、微信小程序服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8项目构建&#xff1a;maven数据库&#xff1a;mysql5.7前端技术&#xff1a;vue、uniapp服务端技术&#xff1a;springbootmybatis本系统分微信小程序和管理后台两部分&#xff0c;项目采用…

家政服务小程序实战教程05-集成腾讯地图

我们在用户注册数据源部分设计了一个地图字段&#xff0c;地图字段在生成页面的时候对应着地图定位组件 要调用地图定位组件&#xff0c;需要先创建API&#xff0c;点击新建API 选择腾讯地图 创建API的时候需要输入API key 进入到腾讯位置服务里申请Key 首先需要创建一个应用 创…

Go 全栈博客实战项目 gin-vue-blog

Go 全栈博客 gin-vue-blog写在前面博客介绍在线预览项目介绍技术介绍目录结构环境介绍线上环境开发环境VsCode 插件快速开始拉取项目前的准备 (Windows)方式一&#xff1a;Docker Compose 一键运行方式二&#xff1a;常规运行项目部署总结鸣谢后续计划写在前面 这个项目其实已经…