C/C++语言基础--C++模板与元编程系列三(变量模板、constexpr、萃取等…………)
本专栏目的
- 更新C/C++的基础语法,包括C++的一些新特性
前言
- 模板与元编程是C++的重要特点,也是难点,本人预计将会更新10期左右进行讲解,这是第三期,讲变量模板、constexpr、萃取等知识;
- C语言后面也会继续更新知识点,如内联汇编;
- 欢迎收藏 + 关注,本人将会持续更新。
文章目录
- 非类型模板参数
- 别名模板
- 变量模板(C++14)
- 定义格式
- cosntexpr
- constexpr定义的一定是常量
- constexpr与函数
- constexpr修饰接收函数返回值变量
- 变量模板的使用
- 非静态变量
- 类静态变量
- 在C++14之前没有变量模板之前
- 类型萃取(简单了解)
- is_pod
前言
上两次笔记咱们已经学习了C/C++语言基础–C++模板与元编程系列一(泛型、模板、函数模板、全特化函数模板………)和C/C++语言基础–C++模板与元编程系列二类模板、全特化、偏特化、编译模型简介、实现简单Vetctor等…………),加下来学习另外两个模板。
- 别名模板(C++11):
- 变量模板(C++14):
非类型模板参数
📘 准备
模板参数并不局限于定义类型,可以使用编译器内置类型作为参数,在编译期间变成模板的特定常量,这也是元编程的思想之一,C++元编程中核心思想之一就是在编译的时候确定类型,从而对不同类型进行不同计算。
非类型模板参数:就是说在template
定义模板类型的时候,可以定义编译器内置类型,这样就不是定义模板类型参数了。
👓 示例:
编写一个封装了静态数组的类,类名为Array
#include <iostream>
// 简单版本,不扩容
template<typename T, size_t _size=10>
class Array
{
public:
void push_back(T data)
{
_array[_index++] = data;
}
T& operator[](size_t index)
{
return _array[index];
}
size_t size()const
{
return _index;
}
private:
T _array[_size]{};
size_t _index{};
};
int main()
{
Array<int> array;
for (int i = 1; i <= 10; i++) {
array.push_back(i);
}
for (int i = 0; i < array.size(); i++) {
std::cout << array[i] << " ";
}
/*
输出:1 2 3 4 5 6 7 8 9 10
*/
return 0;
}
☀️ 在案例中:
- 我们定义了一个模板类型参数
T
,同时也定义了一个非模板类型参数size_t
类型,并且赋值为10; - 定义的非模板类型参数也是一样可以在类中使用,有点像函数的默认参数。
别名模板
我们在写程序的过程中,总是希望写简短的代码,给一些复杂的名称简化,或者取一个简短的名字,于是又有了类型别名,用来重新定义复杂的名字。
🐤 C的typedef
使用typedef
不仅可以对遍历取别名,还可以为模板具体化指定别名,🍡 🍡🍡 , 注意:是模板具体化
//模板别名
//1,typedef
typedef Array<int, 20> IntArr;
注意: 不能为模板类取别名。
// 如:
template <typename T>
typedef Array<T, 20> _Array;
报错:
解决方法:C++新特性using
⛅️ C++新特性using
typdef定义不了别名模板,所以C++11新增了一项功能——使用模板提供一系列别名(模板别名),当然也同样可以对编译器类型取别名。
🔛 模板具体类取别名
//2,using
using _IntArr = Array<int, 20>; // 语法
🎊 模板类取别名
template <typename T>
using _Array = Array<T, 20>;
这样定义好之后,使用_Array
就相当于使用Array<T,20>
。
变量模板(C++14)
概念:变量模板(Variable Templates)是 C++14 引入的一个新特性,它允许你定义一个模板,该模板在实例化时会生成一个具有特定类型的变量。变量模板的主要作用是减少代码冗余,提高代码的灵活性和可维护性。
概括:
- 模板实例化生成具体类型变量
- 减少代码冗余
定义格式
template<typename T>
T name = value;
- name:变量名
- value:初始值
cosntexpr
变量模板一般会结合constexpr使用,constexpr
是const
的升级优化版,最大区别是:const
定义的不一定是常量,但是constexpr
一定是常量,这个什么意思呢??
如下代码:
int sum = 10;
const int csum = sum;
这里,将sum
值赋值给const变量csum,但是csum
是一个变量,不是常量啊,他在程序运行的时候才会是常量。
constexpr定义的一定是常量
C++针对以上情况,提出了cosntexpr
这个关键字,这个关键字定义的变量是在编译的时候计算出来的,一定是常量,不可能是变量,如下代码:
int sum = 10;
constexpr int csum = sum;
报错:error C2131: 表达式的计算结果不是常数
。
constexpr与函数
由于constexpr
是在编译的时候检查的,所以把函数声明为constexpr
了,这样编译器就会大胆优化,只要是出现CharMax()的地方,直接用127替代,是不是和宏很类似?确实,但是宏没有运行时安全检查,这也是为什么有些大佬提议用C++重写Linux内核的原因之一。
注意 : constexpr声明的函数,函数体不要写的太复杂(以前只允许一行代码)
如下:
constexpr uint8_t CharMax()
{
int max = 117;
for (int i = 0; i < 10; i++)
{
++max;
}
return max;
}
这个时候,调用constexpr
返回的就是127,常量的127
constexpr修饰接收函数返回值变量
废话不多说,直接上代码:
size_t getNum()
{
return 127;
}
// 调用
constexpr size_t num = getNum();
// 报错:表达式必须含有常量值无法调用非 constexpr 函数"getNum"(已声明 所在行数:31)
☀️ 总结:
-
如果用
constexpr修饰接收函数返回值变量
,那么这个函数返回值夜泊徐要用constexpr
修饰。 -
解决方法如下:
-
constexpr size_t getNum() { return 127; }
变量模板的使用
非静态变量
-
从变量模板实例化的变量被称为**
被实例化变量
,从静态数据成员模板实例化的变量被称为被实例化静态数据成员
。** -
一般结合:
constexpr
使用,如下代码:
template<typename T>
constexpr T PI = T(3.14159265358L);
template<typename T>
T circle_area(T r)
{
return PI<T> *r * r; //PI<是变量模板实例化>
}
首先声明了一个变量模板PI,在后面就可以使用各种类型的PI了(如:PI、PI、PI等等),非常方便,简化了代码。
类静态变量
在类作用域中使用时,变量模板声明一个静态数据成员模板。与其他静态成员一样,静态数据成员模板的需要一个定义。这种定义可以在类定义外提供:
- 静态数据,成员模板,静态成员没有
加const
,需要在类外中初始化。
struct Limits
{
template<typename T> // 静态数据的 成员变量模板
static T max;
};
template<typename T>
const T Limits::max = {}; //注意 注意 注意,这时不需要加static
- 类模板的非静态数据成员
template<typename T>
struct Foo
{
static const T foo;
};
template<typename T>
const T Foo<T>::foo = //是类模板,一定要与“静态成员,成员模板”做好区分,是在哪里进行显示转换
其实如果静态变量声明的是const,并且有初始值,那么可以不用在内外定义,会自动内联。(注意此时不能在外面声明);
class Test
{
public:
static const int count = 0;
};
cout << Test::count << endl; //可以直接使用
在C++14之前没有变量模板之前
在 C++14 引入变量模板前,参数化变量通常实现为类模板的静态数据成员,或返回所需值的 constexpr 函数模板。
#include <iostream>
using namespace std;
//1,使用变量模板
template<typename T>
constexpr T PI = T(3.14159265358);
//2,使用函数模板
template<typename T>
constexpr T getPI()
{
return T(3.14159265358);
}
//3,使用类模板
template<typename T>
struct Math
{
static constexpr T PI = T(3.14159265358);
};
int main()
{
cout << PI<int> << " " << PI<float> << endl;
cout << getPI<int>() << " " << getPI<float>() << endl;
cout << Math<int>::PI << " " << Math<float>::PI << endl;
/*
3 3.14159
3 3.14159
3 3.14159
*/
return 0;
}
类型萃取(简单了解)
类型萃取,可简单理解为类型获取。对不同类型对象进行不同处理,可以提升程序效率,要注意的是:是在编译的时候确定类型,这也是元编程的核心思想之一,在编译的时候去区分、确定类型,然后针对不同类型做不同处理。
is_pod
萃取的典型应用是在模板函数中区分T的类型是原生类型POD,还是自定义类型,POD全称plain old data,简单理解就是C++从C继承来的基本数据类型,如int、double等。
之所以需要区分类型,主要是因为POD类型与自定义类型的很多处理方法不同,典型的就是copy,POD可以直接使用C库提供的memcpy,它主要是实现内存层面的拷贝,而非POD类型需要使用for循环挨个拷贝,因为涉及到深拷贝与浅拷贝的问题,所以在模板中需要识别数据类型,再做不同处理。
如以下代码,用来判断int类型是不是POD类型,有三种使用方式:
cout << std::boolalpha << is_pod<int>::value << endl; //获取静态成员
cout << std::boolalpha << is_pod<int>() << endl; //类型转换函数
cout << std::boolalpha << is_pod<int>()() << endl; //operaotr()函数
输出结果:
true
true
true
说明int
确实是POD类型。