从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

目录
相关文章
|
8天前
|
安全 算法 C语言
【C++进阶】深入STL之string:掌握高效字符串处理的关键
【C++进阶】深入STL之string:掌握高效字符串处理的关键
14 1
【C++进阶】深入STL之string:掌握高效字符串处理的关键
|
1天前
|
存储 Linux C语言
c++进阶篇——初窥多线程(二) 基于C语言实现的多线程编写
本文介绍了C++中使用C语言的pthread库实现多线程编程。`pthread_create`用于创建新线程,`pthread_self`返回当前线程ID。示例展示了如何创建线程并打印线程ID,强调了线程同步的重要性,如使用`sleep`防止主线程提前结束导致子线程未执行完。`pthread_exit`用于线程退出,`pthread_join`用来等待并回收子线程,`pthread_detach`则分离线程。文中还提到了线程取消功能,通过`pthread_cancel`实现。这些基本操作是理解和使用C/C++多线程的关键。
|
2天前
|
存储 算法 C++
C++一分钟之-标准模板库(STL)简介
【6月更文挑战第21天】C++ STL是高效通用的算法和数据结构集,简化编程任务。核心包括容器(如vector、list)、迭代器、算法(如sort、find)和适配器。常见问题涉及内存泄漏、迭代器失效、效率和算法误用。通过示例展示了如何排序、遍历和查找元素。掌握STL能提升效率,学习过程需注意常见陷阱。
20 4
|
2天前
|
编译器 程序员 C++
C++一分钟之-模板基础:泛型编程
【6月更文挑战第21天】C++模板,泛型编程的关键,让代码跨类型工作,增强重用与灵活性。理解模板基础,如函数和类模板,注意避免特化与偏特化的混淆、编译时膨胀及复杂的错误调试。通过明确特化目的、限制模板使用及应用现代C++技术来优化。示例展示了模板如何自动或显式推导类型。模板元编程虽强大,但初学者宜从基础开始。正确使用模板,提升代码质量,同时保持简洁。
21 3
|
3天前
|
C++
C++:模板
C++:模板
13 3
|
3天前
|
C++
C++类和类模板——入门
C++类和类模板——入门
9 1
|
5天前
|
数据安全/隐私保护 C++
C++ 中的类是一种用户定义的数据类型,用于表示具有相似特征和行为的对象的模板。
C++ 中的类是一种用户定义的数据类型,用于表示具有相似特征和行为的对象的模板。
|
8天前
|
存储 算法 程序员
【C++进阶】深入STL之 栈与队列:数据结构探索之旅
【C++进阶】深入STL之 栈与队列:数据结构探索之旅
16 4
|
8天前
|
算法 安全 编译器
【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想
【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想
22 1
|
8天前
|
存储 算法 程序员
【C++进阶】深入STL之vector:构建高效C++程序的基石
【C++进阶】深入STL之vector:构建高效C++程序的基石
13 1