现在进入了c++高级课程了,前面5节是c++的语法基础,从现在开始就是c++提高篇了。
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
6.1 函数模板
我们可以用函数模板来定义函数:
//模板技术,类型参数化,编写代码可以忽略类型。
template//这一个是函数模板的关键字,为了区别函数模板和普通函数
void MySwap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
//也可以吧class写成typename
template
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(a, b);
printf("a b %d %d\n", a,b);
return 0;
}
函数模板和普通函数在一起调用规则:
- 函数模板可以像普通函数那样可以被重载
- c++编译器优先考虑普通函数(如果普通函数满足的话)
- 如果函数模板可以产生一个更好的匹配,那么选择模板
- 可以通过空模板实参列表的语法限定编译器只能通过模板匹配(不是很懂)
- 函数模板不允许自动类型转化
- 普通函数能够自动类型转化
函数模板机制结论:
- 编译器并不是把函数模板处理成能够处理任何类型的函数
- 函数模板通过具体类型产生不同函数
- 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
6.2.1 类模板 正如我们上面定义的函数模板一样,类也是有模板的,用法跟函数模板差不多:
template
class Stack {
private:
vector 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 Animal
{
public:
T m_age;
private:
};
//有两种继承的方式
//1.子类也是类模板
template
class cat : public Animal
{
public:
private:
};
//2.可以实例化的类,把实际类型替换掉T
class cat1 : public Animal
{
public:
private:
};
//使用方法
cat c;
6.2.3 类模板类外实现 类模版在类内实现,这个比较简单,把例子发出来就可以了:
template
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 cat : public Animal
{
public:
cat() : Animal(10)
{}private:
};
类内部实现,可以直接使用,比较简单,但是c++支持多文件,所以有时候也需要在类外部实现。
实现一个类模板类外实现:
//类的声明
template
class Animal
{
public:
Animal(T age);
void show();
private:
T m_age;
};
template
class cat : public Animal
{
public:
cat();
private:
};
//类的实现
template//函数上面添加模板声明
Animal::Animal(T age){//类名后面也要加
this->m_age = age;
}template
void Animal::show(){
printf("Animal %d\n", m_age);
}template
cat::cat() : Animal(10)
{}
【C/C++|c++学习笔记(六、模板和类型转换)】当类外实现的时候遇上了友元函数,那就更难受了:
template class Animal;
template ostream& operator<<(ostream& os, Animal& a);
template
class Animal
{
public:
Animal(T age);
void show();
//重载<<操作符
friend ostream& operator<<(ostream& os, Animal& a);
private:
T m_age;
};
template
ostream& operator<<(ostream& os, Animal& a)
{
os << a.m_age << "年龄" << endl;
return os;
}
友元函数需要注意3个地方:
第一个首先要声明
第二个就是在类中声明的friend中需要在函数名后面加
第三个就是实现的时候,要跟其他的函数一样,要加template
需要更注意一点,类外实现,少用友元函数
6.2.4 多文件类模板外实现 想不到多文件实现这个类模板也这么难受
animal.h
#pragma once#include template class Animal;
template void show_aa(Animal& a);
template
class Animal
{
public:
Animal(T age);
void show();
//重载<<操作符
friend void show_aa(Animal& a);
private:
T m_age;
};
//有两种继承的方式
//1.子类也是类模板
template
class cat : public Animal
{
public:
cat();
private:
};
animal.cpp
#include "animal.h"template
Animal::Animal(T age){
this->m_age = age;
}template
void Animal::show(){
printf("Animal %d\n", m_age);
}template
cat::cat() : Animal(10)
{}template
void show_aa(Animal& a)
{
printf("gjah%d\n", a->m_age);
}
main.c
int main(int argc, char **argv)
{
//cat 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 Animal
{
public:
Animal(T age);
void show();
//重载<<操作符
friend void show_aa(Animal& a);
static int aa;
private:
T m_age;
};
template int Animal::aa = 0;
类模板中初始化静态变量跟也差不多,但是有一个问题是这个共享的变量aa,是跟那些类共享的。
其实我们类模板是一个具体类的一个模板,到具体使用的时候,还是需要定义一个具体类,这个具体类是不一样的,比如上面的类模板,我们可以定义Animal, Animal,这两种不同的类,当然还有其他的,这里就举两个例子,这两个类都是具体的类,所以静态变量aa,是Animal
T&& 是对右值取引用。
6.3 类型转换
说到类型转换,c语言的类型转换是强制的,也最简单(type)变量,这样就可以强制转换。这样子存在很大的问题,不明显,并且没有类型检查,直接可以转,c++为了克服这些缺点,引进了4个新的类型转换操作符。
c++提供了四种类型转换,分别适用于其他场景 | |
---|---|
static_cast | 用于内置的数据类型转换。(具体也不清楚) |
dynamic_cast | 子类和父类之间的多态类型转换。(转换之前做类型检查) |
const_cast | 添加或去掉const属性转换 |
reinterpreter_cast | 强制类型转换。(想怎么转就怎么转) |
类型转换操作符 <目标类型> 变量
一般情况下,不建议做类型转换。
推荐阅读
- #ifndef作用
- 数据结构与算法|315,关于《C程序设计伴侣》一书致人民邮电出版社的公开信
- #|C语言
- c/c++|嵌入式C语言自我修养 01(Linux 内核中的GNU C语言语法扩展)
- c++编程|教你了解什么是C语言(快来看看吧)
- C/C++编程语言中的指针(pointer)你了解吗
- c/c++|step1 . day2(Linux系统基础知识)
- c/c++|step1.day12 Linux下使用C语言编程基础总结
- C|内存管理那些事儿_指针参数是如何传递内存的?