C++学习笔记(曾经我看不懂的代码2:基于范围的for循环、auto使用、stl容器、template模板、lambda表达式、结构体继承、仿函数)

不知不觉c++程序设计:标准库已经看了一大半了,学到了很多,很多曾经在网上和在书上看到却看不懂的代码,在看完标准库中的大半内容以后,都能大致的理清代码的含义。

代码模板一:

for (auto &a:arr) 
1、基于范围的for循环:

a为迭代变量,arr为迭代范围,&表示引用。

写一个例子:

#include<iostream>
using namespace std;
int main() {
    int arr[3]={0,1,2};
    int i;
    for (auto &a:arr) {        //每一个循环创建一个引用
        cout << "a="<< a<<endl;
        cout << "arr地址:"<<&arr[i]<<endl;
        cout << "a的地址:"<<&a<<endl;
        i++;
    }
}

输出结果:
在这里插入图片描述
可以看出此处for循环是对数组的一个遍历。由输出的地址,可以得出for循环每遍历一个元素,就会自动生成一个引用量,引用指向数组中当前遍历元素,所以可以通过修改引用量a来修改数组元素的值。

不加引用也是可以的:

    int arr[3]={0,1,2};
    int i;
    for (auto a:arr) {        //每一个循环创建一个引用
        cout << "a="<< a<<endl;
        cout << "arr地址:"<<&arr[i]<<endl;
        cout << "a的地址:"<<&a<<endl;
        i++;
    }

输出:
在这里插入图片描述
则每遍历一个数组元素,都会新生成一个临时变量a,并为其分配内存。

2、auto使用:

auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型,会在编译时进行类型推导,类似js中的var

auto a =1.11;
cout<< typeid(a).name();
//输出 d

代码模板二:

void increment_all(vector<int>& v)
{
    for_each(v.begin(),v.end(),[](int& x){++x;});
}

这是STL算法中的一段。vector<int>& v表示存放int型的容器且引用。for_each(a,b,f(x))STL算法algorithm库中的方法,会对[a,b)范围的每个元素遍历,并执行f(x),且将f(x)的返回值作为返回。[](int& x){++x}lambda表达式,本质也是个函数。

1、stl容器:
(1)容器分类:
  • 顺序容器:提供对元素(半开)序列的访问。
  • 关联容器:提供基于关键字的关联查询。

其他保存元素的对象类型:

  • 容器适配器:提供对底层容器的特殊访问。
  • 拟容器:保存元素序列,提供容器的大部分但非全部功能。
(2)常用容器:

来自c++标准库的stl容器头文件内容,这里我稍做了一些修改和内容的添加:

顺序容器解释作用
vector<T,A>可变大小一维数组空间里连续分配的T类型元素序列;默认选择容器
deque<T,A>双端队列T类型元素双端队列;向量和链表的混合;对大多数应用而言,都比向量和链表其中之一要慢
forward_list<T,A>单向链表T类型元素单向链表;很短的或空序列的理想选择
list<T,A>双向链表T类型元素双向链表;当需要插入/删除元素但不移动已有元素时选择它
有序关联容器解释作用
map<K,V,C,A>关联数组从K到V的有序映射;一个(K,V)对序列
multimap<K,V,C,A>多重关联数组从K到V的有序映射;允许重复关键字
set<K,C,A>集合K的有序集合
multiset<K,C,A>多重集合K的有序集合;允许重复关键字
无序关联容器解释作用
unordered_map<K,V,H,E,A>哈希关联数组从K到V的无序映射
unordered_multimap<K,V,H,E,A>多重从K到V的无序映射;允许重复关键字
unordered_set<K,H,E,A>哈希集合K的无序集合
unordered_multiset <K,H,E,A>多重
容器适配器解释作用
queue<T,C>队列T的队列,支持push()和pop()操作
priority_queue<T,C,Cmp>优先级队列T的优先队列;Cmp是优先级函数类型
stack<T,C>T的栈,支持push()和pop()操作
拟容器解释作用
T[N]内置数组固定大小的内置数组:N个连续存储的类型为T的元素;没有size()或其他成员函数
array <T,N>一维数组固定大小的数组,N个连续存储的类型为T的元素;类似内置数组,但解决了大部分问题
basic_string<C,Tr,A>字符串类模板一个连续分配空间的类型为C的字符序列,支持文本处理操作,如连接(+和+=);basic_string通常都经过了优化,短字符串无需使用自由存储空间
stringbasic_string< char >
u16stringbasic_string< char16_t >
u32stringbasic_string< char32_t >
wstringbasic_string< wchar_t >
valarry< T >数值向量,支持向量运算,但有一些限制,这些限制是为了鼓励高性能实现;只在做大量向量运算时使用
bitset< N >bool数组,二进制集合N个二进制位的集合,支持集合操作
vector< bool >vector< T >的特例化版本,紧凑保存二进制位

其中,multimapmultiset分别声明在<map><set>中,priority_queue声明在<queue>中,添加了相关的头文件后,就可以使用了。

#include <iostream>
#include <queue>
using namespace std;

int main() {
    priority_queue<int> a;  //优先队列,使用时与queue类似
    a.push(1);
    cout<<a.top();
    a.pop();
    return 0;
}
(3)访问容器内的元素的方法:
  • 数组下标访问:
    有些容器(如initializer_list)未提供下标运算符,想要使用数组访问容器内的元素,可以使用指针下标:
#include <iostream>
#include <initializer_list>
using namespace std;

int main() {
    initializer_list<int> a {1,2,3,4};
    const int* p = a.begin();
    cout<<p[2];
    return 0;
}

//输出:3
  • for循环:
    就是上面写过的基于范围的for循环,所有容器都支持此方法:
void f2(initializer_list<int> lst)
{
    for(auto x:lst)
        cout<<x<<'\n';
}
(4)成员类型:
成员类型
value_type元素类型
allocator_type内存管理器类型
size_type容器下标、元素数目等的无符号类型
difference_type迭代器差异的带符号类型
iterator行为类似 value_type*
const_iterator行为类似 const_value_type*
reverse_iterator行为类似 value_type*
const_reverse_iterator行为类似 const_value_type*
referencevalue_type&
const_referenceconst_value_type&
pointer行为类似 value_type*
const_pointer行为类似 const_value_type*
--
key_type关键字类型:仅关联容器具有
mapped_type映射值类型:仅关联容器具有
key_compare比较标准类型:仅有序容器具有
--
hasher哈希函数类型:仅无序容器具有
key_equal等价性检验函数类型:仅无序容器具有
local_iterator桶迭代器类型:仅无序容器具有
const_local_iterator桶迭代器类型:仅无序容器具有

每个容器和”拟容器“都提供了上表中的大多数成员类型,但不会提供无意义的类型。例如,array没有allocator_typevector没有key_type

(5)容器中结构体:

vector来:

#include <iostream>
#include <set>
#include <vector>

using namespace std;
struct labValue
{
    string label;
    int value;
};

int main() {
    vector<labValue> a{{"s",2},{"f",3}};
    for (auto&x:a)
        cout<<x.value;
    return 0;
}

由于留意到书上使用set存储结构体时,添加了一段对<符号重载的代码,有些不明所以,于是自己用set写了一段代码,结果程序报错:
在这里插入图片描述
于是上网搜索了一番,在set容器中使用结构体时,须要对结构体重载运算符<才能使用。

#include <iostream>
#include <set>
#include <vector>

using namespace std;
struct labValue
{
    string label;
    int value;
};

bool operator<(const labValue&a ,const labValue &b)
{}

int main() {
    set<labValue> a{{"s",2},{"f",3}};
    for (auto&x:a)
        cout<<x.value;
    return 0;
}
(6)容器存储函数指针:
//代码来自stl算法中的一节
using 	Predicate = bool (*)(int); //指向函数的指针,形参为int,返回值为bool
void f(vector<Predict>& v1,vector<int>& v2)

关于容器的内容,书上还有很多,我只能挑一些基本的,还有我不知道的内容记录一下,完整的内容参考标准库中的stl容器章节。

2、lambda表达式:

lambda表达式是一个匿名函数,即没有函数名的函数。

由于没有函数名,所以无法在其他地方调用它,在哪定义就在哪里触发。主要好处是免去了函数声明,然后再去调用的步骤,使得方法可以像值一样传递。

之前在qt中就使用过,如:

connect(ui->pushbutton,&QPushButton::clicked, this, 
                   [](){qDebug()<<u8"点击了按钮";}
                   ); 
//此处将按钮的点击信号与lambda表达式函数绑定,信号触发一次,就执行一次该lambda函数

以下来自:https://blog.csdn.net/a379039233/article/details/83714770
lambda表达式也叫闭包。闭就是封闭的意思,封闭的意思就是其他地方都不调用它,包就是函数。
lambda表达式 其实就是一个函数对象,他内部创建了一个重载()操作符的类。

(1)语法:
[捕获列表] (操作符重载函数参数列表) mutable 或 exception 声明 -> 返回值类型 {函数体;}

以下资料来自:https://www.cnblogs.com/jimodetiantang/p/9016826.html

(2)函数对象参数:

函数对象参数有以下形式:

  • :没有任何函数对象参数。
  • =:函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
  • &:函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是引用传递方式(相当于是编译器自动为我们按引用传递了所有局部变量)。
  • this:函数体内可以使用 Lambda 所在类中的成员变量。
  • a:将 a 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝,因为默认情况下函数是 const 的,要修改传递进来的拷贝,可以添加 mutable 修饰符。
  • &a:将 a 按引用进行传递。
  • a,&b:将 a 按值传递,b 按引用进行传递。
  • =,&a,&b:除 ab 按引用进行传递外,其他参数都按值进行传递。
  • &,a,b:除 ab 按值进行传递外,其他参数都按引用进行传递。

总结来说,就是声明了lambda表达式函数体中会使用哪些外部参数,以及怎样使用这些外部参数,即外部参数的捕获方式,看下面的例子就懂了。

(3)操作符重载函数参数:

标识重载的 () 操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如: (a, b))和按引用 (如: (&a, &b)) 两种方式进行传递。

(4)mutable 或 exception 声明:

这部分可以省略。按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是值本身)。exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)

(5)返回值类型:

标识函数返回值的类型,当返回值为 void,或者函数体中只有一处 return 的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。

(6){函数体}:

标识函数的实现,这部分不能省略,但函数体可以为空。

(7)示例:
[] (int x, int y) { return x + y; } // 隐式返回类型
[] (int& x) { ++x;  } // 没有 return 语句 -> Lambda 函数的返回类型是 'void'
[] () { ++global_x;  } // 没有参数,仅访问某个全局变量
[] { ++global_x; } // 与上一个相同,省略了 (操作符重载函数参数)

可以像下面这样显示指定返回类型:

[] (int x, int y) -> int { int z = x + y; return z; }

在这个例子中创建了一个临时变量 z 来存储中间值。和普通函数一样,这个中间值不会保存到下次调用。什么也不返回的Lambda 函数可以省略返回类型,而不需要使用 -> void 形式。Lambda 函数可以引用在它之外声明的变量. 这些变量的集合叫做一个闭包. 闭包被定义在 Lambda 表达式声明中的方括号 [] 内。这个机制允许这些变量被按值或按引用捕获。

如下图的例子:
在这里插入图片描述
以下(8) (9) (10)参考自:https://blog.csdn.net/a379039233/article/details/83714770

(8)格式理解:

lambda有简单格式:

int main ()
{
    [](){}();     //第一个括号表示函数参数,{}表示函数体,第二个括号表示函数的调用
    }

关于lambda表达式,可能平时见多了的是[](){}(记表达式1),而非[](){}()(记表达式2)。就如上面所写,最后的括号表示调用,表达式1在代码中使用并不会输出什么,它等于一个函数的定义,而表达式2在后面加了个括号就表示了函数定义完的调用。下面写个例子:

#include <iostream>
using namespace std;

int main ()
{
    [](){cout<<"这是表达式1"<<endl;};   //定义了匿名函数
    [](){cout<<"这是表达式2"<<endl;}();   //定义了匿名函数并进行调用
}

输出结果:
在这里插入图片描述
修改一下:

#include <iostream>
using namespace std;

int main ()
{
    auto out =[](){cout<<"这是表达式1"<<endl;};
    out();  //调用
    [](){cout<<"这是表达式2"<<endl;}();
}

输出结果:
在这里插入图片描述
对于表达式1由于只是定义,所以能够适用于信号槽的绑定,信号触发时就回去调用。而只在函数的某个角落执行任务的函数就适用于表达式2

(9)使用:
int main ()
{ 
    //  1、
    [](){cout<<"ss";}();
    //  2、
    auto lam = [] { cout << "Hello, World!"<<endl; };
	lam();
    //  3、返回值:
    auto lam1 = [](){ cout << "Hello, World!"<<endl; return 1;};
    auto lam2 = []()->int { cout << "Hello, World!"<<endl; return 1;};
    }

捕获变量:

#include <iostream>
using namespace std;

int main ()
{
    int a=1,b=2,c=3;
    cout<<"a的地址:"<<&a<<endl<<"b的地址:"<<&b<<endl<<"c的地址:"<<&c<<endl;
    //按照上面的捕获方式,这里 [&,a] 表示除了 a 用值传递,其他都是引用传递
    auto lam2 = [&,a](){  //b,c 引用捕获,a 值捕获
        cout<<"捕获后:----------------------------"<<endl;
        cout << a<<b<<c<<endl;
        cout<<"a的地址:"<<&a<<endl<<"b的地址:"<<&b<<endl<<"c的地址:"<<&c<<endl;
        b=5;c=6;                  //a =1; a不能赋值
        cout<<"赋值后:----------------------------"<<endl;    
        cout << a<<b<<c<<endl;
    };
    lam2();
    cout<<"外部的地址和值:----------------------"<<endl;
    cout<<a<<b<<c<<endl;
    cout<<"a的地址:"<<&a<<endl<<"b的地址:"<<&b<<endl<<"c的地址:"<<&c<<endl;
}

输出结果:
在这里插入图片描述

可以看到,b,c的引用捕获等于引用了main函数中的b,c,而由于a是值捕获,所以lambda表达式中的a实际上是重新申请的内存,它的值与捕获对象a的值相同。

结合algorithm库的方法:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main ()
{  int arr[] = {6,4,3,2,1,5};
    std::sort(arr, arr+6, [](const int& a,const int& b){return a>b;});//降序排序
    //std::sort(arr, arr+6, [](const auto& a,const auto& b){return a>b;}); //C++14支持基于类型推断的泛型lambda表达式。
    std::for_each(begin(arr),end(arr),[](const int& e){cout<<"After:"<<e<<endl;});//6,5,4,3,2,1
}

输出:
在这里插入图片描述

(10)lambda表达式的类型:

也是参照的上面链接的博客。由于auto可以自动去推导变量类型,所以可以将lambda表达式赋给auto类型变量,然后输出其类型就能得到lambda表达式的类型:

#include <iostream>
using namespace std;
int main ()
{
    auto lam = [](){cout<<"hi"<<endl;};
    cout<<typeid(lam).name()<<endl;
}

我这里输出的好像有点问题,不管了:
在这里插入图片描述

代码模板三:

template <typename Rep,typename Period = ratio<1>>
class duration{
public:
    using rep = Rep;
    using period = Period;
    //...
};

这是c++标准库中duration类型中的代码,下面的classusing定义别名就不说了,上面的template表示它是一个模板类,模板函数之前用过很多次,一直没用过模板类,所以看到这个代码就来记录一下。

1、template模板:

通过把类型定义为参数,提高代码的可重用性。

定义的模板类或模板函数前需要加上类似的结构:

//只作用于下方第一个函数或类
template <typename T>  
// T 为函数类型参数,在模板下方的类或函数中的类型可以使用T来替换,模板会自动进行类型推导

例子:

#include <iostream>
using namespace std;

int add1(int a,int b)
{
    return a+b;
}
float add2(float a,float b)
{
    return a+b;
}

int main ()
{
    cout<<add1(3,4)<<endl;
    cout<<add2(2.1,3.5)<<endl;
    return 0;
}

通过使用模板,可以把它写成:

#include <iostream>
using namespace std;

template <typename T>
T add(T a,T b)
{
    return a+b;
}

int main ()
{
    cout<<add(3,4)<<endl;
    cout<<add(2.1,3.5)<<endl;
    return 0;
}

模板会像auto一样自动推导函数类型。

(1)语法:

参照:http://blog.sina.com.cn/s/blog_9d48d26f01015snt.html

//模板函数
template < typename {类型参数名称}, [ int {Name}=...][, ...] > {函数定义}
//模板类
template < typename ... , [ int {Name}=...] >   class ...
(2)使用方式:
  • 隐式类型参数:模板自动推导函数类型。
  • 显式类型参数:在函数名后添加类型参数表,指定函数参数类型,而非让模板自动推导类型。如:add<int>(2.1,3.5);

修改代码:

#include <iostream>
using namespace std;

template <typename T>
T add(T a,T b)
{
    cout<<a<<"    "<<b<<endl;
    return a+b;
}

int main ()
{
    add(3,4);
    add<int>(2.1,3.5);
    return 0;
}

输出:
在这里插入图片描述

可以看到,添加<int>后,T就会指向int,输入的参数以及返回值等都会被变成int型。

(3)函数模板、结构体模板和类模板:
  • 函数模板:
template <typename type> ret-type func-name(parameter list)
{
   // 函数的主体
}

创建和使用上面有了。

  • 结构体模板:

构建和使用:

#include <iostream>
using namespace std;

template<typename T1,typename T2>
struct dd{
    T1 a;
    T2 b;
 };

int main()
{
    dd<int,float> d1;
    d1.a=1;
    d1.b=1.2;
    cout <<d1.a<<"    "<<d1.b<<endl;

    dd<float,int> d2;
    d2.a=1.23;
    d2.b=1.1;
    cout <<d2.a<<"    "<<d2.b;
}

输出:
在这里插入图片描述

使用class替换typename

template <class type> 
class class-name {
}

template <类型参数表>   //类参数表中可以有多个类参数:class类塑参数1, class类型参数2, ...
class 类模板名{
    成员函数和成员变量
};

模板类的成员函数在外部定义时,也需要将其定义为模板类:

template <类型参数表>
返回值类型  类模板名<类型参数名列表>::成员函数名(参数表)
{
    ...
}
//如:
template<class T1,class T2>
int Pair<T1,T2>::add(int a,int b)
{
    return a+b;
}

构造类的对象时需要按照格式:

类模板名<真实类型参数表> 对象名(构造函数实际参数表);

//如:
Pair<string,int> student;

例如下的模板类定义:

#include <iostream>
#include <string>
using namespace std;
template <class T1,class T2>
class Pair
{
public:
    T1 key;  //关键字
    T2 value;  //值
    Pair(T1 k,T2 v):key(k),value(v) { };
    int add(int a ,int b );
    bool operator < (const Pair<T1,T2> & p) const;
    //由于p的类型为 Pair<T1,T2>,所以参数p为类本身
};

template<class T1,class T2>
bool Pair<T1,T2>::operator < (const Pair<T1,T2> & p) const   //对操作符 < 重载,形参为引用p
{ 
    return key < p.key;   //返回类的成员 key 和 p 比大小
}

template<class T1,class T2>
int Pair<T1,T2>::add(int a,int b)
{
    return a+b;
}

int main()
{
    Pair<string,int> student("Tom",19); 
    //实例化出一个类 Pair<string,int>,string替换T1,int替换T2
    cout << student.key << " " << student.value;
    return 0;
}
  • 类模板中的函数模板:
#include <iostream>
using namespace std;
template <class T>   //类模板
class A
{
public:
    template <class T2>
    void Func(T2 t) { cout << t; }  //成员函数模板
};
int main()
{
    A<int> a;    //注意此处的int替换的并不是函数模板的T2,而是类的模板T
    a.Func<char>('K');        //显式的指定函数模板的类型
    a.Func<string>("hello");   //显式的指定函数模板的类型
    return 0;
}
(4)默认模板参数:

经常在书上看到类似下面的代码:

template<typename=int,typename T2>
//这段实际是相当于
template<typename T=int,typename T2>

代码使用了默认模板参数:

#include <iostream>
using namespace std;
//template<typename T1,typename T2>   不使用默认模板参数
template<typename T1=int,typename T2>   //使用默认模板参数
void printa(T2 a)
{
    cout<<"a="<<a<<endl;
}

int main()
{
    printa("ss");  //错误,会报错
    //printa<int>("ss");    //正确,需要像这样指定 T2 的类型。
}

在如上代码中,如果在函数体中不使用所有模板参数,或不指定所有模板参数的类型时,就会报错。

使用默认模板参数后,使用了默认模板参数的模板参数就可用也可不使用,或者显式的指定类型了。


我在标准库的ratio的定义中看到如下代码:

template<intmax_t N,intmax_t D=1>
struct ratio{
...
}

一开始我还没看明白,intmax_t实际就是一个数据类型,在模板中定义参数,就可以在函数或类中使用,但是赋值操作只能在模板中。如下代码:

#include <iostream>
using namespace std;

template<int s,int T=1>
void printT()
{
    cout<<T<<endl;
    cout<<s<<endl;
}

int main()
{
    //模板s没有初值,使用时需要模板提供参数,T则不需要
    printT<3>();
}

输出:
在这里插入图片描述
还看到一种写法,一样的意思:

template <typename T, typename U = int, U N = 0>
(5)模板特化:

模板特化不同于模板的实例化,模板参数在某种特定类型下的具体实现称为模板的特化。模板特化有时也称之为模板的具体化,分别有函数模板特化和类模板特化。模板的特化有全特化偏特化

我来概括一下,特化可以理解为:指定模板内的模板参数类型,比如把模板参数T转变为int的一个分支。全特换即分支中把所有模板参数替换为具体类型,偏特化即将分支中部分而非全部的模板参数替换为具体类型。这与函数的重载类似,只是函数模板能够重载,而类模板却不能被重载。

函数模板重载:(此段代码来自:https://blog.csdn.net/lyn_00/article/details/83548629

#include <iostream>
using namespace std;
//函数模板
template <typename T>
int fun(T){
    return 1;
}
//函数模板的重载
template <typename T>
int fun(T*){
    return 2;
}
int main(){
    cout << fun<int*>((int*)0) <<endl;
    cout << fun<int>((int*)0) <<endl;
    return 0;
}
  • 函数模板特化:

函数的全特化,我写个代码演示,其实跟重载一个意思:

#include <iostream>
using namespace std;

template<typename T>
T ag(T x, T y)
{
    cout<<"未特化"<<endl;
}

template<>   //特化需要添加这一句
int ag(int x, int y)   //将T特化为int
{
    cout<<"特化了"<<endl;
}

int main()
{
     ag(2,4);
     ag(2.2,4.2);
}

输出结果:
在这里插入图片描述

可以看到,由于ag(2,4)满足特化的模板参数,所以它走的特化后的那一路分支,而ag(2.2,4.2)由于不满足特化的模板参数,所以走的未特化的分支。

关于函数的偏特化,很多文章中都说函数模板只有全特化,关于为什么会这样,我看到这一篇里有解释:https://blog.csdn.net/xuminggang/article/details/4333214
但是这个文章是很多年前的了,暂时没有找到现在可以偏特化的的消息,我自己试了一下,发现确实是不可行的,两个以上的模板参数时,全特化也只有在两个模板参数一样的时候奏效。下面是我尝试的代码:

#include <iostream>
using namespace std;

template<typename T1,typename  T2>
T1 ag(T1 x, T2 y)
{
    cout<<"函数未偏特化"<<endl;
}

template<typename T1>   //特化需要添加这一句
T1 ag(T1 x, float y)   //将T特化为int
{
    cout<<"函数偏特化了"<<endl;
}

int main()
{
     ag(2,4) ;
     ag(2,4.2);
}

运行结果:
在这里插入图片描述

可以看到,并走不到偏特化的分支。

  • 类模板特化:

首先是类的全特化:

#include <iostream>
using namespace std;

template <class T1,class T2>
class AA
{
public:
    T1 key;
    T2 value;
    T1 out(){cout<<"特化前"<<endl;};
};

template<>
class AA<int,float>
{
public:
    int key;
    float value;
    int out(){cout<<"特化后"<<endl;};
};

int main()
{
    AA<int,float> student;
    AA<int,int> bug;

    student.out();
    bug.out();
    return 0;
}

输出:
在这里插入图片描述

可以看到,由于student的模板参数符合特化类型,所以模板类中的函数走的是特化后的分支路线。而bug的模板参数不符合特化类型,所以走的未特化的类型。

类的偏特化,即两个以上模板参数时,对其中一个模板参数进行特化,另一个则保留。对于上面的代码,只需做部分修改:

#include <iostream>
using namespace std;

template <class T1,class T2>
class AA
{
public:
    T1 key;
    T2 value;
    T1 out(){cout<<"偏特化前"<<endl;};
};

template<class T1>   //特化 T2,保留 T1
class AA<T1,float>
{
public:
    T1 key;
    float value;
    int out(){cout<<"偏特化后"<<endl;};
};

int main()
{
    AA<int,float> student;
    AA<int,int> bug;

    student.out();
    bug.out();
    return 0;
}

由于student的第二个参数符合偏特化,而bug的第二个参数不符合偏特化,所以student进行了偏特化,运行结果还和全特化一样。

(6)模板结合容器:
#include <iostream>
#include <list>
using namespace std;

template<typename T>
class AA
{
public:
    list<T> list1;
};

int main()
{
    AA<int> aa;
    aa.list1.push_back(11);
    cout<<aa.list1.back();
}

代码模板四:

template<typename _Tp> 
struct less : public binary_function<_Tp, _Tp, bool> 
{
      bool operator()(const _Tp& __x, const _Tp& __y) const   //操作符()重载
      { return __x < __y; }
};

代码来自:https://blog.csdn.net/K346K346/article/details/82818801

在看仿函数的信息时,找到这个,然后试了一下,发现结构体也是能和继承的。

1、结构体:
#include <iostream>
using namespace std;

struct a{
public:
   int out(){cout<<"我是结构体";}      //结构体函数
};

struct b:public a    //结构体继承
{
};

int main()
{
    b b1;
    b1.out();
    return 0;
}

输出:
在这里插入图片描述

2、仿函数:

仿函数是一个类或结构体,通过重载()运算符,使这个类具有类似函数的用法并实现相关功能。如:

#include <iostream>
using namespace std;
struct add  {
    int operator()(const int& x, const int& y) const
    { return x + y; }
};

int main()
{
    cout<<add()(2,4);
}

关于仿函数更多的东西,我觉得这个链接里讲的还蛮清除的:https://blog.csdn.net/u010710458/article/details/79734558

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值