函数模板
// 函数模板示例
template <typename T1, typename T2>
void Swap(T1 &a, T2 &b) { // typename 也可使用 class 替换,推荐使用 typenamea = a + b;b = a - b;a = a - b;std::cout << "template Swap a: " << a << "\tc: " << b << std::endl;
}void Swap(int &a, int &b) { // 重载函数模板 Swap 函数a = a + b;b = a - b;a = a - b;std::cout << "Swap a: " << a << "\tb: " << b << std::endl;
}int main(int argc, char *argv[])
{int a = 10;int b = 20;short c = 'C';// 调用规则// 1. 编译器优先调用普通匹配函数// 2. 如果函数模板严格匹配,则调用函数模板// 3. 通过空模板实参限定编译器只通过模板匹配Swap(a, b); // 有匹配的普通函数,调用普通函数(包括隐式转换)Swap(a, c); // 函数模板进行函数调用时进行严格的类型匹配Swap<>(a, b); // 限定使用函数模板调用// Swap<short, short>(a, b); // 参数类型和实际变量类型不匹配return 0;
}
输出结果:
Swap a: 20 b: 10
template Swap a: 67 c: 20
template Swap a: 10 c: 67
- 模板包含函数模板和类模板,本质是类型参数化
- 模板提供了一种将类型参数化的机制,减少代码的冗余
// 对应上边代码的汇编片段23 [1] {
0x401604 55 push %rbp
0x401605 <+ 1> 48 89 e5 mov %rsp,%rbp
0x401608 <+ 4> 48 83 ec 30 sub $0x30,%rsp
0x40160c <+ 8> 89 4d 10 mov %ecx,0x10(%rbp)
0x40160f <+ 11> 48 89 55 18 mov %rdx,0x18(%rbp)
0x401613 <+ 15> e8 b8 01 00 00 callq 0x4017d0 <__main>24 [1] int a = 10;
0x401618 <+ 20> c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp)25 [1] int b = 20;
0x40161f <+ 27> c7 45 f8 14 00 00 00 movl $0x14,-0x8(%rbp)26 [1] short c = 'C';
0x401626 <+ 34> 66 c7 45 f6 43 00 movw $0x43,-0xa(%rbp)32 [1] Swap(a, b);
0x40162c <+ 40> 48 8d 55 f8 lea -0x8(%rbp),%rdx
0x401630 <+ 44> 48 8d 45 fc lea -0x4(%rbp),%rax
0x401634 <+ 48> 48 89 c1 mov %rax,%rcx
0x401637 <+ 51> e8 24 ff ff ff callq 0x401560 <Swap(int&, int&)>33 [1] Swap(a, c);
0x40163c <+ 56> 48 8d 55 f6 lea -0xa(%rbp),%rdx
0x401640 <+ 60> 48 8d 45 fc lea -0x4(%rbp),%rax
0x401644 <+ 64> 48 89 c1 mov %rax,%rcx
0x401647 <+ 67> e8 24 18 00 00 callq 0x402e70 <Swap<int, short>(int&, short&)>34 [1] Swap<>(a, b);
0x40164c <+ 72> 48 8d 55 f8 lea -0x8(%rbp),%rdx
0x401650 <+ 76> 48 8d 45 fc lea -0x4(%rbp),%rax
0x401654 <+ 80> 48 89 c1 mov %rax,%rcx
0x401657 <+ 83> e8 64 17 00 00 callq 0x402dc0 <Swap<int, int>(int&, int&)>36 [1] return 0;
0x40165c <+ 88> b8 00 00 00 00 mov $0x0,%eax37 [1] }
0x401661 <+ 93> 48 83 c4 30 add $0x30,%rsp
0x401665 <+ 97> 5d pop %rbp
0x401666 <+ 98> c3 retq
- 重点看 callq,也就是函数调用,将参数类型泛化可知,泛化类型至少包含好几种参数类型,猪观理解是c++在编译阶段会创建和所包含类型相关的所有重载函数,而实际上c++对模板的处理采取二次编译,c++在初次编译阶段并未创建模板包含类型的重载函数,而仅仅只是对模板编写语法等分析,确保编译阶段通过,调用时c++对模板调用进行二次编译,且仅仅只创建生成具体的模板,不会创建其它类型
类模板
// 类模板示例
template <typename T>
class CBase {
public:void SetId(T id) { // 类中实现模板类成员函数m_id = id;}void Print();
private:T m_id;
};template <typename T>
void CBase<T>::Print() { // 类外实现模板类成员函数,在同一个文件中的写法std::cout << "id : " << m_id << std::endl;
}int main(int argc, char *argv[])
{CBase<int> base; // 创建对象时必须指定类型base.SetId(10);base.Print();return 0;
}
输出结果:
id : 10
// 从模板类派生普通类
template <typename T>
class CBase {
public:void SetId(T id) { // 类中实现模板类成员函数m_id = id;}void Print();
private:T m_id;
};class CDerive : public CBase<int> { // 类模板 继承
public:};template <typename T>
void CBase<T>::Print() { // 类外实现模板类成员函数,在同一个文件中的写法std::cout << "id : " << m_id << std::endl;
}int main(int argc, char *argv[])
{CDerive d; // 创建对象时必须指定类型d.SetId(10);d.Print();return 0;
}
输出结果:
id : 10
// 从模板类派生模板类
template <typename T>
class CBase {
public:void SetId(T id) { // 类中实现模板类成员函数m_id = id;}void Print();
private:T m_id;
};template <typename T>
class CDerive : public CBase<T> { // 类模板 继承
public:};template <typename T>
void CBase<T>::Print() { // 类外实现模板类成员函数,在同一个文件中的写法std::cout << "id : " << m_id << std::endl;
}int main(int argc, char *argv[])
{CDerive<int> d; // 创建对象时必须指定类型d.SetId(10);d.Print();return 0;
}
输出结果:
id : 10
- 模板类和函数模板一样,都是类型参数化,只不过类模板语法规则复杂
- 模板类也可以继承,必须参数具体化,如果需要显式初始化,派生类需要调用基类构造函数,默认调用无参构造
- 模板类可派生普通类,也可派生模板类
template <typename T>
class CBase {
public:void SetId(T id) { // 类中实现模板类成员函数m_id = id;}void Print();template <typename>friend std::ostream &operator<< (std::ostream &os, CBase<T>& obj);T m_id;template <typename>friend void Report(CBase<int>& obj);
};template <typename T>
void CBase<T>::Print() { // 类外实现模板类成员函数,在同一个文件中的写法std::cout << "id : " << m_id << std::endl;
}template <typename T>
std::ostream & operator<<(std::ostream &os, CBase<T>& obj) {os << "id : " << obj.m_id << std::endl;return os;
}void Report(CBase<int>& obj) {std::cout << "id : " << obj.m_id << std::endl;
}int main(int argc, char *argv[])
{CBase<int> base; // 创建对象时必须指定类型base.SetId(10);base.Print();std::cout << base;Report(base);return 0;
}
输出结果:
id : 10
id : 10
id : 10
- 友元函数使用在模板类中比较复杂,防止滥用友元
template <typename T>
class CBase {
public:static int m_id;
};template <typename T>
int CBase<T>::m_id = 10;int main(int argc, char *argv[])
{printf("int %#p\n", &CBase<int>::m_id);printf("double %#p\n", &CBase<double>::m_id);return 0;
}
输出结果:
int 0x408120
double 0x408110
- 模板类使用 static 关键字时,不同的具体类有各自的 static 变量
异常
// 模拟抛出异常
void Exception(int i) {if (i == 10)throw "i == 10";
}int main(int argc, char *argv[])
{try {Exception(10);} catch (const char* e) {std::cout << e << std::endl;}return 0;
}
输出结果:
i == 10
- 异常与函数独立互补,函数出错可抛出异常,异常捕获方式是严格基于类型匹配的
void Exception(int i) {if (i == 10)throw "i == 10";
}void Throw() {try {Exception(10);} catch (const char* e) {std::cout << "throw e : " << e << std::endl;throw e; // 继续抛出异常,不处理}
}int main(int argc, char *argv[])
{try {Throw();} catch (const char* e) {std::cout << e << std::endl;}return 0;
}
输结果:
throw e : i == 10
i == 10
- 异常抛出如果不处理程序中断,不处理异常可以继续向上抛出异常
// 栈解旋
class CBase {
public:CBase() {std::cout << "constructor" << std::endl;}~CBase() {std::cout << "destructor" << std::endl;}
};
void Exception(int i) {CBase t;if (i == 10)throw "i == 10";
}int main(int argc, char *argv[])
{try {Exception(10);} catch (const char* e) {std::cout << e << std::endl;}return 0;
}
输出结果:
constructor
destructor
i == 10
- 异常被抛出后,从进入 try 语句块起,到异常被抛出前,在这期间在栈上创建的所有对象都会被自动析构,析构顺序与构造顺序相反,这一过程称为栈解旋
// 异常接口
void Exception1(int i) { // 默认可抛出任何异常if (i == 10)throw "i == 10";
}void Exception2(int i) throw (int){ // 默认可抛出 int 异常if (i == 10)throw "i == 10";
}void Exception3(int i) throw (){ // 默认不抛出任何异常if (i == 10);
}int main(int argc, char *argv[])
{try {
// Exception1(10);
// Exception2(10);Exception3(10);} catch (...) {;}return 0;
}
// 抛出异常类
class CException {};void Exception(int i) { if (i == 10)throw CException(); // 抛出异常类
}int main(int argc, char *argv[])
{try {Exception(10);} catch (CException e) {std::cout << "recv exception" << std::endl;}return 0;
}
// catch 接收异常类,普通变量方式
class CException {
public:CException() {std::cout << "CException()" << std::endl;}CException(const CException&) {std::cout << "CException(const CException&)" << std::endl;}~CException() {std::cout << "~CException" << std::endl;}
};void Exception(int i) { // 默认可抛出任何异常if (i == 10)throw CException(); // 匿名对象
}int main(int argc, char *argv[])
{try {Exception(10);} catch (CException e) { // 调用拷贝构造函数捕获抛出的异常类对象std::cout << "recv exception" << std::endl;}return 0;
}
输出结果:
CException()
CException(const CException&)
recv exception
~CException
~CException
// catch 接收异常类,引用方式
class CException {
public:CException() {std::cout << "CException()" << std::endl;}CException(const CException&) {std::cout << "CException(const CException&)" << std::endl;}~CException() {std::cout << "~CException" << std::endl;}
};void Exception(int i) {if (i == 10)throw CException();
}int main(int argc, char *argv[])
{try {Exception(10);} catch (const CException &e) { // 匿名对象初始化引用,同一块空间std::cout << "recv exception" << std::endl;}return 0;
}
输出结果:
CException()
recv exception
~CException
// catch 接收异常类,指针方式
class CException {
public:CException() {std::cout << "CException()" << std::endl;}CException(const CException&) {std::cout << "CException(const CException&)" << std::endl;}~CException() {std::cout << "~CException" << std::endl;}
};void Exception(int i) { // 默认可抛出任何异常if (i == 10)throw new CException(); // 申请堆内存
}int main(int argc, char *argv[])
{try {Exception(10);} catch (const CException *e) {std::cout << "recv exception" << std::endl;delete e; // 释放内存}return 0;
}
输出结果:
CException()
recv exception
~CException
文件操作
文件打开模式 | 描述 |
---|---|
ios::in | 只读 |
ios::out | 只写 |
ios::app | 从末尾追加 |
ios::binary | 二进制模式 |
ios::nocreate | 打开文件时,文件不存在,不创建文件 |
ios::noreplace | 打开文件时,文件不存在,则创建该文件 |
ios::trunc | 打开文件后,清空文件内容 |
ios::ate | 打开文件后,将文件指针位置移动到文件末尾 |
文件指针位置 | 描述 |
---|---|
ios::beg | 文件开始 |
ios::end | 文件末尾 |
ios::cur | 当前位置 |
#include <vector>
#include <string>
#include <fstream>
#include <iostream>int main()
{// 读写文本文件std::ifstream in("hello.txt");std::ofstream out("out.txt", std::ios::app);if (!in.is_open()) {std::cout << "open file failed" << std::endl;return -1;}std::string temp;while(getline(in, temp)) {out << temp;out << std::endl;}in.close();out.close();return 0;
}
int main()
{// 写二进制文件std::ofstream out("out.dat", std::ios::binary);int num = 20;std::string str("Hello, World");out.write((char*)&num, sizeof(int));out.write(str.c_str(), str.size());out.close();// 读二进制文件std::ifstream in("out.dat", std::ios::binary);char buf[256] = {0};in.read((char*)&num, sizeof(int));in.read(buf, 256);std::cout << "int : " << num << std::endl;std::cout << "str : " << buf << std::endl;in.close();return 0;
}
输出结果:
int : 20
str : Hello, world
// 读取终端输入保存到文件
int main()
{std::ofstream out("out.txt", std::ios::app);std::string str;std::cin >> str; // 会在空格处截断输入的内容out.write(str.c_str(), str.size());out.close();return 0;
}
- 注意 std::cin 的用法,获取终端输入一行的内容可使用 getline 函数