C++知识点累积(九)函数

2020-11-23 C++知识点累积(九)

**

数组参数如何传参

**
在这里插入图片描述
为什么传递数组的时候一定要带一个数组长度count参数:
因为不确定传过来的数组长度是多少

**

多维数组传参

**
在这里插入图片描述
前面的数组长度可以不用写,但是后面的数组长度必须确定

**

引用参数和指针参数

**
引用参数和指针参数处理数据底层都是一样的,那怎么用才好呢,他们的区别有哪些?
区别:
1.引用不能为空,指针可以为空;
2.“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小
3.引用是类型安全的,而指针不是 (引用比指针多了类型检查)
4.引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
5.指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。

用法个人理解:
处理内存地址用指针,处理数据用引用,引用改变不了指向

**

不定量参数

**
不定量参数是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。

使用不定量参数需要头文件

#include <cstdarg>

需要函数:
1、va_start(va_list arg,unsigned count);
目的: 找到不定量参数临时占用的栈空间首地址,并告诉编译器参数个数

2、va_arg(va_list arg,typename);
目的: 根据不定量参数的首地址和类型名,来进行不同的偏移取值
注意: 这个函数每用一次,参数便读一次。通过多次使用这个函数来达到读取全部参数的功能

3、va_end(va_list arg);
目的: 释放这块内存

VA_LIST的用法:
(1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
(2)然后用VA_START宏初始化变量刚定义的VA_LIST变量;
(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
(4)最后用VA_END宏结束可变参数的获取。

例子:

#include <iostream>
#include <stdarg.h>

void Printf(int count, ...)
{
	va_list ap;//va_list表示可变参数列表类型
	//char* ap;//也可以用指针接受char*意义和va_list一样
	va_start(ap, count);
	for (int i = 0; i < count; i++)
	{
		std::cout <<va_arg(ap,int)<<"\n" ;
	}
	va_end(ap);//释放内存
}

void main(){
	int c = 10;
	Printf(4, 5,4,3,2);
}

**

返回指针和引用

**
函数返回值是指针变量时,不要返回函数内局部变量,因为局部变量的作用域为函数内,函数结束时会把函数内局部变量的内存地址在栈上删除
错误示例:

#include <iostream>
#include <string>

typedef struct Role
{
	char* Name;
	int HP;
	int MaxHp;
	int MP;
	int MaxMP;
}*PROLE;

PROLE CreateMonster(char* name,int hp,int maxhp,int mp, int maxmp) 
{
	Role monster{name,hp,maxhp,mp,maxmp};
	return &monster;
}

正确示例:

#include <iostream>
#include <string>

typedef struct Role
{
	char* Name;
	int HP;
	int MaxHp;
	int MP;
	int MaxMP;
}*PROLE;

PROLE CreateMonster(char* name,int hp,int maxhp,int mp, int maxmp) 
{
	PROLE monster=new Role{name,hp,maxhp,mp,maxmp};
	return monster;
}

返回引用
示例:

#include <iostream>
#include <string>

typedef struct Role
{
	char* Name;
	int HP;
	int MaxHp;
	int MP;
	int MaxMP;
}*PROLE;

int clen(const char* str) 
{
	int i;
	for (i = 0; str[i]; i++);
	return ++i;
}

char* cstr(const char* str) 
{
	int len = clen(str);
	char* strRt = new char[len];
	memcpy(strRt, str, len);
	return strRt;
}

Role& CreateMonster(const char* name,int hp,int mp) 
{
	Role *monster=new Role{cstr(name),hp,hp,mp,mp};
	return *monster;
}

int main()
{
	Role& monster=CreateMonster((char*)"qwe",100,50);
	std::cout << "名字:" << monster.Name << "血量:" << monster.HP << "/" << monster.MaxHp;
}

传递引用参数时需要严格遵守数据类型一一对应;Add2()这种用法就不行
在这里插入图片描述
数组的引用写法如下:
在这里插入图片描述
传递数组引用写法如下:
在这里插入图片描述
**

右值引用

**
在这里插入图片描述

所谓右值引用就是必须绑定到右值的引用。我们通过&&而不是&来获得右值引用。如我们将要看到的,右值引用有一个重要的性质—只能绑定到一个将要销毁的对象。 因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。

   一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。

在这里插入图片描述
右边第一种可以执行函数,但是会产生内存的消耗,第二种无法达到效果,因为引用地址是局部变量,所以用左边的右值引用效果会达到最好

右值引用可以直接传递右值,不用定义一个变量去接收

1.左值持久,右值短暂
左值有持久的状态,而右值要么是字面值常量,要么是表达式求值过程中创建的临时对象。
由于右值引用只能绑定到临时对象,我们得知
1.所引用的对象将要被销毁
2,.该对象没有其他用户

这两个特征意味着:使用右值引用的代码可以自由地接管所引用的对象的资源。

2.变量是左值
变量可以看作只有一个运算对象而没有运算符的表达式,虽然我们很少这样看待变量。类似于其他任何表达式,变量表达式也有左值/右值属性。变量表达式都是左值,带来的结果就是,我们不能将一个右值引用绑定到一个右值引用类型的变量上。

int &&rr1 =42; //正确,字面值常量是右值
int &&r2 =rr1; //错误,表达式rr1是左值!
1
2
注意: 变量是左值,因此我们不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不可以。

3.标准库move函数
虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型。我们可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件utility中。

int &&rr3 =std::move(rr1);  //OK
   move调用告诉编译器:我们有一个左值,但我们希望像右值一样处理它。我们必须认识到,调用move就意味着承诺:除了对rr1赋值或者销毁之外,我们将不再使用它。在调用move之后,我们不能对移后源对象的值做任何假设。

注意:
1.我们可以销毁一个移后源对象,也可以赋予它新值,但是不能使用一个移后源对象的值。
2.对于move的使用应该是std:move而不是move。这样做可以避免潜在的名字冲突。

**

函数的本质

**
在这里插入图片描述
**

函数指针

**
函数指针是指向函数的指针变量。
通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。
函数指针可以像一般函数一样,用于调用函数、传递参数。
格式:函数返回类型(*函数指针变量名)(参数类型,…参数类型);
如:int(*pAdd)(int, int);
例子:

#include <iostream>

typedef char (*x)(int, int);//函数指针的重新定义类型名:方法1

using x1 = char (*)(int, int); //函数指针的重新定义类型名:方法2

int max(int x, int y)
{
    return x > y ? x : y;
}

int main(void)
{
    /* p 是函数指针 */
    int (*p)(int, int) = &max; // &可以省略
    int a=62, b=63, c=65, d;

    /* 与直接调用函数等价,d = max(max(a, b), c) */
    printf("最大的数字是: %d\n", p(p(a, b), c));
    /* 函数指针的强制转换把max函数返回值强转成char */
    x1 p1 = (x)max;
    std::cout<< "字母为:" <<p1(p(a, b), c)<< "\n";
    return 0;
}

结果:
在这里插入图片描述
函数指针与结构体和回调函数

#include <iostream>

using X = int (*)(int);
using Player = void (*)(int,int);

typedef  struct  Role
{
    int   hp;
    int   mp;
    void  bAct (int a, X x ) //传递函数指针为参数,作用相当于回调函数
    {
        std::cout<< "HP:"<<x(hp);
    };
} pRole;    //定义一个结构体

int Act(int hp) 
{
    return hp / 2;
}

void Play(Role role) 
{
    role.bAct(role.hp, Act);
}

void main() 
{
    pRole  role = { 100,200 };   //实现一个结构体                      
    Player play = (Player)Play;  //传递结构体参数时其实就是传递结构体内部的数据,所以也可以这种写法
    play(role.hp,role.mp);
}

结果:
在这里插入图片描述
**

函数重载

**

#include <iostream>

int GetAVG(int a,int b) 
{
	std::cout << "a,b的平均值整数为:" << (a + b) / 2 << std::endl;
}

int GetAVG(int a, int b , int c)
{
	std::cout << "a,b的平均值整数为:" << (a + b+c) / 2 << std::endl;
}

float GetAVG(float a, float b)
{
	std::cout << "a,b的平均值小数为:" << (a + b) / 2 << std::endl;
}

void main() {
	GetAVG(6, 8);
	GetAVG(6, 10, 8);
	GetAVG( 1.2f, 6.9f);
}

**

函数模板

**

函数模板是通用的函数描述,它们使用泛型来定义函数,其中的泛型可用具体的类型替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时候也被称为通用编程。

函数模板的声明形式为:

template<typename 数据类型参数标识符>

<返回类型><函数名>(参数表)

{
函数体
}
例子:

#include <iostream>

template<typename type> 
type GetAVG(type a, type b)
{
	std::cout << "a,b的平均值为:" << (a + b) / 2 << std::endl;
};

void main() {
	GetAVG(11,12);
}

关键字typename也可以使用关键字class,这时数据类型参数标识符就可以使用所有的C++数据类型。
普通函数和函数模板,如果都可以执行,则普通函数优先于函数模板
函数模板也可以重载,和普通函数重载一样根据参数区分
函数模板返回值需要注意的例子:

template< typename T1, typename T2>
decltype(auto) GetAVG(T1 a, T2 b)
{
	return a > b ? a : b;//int和float运算,所以返回值为float类型,为什么这里返回值不是float&呢,因为a为int类型,如果为int就不能被float类型引用
};

void main() {
	GetAVG(110,22.8f);
}

函数模板传递数组参数

template< typename T, int count>
void Sort(T (&a)[count]){}

例子:
多类型排序程序

#include <iostream>
#include <string>

template< typename T>
void sawp(T& a,T& b) 
{
	T c = a;
	a = b;
	b = c;
}

template< typename T, int count,bool bigSort=false>
void Sort(T (&a)[count])
{
	for (int i = 0; i < count; i++)
	{
		for (int j = i; j < count-1; j++)
		{
			if (bigSort?a[i]>=a[j+1]: a[i] < a[j + 1])
			{
				sawp(a[i],a[j+1]);
			}
		}
	}
};

void main() {
	int a[5]{5465,86,534,8445,655};
	Sort(a);
	for (int i :a)
	{
		std::cout << i << std::endl;
	}
	std::cout << "---------------"<<std::endl;
	short b[5]{ 5465,86,534,8445,655 };
	Sort(b);
	for (short i : b)
	{
		std::cout << i << std::endl;
	}
	std::cout << "---------------" << std::endl;
	std::string c[5]{ "abc","bcd","cde","fgh","ijk" };
	Sort(c);
	for (std::string i : c)
	{
		std::cout << i << std::endl;
	}
}

函数模板具体加粗样式理解

**

Auto

**

auto的特性
在这里插入图片描述
拖尾函数
在这里插入图片描述
使用->加数据类型,可以强行把函数返回值修改成->后的类型

**

decltype

**
语法:
在这里插入图片描述
特性:
1.表达式没经过运算
在这里插入图片描述
2.表达式经过运算
在这里插入图片描述
3.表达式是一个函数
在这里插入图片描述
注:decltype判断类型不会真的执行表达式,只是根据表达式来推断结果的类型

int a;
decltype(a++) x;//x的类型为int,因为a++是先完成操作后运算
decltype(++a) y;//y的类型为int&,因为++a是先完成运算后操作

decltype和auto结合
结合拖尾函数可以写成
C++14之前:

auto GetAVG(int& a, int& b)->decltype(a)
{
	std::cout << "a,b的平均值为:" << (a + b) / 2 << std::endl;
	a = b;
	return a;
};

void main() {
	int a = 11;
	int b = 13;
	GetAVG(a,b);
}

C++14之后:

decltype(auto) GetAVG(int& a, int& b)//c++14之后可以这种写法,并且兼容之前的写法
{
	std::cout << "a,b的平均值为:" << (a + b) / 2 << std::endl;
	a = b;
	return a;
};

void main() {
	int a = 11;
	int b = 13;
	GetAVG(a,b);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值