c++随笔-7

函数模板

// 函数模板示例
template <typename T1, typename T2>
void 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<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,%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++对模板调用进行二次编译,且仅仅只创建生成具体的模板,不会创建其它类型

类模板

// 类模板示例
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 函数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值