从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

目录
相关文章
|
3月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
128 26
|
12月前
|
安全 编译器 C语言
C++入门1——从C语言到C++的过渡
C++入门1——从C语言到C++的过渡
184 2
|
10月前
|
存储 对象存储 C++
C++ 中 std::array<int, array_size> 与 std::vector<int> 的深入对比
本文深入对比了 C++ 标准库中的 `std::array` 和 `std::vector`,从内存管理、性能、功能特性、使用场景等方面详细分析了两者的差异。`std::array` 适合固定大小的数据和高性能需求,而 `std::vector` 则提供了动态调整大小的灵活性,适用于数据量不确定或需要频繁操作的场景。选择合适的容器可以提高代码的效率和可靠性。
448 0
|
10月前
|
算法 编译器 C语言
【C语言】C++ 和 C 的优缺点是什么?
C 和 C++ 是两种强大的编程语言,各有其优缺点。C 语言以其高效性、底层控制和简洁性广泛应用于系统编程和嵌入式系统。C++ 在 C 语言的基础上引入了面向对象编程、模板编程和丰富的标准库,使其适合开发大型、复杂的软件系统。 在选择使用 C 还是 C++ 时,开发者需要根据项目的需求、语言的特性以及团队的技术栈来做出决策。无论是 C 语言还是 C++,了解其优缺点和适用场景能够帮助开发者在实际开发中做出更明智的选择,从而更好地应对挑战,实现项目目标。
361 0
|
12月前
|
C语言 C++
C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在C语言中,`static`关键字主要用于变量声明,使得该变量的作用域被限制在其被声明的函数内部,且在整个程序运行期间保留其值。而在C++中,除了继承了C的特性外,`static`还可以用于类成员,使该成员被所有类实例共享,同时在类外进行初始化。这使得C++中的`static`具有更广泛的应用场景,不仅限于控制变量的作用域和生存期。
258 10
|
算法 机器人 C语言
ROS仿真支持C++和C语言
ROS仿真支持C++和C语言
395 1
|
12月前
|
C语言 C++
实现两个变量值的互换[C语言和C++的区别]
实现两个变量值的互换[C语言和C++的区别]
136 0
|
编译器 Linux C语言
【C++小知识】为什么C语言不支持函数重载,而C++支持
【C++小知识】为什么C语言不支持函数重载,而C++支持
|
8月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
4月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
94 0