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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
学习并掌握C++2.0(11+14+17+20)的新特性,学习线程及线程池的应用 ---------------------------------------------------给小白学员的3年学习路径及计划技术方面分三块:1.纯开发技术方向2.音视频流媒体专业方向3.项目实战---------------------------------------------------1.纯开发技术方向(1) C++必须要过硬(至少学会10本经典好书)(2) 系统级编程(Windows、Linux),必须特别熟练系统API,灵活运用(3) 框架与工具(Qt、MFC):必须精通其中一种。(4) 架构与设计模式:需要提升一个高度,不再是简单的编码,而是思维模式。(5) 驱动级别(如果有兴趣,可以深入到驱动级:包括Windows、Linux)(6) 最好学习点Java+Html+javascript等WEB技术。2.音视频流媒体专业方向(1) 音视频流媒体基础理论:   必须认真学会,否则看代码就是看天书(2) 编解码方向:精通h.264,h.265(hevc), 包括理论和各个开源库(ffmpeg,libx264,libx265,...)。(3) 直播方向:  精通各种直播协议(rtsp,rtmp,hls,http-flv,...), 钻研各个开源库(live555,darwin,srs,zlmediakit,crtmpserver,...)(4) 视频监控:  理论+开源库(onvif+281818)(EasyMonitor、iSpy、ZoneMinder(web)、...) 3.项目实战(1) Qt项目:  至少要亲手练习10个实战项目(网络服务器、多线程、数据库、图像处理、多人聊天、等等)(2)音视频项目:包括编解码、视频监控、直播等各个方向,都需要亲手实战项目,包括视频服务器、后台管理系统、前端播放器(多端)---------------------------------------------------  第1章 C++11新特性 41). nullptr关键字与新语法 42). auto和decltype类型推导 6 auto讲解 6 auto示例 7 decltype 83). for区间迭代 94). 初始化列表 105). 模板增强 11外部模板 11类型别名模板 12默认模板参数 126). 构造函数 13委托构造 13继承构造 147). Lambda 表达式 158). 新增容器 20std::array 20std::forward_list 21无序容器 22元组 std::tuple 239). 正则表达式 2610). 语言级线程支持 28多线程库简介 2811). 右值引用和move语义 31右值引用和move语义 32转移左值 3412). constexpr 35第2章 C++14新特性 36Lambda 函数 36类型推导 37返回值类型推导(Return type deduction) 37泛型lambda 39[[弃用的]]  [[deprecated]]属性 40二进制数字和数字分隔符 41第3章 C++17新特性 42安装GCC10.2 42安装msys2-x86_64-20200720 42更新镜像 42更新软件库 43安装 MinGW64 等必要的软件 43环境变量Path 43编译命令 43constexpr 44typename 45折叠表达式 47结构化绑定 48条件分支语句初始化 49聚合初始化 50嵌套命名空间 52lambda表达式捕获*this的值 53改写/继承构造函数 54用auto作为非类型模板参数 55__has_include 56fallthrough 57nodiscard 57maybe_unused 58第4章 C++20新特性 59编译命令 59concept 59typename 60explicit 61constinit 62位域变量的默认成员初始化 62指定初始化 63基于范围的for循环初始化 64放宽基于范围的for循环,新增自定义范围方法 65嵌套内联命名空间 66允许用圆括弧的值进行聚合初始化 67unicode字符串字面量 68允许转换成未知边界的数组 68likely和unlikely 69第5章 C++2.0(11/14/17/20)总结与分析 705.1 C语言与C++ 715.2 语言可用性的强化 725.2.1 常量 725.2.2 变量及其初始化 735.2.3 类型推导 745.2.4 控制流 765.2.5 模板 775.2.6 面向对象 815.3 语言运行期的强化 835.3.1 Lambda 表达式 835.3.2 右值引用 865.4 容器 885.4.1 线性容器 885.4.2 无序容器 895.4.3 元组 895.5 智能指针与内存管理 905.5.1 RAII 与引用计数 905.5.2 std::shared_ptr 905.5.3 std::unique_ptr 915.5.4 std::weak_ptr 91第6章 C++2.0多线程原理与实战 93什么是并发 93并发的方式 93为什么使用并发 95线程简介 96创建线程的三种方式 971. 通过函数 972.通过类对象创建线程 993.通过lambda表达式创建线程 101thread线程的使用 101互斥量与临界区 105期物Future 111条件变量 112原子操作 114内存模型 118第7章 C++2.0线程池原理与实战 120线程与线程池的基本原理 1201)、线程 1202)、线程的生命周期 1213)、什么是单线程和多线程 1214)、线程池 1225)、四种常见的线程池 123线程池的架构与流程 123线程池代码实战 125    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值