c++学习笔记(六、模板和类型转换)

现在进入了c++高级课程了,前面5节是c++的语法基础,从现在开始就是c++提高篇了。

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。

6.1 函数模板

我们可以用函数模板来定义函数:

//模板技术,类型参数化,编写代码可以忽略类型。
template<class T>           //这一个是函数模板的关键字,为了区别函数模板和普通函数
void MySwap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}
//也可以吧class写成typename
template<typename T>
void MySwap1(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

//使用方法有两种:
int main(int argc, char **argv)
{
    int a = 10;
    int b = 11;

    //1. 自动类型推导
    printf("a b %d %d\n", a,b);
    MySwap(a, b);
    printf("a b %d %d\n", a,b);
    MySwap1(a, b);
    printf("a b %d %d\n", a,b);

    //2.显示的指定类型
    printf("a b %d %d\n", a,b);
    MySwap<int>(a, b);
    printf("a b %d %d\n", a,b);

    return 0;
}

函数模板和普通函数在一起调用规则:

  1. 函数模板可以像普通函数那样可以被重载
  2. c++编译器优先考虑普通函数(如果普通函数满足的话)
  3. 如果函数模板可以产生一个更好的匹配,那么选择模板
  4. 可以通过空模板实参列表的语法限定编译器只能通过模板匹配(不是很懂)

函数模板和普通函数的区别:

  1. 函数模板不允许自动类型转化
  2. 普通函数能够自动类型转化

强制调用模板 用MySwap<>(a, b);

函数模板机制结论:

  1. 编译器并不是把函数模板处理成能够处理任何类型的函数
  2. 函数模板通过具体类型产生不同函数
  3. 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

6.2 类模板

6.2.1 类模板

正如我们上面定义的函数模板一样,类也是有模板的,用法跟函数模板差不多:

template <class T>
class Stack { 
  private: 
    vector<T> elems;     // 元素 
 
  public: 
    void push(T const&);  // 入栈
    void pop();               // 出栈
    T top() const;            // 返回栈顶元素
    bool empty() const{       // 如果为空则返回真。
        return elems.empty(); 
    } 
}; 

参考菜鸟教程:https://www.runoob.com/cplusplus/cpp-templates.html
我觉得菜鸟教程 举的例子很不错,实现了一个栈,栈的元素确实不确定,挺好的,我这里就不写了,写了也是抄过来。

6.2.2 类模板和继承

template<class T>
class Animal
{
public:
    T m_age;
private:
};

//有两种继承的方式
//1.子类也是类模板
template<class T>
class cat : public Animal<T>
{
public:
private:
};

//2.可以实例化的类,把实际类型替换掉T
class cat1 : public Animal<int>
{
public:
private:
};

//使用方法
cat<int> c;

6.2.3 类模板类外实现

类模版在类内实现,这个比较简单,把例子发出来就可以了:

template<class T>
class Animal
{
public:
    Animal(T age)
    {
        this->m_age = age;
    }

    void show(){
        printf("Animal %d\n", m_age);
    }

private:
    T m_age;
};

//有两种继承的方式
//1.子类也是类模板
template<class T>
class cat : public Animal<T>
{
public:
    cat() : Animal<T>(10)
    {}

private:
};

类内部实现,可以直接使用,比较简单,但是c++支持多文件,所以有时候也需要在类外部实现。

实现一个类模板类外实现

//类的声明
template<class T>
class Animal
{
public:
    Animal(T age);

    void show();

private:
    T m_age;
};

template<class T>
class cat : public Animal<T>
{
public:
    cat();

private:
};

//类的实现
template<class T>    		//函数上面添加模板声明
Animal<T>::Animal(T age){    //类名后面也要加<T>
    this->m_age = age;
}

template<class T>
void Animal<T>::show(){
    printf("Animal %d\n", m_age);
}

template<class T>
cat<T>::cat() : Animal<T>(10)
{

}

当类外实现的时候遇上了友元函数,那就更难受了:

template<class T> class Animal;
template<class T> ostream& operator<<(ostream& os, Animal<T>& a);

template<class T>
class Animal
{
public:
    Animal(T age);

    void show();

    //重载<<操作符
    friend ostream& operator<<<T>(ostream& os, Animal<T>& a);

private:
    T m_age;
};

template<class T>
ostream& operator<<(ostream& os, Animal<T>& a)
{
    os << a.m_age << "年龄" << endl;
    return os;
}

友元函数需要注意3个地方:
第一个首先要声明
第二个就是在类中声明的friend中需要在函数名后面加
第三个就是实现的时候,要跟其他的函数一样,要加template

需要更注意一点,类外实现,少用友元函数

6.2.4 多文件类模板外实现

想不到多文件实现这个类模板也这么难受
animal.h

#pragma once

#include <iostream>

template<class T> class Animal;
template<class T> void show_aa(Animal<T>& a);

template<class T>
class Animal
{
public:
    Animal(T age);

    void show();

    //重载<<操作符
    friend void show_aa<T>(Animal<T>& a);

private:
    T m_age;
};

//有两种继承的方式
//1.子类也是类模板
template<class T>
class cat : public Animal<T>
{
public:
    cat();

private:
};

animal.cpp

#include "animal.h"


template<class T>
Animal<T>::Animal(T age){
    this->m_age = age;
}

template<class T>
void Animal<T>::show(){
    printf("Animal %d\n", m_age);
}

template<class T>
cat<T>::cat() : Animal<T>(10)
{

}

template<class T>
void show_aa(Animal<T>& a)
{
   printf("gjah  %d\n", a->m_age);
}

main.c

int main(int argc, char **argv)
{
    //cat<int> c;
    //c.show();

    //cout << c;
    return 0;
}

如果把main.c中申请的对象屏蔽掉,就不会出现错误,如果打开,会出现以下错误:
在这里插入图片描述
这个跟类模板两次编译有关,因为是多文件的时候,是每个文件自己单独编译成.o文件,然后在链接器链接每个.o,才会生成可执行文件。

问题就出在编译成.o的时候,因为animal.cpp是一个函数模板,这时候编译的话,是属于第一次编译,只是检查函数模板是否有语法问题,结果这个cpp没有语法没问题,所以编译通过,生成.o文件,接下来main.c这个直接调用了模板函数,但是这个文件也没有声明,所以main.c中会标注一些符号,代表着这个需要到链接的时候由链接器寻找,所以也没有问题,编译通过。

这时候到链接器,链接器就拿到main.c中的cat::cat(),去各个.o中寻找有没没对应的函数,这时候animal.o中只有原来的函数模板,没有具体编译成函数,因为函数模板是需要两次编译的,一次是调用的时候,所以这时就没有找到对应的函数,所以链接的时候报错。

有解决方法,就是把包含的头文件修改成cpp文件即可编译完成,这时候main.cpp中就有函数模板的声明和调用了,就可以完成二次编译了。

但是正是的工程中,不会这么使用的,而是把函数模板的声明和实现写在一个文件中,这个文件的后缀就是hpp.
怪不得当初不知道hpp是什么意思,现在终于知道了。

6.2.5 类模板碰到static成员

template<class T>
class Animal
{
public:
    Animal(T age);

    void show();

    //重载<<操作符
    friend void show_aa<T>(Animal<T>& a);

    static int aa;
private:
    T m_age;
};

template<class T> int Animal<T>::aa = 0;

类模板中初始化静态变量跟也差不多,但是有一个问题是这个共享的变量aa,是跟那些类共享的。

其实我们类模板是一个具体类的一个模板,到具体使用的时候,还是需要定义一个具体类,这个具体类是不一样的,比如上面的类模板,我们可以定义Animal, Animal,这两种不同的类,当然还有其他的,这里就举两个例子,这两个类都是具体的类,所以静态变量aa,是Animal<int>这个家族类共有一个,Animal<char>这个家族类也共有一个

T&& 是对右值取引用。

6.3 类型转换

说到类型转换,c语言的类型转换是强制的,也最简单(type)变量,这样就可以强制转换。这样子存在很大的问题,不明显,并且没有类型检查,直接可以转,c++为了克服这些缺点,引进了4个新的类型转换操作符。

c++提供了四种类型转换,分别适用于其他场景
static_cast 用于内置的数据类型转换。(具体也不清楚)
dynamic_cast 子类和父类之间的多态类型转换。(转换之前做类型检查)
const_cast 添加或去掉const属性转换
reinterpreter_cast 强制类型转换。(想怎么转就怎么转)

用法也简单:
类型转换操作符 <目标类型> 变量

一般情况下,不建议做类型转换。

发布了32 篇原创文章 · 获赞 26 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/C1033177205/article/details/104102378