十九、函数(二)1、函数参数之接受不定量参数
1)普通函数不定量传参用法
//接受不定量参数的函数#include //引入头文件cstdargint Add(unsigned count, ...) //第一个参数为参数的个数,第二个参数为三个.{int rt{};char* c_arg; //声明一个指针变量va_start(c_arg, count); //将参数数据指针赋值给c_argfor (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 #include int Average(unsigned count, ...){va_list arg; //va_list 是一个char类型的指针,相当于char* arg;va_start(arg, count); //第一个参数为接受数据的指针,第二个参数为参数的个数,目的是为了将参数的地址放到arg中。此处做了一个内存分配给argint 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 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 int main(){char* str;str = (char*)"你好"; //强制类型转化,"你好"是一个常量;强行的使得str指向了"你好"的地址//str没有自己的内存空间,str只是"你好"字符串的一个副本std::cout << str << std::endl;//str[0]=0; 不允许修改值,因为指向的是一个常量的内存地址}
//通过函数输出字符串#include //求字符串占用多少内存的函数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 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 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 <Name << std::endl;std::cout <Hp << "/" <maxHp << std::endl;}
//函数返回一个引用#include 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 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 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 void Add(int&& a) //右值引用{std::cout << a << std::endl;}int main(){Add(320 + 250); //如果函数的参数是一个引用,服务直接进行计算传值}
//右值引用示例#include 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 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=espreturn 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 #include 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(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 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 //把(char (*)(int, int)类型定义为新的类型pFaddtypedef 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 //把(char (*)(int, int)类型定义为新的类型pFaddusing 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 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)栈和栈的意义
我们都知道,变量的本质是对应的内存空间,因此每个变量都需要独立的内存空间,问题是,在实际开发过程中,一个函数可能会被反复调用,如果每次都分配内存空间,那么系统开销将非常大,如果为这样的变量都分配固定的内存空间,又非常的浪费内存资源,所以才有了栈的感念,栈的本质是一段提前分配好的内存空间,主要就是用来存放临时变量!这样我们只需要管理好栈的读写就可以避免频繁的内存分配和不必要的内存浪费!
栈是连续的内存空间。