十八、函数(一)

1、函数概述

1)函数带来的好处

①代码模块化,便于阅读维护

②代码模块化以后,能够实现分工合作

③减少重复代码,降低工作流

2)函数的语法

//函数的语法
返回类型 函数名称(参数,参数,参数,参数)  //参数的语法包括:参数类型 参数名称
{
    函数的功能区;
    return 返回值;
}

//函数的声明示范一
int Add(int a,int b)
{
    return a+b;
}
使用:
int a=Add(100,200);   //a=300

//函数的声明示范二
void PrintLog(char *str)
{
    std::cout<<"日志:"<<str;
}
使用:
Printlog("你好世界!");
//函数的声明示例
// 函数一.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>

int Add(int a, int b)   //函数的声明
{
    return a + b;       ////返回的是什么类型的值,就用什么类型接受
}
//不需要返回值的函数,使用的返回类型为void
void PrintEnding(const char* str,int x)
{
    std::cout << str << x << std::endl;
}

int main()
{
   int x{ Add(100, 100) };  //函数的使用
   PrintEnding("生命值:",x);
   x =Add(200,300);
   PrintEnding("内力值:",x);
}

注:每个函数都必须有return函数返回值,main()函数编译器会自动在函数结尾默认加一个return

//用函数实现游戏麟江湖排行榜设计修
//GetDataCount函数统计有几个角色的信息
int GetDataCount(string str)
{
	int icount{};
	for (int i = 0; i < str.length(); i++)
	{
		if (str[i] == ';')
		{
			icount++;
			i += 3;
		}
	}
	return icount/2;
}

//获取字符串中的数据
string GetDataStr(string str, int istart)
{
	string strReturn{};
	int iend{};
	istart = str.find("id=", istart);
	if (istart == std::string::npos) return strReturn;
	iend = str.find(";", istart + 3);
	strReturn = str.substr(istart + 3, iend - istart - 3);
	return strReturn;
	
}
2、函数参数之指针参数

函数分为三部分:包括函数的头部、函数体、函数的尾部(一个函数有可能有很多尾部)、

函数头中的变量只申请了内存空间、但是没有赋值,没有初始化

函数尾是为了返回值

//直接将实参传递给形参
#include <iostream>

int Add(int x, int y)  //形参
{
	x *= 100;
	y *= 100;
	return x + y;
}

int main()
{
	int x = 2, y = 1;
	int c = Add(x,y);  //实参
	std::cout << "c=" << c << "   x=" << x << "   y=" << y << std::endl;
}
//通过此种方式无法得到x和y变化后的结果

//向函数中传入指针参数(将函数中的值读取去出来,获得回传值)
#include <iostream>

int Add(int* x, int* y)  //形参
{
	(*x) *= 100;       //x和y次数是一个指针
	(*y) *= 100;
	return (*x) + (*y);
}

int main()
{
	int x = 2, y = 1;
	int c = Add(&x, &y);  //实参传入x和y的内存地址
	std::cout << "c=" << c << "   x=" << x << "   y=" << y << std::endl;
}
//通过结果发现c的值没有发生变化,但是x和y的值发生了变化。成为函数中最终计算的x和y的结果

2)指针参数的常见应用

​ 函数头部中的形参在定义时,会分配整个结构体内部成员变量总和的内存空间,会存在内存空间开销过剩的问题;但是如果函数头部中传入的参数为指针参数,那么只需要分配指针大小的函数空间,即4字节.

//不使用指针参数,给函数传值
#include <iostream>

struct Role
{
	int Hp;
	int Mp;
};

int Exp(Role role)
{
	return role.Hp + role.Mp;
}

int main()
{
	Role role{ 500,1000 };
	int totle =Exp(role);  //看起来函数在调用时传入了一个参数,但是实际上传入了两个参数
	std::cout << "角色战斗力为:" << totle << std::endl;
}

//通过指针参数,给函数传值
#include <iostream>

struct Role
{
	int Hp;
	int Mp;
};

int Exp(Role* role)
{
	return role->Hp + role->Mp;     //不再使用.取实体,而是通过->偏移符号通过地址传值
}

int main()
{
	Role role{ 500,1000 };
	int totle = Exp(&role);  //取地址
	std::cout << "角色战斗力为:" << totle << std::endl;
}
//通过此方式可大大提升效率

3)指针参数之常量指针用法

回顾:常量指针,表示指针指向的内容是一个常量,指针指向的内容不可以发生变化,即值可以读,但不可以写,防止修改数据

//初始化一个玩家和怪物,定义一个函数,返回一个bool值,当值为true的时候,说明角色死亡
#include <iostream>

struct Role
{
	int Hp;
	int Mp;
	int damage;
};

bool Exp(const Role* Acter, Role* beActer)  //攻击者者和被攻击者,攻击者的属性不能够修改
{
	beActer->Hp -= Acter->damage;  //被攻击者血量=被攻击者血量-攻击者伤害值
	return beActer->Hp <= 0;
}

int main()
{
	Role user{ 500,1000,1200 };
	Role monster{ 1500,1000,1000 };

	if (Exp(&monster, &user)) 
			std::cout << "角色死亡!" << std::endl;
	else 
		if (Exp(&user ,&monster))
			std::cout << "怪物死亡" << std::endl;
}
3、函数参数之数组参数

1)数组本质上就是一个指针,传入数组参数时,要传入数组的长度。提高如下两种数组参数的定义方法

//传入数组参数时,必须要传入数组的长度
//数组参数方法一
Void Sort(int ary[],unsigned count)   //推荐
{
    for(int i=1;i<count;i++)
}
int main()
{
    int a[5]{ 2302,5212,3653,9480,5200 };
	Sort(a, 5);
}

//数组参数方法一
Void Sort(int* ary,unsigned count)   //推荐
{
    for(int i=1;i<count;i++)
}
int main()
{
    int a[5]{ 2302,5212,3653,9480,5200 };
	Sort(a, 5);
}
//将数组进行排序案例
#include <iostream>

void Sort(int ary[],unsigned count)   
{
		for (int i = 1; i < count; i++)
		{
			if (ary[i] > ary[i - 1])
			{
				int tmp = ary[i];
				ary[i] = ary[i - 1];
				ary[i - 1] = tmp;
			}
		}
}

int main()
{
	int a[5]{ 2302,5212,3653,9480,5200 };
	Sort(a, 5);
	for (auto x : a)std::cout << x << std::endl;
}

2)多维数组的传参

//多维数组的传参,无法写成指针的形式
Void Sort(int ary[][2],unsigned count)   //推荐
{
    f
}
int main()
{
    int a[3][2]{{1,2},{3,4},{5,6}};
	Sort(a, 3);  //传入数组的长度
}
4、函数参数之引用参数
//引用回顾
int a=5;
int &b = a;      //引用就相当于被阉割了的指针
则b++就相当于a++

1)引用也可以使用const进行限定只能读,不能写

#include <iostream>

struct Role
{
	int Hp;
	int Mp;
	int damage;
};

bool Exp(const Role& Acter, Role& beActer)  //引用,相当于Role& Acter = user;Role& beActer=monster
{
	beActer.Hp -= Acter.damage;  
	return beActer.Hp <= 0;
}

int main()
{
	Role user{ 5000,1000,12000 };
	Role monster{ 1500,1000,1000 };
	if (Exp(user, monster))std::cout << "怪物死亡,获得经验值XXX" << std::endl;
}

2)引用和指针的区别

引用要想使用,必须先得初始化;而指针可以先传入一个空指针,先不进行初始化

//此种方式错误,引用必须初始化
bool Act(Role& Acter,Role& beAct)
{
    return true;
}
Act(nullptr,nullptr);

//指针可以不进行初始化
bool Act(Role* Acter,Role* beAct)
{
    return true;
}
Act(nullptr,nullptr);

3)指针类型的引用

//指针类型的引用
#include <iostream>

struct Role
{
	char name[0x20];
	int Hp;
	int Mp;
	int damage;
};

bool Exp(const Role& Acter, Role*& beActer)  //Role*指针类型的引用
{
	beActer->Hp -= Acter.damage;
	bool End = beActer->Hp < 0;
	beActer = (Role*)&Acter;  //通过指针类型的引用,可以修改参数的指向,此处将被攻击者修改为攻击者
	return End;
}

int main()
{
	Role user{ "奥特曼",5000,1000,12000 };
	Role monster{ "小怪兽",1500,1000,1000 };
	Role* pRole = &monster;
	if (Exp(user, pRole))std::cout << pRole->name<<"     怪物死亡,获得经验值XXX" << std::endl;
}

可以使用如下写法

//指针类型的引用的其他写法
#include <iostream>

typedef struct Role
{
	char name[0x20];
	int Hp;
	int Mp;
	int damage;
}*PROLE;     //Role类型的指针PROLE  

bool Exp(const Role& Acter, PROLE& beActer)  //Role类型指针PROLE的引用
{
	beActer->Hp -= Acter.damage;
	bool End = beActer->Hp < 0;
	beActer = (Role*)&Acter;  //通过指针类型的引用,可以修改参数的指向,此处将被攻击者修改为攻击者
	return End;
}

int main()
{
	Role user{ "奥特曼",5000,1000,12000 };
	Role monster{ "小怪兽",1500,1000,1000 };
	PROLE pRole = &monster;  ////Role类型指针PROLE
	if (Exp(user, pRole))std::cout << pRole->name<<"     怪物死亡,获得经验值XXX" << std::endl;
}
5、函数参数之默认实参

1)默认参数引入

//传入一组数,对其进行排序
#include <iostream>

void Swap(int &a,int &b)  //定义函数专门用来交换两个数
{
	int tmp = a;
	a = b;
	b = tmp;
}

void Sort(int ary[], unsigned count,bool BigSort)  //定义函数用来排序
{
	for(int i=1;i<count;i++)
	{ 
		bool bcase = BigSort ? ary[i] > ary[i - 1]:ary[i - 1] > ary[i];
		if (bcase)Swap(ary[i], ary[i - 1]);
	}
}

int main()
{
	int a[5]{ 2302,5212,3653,9480,5200 };
	Sort(a, 5,false);  //true表示从大到小排序,false表示从小到大排序
	for (auto x : a)std::cout << x << std::endl;
}

2)默认参数使用

​ 在函数调用时,若不传递值,则使用函数声明时设置的默认参数。默认参数只可以放置在最后,不可以放置在最前面或中间;若有多个默认参数,需要依次写在最后面

//默认参数使用
#include <iostream>

void Swap(int &a,int &b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

void Sort(int ary[], unsigned count,bool BigSort=true)  //定义函数默认值参数
{
	for(int i=1;i<count;i++)
	{ 
		bool bcase = BigSort ? ary[i] > ary[i - 1]:ary[i - 1] > ary[i];
		if (bcase)Swap(ary[i], ary[i - 1]);
	}
}

int main()
{
	int a[5]{ 2302,5212,3653,9480,5200 };
	Sort(a, 5); //函数调用时,若不传入参数,默认使用函数声明时形参的默认值
	for (auto x : a)std::cout << x << std::endl;
}
6、函数参数之不定量参数(一)
//main()函数的另一面
int main(int argcount,char* c_arg[])
{
    std::cout<<"参数个数:"<<argcount;
}

//argcount表示传入的参数个数,即有效参数
//char* c_arg[]为指针数组,表示要传入的参数,第一个参数为程序的路径及文件名,最后一个参数为nullptr空指针,即无效参数

原理:将字符串通过空格打断,依次将字符串的地址放入了指针数组中

#include <iostream>

int main(unsigned argcount,char* arg[])
{
    for (int i = 0; i < argcount; i++)
    {
        std::cout << "第" << i << "个参数的值为" << arg[i] << ",地址为" << (int)arg[i] << std::endl;
    }
}

7、麟江湖注册命令的设计
项目需求:设计麟江湖的注册程序linReg.exe

1)用户在控制台输入linReg和相关参数可以完成注册账号的操作

2)用户输入linReg /?可以得到相关的帮助文档

3)程序中用到的字符处理相关函数均需自己设计

4)自行设计注册账户的参数,可以参照如下

linReg id:tigerinf pass:handsome country:China

结果显示:

注册成功!

=========================================

linReg id:tigerinf pass:handsome country:China

点击项目属性-常规-目标文件名修改为要生成的exe程序名称,此处为linReg

重新生成解决方案,应用名称就是linReg.exe

#include <iostream>

//linReg id:tigerinf pass:handsome country:China
char* ReadRef(const char* ref, const char* cmds)  //第一个参数读取id:后面的内容即tigerinf,第二个参数为整个传入的参数。因为只读取,不修改。所以使用const
	//先从首字母比较,即将id:的第一个字符i与完整的字符串比较。如果一样,继续比较,否则跳过去,继续比较下一个字符d,依次比较,如果一致,则返回
{
	//id:tigerinf pass:handsome country:China
	for (int i = 0; cmds[i]; i++)  
	{
		if (cmds[i] == ref[0])  ////将长字符串的第一个字符和参数的第一个字符依次比较
		{
			bool bfind = true;
			int x = 0;  //len用于计算参数的长度,即pass:的长度
			for (x = 0; ref[x]; x++)    //ref[x]=0,表示字符串已经结束了
			{
				if (ref[x] != cmds[i + x])
				{
					bfind = false;
					break;
				}
			}
			if (bfind)return (char*)&cmds[i + x];  //因cmds[i+x]是字符,而此处需要返回其位置,指针,所以要加&符号

		}
	}
	return  nullptr;          //如果循环完成,一直没有找到字符串,则返回空指针
}


int main(int count, char* arc[])
{
	char* id{};
	char* pass{};
	char* country{};
	const char* idRef{"id:"};
	const char* passRef{ "pass:" };
	const char* countryRef{ "country:" };

	for (int i = 1; arc[i]; i++)
	{
		if(id==nullptr)
		{ 
			id = ReadRef(idRef, arc[i]);
			if (id != nullptr)continue;
		}
		if (pass == nullptr)
		{
			pass = ReadRef(passRef, arc[i]);
			if (pass != nullptr)continue;
		}
		if (country == nullptr)
		{
			country = ReadRef(countryRef, arc[i]);
			if (country != nullptr)continue;
		}
		
	}
	if((int)id * (int)pass * (int)country)
	{ 
	std::cout << "注册成功" << std::endl;
	std::cout << "================================" << std::endl;
	std::cout << "账号" << id << std::endl;
	std::cout << "密码" << pass << std::endl;
	std::cout << "国家" << country << std::endl;
	}
	else
	{
		std::cout << "请使用命令的方式调用本程序!!!" << std::endl;
	}
}