C++泛编程

在这里插入图片描述

自动推导类型

在C++11中,赋予了auto全新的含义,不再用于修饰变量,而是作为一个类型指示符,指示编译器在编译时推导auto声明的变量的数据类型。

auto 变量名 = 初始值;
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
          

int main()
{
    auto a = 3; cout<<"a="<<a<<endl;
    auto b = 3.3; cout<<"b="<<b<<endl;
    auto c = "西施"; cout<<"c="<<c<<endl;
}

不要滥用auto,auto在编程时真正的用途如下:
1)代替冗长复杂的变量声明。
2)在模板中,用于声明依赖模板参数的变量。
3)函数模板依赖模板参数的返回值。
4)用于lambda表达式中。

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
          
double func(double b, const char* c, float d, short e, long f)
{
	cout << ",b=" << b << ",c=" << c << ",d=" << d << ",e=" << e << ",f=" << f << endl;
	return 5.5;
}
          
int main()
{
    cout<<"使用函数指针"<<endl;
	double (*pf)( double , const char* , float , short , long );    // 声明函数指针pf。
  	pf = func;
	pf( 2, "西施", 3, 4, 5);

    cout<<"使用auto"<<endl;
	auto pf1 = func;
	pf1(2, "西施2", 3, 4, 5);
}

函数模板

函数模板是通用的函数描述,使用任意类型(泛型)来描述函数。

编译的时候,编译器推导实参的数据类型,根据实参的数据类型和函数模板,生成该类型的函数定义。

创建交换两个变量的函数模板:

template <typename T>
void Swap(T &a, T &b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
          
template <typename T>
void Swap(T &a, T &b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
    
int main()
{
    int a,b;
    a = 1;
    b = 2;
    Swap(a,b);
    cout<<a<<"   "<<b<<endl;
}

函数模板使用typename,类模板使用class

1)可以为类的成员函数创建模板,但不能是虚函数和析构函数。

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
          
class CGirl{
public:
    template <typename T>
    CGirl(T a){
        cout<<"a = "<<a<<endl;
    }
    template <typename T>
    void show(T b){
        cout<<"b = "<<b<<endl;
    }
};

    
int main()
{
    CGirl girl("aa");
    girl.show(111);

    CGirl girl2(2);
    girl2.show(222);

    return 0;
}

2)使用函数模板时,必须明确数据类型,确保实参与函数模板能匹配上。
3)使用函数模板时,推导的数据类型必须适应函数模板中的代码。
4)使用函数模板时,如果是自动类型推导,不会发生隐式类型转换,如果显式指定了函数模板的数据类型,可以发生隐式类型转换。

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
          
template <typename T>
T Add(T a, T b){

    return a+b;
}

    
int main()
{
    int a = 10;
    char b = 20;

    int c = Add<int>(a,b);
    cout<<"c = "<<c<<endl;

    return 0;
}

5)函数模板支持多个通用数据类型的参数。

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
          
template <typename T1,typename T2>
void show(T1 a, T2 b){

    cout<<"a = "<<a<<"  b = "<<b<<endl;
}

    
int main()
{
    int a = 1;
    string b = "b";
    show(a,b);
}

6)函数模板支持重载,可以有非通用数据类型的参数。

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

 template <typename T1>
void show(T1 a){

    cout<<"a = "<<a<<endl;
}

template <typename T1,typename T2>
void show(T1 a, T2 b){

    cout<<"a = "<<a<<"  b = "<<b<<endl;
}

template <typename T1,typename T2,typename T3>
void show(T1 a, T2 b,T3 c){

    cout<<"a = "<<a<<"  b = "<<b<<"c = "<<c<<endl;
}

    
int main()
{
    int a = 1;
    string b = "b";
    float c = 1.1111;
    show(a,b);
    show(a,b,c);
}

函数模板的具体化

可以提供一个具体化的函数定义,当编译器找到与函数调用匹配的具体化定义时,将使用该定义,不再寻找模板。

template<> void 函数模板名<数据类型>(参数列表)
template<> void 函数模板名 (参数列表)
{
	// 函数体。
}
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
                      
class CGirl            // 超女类。
{
public:
	int m_bh;              // 编号。
	string m_name;   // 姓名。
	int m_rank;          // 排名。
};
              
template <typename T>
void Swap(T& a, T& b);      // 交换两个变量的值函数模板。
            
template<> 
void Swap<CGirl>(CGirl& g1, CGirl& g2);      // 交换两个超女对象的排名。
// template<> 
// void Swap(CGirl& g1, CGirl& g2);      // 交换两个超女对象的排名。
           
int main()
{
	int a = 10, b = 20;
	Swap(a, b);           // 使用了函数模板。
	cout << "a=" << a << ",b=" << b << endl;
            
	CGirl g1, g2;
	g1.m_rank = 1; g2.m_rank = 2;
	Swap(g1, g2);     // 使用了超女类的具体化函数。
	cout << "g1.m_rank=" << g1.m_rank << ",g2.m_rank=" << g2.m_rank << endl;
}
            
template <typename T>
void Swap(T& a, T& b)      // 交换两个变量的值函数模板。
{
	T tmp = a;
	a = b;
	b = tmp;
	cout << "调用了Swap(T& a, T& b)\n";
}
        
template<> 
void Swap<CGirl>(CGirl& g1, CGirl& g2)      // 交换两个超女对象的排名。
// template<> 
// void Swap(CGirl& g1, CGirl& g2)      // 交换两个超女对象的排名。
{
	int tmp = g1.m_rank;
	g1.m_rank = g2.m_rank;
	g2.m_rank = tmp;
	cout << "调用了Swap(CGirl& g1, CGirl& g2)\n";
}

具体代码如下:
在这里插入图片描述
在这里插入图片描述

对于给定的函数名,可以有普通函数、函数模板和具体化的函数模板,以及它们的重载版本。

1)具体化优先于常规模板,普通函数优先于具体化和常规模板。
2)如果希望使用函数模板,可以用空模板参数强制使用函数模板。

Swap<>(1,2);

3)如果函数模板能产生更好的匹配,将优先于普通函数。
在这里插入图片描述

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

void Swap(int a, int b)      // 普通函数。
{ 	
	cout << "使用了普通函数。\n";  
}

template <typename T> 
void Swap(T a, T b)          // 函数模板。
{	
	cout << "使用了函数模板。\n";  
}

template <> 
void Swap(int a, int b)     // 函数模板的具体化版本。
{ 	
	cout << "使用了具体化的函数模板。\n";  
}
         
int main()
{
	Swap('c', 'd');
    float a = 1.11;
    float b =2.222;
    Swap(a,b);
	Swap<>(1,2);
}

普通函数优于具有函数模板
在这里插入图片描述

具体函数模板化优于普通函数模板
在这里插入图片描述

使用空模板参数强制使用函数模板
在这里插入图片描述

函数模板分文件编写

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


在这里插入图片描述

  1. 函数模板只是函数的描述,没有实体,创建函数模板的代码放在头文件中。
  2. 函数模板的具体化有实体,编译的原理和普通函数一样,所以,声明放在头文件中,定义放在源文件中。
/
// public.h
#pragma once
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

void Swap(int a, int b);      // 普通函数。

template <typename T>
void Swap(T a, T b)         // 函数模板。
{
	cout << "使用了函数模板。\n";
}

template <>
void Swap(int a, int b);    // 函数模板的具体化版本。
/

/
// public.cpp
#include "public.h"

void Swap(int a, int b)      // 普通函数。
{
	cout << "使用了普通函数。\n";
}

template <>
void Swap(int a, int b)     // 函数模板的具体化版本。
{
	cout << "使用了具体化的函数模板。\n";
}
/

/
// demo01.cpp
#include "public.h"

int main()
{
	Swap(1,2);         // 将使用普通函数。
	Swap(1.3, 3.5);  // 将使用具体化的函数模板。
	Swap('c', 'd');    // 将使用函数模板。
}
/

在这里插入图片描述

在这里插入图片描述

函数模板高级

decltype关键字

在C++11中,decltype操作符,用于查询表达式的数据类型。
语法:

decltype(expression) var;

decltype分析表达式并得到它的类型,不会计算执行表达式。函数调用也一种表达式,因此不必担心在使用decltype时执行了函数。

推导规则:

  1. 如果expression是一个没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符。
  2. 如果expression是一个函数调用,则var的类型与函数的返回值类型相同(函数不能返回void,但可以返回void *)。
  3. 如果expression是一个左值(能取地址)(要排除第一种情况)、或者用括号括起来的标识符,那么var的类型是expression的引用。
  4. 如果上面的条件都不满足,则var的类型与expression的类型相同。
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。


// 1)如果expression是一个没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符。
// 2)如果expression是一个函数调用,则var的类型与函数的返回值类型相同(函数不能返回void,但可以返回void *)。
// 3)如果expression是一个左值(能取地址)(要排除第一种情况)、或者用括号括起来的标识符,那么var的类型是expression的引用。
// 4)如果上面的条件都不满足,则var的类型与expression的类型相同。

         
int func1(){
    cout<<"调用了func"<<endl;

    return 1;
};

int main()
{
    cout<<"如果expression是一个没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符。"<<endl;
    short a = 5;
    decltype(a) da;

    short* b = &a;
    decltype(b) db;

    short& c = a;
    decltype(c) dc = a;
    cout<<dc<<endl;

    cout<<"如果expression是一个函数调用,则var的类型与函数的返回值类型相同(函数不能返回void,但可以返回void *)。"<<endl;
    int a1 = 1;
    cout<<"不会调用函数"<<endl;
    decltype(func1())dfunc = a1;
    cout<<"传递函数名,并使用函数指针.."<<endl;
    decltype(func1)* pfunc = func1;
    pfunc();

    cout<<"如果expression是一个左值(能取地址)(要排除第一种情况)、或者用括号括起来的标识符,那么var的类型是expression的引用。"<<endl;
    decltype((a1)) a2=a1;
    cout<<a1<<endl;
    decltype((func1)) myFunc = func1;
    myFunc();

    return 0;
}

如果expression是一个没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如果expression是一个函数调用,则var的类型与函数的返回值类型相同(函数不能返回void,但可以返回void *)
在这里插入图片描述

没有调用函数

在这里插入图片描述
如果expression是一个左值(能取地址)(要排除第一种情况)、或者用括号括起来的标识符,那么var的类型是expression的引用。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

decltype的结果要么和表达式的类型相同,要么就是表达式类型的引用

decltype和auto的区别

在这里插入图片描述

函数后置返回类型

int func(int x,double y);
等同:
auto func(int x,double y) -> int;
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

template <typename T1, typename T2>
auto func(T1 x, T2 y) -> decltype(x + y)
{
	// 其它的代码。

	decltype(x+y)  tmp = x + y;
	cout << "tmp=" << tmp << endl;

	return tmp;
}

int main()
{
	cout<<func(3, 5.8)<<endl;

    return 0;
}

C++14的返回值使用auto关键字

C++14标准对函数返回类型推导规则做了优化,函数的返回值可以用auto,不必尾随返回类型。

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

template <typename T1, typename T2>
auto func(T1 x, T2 y) 
{
	// 其它的代码。

	decltype(x+y)  tmp = x + y;
	cout << "tmp=" << tmp << endl;

	return tmp;
}

int main()
{
	cout<<func(3, 5.8)<<endl;

    return 0;
}

类模板

类模板是通用类的描述,使用任意类型(泛型)来描述类的定义。

函数模板建议用typename描述通用数据类型,类模板建议用class

template <class T>
class 类模板名
{
	类的定义;
};

1)在创建对象的时候,必须指明具体的数据类型。
2)使用类模板时,数据类型必须适应类模板中的代码。
3)类模板可以为通用数据类型指定缺省的数据类型(C++11标准的函数模板也可以)。
4)模板类的成员函数可以在类外实现。
5)可以用new创建模板类对象。
6)在程序中,模板类的成员函数使用了才会创建。

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
          
template <class T1, class T2=string>
class AA
{
public:
	T1 m_a;      // 通用类型用于成员变量。
	T2 m_b;      // 通用类型用于成员变量。
          
	AA() {  }        // 默认构造函数是空的。
	// 通用类型用于成员函数的参数。
	AA(T1 a,T2 b):m_a(a),m_b(b) {  }
	// 通用类型用于成员函数的返回值。
	T1 geta()            // 获取成员m_a的值。
	{
		T1 a = 2;        // 通用类型用于成员函数的代码中。
		return m_a + a;
	}
	T2 getb();            // 获取成员m_b的值。
};
           
template <class T1, class T2>
T2 AA<T1,T2>::getb()            // 获取成员m_b的值。
{
	return m_b;
}
            
int main()
{
	AA<int, string>* a = new AA<int, string>(3, "西施");     // 用模板类AA创建对象a。
	
	cout << "a->geta()=" << a->geta() << endl;
	cout << "a->getb()=" << a->getb() << endl;
         
	delete a;
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

模板可以为通用数据类型指定缺省的数据类型(C++11标准的函数模板也可以)。
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值