首页 > 其他分享 >十九、函数(二)

十九、函数(二)

时间:2023-12-01 23:55:05浏览次数:18  
标签:return 函数 int char str include 十九 strRt

十九、函数(二)

1、函数参数之接受不定量参数

1)普通函数不定量传参用法

//接受不定量参数的函数
#include <cstdarg>       //引入头文件cstdarg
int Add(unsigned count, ...)  //第一个参数为参数的个数,第二个参数为三个.
{
	int rt{};
	char* c_arg; //声明一个指针变量
	va_start(c_arg, count); //将参数数据指针赋值给c_arg
	for (int i = 0; i < count; i++) rt += va_arg(c_arg, int);
	va_end(c_arg);  //释放指针
	return rt;
}

std::cout << Add(5, 1, 2, 3, 4, 5); //函数参赛数,需要依次传入各个参数

2)示例:计算多个数的平均值

//通过不定量参数函数,求多个数的平均数
#include <iostream>
#include <cstdarg>

int Average(unsigned count, ...)
{
	va_list  arg;          //va_list 是一个char类型的指针,相当于char* arg;
	va_start(arg, count);  //第一个参数为接受数据的指针,第二个参数为参数的个数,目的是为了将参数的地址放到arg中。此处做了一个内存分配给arg
	int sum{};
	
	for (int i{}; i < count; i++)
	{
		//注:每调用一次va_arg()函数,都会将参数切换至下一个;va_arg()第一个参数为指针,第二个参数为参数的类型         
		sum += va_arg(arg, int);   //相当于把arg当成int类型的值解读,每解读一个,则切换为下一个
		std::cout << "arg地址:" << (int)arg << std::endl;  //本质还是利用了连续的内存空间
	}
	va_end(arg);  //释放arg内存
	sum = sum / count;

	return sum;
}

int main()
{
	int x = Average(5, 221, 331, 202, 555, 776);
	std::cout << "平均数为:" << x << std::endl;
}


3)自己设计一个函数,计算多个数的平均值

//自己设计一个函数,计算多个数的平均值
#include <iostream>

struct Sarg
{
	int count; //统计参数的个数
	char* cMem; //参数的地址
};
int Avg(Sarg& y)
{
	int sum{};
	int* arg = (int*)y.cMem;
	for (int i = 0; i < y.count; i++)
	{
		sum += arg[i];
	}
	return sum / y.count;
}

void main()
{
	Sarg y;
	y.count = 5;
	y.cMem = (char*)new int[5]{ 221, 331, 202, 555, 776 };
	int x = Avg(y);
	std::cout << "平均数为:" << x << std::endl;

}

2、函数返回之返回指针和引用

1)项目设计:设计一个函数,能够让我们直接为c字符串赋值

//设计一个函数,能够让我们直接为c字符串赋值,如
char* str;
str = cstr("你好");
std::cout<<str;
//输出你好
//直接使用强制类型转化,将一个字符串进行赋值
#include <iostream>

int  main()
{
	char* str;
	str = (char*)"你好";  //强制类型转化,"你好"是一个常量;强行的使得str指向了"你好"的地址
	//str没有自己的内存空间,str只是"你好"字符串的一个副本
	std::cout << str << std::endl;
	//str[0]=0;  不允许修改值,因为指向的是一个常量的内存地址
}
//通过函数输出字符串
#include <iostream>

//求字符串占用多少内存的函数
int clen(const char* str)   
{
	int i;
	for (i = 0; str[i]; i++);   //当字符串最后一位为0,表示字符串结束
	return ++i;
}
char* cstr(const char* str)
{
	//将字符串传递出去
	int len = clen(str);   //求出字符串长度
	//char strRt[0x20];  //错误,因为此处strRt为局部变量,没有自己的内存空间,必须返回一个指针
	char* strRt = new char[len];
	memcpy(strRt, str, len);   //memcpy(目标,源,长度)
	return strRt;
}

int  main()
{
	char* str;
	str = cstr("你好");  //强制类型转化,"你好"是一个常量;强行的使得str指向了"你好"的地址
	std::cout << str << std::endl;
}

注:返回指针时,一定不能返回一个局部变量

2)项目设计:游戏麟江湖新手村有6中怪物,要求设计一个函数来创建怪物,怪物结构如下:

typedef struct Role
{
    char* Name;
    int Hp;
    int maxHp;
    int Mp;
    int maxMp;
}*PROLE;
//性能损耗较大
#include <iostream>

typedef struct Role
{
    char* Name;
    int Hp;
    int maxHp;
    int Mp;
    int maxMp;
    int lv;
}*PROLE,ROLE;

int clen(const char* str)   
{
	int i;
	for (i = 0; str[i]; i++);   //当字符串最后一位为0,表示字符串结束
	return ++i;
}
char* cstr(const char* str)
{
	//将字符串传递出去
	int len = clen(str);   //求出字符串长度
	//char strRt[0x20];  //错误,因为此处strRt为局部变量,没有自己的内存空间,必须返回一个指针
	char* strRt = new char[len];
	memcpy(strRt, str, len);   //memcpy(目标,源,长度)
	return strRt;
}

ROLE CreateMonster(const char* str, int Hp, int Mp)
{
	Role rt{ cstr(str),Hp,Hp,Mp,Mp,1 };
	return rt;  //将整个结构体的成员进行了返回,性能损耗较大
}
int main()
{
	ROLE role = CreateMonster("aoteman", 1500, 1500);  //实际项目中,不会使用结构体实体创建对象,因为性能损耗非常大
	std::cout << role.Name << std::endl;
	std::cout << role.Hp << "/" << role.maxHp << std::endl;
}
//上述代码优化,函数返回指针
#include <iostream>

typedef struct Role
{
	char* Name;
	int Hp;
	int maxHp;
	int Mp;
	int maxMp;
	int lv;
}*PROLE, ROLE;

int clen(const char* str)
{
	int i;
	for (i = 0; str[i]; i++);   //当字符串最后一位为0,表示字符串结束
	return ++i;
}
char* cstr(const char* str)
{
	//将字符串传递出去
	int len = clen(str);   //求出字符串长度
	//char strRt[0x20];  //错误,因为此处strRt为局部变量,没有自己的内存空间,必须返回一个指针
	char* strRt = new char[len];
	memcpy(strRt, str, len);   //memcpy(目标,源,长度)
	return strRt;
}

PROLE CreateMonster(const char* str, int Hp, int Mp)  
{
	PROLE rt = new Role{ cstr(str),Hp,Hp,Mp,Mp,1 };  //申请一个结构体rt类型大小的内存空间
	return rt;  //返回值是一个指针
}
int main()
{
	PROLE role = CreateMonster("aoteman", 1500, 1500);  //实际项目中,不会使用结构体实体创建对象,因为性能损耗非常大
	std::cout << role->Name << std::endl;
	std::cout << role->Hp << "/" << role->maxHp << std::endl;
}

//函数返回一个引用
#include <iostream>

typedef struct Role
{
	char* Name;
	int Hp;
	int maxHp;
	int Mp;
	int maxMp;
	int lv;
}*PROLE, ROLE;

int clen(const char* str)
{
	int i;
	for (i = 0; str[i]; i++);   //当字符串最后一位为0,表示字符串结束
	return ++i;
}
char* cstr(const char* str)
{
	//将字符串传递出去
	int len = clen(str);   //求出字符串长度
	//char strRt[0x20];  //错误,因为此处strRt为局部变量,没有自己的内存空间,必须返回一个指针
	char* strRt = new char[len];
	memcpy(strRt, str, len);   //memcpy(目标,源,长度)
	return strRt;
}

ROLE& CreateMonster(const char* str, int Hp, int Mp)   //返回一个引用
{
	PROLE rt = new Role{ cstr(str),Hp,Hp,Mp,Mp,1 };  //申请一个结构体rt类型大小的内存空间
	return *rt;  //  rt表示指针,*rt标志指针的值。若此处是个控制在程序会报错,因为引用必须初始化
}
int main()
{
	Role& role = CreateMonster("aoteman", 1500, 1500);  
	std::cout << role.Name << std::endl;           //引用需要使用实体调用结构体成员变量
	std::cout << role.Hp << "/" << role.maxHp << std::endl;
}

3)传递引用参数时的类型转化

//传递引用参数时存在一个隐士的类型转化
#include <iostream>

int Add1(int a, int b)
{
    return a + b;
}
int Add2(int& a, int& b)
{
    return a + b;
}
int main()
{
    float a = 200.0f;
    float b = 125.53f;
    std::cout << Add1(a, b) << std::endl;  //如果函数的参数不是引用,可以直接传入其他类型的值
    std::cout << Add2(a,b) << std::endl;  //错误。如果函数的参数是引用,必须传入对于引用类型的值,否则报错
}


总结:如果函数的参数不是引用,可以直接传入其他类型的值;如果函数的参数是引用,必须传入对于引用类型的值,否则报错

4)数组的引用

//int类型的引用定义
int a;
int & b=a;

//数组的引用定义
int c[100];
//int & d[100]=c;  //此写法错误
int (&e)[100]=c;  //创建c的引用e,且e的数组长度必须和c的一致。e首先要是个引用,且e中有100个元素
//数组的引用用法
//缺点:若数组的元素不固定,则无法进行定义
#include <iostream>

void ave(int (&art)[5])   //传入数组引用参数
{
	std::cout << sizeof(art) << std::endl;
	for (auto x : art)std::cout << x << std::endl;
}
int main()
{
	int a[5]{1,2,3,4,5};
	ave(a);
}

3、函数参数之右值引用

左值:有着明确的内存空间,可以往里面写入值,就叫做左值。如int c = 320,则c就是一个左值

右值:临时空间存放的值,无法往里面写入值,就叫做右值。如上面的230+250。

//右值引用语法
int&& a = 320+230;   //右值引用指向的是临时的值
//a = 1500; //错误,无法给右值引用进行传值

右值引用可以解决上述问题,并且可以节省变量

#include <iostream>

void Add(int&& a)   //右值引用
{
	std::cout << a << std::endl;
}
int main()
{
	Add(320 + 250);  //如果函数的参数是一个引用,服务直接进行计算传值
}

//右值引用示例
#include <iostream>
struct Role
{
	int Hp;
	int Mp;
};

Role CreateMonster()
{
	Role rt{ 100,200 };
	return rt;
}

void show(Role&& r1)   //使用右值引用,没有再创建变量,而是直接接受CreateMonster()传递过来的rt
{
	std::cout << r1.Hp << std::endl;
	std::cout << r1.Mp << std::endl;
}

int main()
{
	show(CreateMonster());
}

4、函数的本质

1)分析函数汇编代码时,先将调试方式设置为release,再打开项目属性页,将C/C++优化功能关闭

2)汇编代码指令说明:

//部分汇编代码指令说明:
push   x        //将x的内容方放到临时变量的内存区域(栈)
call   x        //让CPU去执行内存地址X处的代码
ret           //让CPU返回跳转前的位置
//C++函数
#include <iostream>

int Add(int a, int b)
{
	return a + b;
}

int main()
{
	int c = Add(1, 2);
	std::cout << c;
}

//汇编代码
int Add(int a, int b)
{
00F71000  push        ebp            //将ebp放如是临时变量区,即栈区    
00F71001  mov         ebp,esp  //esp表示栈的位置,ebp=esp
	return a + b;
00F71003  mov         eax,dword ptr [ebp+8]  //将[ebp+8]内存地址中的值放入到eax寄存器
00F71006  add         eax,dword ptr [ebp+0Ch]  //eax=eax+[ebp+0Ch]内存地址中的值,即a和b的加法操作
}
00F71009  pop         ebp  
00F7100A  ret               //ret表示返回值跳转前的位置
int main()
{
00F71010  push        ebp  
00F71011  mov         ebp,esp  
00F71013  push        ecx  
	int c = Add(1, 2);
00F71014  push        2    //push用户给函数传递参数,即将2推送至栈区
00F71016  push        1    //先push最后一个参数
00F71018  call        00F71000  //call表示CPU跳转到目标地址去指向,即此处CPU跳转到00F71000地址
00F7101D  add         esp,8  
00F71020  mov         dword ptr [ebp-4],eax  
	std::cout << c;
00F71023  mov         eax,dword ptr [ebp-4]  
00F71026  push        eax  
00F71027  mov         ecx,dword ptr ds:[00F72038h]  
00F7102D  call        dword ptr ds:[00F72034h]  
}
00F71033  xor         eax,eax  
00F71035  mov         esp,ebp  
00F71037  pop         ebp  
00F71038  ret    //函数尾,有几个ret就有几个函数

3)函数的本质

​ 经过上面的分析,可知函数的本质是一段内存里的二进制数据,我们写下的C++代码会翻译成对应的二进制数据,程序运行的时候会通过某种规则来加载到我们的内存里,一个程序一旦编译(生成),这个程序的二进制数据就不会再发生变化

​ ①程序的生成:C++代码=>二进制数据=>程序文件(硬盘)

​ ②程序的运行:程序文件(硬盘)=>加载到内存中

注:函数名的本质就是一个内存地址

#include <iostream>
#include <bitset>
int Add(int a, int b)
{
	return a + b;
}

int main()
{
	int c = Add(1, 2);
	std::cout <<"函数名的地址为:"<< Add << std::endl;;
	char* str = (char*)Add ;
	for (int i = 0; i < 30; i++)     //将函数的内容显示出来
	{
		std::cout << std::bitset<8>(str[i]) << std::endl;  //函数的内容2进制表示
		//std::cout << std::hex<<(unsigned)str[i] << std::endl;   //函数的内容16进制表示
		//printf("%X\n", (unsigned char)str[i]);
	}
}

5、函数指针

1)函数指针声明

//函数指针声明语法
函数返回类型 (*函数指针变量名)(参数类型 参数名称,......参数类型 参数名称);

//示例
int (*pAdd)(int a,int b)
//函数指针简单用法
#include <iostream>

int Add(int a, int b)
{
	return a + b;
}
int Add_X(int a, int b)
{
	return (a + b)/2;
}

int main()
{
	int (*pAdd)(int c, int d) {Add};  //申明一个函数指针,并将其初始化为函数Add的地址

	std::cout << pAdd(100, 200) << std::endl;
	std::cout << "函数指针大小为:"<<sizeof(pAdd(100, 200)) << std::endl;

	char (*pAdd_X)(int ,int ) { (char (*)(int,int))Add_X };  //如果函数的返回值类型和函数指针的返回值类型不同,需要进行强制类型转化
	std::cout << pAdd_X(110, 20) << std::endl;
}




2)函数指针的类型的自定义

//通过typedef自定义函数指针的类型
#include <iostream>

//把(char (*)(int, int)类型定义为新的类型pFadd
typedef char(*pFadd)(int, int);  //声明函数指针类型


int Add_X(int a, int b)
{
	return (a + b) / 2;
}

int main()
{
	pFadd pAdd_X = (pFadd)Add_X;   //pFadd就相当于(char (*)(int, int)
	std::cout << pAdd_X(110, 20) << std::endl;
}
//通过using自定义函数指针的类型
#include <iostream>

//把(char (*)(int, int)类型定义为新的类型pFadd
using pFadd =  char(*)(int, int);  //声明函数指针类型

int Add_X(int a, int b)
{
	return (a + b) / 2;
}

int main()
{
	pFadd pAdd_X = (pFadd)Add_X;   //pFadd就相当于(char (*)(int, int)
	std::cout << pAdd_X(110, 20) << std::endl;
}

3)函数指针和指针函数

①函数指针本事是个指针,即一个可以指向特定类型函数的指针,如int (*pAdd)(int a,int b);

②指针函数是指一个返回指针的函数,如int* xAdd(int a,int b);

//函数指针类型也可以被当作函数参数
#include <iostream>

using pRole = int(*)(int hp, int mp);   //自定义一个函数指针类型

int Test(int a,int b,pRole x)   //传入一个函数指针类型
{
	return x(a, b);
}

int Add(int a, int b)
{
	return a + b;
}

int main()
{
	pRole pAdd{ Add };  //声明一个函数指针
	std::cout << Test(100, 200, pAdd);  //将100,200传入函数指针
}

6、从函数的角度认识栈

1)栈和栈的意义

​ 我们都知道,变量的本质是对应的内存空间,因此每个变量都需要独立的内存空间,问题是,在实际开发过程中,一个函数可能会被反复调用,如果每次都分配内存空间,那么系统开销将非常大,如果为这样的变量都分配固定的内存空间,又非常的浪费内存资源,所以才有了栈的感念,栈的本质是一段提前分配好的内存空间,主要就是用来存放临时变量!这样我们只需要管理好栈的读写就可以避免频繁的内存分配和不必要的内存浪费!

​ 栈是连续的内存空间。

标签:return,函数,int,char,str,include,十九,strRt
From: https://www.cnblogs.com/piaolaipiaoqu/p/17871109.html

相关文章

  • 欧拉函数
    定义欧拉函数\(\phi(n)\)代表的是\([1,n]\)之间与\(n\)互质的数量。公式\(\phi(n)=n\times(1-\frac{1}{p_1})\times(1-\frac{1}{p_2})\times(1-\frac{1}{p_3})\times……\times(1-\frac{1}{p_k})\)其中:\(n\)有\(k\)个质因数,而\(p_i\)就是其中的一个......
  • matlab中绘制三维柱状图bar3函数的使用方法
    ✅作者简介:热爱科研的算法开发者,Python、Matlab项目可交流、沟通、学习。......
  • C++学习笔记——函数探幽
    C++内联函数内联函数是一种用空间换时间的技术,是C++提高程序运行速度做的改进。运行程序时操作系统将指令载入计算机内存中,并逐条执行这些指令,遇到循环或分支时向前或向后跳转到特定的地址(每条指令都有特定的内存地址)。常规函数也是如此,在调用常规函数时立即存储该指令的地址......
  • 7、oracle迁移到postgres-逗号拼接函数listagg与string_agg
    oracle迁移到postgres-逗号拼接函数listagg与string_aggoracle中的listagg函数与postgres中的string_agg函数都可以实现逗号拼接字符1、listagg函数SELECTt.id,listagg(字段1,',')withinGROUP(ORDERBY字段1)ascheck_msg2FROMdual;within......
  • 软件测试/人工智能|Python函数与调用:解放编程力量的关键
    简介Python作为一门强大而灵活的编程语言,其函数机制为我们提供了一个重要的工具,使得代码更为模块化、可重用。在本文中,我们将深入探讨Python中函数的各个方面,包括什么是函数、内置函数、函数的定义和函数的调用,以及通过示例展示函数在实际编程中的应用。什么是函数?在Python中,......
  • @RequestParam 注解导致无法自动将请求参数填充到函数参数中
    @RequestParam注解导致无法自动将请求参数填充到函数参数中@RequestParam注解通常用于从HTTP请求中提取单个参数值。它将参数值映射到方法的参数上,并且默认情况下不会自动将值填充到类的字段中。以下面的代码为例:classPageParam{ privateIntegerpage;privateInte......
  • 微信小程序开发的聚合函数排序.aggregate.sort
    //普通查询用.orderBy('add_time','desc'),聚合查询用.sort({ins_time:-1})'usestrict';constdb=uniCloud.database()//对数据库的对象获取;exports.main=async(event,context)=>{ letstart=newDate().getTime(); constcollection=db......
  • PHP---开发常用助手函数
    在PHP项目开发过程中,常用的助手函数://获取用户浏览器类型functionget_user_bs($bs=null){if(!isset($_SERVER["HTTP_USER_AGENT"]))returnnull;$user_agent=strtolower($_SERVER["HTTP_USER_AGENT"]);//直接检测传递的值if($bs)returnstr......
  • 子查询、Concat 字符拼接 ,Cast截取小数位 函数使用
    selectqh.CaseId,(selectsh.CaseIdfromServiceQuot.dbo.Headershwhereqh.QutoNo=sh.HeaderNo),qh.ApplierDate,qh.BU,qh.Site,qh.HeaderNo,qh.Currency(selectsh.CustomerfromServiceQuot.dbo.Headershwhereqh.QutoNo=sh.HeaderNo),qh.PN......
  • Matlab获取鼠标坐标值的ginput()函数
    ​获取鼠标坐标值的第一种途径:利用Matlab7.0中figure的WindowButtonDownFcn属性。当你在图上按下鼠标的时候,可通过该属性定义一个回调程序。回调程序可以是一个有效的Matlab表达式或者一个M文件。那么为显示当前鼠标按下时的坐标值,我们可以将其定义为一个坐标获取和显示程序。......