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);
}