本专栏目的

  • 更新C/C++的基础语法,包括C++的一些新特性

前言

  • 模板与元编程是C++的重要特点,也是难点,本人预计将会更新10期左右进行讲解,这是第三期,讲变量模板、constexpr、萃取等知识
  • C语言后面也会继续更新知识点,如内联汇编;
  • 欢迎收藏 + 关注,本人将会持续更新。

前言
上两次笔记咱们已经学习了C/C++语言基础–C++模板与元编程系列一(泛型、模板、函数模板、全特化函数模板………)C/C++语言基础–C++模板与元编程系列二类模板、全特化、偏特化、编译模型简介、实现简单Vetctor等…………),加下来学习另外两个模板。

  • 别名模板(C++11):
  • 变量模板(C++14):

非类型模板参数

📘

模板参数并不局限于定义类型,可以使用编译器内置类型作为参数,在编译期间变成模板的特定常量,这也是元编程的思想之一,。

非类型模板参数:就是说在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;
  • 定义的非模板类型参数也是一样可以在类中使用,有点像函数的默认参数。

别名模板

我们在写程序的过程中,总是希望写简短的代码,给一些复杂的名称简化,或者取一个简短的名字,于是又有了类型别名,用来重新定义复杂的名字

🐤

使用typedef不仅可以对遍历取别名,还可以为模板具体化指定别名,🍡 🍡🍡 ​, 注意:是模板具体化

//模板别名
//1,typedef
typedef Array<int, 20> IntArr;

注意: 不能为模板类取别名。

// 如:
template <typename T>
typedef Array<T, 20> _Array;

报错:

C/C++语言基础--C++模板与元编程系列三(变量模板、constexpr、萃取等…………)-LMLPHP

解决方法: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使用,constexprconst的升级优化版,最大区别是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 引入变量模板前,参数化变量。

#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类型。

11-03 06:34