从C语言到C++_21(模板进阶+array)+相关笔试题(上)

简介: 从C语言到C++_21(模板进阶+array)+相关笔试题

1. 非类型模板参数

对于函数模板和类模板,模板参数并不局限于类型,普通值也可以作为模板参数。

STL 的 array 就有一个非类型模板参数。

T 是类型,而 N 这里并不是类型,而是一个常量。

类型模板参数定义的是虚拟类型,注重的是你要传什么,而非类型模板参数定义的是常量。

1.1 array


array是一个固定大小的顺序容器(静态数组),是 C++11 新增的,它有什么独特的地方吗?

很可惜,基本没有,并且 vector 可以完全碾压 array,这就是为什么只在这里简单地讲讲 array。

看段代码:

#include <iostream>
#include <array>
#include <vector>
using namespace std;
 
int main()
{
    vector<int> v(100, 0);
    array<int, 100> arr;
 
    cout << "v : " << sizeof(v) << endl;
    //这里sizeof算的是成员变量的大小,VS2022下vector应该有四个成员变量,32位平台每个指针是4个字节,因此16字节
    cout << "arr : " << sizeof(arr) << endl;
 
    return 0;
}

vector 是开在空间大的堆上的而 array 是开在栈上的,堆可比栈的空间大太多太多了。


array 能做的操作几乎 vector 都能做,因为 vector 的存在 array 显得有些一无是处。


所以我们拿 array 去对标 vector 是不对的,拿去和原生数组比还是可以对比的。

但是 array 也只是封装过的原生数组罢了,就是有了接口函数,

比起原生数组,array 的最大优势也只是有一个越界的检查,读和写都可以检查到是否越界。

原生数组的读检查不到,写只能检查到后面几个数,

#include <iostream>
#include <array>
#include <vector>
using namespace std;
 
int main()
{
    int a[10];
    array<int, 10> arr; // array也不会初始化
 
    int x = a[15]; // 没报错
    a[10] = 2; // 报错
    a[11] = 2; // 没报错
 
    int y = arr[15]; // 报错
    arr[10] = 2; // 报错
    arr[11] = 2; // 报错
 
    return 0;
}

在 C++11 增加完 array 后备受吐槽,从简化的角度来说完全可以不增加 array。

并且现在大多数人都习惯了用原生数组,基本没人用array。

1.2 非类型模板参数的使用场景

假设我们要定义一个静态栈:

#define N 100
 
template<class T>
class Stack
{
private:
    int _arr[N];
    int _top;
};

如果定义两个容量不一样的栈,一个容量是100 另一个是 500,能做到吗?


这就像 typedef 做不到一个存 int 一个存 double,而使用模板可以做到 st1 存 int,st2 存 double。


这里你的 #define 无论是改 100 还是改 500 都没办法解决这里的问题,


对应的,这里使用非类型模板参数就可以做到 s1 存 100,s2 存 500。

#include <iostream>
using namespace std;
 
template<class T, size_t N>
class Stack
{
private:
    int _arr[N];
    int _top;
};
 
int main()
{
    Stack<int, 100> st1;  // 大小是100
    Stack<int, 500> st2;  // 大小是500
 
    return 0;
}

在模板这定义一个常量 N,派遣它去做数组的大小。

于是我们就可以在实例化 Stack 的时候指定其实例化对象的大小了,分别传 100 和 500。

1.3 注意事项

注意事项 ①:非类型模板参数是是常量,是不能修改的。

#include <iostream>
using namespace std;
 
template<class T, size_t N>
class Stack 
{
public:
    void modify()
    {
        N = 10; // 错误 C2106 “ = ”: 左操作数必须为左值  
    }
private:
    int _arr[N];
    int _top;
};
 
int main()
{
    Stack<int, 100> st1;
    st1.modify();
 
    return 0;
}

注意事项 ②:有些类型是不能作为非类型模板参数的,比如浮点数、类对象以及字符串。

非类型模板参数基本上都是整型家族,char也是整形家族,也只有整型家族是有意义和价值的

#include <iostream>
using namespace std;
 
template<class T, double N> // 错误 C2058 常量表达式不是整型
class Stack 
{
 
private:
    int _arr[N];
    int _top;
};
 
int main()
{
    Stack<int, 100> st1;
 
    return 0;
}

注意事项 ③:非类型的模板参数必须在编译期就能确认结果。

即非类型模板参数的实参只能是常量。

#include <iostream>
using namespace std;
 
template<class T, size_t N> // 错误 C2058 常量表达式不是整型
class Stack 
{
 
private:
    int _arr[N];
    int _top;
};
 
int main()
{
    size_t N;
    cin >> N;
 
    Stack<int, N> st1; // 错误  C2971 “Stack” : 模板参数“N”:“N”: 包含非静态存储持续时间的变量不能用作非类型参数
 
    return 0;
}

2. 模板的特化

通常情况下,使用模板可以实现一些与类型无关的代码

但是,对于一些特殊类型,可能我们就要对其进行一些 "特殊化的处理" 。

举例:如果不对特殊类型进行特殊处理就可能会出现一些问题,比如:

#include <iostream>
using namespace std;
 
class Date // 简化的日期类
{
public:
  Date(int year, int month, int day)
    :_year(year)
    , _month(month)
    , _day(day)
  {}
 
  bool operator<(const Date& d) const
  {
    if ((_year < d._year)
      || (_year == d._year && _month < d._month)
      || (_year == d._year && _month == d._month && _day < d._day))
    {
      return true;
    }
    else
    {
      return false;
    }
  }
private:
  int _year;
  int _month;
  int _day;
};
 
template<class T> // 函数模板 -- 参数匹配
bool Less(T left, T right)
{
  return left < right;
}
 
int main()
{
  cout << Less(1, 2) << endl;   // 可以比较,结果正确
 
  Date d1(2023, 1, 1);
  Date d2(2023, 1, 2);
  cout << Less(d1, d2) << endl;  // 可以比较,结果正确
 
  Date* p2 = &d2;
  Date* p1 = &d1;
  cout << Less(p1, p2) << endl;  // 可以比较,结果错误
 
  return 0;
}

这里我们想比较的是指针指向的内容,而不是指针本身,怎么解决?

2.1 函数模板的特化

首先,必须要先有一个基础的函数模板。

其次,关键字 template 后面接上一对空的 <> 尖括号。

然后,函数名后跟一对尖括号,尖括号中指定需要特化的内容。

最后,函数形参表必须要和模板函数的基础参数类型完全相同。

template<class T> // 函数模板 -- 参数匹配
bool Less(T left, T right)
{
  return left < right;
}
 
template<> // 针对某些类型要特殊化处理 ———— 使用模板的特化解决
bool Less<Date*>(Date* left, Date* right) {
  return *left < *right;
}

代码演示:

#include <iostream>
using namespace std;
 
class Date // 简化的日期类
{
public:
  Date(int year, int month, int day)
    :_year(year)
    , _month(month)
    , _day(day)
  {}
 
  bool operator<(const Date& d) const
  {
    if ((_year < d._year)
      || (_year == d._year && _month < d._month)
      || (_year == d._year && _month == d._month && _day < d._day))
    {
      return true;
    }
    else
    {
      return false;
    }
  }
private:
  int _year;
  int _month;
  int _day;
};
 
template<class T> // 函数模板 -- 参数匹配
bool Less(T left, T right)
{
  return left < right;
}
 
template<> // 针对某些类型要特殊化处理 ———— 使用模板的特化解决
bool Less<Date*>(Date* left, Date* right) 
{
  return *left < *right;
}
 
int main()
{
  cout << Less(1, 2) << endl;   // 可以比较,结果正确
 
  Date d1(2023, 1, 1);
  Date d2(2023, 1, 2);
  cout << Less(d1, d2) << endl;  // 可以比较,结果正确
 
  Date* p2 = &d2;
  Date* p1 = &d1;
  cout << Less(p1, p2) << endl;  // 可以比较,结果正确
 
  return 0;
}

解读:对于普通类型,它还是会调正常的模板。对于 Date* 编译器就会发现这里有个

专门为 Date* 而准备的特化版本,编译器会优先选择该特化版本。这就是函数模板的特化。

思考:现在我们加一个普通Less函数的函数重载,Date* 会走哪个版本?

 
bool Less(Date* left, Date* right) 
{
    return *left < *right;
}

答案:函数重载,会走直接匹配的普通函数版本,因为是现成的,不用实例化。

你可以这么理解:原模板是生肉,模板特化是半生不熟的肉,直接匹配的普通函数是熟肉。

所以:函数模板不一定非要特化,因为在参数里面就可以处理,

写一个匹配参数的普通函数也更容易理解。

从C语言到C++_21(模板进阶+array)+相关笔试题(下):https://developer.aliyun.com/article/1521899?spm=a2c6h.13148508.setting.20.712b4f0eDngT44

目录
打赏
0
0
0
0
47
分享
相关文章
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!
C++入门1——从C语言到C++的过渡
C++入门1——从C语言到C++的过渡
118 2
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
|
3月前
|
【c++】模板详解(2)
本文深入探讨了C++模板的高级特性,包括非类型模板参数、模板特化和模板分离编译。通过具体代码示例,详细讲解了非类型参数的应用场景及其限制,函数模板和类模板的特化方式,以及分离编译时可能出现的链接错误及解决方案。最后总结了模板的优点如提高代码复用性和类型安全,以及缺点如增加编译时间和代码复杂度。通过本文的学习,读者可以进一步加深对C++模板的理解并灵活应用于实际编程中。
44 0
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
187 4
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
75 3
C++ 中 std::array<int, array_size> 与 std::vector<int> 的深入对比
本文深入对比了 C++ 标准库中的 `std::array` 和 `std::vector`,从内存管理、性能、功能特性、使用场景等方面详细分析了两者的差异。`std::array` 适合固定大小的数据和高性能需求,而 `std::vector` 则提供了动态调整大小的灵活性,适用于数据量不确定或需要频繁操作的场景。选择合适的容器可以提高代码的效率和可靠性。
172 0
【C语言】C++ 和 C 的优缺点是什么?
C 和 C++ 是两种强大的编程语言,各有其优缺点。C 语言以其高效性、底层控制和简洁性广泛应用于系统编程和嵌入式系统。C++ 在 C 语言的基础上引入了面向对象编程、模板编程和丰富的标准库,使其适合开发大型、复杂的软件系统。 在选择使用 C 还是 C++ 时,开发者需要根据项目的需求、语言的特性以及团队的技术栈来做出决策。无论是 C 语言还是 C++,了解其优缺点和适用场景能够帮助开发者在实际开发中做出更明智的选择,从而更好地应对挑战,实现项目目标。
214 0
|
6月前
|
【c++】模板详解(1)
本文介绍了C++中的模板概念,包括函数模板和类模板,强调了模板作为泛型编程基础的重要性。函数模板允许创建类型无关的函数,类模板则能根据不同的类型生成不同的类。文章通过具体示例详细解释了模板的定义、实例化及匹配原则,帮助读者理解模板机制,为学习STL打下基础。
69 0

热门文章

最新文章