关于cpp的范型编程,其中包括了:模板函数,模板类,模板类的继承,以及模板类的写法,还有比较特殊的友元函数在模板类外实现的方法。

目录

写在前面:

模板与泛型编程

函数模板

类模板

总结

致谢

appendix


写在前面:

终于结束了关于面向编程的基础学习,现在博主要进入到cpp的另一大难点,模板泛型编程中了,博主最近觉得有一些疲惫和惰性,但是还是坚持着周日起来写下了这一篇文章。还有就是下一周是我的seminar汇报了,然而博主的计算还没有开始写,所以下一周随缘了,我尽力继续坚持。希望各位和我一起不忘初心,少一些懒惰,多一些坚持。大家共勉。

模板与泛型编程

函数模板
  • 为什么我们要使用函数模板

    • 使用模板可以解决代码的重复问题,比如比大小这种问题,我们需要对不同类型的传入参数分别设计各自的函数,这绝对是一种冗余的写法

    • 安全性,使用模板比使用 void* 指针或其他非类型安全的方法更安全。模板在编译时进行类型检查,这有助于避免类型错误和运行时错误。

    • 在 C++11 及更高版本中,编译器能够自动推导模板参数的类型,使得模板的使用更加方便和直观,此外stl中充斥着泛型编程,这个真的很重要。

    • 性能的优化,模板通常是在编译时实例化的,这意味着对于特定类型的模板函数,编译器可以生成优化的代码。这与运行时的类型检查或分派相比,可以带来性能上的优势。

    • 灵活性,模板可以与模板特化和偏特化结合使用,这为处理特定类型提供了额外的灵活性。你可以为一般情况编写一个模板,并为特定类型提供特化的实现。

     int Max(int a, int b){
         return a > b ? a : b;
     }
     ​
     int Max(double a, double b){
         return a > b ? a : b;
     }
     ​
     int Max(char a, char b){
         return a > b ? a : b;
     }
     //Isn't it dirty?

  • 怎么使用函数模板

    • 关键字 template, 然后T就可以在你的函数中当作一个变量类型来使用了,这个使用的类型由编译器自动推导,支持基本类型以及用户自定义的类型。(但是有时候有些操作符号需要做相应的修改,在我们之前的面向对象编程的操作服重载中已经说的很明白了,如果未定义,又没有默认的方法,程序会停止的。)

    • 那么现在开始我们来写一段应用模板的代码,文件名字就叫main.cpp吧,如果我们不使用模板,我们就要为double和int显示定义两个函数,如果还有别的甚至會更多,那么我们来对比一下两种方法,并体会一下模板编程的优势吧。

     #include 
     ​
     /*
     int Max(int a, int b){
         return a > b ? a : b;
     }
     ​
     int Max(double a, double b){
         return a > b ? a : b;
     }
     ​
     int Max(char a, char b){
         return a > b ? a : b;
     }
     */
     ​
     template
     T Max(T a, T b){
         return a > b ? a: b;
     }
     //which one is preferable?
     ​
     int main() {
         std::cout << "max in integer (2, 3) is " << Max(2, 3) << std::endl;
         std::cout << "max in double (2.5, 3.5) is " << Max(2.5, 3.5) << std::endl;
         return 0;
     }
    • 运行结果如下:

     /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week02/day07/project01/cmake-build-debug/project01
     max in integer (2, 3) is 3
     max in double (2.5, 3.5) is 3.5
     ​
     Process finished with exit code 0

  • 编译器眼中的模板

    • 对于这个模板大家一定很好奇cpp编译器到底是如何实现的呢?难道所谓模板函数真的是一个万能函数么?

    • 首先我们来回顾下g++下我们如何编译并运行一段cpp代码

    • g++编译器在编译一个c++程序的过程中一般有四个步骤

      • 预处理:展开头文件和宏定义并处理各种预处理指令

      • 汇编:将处理后的文件转换成汇编语言,汇编语言再次不多涉及

      • 编译:将汇编语言转换成计算机能看的懂得机器语言并生成对象文件

      • 链接:将多个对象文件以及库文件一起链接最后形成一个可执行文件

    • 具体g++下的实现如下:

      •  
        #Preprocessing
         g++ -E main.cpp -o main.i
         #Assembly
         g++ -S main.i -o main.s
         #Compilation
         g++ -c main.s -o main.o
         #Linking
         g++ main.o -o a.out

    • 然后我们通过g++命令来将这段程序翻译成汇编语言

     g++ -S main.cpp -o main.s
    • 汇编的结果如下

         .file   "main.cpp"
         .text
         .local  _ZStL8__ioinit
         .comm   _ZStL8__ioinit,1,1
         .globl  main
         .type   main, @function
     main:
     .LFB1762:
         .cfi_startproc
         endbr64
         pushq   %rbp
         .cfi_def_cfa_offset 16
         .cfi_offset 6, -16
         movq    %rsp, %rbp
         .cfi_def_cfa_register 6
         movl    $3, %esi
         movl    $2, %edi
         call    _Z3MaxIiET_S0_S0_
         movl    %eax, %esi
         leaq    _ZSt4cout(%rip), %rax
         movq    %rax, %rdi
         call    _ZNSolsEi@PLT
         movq    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
         movq    %rdx, %rsi
         movq    %rax, %rdi
         call    _ZNSolsEPFRSoS_E@PLT
         movsd   .LC0(%rip), %xmm0
         movq    .LC1(%rip), %rax
         movapd  %xmm0, %xmm1
         movq    %rax, %xmm0
         call    _Z3MaxIdET_S0_S0_
         movq    %xmm0, %rax
         movq    %rax, %xmm0
         leaq    _ZSt4cout(%rip), %rax
         movq    %rax, %rdi
         call    _ZNSolsEd@PLT
         movq    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
         movq    %rdx, %rsi
         movq    %rax, %rdi
         call    _ZNSolsEPFRSoS_E@PLT
         movl    $0, %eax
         popq    %rbp
         .cfi_def_cfa 7, 8
         ret
         .cfi_endproc
     .LFE1762:
         .size   main, .-main
         .section    .text._Z3MaxIiET_S0_S0_,"axG",@progbits,_Z3MaxIiET_S0_S0_,comdat
         .weak   _Z3MaxIiET_S0_S0_
         .type   _Z3MaxIiET_S0_S0_, @function
     _Z3MaxIiET_S0_S0_:
     .LFB2026:class Derived:public Base{
     public:
         Derived(int d): Base(0){
            this->d_ = d;
         }
     private:
         int d_;
     };
         .cfi_startproc
         endbr64
         pushq   %rbp
         .cfi_def_cfa_offset 16
         .cfi_offset 6, -16
         movq    %rsp, %rbp
         .cfi_def_cfa_register 6
         movl    %edi, -4(%rbp)
         movl    %esi, -8(%rbp)
         movl    -4(%rbp), %eax
         cmpl    -8(%rbp), %eax
         jle .L4
         movl    -4(%rbp), %eax
         jmp .L6
     .L4:
         movl    -8(%rbp), %eax
     .L6:
         popq    %rbp
         .cfi_def_cfa 7, 8
         ret
         .cfi_endproc
     .LFE2026:
         .size   _Z3MaxIiET_S0_S0_, .-_Z3MaxIiET_S0_S0_
         .section    .text._Z3MaxIdET_S0_S0_,"axG",@progbits,_Z3MaxIdET_S0_S0_,comdat
         .weak   _Z3MaxIdET_S0_S0_
         .type   _Z3MaxIdET_S0_S0_, @function
     _Z3MaxIdET_S0_S0_:
     .LFB2029:
         .cfi_startproc
         endbr64_Z3MaxIdET_S0_S0_
         pushq   %rbp
         .cfi_def_cfa_offset 16
         .cfi_offset 6, -16
         movq    %rsp, %rbp
         .cfi_def_cfa_register 6
         movsd   %xmm0, -8(%rbp)
         movsd   %xmm1, -16(%rbp)
         movsd   -8(%rbp), %xmm0
         comisd  -16(%rbp), %xmm0
         jbe .L13
         movsd   -8(%rbp), %xmm0
         jmp .L11
     .L13:
         movsd   -16(%rbp), %xmm0
     .L11:
         movq    %xmm0, %rax
         movq    %rax, %xmm0
         popq    %rbp
         .cfi_def_cfa 7, 8
         ret
         .cfi_endproc
     .LFE2029:
         .size   _Z3MaxIdET_S0_S0_, .-_Z3MaxIdET_S0_S0_
         .textp
         .type   _Z41__static_initialization_and_destruction_0ii, @function
     _Z41__static_initialization_and_destruction_0ii:
     .LFB2294:
         .cfi_startproc
         endbr64
         pushq   %rbp
         .cfi_def_cfa_offset 16
         .cfi_offset 6, -16
         movq    %rsp, %rbp
         .cfi_def_cfa_register 6
         subq    $16, %rsp
         movl    %edi, -4(%rbp)
         movl    %esi, -8(%rbp)
         cmpl    $1, -4(%rbp)
         jne .L16
         cmpl    $65535, -8(%rbp)
         jne .L16
         leaq    _ZStL8__ioinit(%rip), %rax
         movq    %rax, %rdi
         call    _ZNSt8ios_base4InitC1Ev@PLT
         leaq    __dso_handle(%rip), %rax
         movq    %rax, %rdx
         leaq    _ZStL8__ioinit(%rip), %rax
         movq    %rax, %rsi
         movq    _ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
         movq    %rax, %rdi
         call    __cxa_atexit@PLT
     .L16:
         nop
         leave
         .cfi_def_cfa 7, 8
         ret
         .cfi_endproc
     .LFE2294:
         .size   _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
         .type   _GLOBAL__sub_I_main, @function
     _GLOBAL__sub_I_main:
     .LFB2295:
         .cfi_startproc
         endbr64
         pushq   %rbp
         .cfi_def_cfa_offset 16
         .cfi_offset 6, -16
         movq    %rsp, %rbp
         .cfi_def_cfa_register 6
         movl    $65535, %esi
         movl    $1, %edi
         call    _Z41__static_initialization_and_destruction_0iiclass Derived:public Base{
     public:
         Derived(int d): Base(0){
            this->d_ = d;
         }
     private:
         int d_;
     };
         popq    %rbp
         .cfi_def_cfa 7, 8
         ret
         .cfi_endproc
     .LFE2295:
         .size   _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
         .section    .init_array,"aw"
         .align 8
         .quad   _GLOBAL__sub_I_main
         .section    .rodata
         .align 8
     .LC0:
         .long   0
         .long   1074528256
         .align 8
     .LC1:
         .long   0
         .long   1074003968
         .hidden __dso_handle
         .ident  "GCC: (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0"
         .section    .note.GNU-stack,"",@progbits
         .section    .note.gnu.property,"a"
         .align 8
         .long   1f - 0f
         .long   4f - 1f
         .long   5
     0:
         .string "GNU"
     1:
         .align 8
         .long   0xc0000002
         .long   3f - 2f
     2:
         .long   0x3
     3:class Derived:public Base{
     public:
         Derived(int d): Base(0){
            this->d_ = d;
         }
     private:
         int d_;
     };
         .align 8
     4:
    • 简单理解一下以上代码可以分为三个函数:

      • main 函数

       main:
       .LFB1762:
           [函数的初始设置和栈帧构建]
           movl    $3, %esi                 ; 将3放入esi寄存器
           movl    $2, %edi                 ; 将2放入edi寄存器
           call    _Z3MaxIiET_S0_S0_        ; 调用 Max 函数(int 版本)
           [处理 Max 函数返回的结果和输出]
           movsd   .LC0(%rip), %xmm0        ; 加载一个 double 类型的常量
           movq    .LC1(%rip), %rax
           [调用 Max 函数(double 版本)和处理返回结果]
           movl    $0, %eax                 ; 将返回值设为0
           popq    %rbp                     ; 恢复栈帧
           ret                              ; 返回
       ​
      • Max 函数(int类型)Z3MaxIiET_S0_S0

       _Z3MaxIiET_S0_S0_:
       .LFB2026:
           [函数的初始设置和栈帧构建]
           movl    %edi, -4(%rbp)           ; 将第一个参数放在栈上
           movl    %esi, -8(%rbp)           ; 将第二个参数放在栈上
           movl    -4(%rbp), %eax           ; 将第一个参数加载到eax
           cmpl    -8(%rbp), %eax           ; 比较两个参数
           jle     .L4                      ; 如果第一个参数小于等于第二个参数,跳转到.L4
           movl    -4(%rbp), %eax           ; 否则,将第一个参数作为返回值
           jmp     .L6                      ; 跳转到结束标签
       .L4:
           movl    -8(%rbp), %eax           ; 将第二个参数作为返回值
       .L6:
           popq    %rbp                     ; 恢复栈帧
           ret                              ; 返回
       ​
      • Max 函数(double类型)Z3MaxIdET_S0_S0

       _Z3MaxIdET_S0_S0_:
       .LFB2029:
           [函数的初始设置和栈帧构建]
           movsd   %xmm0, -8(%rbp)          ; 将第一个参数放在栈上
           movsd   %xmm1, -16(%rbp)         ; 将第二个参数放在栈上
           movsd   -8(%rbp), %xmm0          ; 将第一个参数加载到xmm0
           comisd  -16(%rbp), %xmm0         ; 比较两个参数
           jbe     .L13                     ; 如果第一个参数小于等于第二个参数,跳转到.L13
           movsd   -8(%rbp), %xmm0          ; 否则,将第一个参数作为返回值
           jmp     .L11                     ; 跳转到结束标签
       .L13:
           movsd   -16(%rbp), %xmm0         ; 将第二个参数作为返回值
       .L11:
           popq    %rbp                     ; 恢复栈帧
           ret                              ; 返回
      • 由此可见其实模板的本质就是针对不同的输入类型编译器自动生成相应的函数。

      • 模板并不是一个通用的能够处理任意类型的函数。

类模板
  • 为什么我们要使用类模板?

    • 同前面的函数模板类似这里不过多赘述。

  • 类模板的使用

    • 还是一样的关键字template

    • 新标准不许要显示指定模板的类型了,原来是必须找指定的,大家为了可移植性还是尽量都显示指定吧。下面是一个模板类的例子:

    #include 
    
    /*
    class A{
    public:
        A(int t = 0){
            this->t_ = t;
        }
        int& getT(){
            return this->t_;
        }
    private:
        int t_;
    };
    */
    
    
    template
    class A{
    public:
        A(T t = 0){
           this->t_ = t;
        }
        T& getT(){
           return this->t_;
        }
    private:
        T t_;
    };
    
    
    int main() {
        A a(1);
        //A a(1);
        std::cout << a.getT() << std::endl;
        a.getT()++;
        std::cout << a.getT() << std::endl;
        return 0;
    }
    • 运行结果如下:

    /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week02/day07/project01/cmake-build-debug/project01
    1
    2
    
    Process finished with exit code 0

  • 类模板与继承

    • 父类为一般类型,子类为模板类(与一般情况的继承没有区别)

    class B{
    private:
        int b_;
    public:
        B(int b){
            this->b_ = b;
        }
    };
    
    template
    class A: public B{
    public:
        A(T t = 0):B(0){
           this->t_ = t;
        }
        T& getT(){
           return this->t_;
        }
    private:
        T t_;
    };

    • 父类为模板类,子类为一般类(在子类构造时必须显示指定父类的模板类型,即实例化父类的参数类型)

    template
    class Base{
    public:
        Base(T t = 0){
           this->t_ = t;
        }
        T& getT(){
           return this->t_;
        }
    private:
        T t_;
    };
    
    class Derived:public Base{
    public:
        Derived(int d): Base(0){
           this->d_ = d;
        }
    private:
        int d_;
    };
    • 父子类都是模板类,但是父类要实例化,但是实例化的方式就很自由了,甚至可以用子类的模板来实例化父类的类型

    template
    class Base{
    public:
        Base(T t = 0){
           this->t_ = t;
        }
        T& getT(){
           return this->t_;
        }
    private:
        T t_;
    };
    
    template
    class Derived:public Base{
    public:
        Derived(TD d): Base(0){
           this->d_ = d;
        }
    private:
        TD d_;
    };

  • 类模板的写法

    • 类模板函数写在类内:就正常写就可以了给个简单例子

      #include 
      
      template
      class A{
      public:
          A(T t = 0){
              this->t_ = t;
          }
          T& getT(){
              return this->t_;
          }
      
          A operator+(const A& other){
              A temp = A(this->t_ + other.t_);
              return temp;
          }
      private:
          T t_;
      };
      • 测试的代码下面都一样吧

      void test_4_template_class(){
          A a(6), b(8);
          std::cout << "6 + 8 using class defined operator is " << (a + b).getT() << std::endl;
      }
      • 运行结果如下

      /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week02/day07/project02/cmake-build-debug/project02
      6 + 8 using class defined operator is 14
      
      Process finished with exit code 0

    • 类模板函数写在类的外部,但是仍然处于一个文件中:

      • 在类外实现的时候我们需要注意,作用域解析运算符(Scope Resolution Operator)中使用到的类名是需要有模板声明的。

      • 构造函数中的类名以及传入的对象类型名都需要显示指定模板参数

      • 在成员函数的实现中,可加可不加。(注意我们现在讨论的是<>在类的类型后面是否要加这个东西)

      template
      class A{
      public:
          A(T t = 0){
              this->t_ = t;
          }
          T& getT(){
              return this->t_;
          }
          //declaration of the function 
          A operator+(const A& other); 
      private:
          T t_;
      };
      
      //definition of the function
      template
      //all the class name in the definition need to clarify the template type
      A A::operator+(const A& other){
          //While within the function body, with or without explict declaration, it is flexible
          A temp = A(this->t_ + other.t_);
          return temp;
      }
      • 运行结果如下

      /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week02/day07/project02/cmake-build-debug/project02
      6 + 8 using class defined operator is 14
      
      Process finished with exit code 0

    • 类模板函数写在类的源文件中:

      • 现在我们直接把刚才在同一个文件中的分离定义copy到一个新的头文件中然后进行测试,然后源文件直接和main函数写到一起,头源文件分开我试了一下,链接错误,这个时候我们要做的就是在main.cpp中引入这个类的源文件(这里时A.cpp)而不是头文件,但是实际上范型这种直接写到一个头文件就很爽。

      • 因此一般来说我们在包含有范型模板的时候源文件写成***.hpp,这是一个不成文的规定。

      • 头文件如下:

      /*
       * Created by herryao on 1/21/24.
       * Email: [email protected]
       * Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
       */
      
      #ifndef PROJECT02_A_H
      #define PROJECT02_A_H
      
      
      template
      class A{
      public:
          A(T t = 0){
              this->t_ = t;
          }
          T& getT(){
              return this->t_;
          }p
          A operator+(const A& other);
      
      private:
          T t_;
      };
      
      
      #endif//PROJECT02_A_H
      • A.cpp/ A.hpp源文件实现如下:

      /*
       * Created by herryao on 1/21/24.
       * Email: [email protected]
       * Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
       */
      
      #include "A.h"
      //definition of the function
      template
      //all the class name in the definition need to clarify the template type
      A A::operator+(const A& other){
          //While within the function body, with or without explict declaration, it is flexible
          A temp = A(this->t_ + other.t_);
          return temp;
      }
      • 那么main.cpp文件就如下:

       #include 
       //#include "A.cpp"
       #include "A.hpp"
       ​
       void test_4_template_class(){
           A a(6), b(8);
           std::cout << "6 + 8 using class defined operator is " << (a + b).getT() << std::endl;
       }
       ​
       int main() {
           test_4_template_class();
           return 0;p
       }
      • 运行结果如下,没什么问题。

       /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week02/day07/project02/cmake-build-debug/project02
       6 + 8 using class defined operator is 14
       ​
       Process finished with exit code 0

    • 特殊情况,友元函数:

      • 首先就是友元函数,他破坏了类的封装性,其实应该慎用。

      • 这真的很坑,我直接说结论吧。

      • 在声明友元函数时,首先我们要知道友元函数是独立于类的,因此我们一定要在类中对友元函数的模板类型进行指定,而且我们要在类中有缘函数的前面声明这个template,不声明就无法访问类的内部成员

      • 还有就是你给友元定义的模板类型要区别于类本身的模板类型(在我这里是这样,别的编译器可能就没事,比如Martin老师的编译器就没事),至于函数实现部分,可以继续用T但是我一般就保持一致了

      • 最后就是跟正常类成员函数的实现一样,你可以在函数内部不使用那个显示类型的声明

      • 大概结论就是上面那些现在看我的实现吧,我就把上一part的内容全包含进来了,首先是头文件A.h

       /*
        * Created by herryao on 1/21/24.
        * Email: [email protected]
        * Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
        */
       ​
       #ifndef PROJECT02_A_H
       #define PROJECT02_A_H
       ​
       ​
       template
       class A{
       public:
           A(T t = 0){
               this->t_ = t;
           }
           T& getT(){
               return this->t_;
           }
           A operator+(const A& other);
       ​
       private:
           T t_;
           //declaration a friend function to achieve the addition operation
           //friend function is independent on the class so the template type need to clarify
           template
           friend A addA(const A&a, const A&b);
       };
       ​
       #endif//PROJECT02_A_H
       ​
      • 源文件的实现,A.hpp

      /*
       * Created by herryao on 1/21/24.
       * Email: [email protected]
       * Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
       */
      
      #include "A.h"
      //definition of the function
      template
      //all the class name in the definition need to clarify the template type
      A A::operator+(const A& other){
          //While within the function body, with or without explict declaration, it is flexible
          A temp = A(this->t_ + other.t_);
          return temp;
      }
      
      template
      A addA(const A&a, const A&b){
          A temp = A(a.t_ + b.t_);
          return temp;
      }
      • 测试函数如下:

      #include "A.hpp"
      #include 
      
      void test_4_friend_template_class(){
          A a(6), b(8);
          std::cout << "6 + 8 using class defined operator is " << addA(a, b).getT() << std::endl;
      }
      • 运行结果如下:

      /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week02/day07/project02/cmake-build-debug/project02
      6 + 8 using class defined operator is 14
      
      Process finished with exit code 0

总结
  • 今天学习了一下模板函数,模板类,模板类的继承,以及模板类的写法,还有比较特殊的友元函数在模板类外实现的方法。

  • 模板也就是范型编程是很重要的一个技术,也是stl的基础。

致谢

  • 现在进入Martin老师的课堂了,感谢Martin老师,希望能够跟老师多学一些东西。

  • 感谢各位的支持,希望大家不断变强。

appendix

  • 本周的学习时间表如下:

 week02
 #Day 01 Mon
 2024-01-15 18:33:42: Total study time: 00:38:57
 2024-01-15 19:06:02: Total study time: 00:16:49
 2024-01-15 19:26:28: Total study time: 00:20:01
 2024-01-15 20:35:16: Total study time: 01:00:18
 2024-01-15 20:45:23: Total study time: 00:10:05
 2024-01-15 21:50:31: Total study time: 00:49:38
 2024-01-15 23:00:57: Total study time: 01:09:23
 #Day 02 Tue
 2024-01-16 20:49:26: Total study time: 04:24:04
 2024-01-16 22:07:59: Total study time: 01:18:22
 #Day 03 Wed
 2024-01-17 13:53:55: Total study time: 00:53:23
 2024-01-17 21:45:36: Total study time: 00:11:06
 2024-01-17 23:40:23: Total study time: 01:31:46
 #Day 04 Thu
 2024-01-18 22:31:41: Total study time: 03:34:14
 #Day 05 Fri
 2024-01-19 20:47:48: Total study time: 03:44:23
 #Day 06 Sat
 2024-01-20 21:45:36: Total study time: 03:51:56
 #Day 07 Sun
 2024-01-21 21:02:36: Total study time: 02:51:56
 2024-01-21 22:35:46: Total study time: 01:00:11
 2024-01-21 22:50:12: Total study time: 00:14:23
  • 本周的项目代码如下:

 $ tree -L 3 week02
 week02
 ├── clion.log
 ├── day01
 │   ├── clion.log
 │   ├── project01
 │   │   ├── cmake-build-debug
 │   │   ├── CMakeLists.txt
 │   │   └── main.cpp
 │   ├── project02
 │   │   ├── Boy.cpp
 │   │   ├── Boy.h
 │   │   ├── clion.log
 │   │   ├── cmake-build-debug
 │   │   ├── CMakeLists.txt
 │   │   ├── Database.cpp
 │   │   ├── Database.h❯ tree -L 3 week02
 week02
 ├── clion.log
 ├── day01
 │   ├── clion.log
 │   ├── project01
 │   │   ├── cmake-build-debug
 │   │   ├── CMakeLists.txt
 │   │   └── main.cpp
 │   ├── project02
 │   │   ├── Boy.cpp
 │   │   ├── Boy.h
 │   │   ├── clion.log
 │   │   ├── cmake-build-debug
 │   │   ├── CMakeLists.txt
 │   │   ├── Database.cpp
 │   │   ├── Database.h
 │   │   ├── Girl.cpp
 │   │   ├── Girl.h
 │   │   ├── main.cpp
 │   │   ├── Person.cpp
 │   │   └── Person.h
 │   ├── project03
 │   │   ├── cmake-build-debug
 │   │   ├── CMakeLists.txt
 │   │   └── main.cpp
 │   └── project04
 │       ├── cmake-build-debug
 │       ├── CMakeLists.txt
 │       ├── Computer.cpp
 │       ├── Computer.h
 │       └── main.cpp
 ├── day02
 │   ├── project01
 │   │   ├── cmake-build-debug
 │   │   ├── CMakeLists.txt
 │   │   ├── Computer.cpp
 │   │   ├── Computer.h
 │   │   ├── ComputerService.cpp
 │   │   ├── ComputerService.h
 │   │   └── main.cpp
 │   ├── project02
 │   │   ├── Caw.cpp
 │   │   ├── Caw.h
 │   │   ├── cmake-build-debug
 │   │   ├── CMakeLists.txt
 │   │   ├── Goat.cpp
 │   │   ├── Goat.h
 │   │   ├── main.cpp
 │   │   ├── Pig.cpp
 │   │   └── Pig.h
 │   └── project03
 │       ├── Boy.cpp
 │       ├── Boy.h
 │       ├── cmake-build-debug
 │       ├── CMakeLists.txt
 │       ├── main.cpp
 │       ├── Man.cpp
 │       └── Man.h
 ├── day03
 │   ├── clion.log
 │   ├── project01
 │   │   ├── Boy.cpp
 │   │   ├── Boy.h
 │   │   ├── clion.log
 │   │   ├── cmake-build-debug
 │   │   ├── CMakeLists.txt
 │   │   ├── Database.cpp
 │   │   ├── Database.h
 │   │   ├── Girl.cpp
 │   │   ├── Girl.h
 │   │   ├── main.cpp
 │   │   ├── Person.cpp
 │   │   └── Person.h
 │   └── project02
 │       ├── cmake-build-debug
 │       ├── CMakeLists.txt
 │       ├── Immortal.cpp
 │       ├── Immortal.h
 │       ├── main.cpp
 │       ├── Monster.cpp
 │       ├── Monster.h
 │       ├── SpriteStone.cpp
 │       └── SpriteStone.h
 ├── day05
 │   ├── project01
 │   │   ├── Base.cpp
 │   │   ├── Base.h
 │   │   ├── cmake-build-debug
 │   │   ├── CMakeLists.txt
 │   │   ├── Derived.cpp
 │   │   ├── Derived.h
 │   │   └── main.cpp
 │   ├── project02
 │   │   ├── a-main.cpp.001l.class
 │   │   ├── a.out
 │   │   ├── cmake-build-debug
 │   │   ├── CMakeLists.txt
 │   │   └── main.cpp
 │   └── project03
 │       ├── a-main.cpp.001l.class
 │       ├── a.out
 │       ├── cmake-build-debug
 │       ├── CMakeLists.txt
 │       └── main.cpp
 └── day07
     ├── project01
     │   ├── cmake-build-debug
     │   ├── CMakeLists.txt
     │   ├── main.cpp
     │   └── main.s
     └── project02
         ├── A.h
         ├── A.hpp
         ├── cmake-build-debug
         ├── CMakeLists.txt
         └── main.cpp
 ​
 33 directories, 82 files

你可能感兴趣的:(c++学习,c++,算法,开发语言)