// 函数模板示例 templatevoid Swap(T1 &a, T2 &b) { // typename 也可使用 class 替换,推荐使用 typename a = 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 (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
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 (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 (int&, int&)>
36 [1] return 0;
0x40165c <+ 88> b8 00 00 00 00 mov $0x0,%eax
37 [1] }
0x401661 <+ 93> 48 83 c4 30 add $0x30,%rsp
0x401665 <+ 97> 5d pop %rbp
0x401666 <+ 98> c3 retq
重点看 callq,也就是函数调用,将参数类型泛化可知,泛化类型至少包含好几种参数类型,猪观理解是c++在编译阶段会创建和所包含类型相关的所有重载函数,而实际上c++对模板的处理采取二次编译,c++在初次编译阶段并未创建模板包含类型的重载函数,而仅仅只是对模板编写语法等分析,确保编译阶段通过,调用时c++对模板调用进行二次编译,且仅仅只创建生成具体的模板,不会创建其它类型
类模板
// 类模板示例 templateclass Cbase { public: void SetId(T id) { // 类中实现模板类成员函数 m_id = id; } void Print(); private: T m_id; }; template void Cbase ::Print() { // 类外实现模板类成员函数,在同一个文件中的写法 std::cout << "id : " << m_id << std::endl; } int main(int argc, char *argv[]) { Cbase base; // 创建对象时必须指定类型 base.SetId(10); base.Print(); return 0; }
输出结果:
id : 10
// 从模板类派生普通类 templateclass Cbase { public: void SetId(T id) { // 类中实现模板类成员函数 m_id = id; } void Print(); private: T m_id; }; class CDerive : public Cbase { // 类模板 继承 public: }; template void Cbase ::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
// 从模板类派生模板类 templateclass Cbase { public: void SetId(T id) { // 类中实现模板类成员函数 m_id = id; } void Print(); private: T m_id; }; template class CDerive : public Cbase { // 类模板 继承 public: }; template void Cbase ::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
模板类和函数模板一样,都是类型参数化,只不过类模板语法规则复杂模板类也可以继承,必须参数具体化,如果需要显式初始化,派生类需要调用基类构造函数,默认调用无参构造模板类可派生普通类,也可派生模板类
templateclass Cbase { public: void SetId(T id) { // 类中实现模板类成员函数 m_id = id; } void Print(); template friend std::ostream &operator<< (std::ostream &os, Cbase & obj); T m_id; template friend void Report(Cbase & obj); }; template void Cbase ::Print() { // 类外实现模板类成员函数,在同一个文件中的写法 std::cout << "id : " << m_id << std::endl; } template std::ostream & operator<<(std::ostream &os, Cbase & obj) { os << "id : " << obj.m_id << std::endl; return os; } void Report(Cbase & obj) { std::cout << "id : " << obj.m_id << std::endl; } int main(int argc, char *argv[]) { Cbase base; // 创建对象时必须指定类型 base.SetId(10); base.Print(); std::cout << base; Report(base); return 0; }
输出结果:
id : 10
id : 10
id : 10
友元函数使用在模板类中比较复杂,防止滥用友元
templateclass Cbase { public: static int m_id; }; template int Cbase ::m_id = 10; int main(int argc, char *argv[]) { printf("int %#pn", &Cbase ::m_id); printf("double %#pn", &Cbase ::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#include #include #include 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 函数



