1. 可变参数
(1)C语言中的可变参数,其原理是利用所有的参数在找空间的保存位置是连续的;内部的程序主要是有va_list指针;
(2)c++的可变参数,就是指initializer_list列表初始化,本质是一个表示某种特定类型的值的数组,可以理解为一个不可扩充的容器;
①初始化的区别:这就是为什么很多c++书籍喜欢用{}进型初始化的原因,因为检查严格;
int c = 3.3; //符合,因为系统内会隐式转换,去尾,C语言风格
int b = {3.3}; //不符合,因为这里默认的是initializer_list<double>,无法实现double到int的转换;
int a {4.5}; //不符合,因为这里默认的是initializer_list<double>,无法实现double到int的转换;
②优点:有严格的类型检查,不会允许不同类型之间的隐式转换,这一点不同于C语言;
③用处:主要用在构造函数,把初始化列表当做参数,示例如下:
#include <iostream>
using namespace std;
class A
{
public:
A(int num):m_num(num)
{
}
A(const initializer_list<int> v) //这里是自定义的,与系统默认生成的带可变参数的构造函数相同,不同的是自定义的可以传多个参数,而系统默认的则只能根据属性(m_t的个数)传递;
{
for(auto temp : v)
{
cout<<temp<<endl;
}
}
int m_num;
};
void func1(initializer_list<int> t) //传进来的是一个可变参数列表,需要通过迭代器遍历
{
//cout<<t<<endl;
auto it = t.begin();
for(; it != t.end(); it++)
{
cout<<*it<<endl;
}
}
int main(int argc, char **argv)
{
initializer_list<int>init_list = {1,3,5,7,9};
auto it = init_list.begin(); //可变参数列表不支持[],需要利用迭代器
for(; it!= init_list.end(); it++)
{
cout<<*it<<endl;
}
func1({2,4,6,8,10});
A a(1);
A a1{200}; //此时,类A中默认生成含有初始化列表的默认构造函数;当没有自定义的initializer_list时,这里的200相当于给m_num赋值,当有时,则不是赋值;
cout<<a1.m_num<<endl;
A a2{3,5,7,9};
cout<<a2.m_num<<endl;
int count = {3}; //带{}的,系统内转化成了initializer_list<int>
// int len = {3.14}; //initializer_list<double> v = 3.14,无法将double赋给int
// int m_count {3.3}; //同样会报错,存在double到int的不合适转化,这里不同于C语言的隐式转换
return 0;
}
2. 强枚举类型
(1)和C语言的枚举类型相比,优势:
①强作用域:使用类型成员的时候必须前缀上所属的枚举类型的名字;
②可以执行底层类型:枚举类型后面使用了冒号以及类型关键字来执行枚举中的枚举值的类型
③转换限制(本质原因是强枚举是一种类型,成员尽管设置成int,但还是该枚举类型下的int,与外部的int不同):强类型枚举成员的值不可以与整型变量进行转换(隐式与显式都不可以)、不能将其与整数数字进行比较、不能对不同的枚举类型的枚举值进行比较、相同类型的枚举值可以比较;示例如下
#include <iostream>
using namespace std;
enum RET1{OK=1,NO=2};
enum class RET2:int{OKK=1,NOO=2}; //表示了指定底层类型的优点
RET2 test(int num) //返回值的类型似乎enum枚举类型,这里不能写作int
{
if(num == 5)
{
return RET2::OKK;
}
else
{
return RET2::NOO;
}
}
int main(int argc, char **argv)
{
RET2 n = test(7); //注意枚举也是一种数据类型,这里不能用作int
if(n == RET2::OKK) //表示了强作用域的优点,需要前缀枚举类型名称;相同的枚举类型之间可以比较;
{
cout<<"等于"<<endl;
}
else
{
cout<<"不等于"<<endl;
}
// int n = RET2::OKK; //不能利用强枚举类型的成员与整型进行转换,隐式与显式的都不可以
return 0;
}
3. 多类型存储(variant)
(1)variant 产生的原因是C语言中的union,其成员只能是同一种类型;而variant则优化了该问题, 是一个类型安全的联合体,本质是一个类模板;其特点是①当前值的类型总是已知的(有成员函数方法判断);②可以有任何指定类型的成员;③因为其是一个类,所以可以派生类;
(2)注意事项
①variant不容许保有引用、数组,或类型void;
②variant容许保有同一类型多于一次,而且保有同一类型的不同cv限定版本;
③默认构造的variant保有其首个选项的值,除非该选型不是可默认构造的??;
(3)当vatiant作为参数传递时,如何知道此时共用体varitant此时保存的是什么类型的成员?可以利用std::holds_allterative<int>(v),如果此时传来的共用体保存的是int类型的成员,那么该语句就是true,示例如下:
#include <iostream>
#include <variant>
using namespace std;
using p_type = std::variant<int ,string, double>;
void print(p_type &v)
{
if(std::holds_alternative<int>(v)) //利用std::holds_alternative<>判断传来的共用体里面是什么类型的值
{
cout<<v.index()<<" "; //输出索引号
cout<<std::get<int>(v)<<endl;
}
if(std::holds_alternative<string>(v))
{
cout<<v.index()<<" ";
cout<<std::get<string>(v)<<endl;
}
if(std::holds_alternative<double>(v))
{
cout<<v.index()<<" ";
cout<<std::get<double>(v)<<endl;
}
}
int main(int argc, char **argv)
{
p_type v;
v = 7;
print(v);
v = "hello";
print(v);
v = 3.14;
print(v);
//std::variant<A,int ,string, double> v2;//无法引用 "std::variant<A, int, std::string, double>" 的默认构造函数 -- 它是已删除的函数C/C++(1790)
std::variant<std::monostate,int ,string, double> v2; //若第一个共用体成员是类型,则要求该类型需要有默认构造函数,因为类型A没有了默认构造函数,所以需要std::monostate占位符,同时不会影响共用体varitant的内部结构,单纯站位,避免报错
return 0;
}
(3)常用方法(成员函数与非成员函数)
成员函数:
①index():输出当前共用体成员在variant共用体中的索引(0-n);
②emplace():原地构造函数??
非成员函数:
①
②
③
④
⑤
⑥