C++ 学习笔记

Posted 腾讯技术工程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 学习笔记相关的知识,希望对你有一定的参考价值。

作者:readywang(王玉龙)

template 是 c++ 相当重要的组成部分,堪称 c++语言的一大利器。在大大小小的 c++ 程序中,模板无处不在。c++ templates 作为模板学习的经典书籍,历来被无数 c++学习者所推崇。第二版书籍覆盖了 c++ 11 14 和 17 标准,值得程序猿们精读学习,特此整理学习笔记,将每一部分自认为较为重要的部分逐条陈列,并对少数错误代码进行修改

  • 可以将函数模板定义为友元,此时若模板参数可推导,在友元声明时可以省略

  • template<typename T>
    class Tree 
    friend class Factory; // OK even if first declaration of Factory
    friend class MyNode<T>; // error MyNode未找到声明
    ;

    template<typename T>
    class Stack 
    public:
        // assign stack of elements of type T2
        template<typename T2>
        Stack<T>&amp; operator= (Stack<T2> const&amp;);
        // to get access to private members of Stack<T2> for any type T2: template<typename> friend class Stack;
    ;
    template<typename T>
    class Test

        friend T; // 可以将类型模板参数定义为友元
    ;

    template<typename T1, typename T2>
       void combine(T1, T2)
    ;
    class Mixer 
        friend void combine<>(int&amp;, int&amp;); //OK:T1 = int&amp;,T2 = int&amp;
        friend void combine<intint>(intint);//OK:T1 = int,T2 = int
        friend void combine<char>(charint);//OK:T1 = charT2 = int
        friend void combine<char>(char&amp;, int);// ERROR: doesn’t match combine() template
        friend void combine<>(longlong)    // ERROR: definition not allowed!
    ;
    十三、模板中的名称

    13.1 名称分类

    1.名称分为受限名称和非受限名称,受限名称前面有显式的出现 ->, ::, . 这三个限定符

    13.2 名称查找

    1. c++名称的普通查找规则为从名称所在的 scope 从内向外依次查找。

    2. ADL( Argument-Dependent Lookup)查找为依赖于参数的查找,是用于函数调用表达式中查找非限定函数名称的规则。当在使用函数的上下文中找不到函数定义,我们可以在其参数的关联类和关联名字空间中查找该函数的定义。

    3. ADL 生效条件:a.使用此规则的函数必须要有参数 b. 使用此规则的函数名称必须为非受限名称

    4.ADL 查找范围:

    (1)对于基本类型(int, char 等), 该集合为空集

    (2)对于指针和数组类型,该集合是所引用类型的关联类和关联名字空间

    (3)对于枚举类型,名字空间是名字空间是枚举声明所在的名字空间,对于类成员,关联类是枚举所在的类

    (4)对于 class(包含联合类型),关联类包括该类本身,他的外围类,直接基类,间接基类。关联名字空间包括每个关联类所在的名字空间。

    (5)对于函数类型, 该集合包含所有参数类型和返回类型的关联类和关联名字空间

    (6)对于类 X 的成员指针类型,除了包括成员相关的关联名字空间,关联类,该集合还包括与 X 相关的关联名字空间和关联类

    #include <iostream>
    namespace X 
        template<typename T> void f(T);

    namespace N 
        using namespace X;
        enum E  e1 ;
        void f(E) 
            std::cout << "N::f(N::E) called\\n";
        

    void f(int)

        std::cout << "::f(int) called\\n";

    int main()

        ::f(N::e1); // qualified function name: no ADL
        f(N::e1); // ordinary lookup finds ::f() and ADL finds N::f(),the latter is preferred

    1. c++ 类中声明的友元函数在类外是不可见的,若未在类外提供定义,要想查找到该函数,只能通过 ADL.
    template<typename T>
    class C 
        friend void f();
        friend void f(C<T> const&amp;);
    ;
    void g (C<int>* p)

        f(); // error 类内声明的友元类外不可见,普通查找无法找到
        f(*p); // ok 虽然不可见,但是可以通过ADL找到

    1. 类名注入:在类作用域中,当前类的名字被当做它如同是一个公开成员名一样;这被称为注入类名(injected-class-name)。
    int X;
    struct X 
        void f() 
            X* p; // OK:X 指代注入类名
            ::X* q; // 错误:名称查找找到变量名,它隐藏 struct 名
        
    ;
    1. 对于类模板,除了注入类名,还可以注入实例化的类名。
    template<typename T> class C 
        using Type = T;
        struct J 
            C* c; // C refers to a current instantiation
            C<Type>* c2; // C<Type> refers to a current instantiation
            I* i; // I refers to an unknown specialization,
            // because I does not enclose J
            J* j; // J refers to a current instantiation
        ;
        struct I 
            C* c; // C refers to a current instantiation
            C<Type>* c2; // C<Type> refers to a current instantiation
            I* i; // I refers to a current instantiation
        ;
    ;

    13.3 模板解析

    1. 大多数编程语言的编译器的两个基本活动是标记化和解析。

    2. X<1>(0) ;被解析时,若 X 是一个类型,<1>则被认为是模板实参;若 X 不是类型,则被认为是 x<1 比较后的结果再和>0 比较。在处理模板时,一定要避免大于符号>被当做模板参数终止符。

    template<bool B>
    class Invert 
        public:
        static bool const result = !B;
    ;
    void g()

        bool test = Invert<1>0>::result; // error 模板参数被提前结束
        bool test = Invert<(1>0)>::result; // ok

    1. 类型名称具有以下性质时,就需要在其前面添加 typename

    a. 名称出现在一个模板中

    b. 名称是受限的

    c. 名称不是用于基类的派生列表或构造函数的初始化列表中

    d. 名称依赖于模板参数

    1. ADL 用于模板函数时,可能会产生错误。
    namespace N 
        class X 

        ;
        template<int I> void select(X*);

    void g (N::X* xp)

        select<3>(xp); // ERROR 编译无法知道<3>是模板参数,进而无法判断select是函数调用

    13.4 派生和类模板

    1. 大多数情况中类模板派生和普通类派生无太大区别。

    2.非依赖型基类:无需知道模板名称就可以完全确定类型的基类。

    3.非依赖型基类的派生类中查找一个非受限名称时,会先从非依赖型基类中查找,然后才是模板参数列表

    template<typename X>
    class Base 
    public:
        int basefield;
        using T = int;
    ;

    class D1: public Base<Base<void>>  // 实际上不是模板
    public:
        void f()  basefield = 3;  // 继承成员普通访问
    ;
    template<typename T>
    class D2 : public Base<double>  // 继承自非依赖型基类
    public:
        void f()  basefield = 7;  // 继承成员普通访问
        T strange; // T 是 Base<double>::T 类型,也就是int, 而不是模板参数T!
    ;
    十四、实例化

    14.1 On-Demand 实例化

    1.模板被实例化时,编译器需要知道实例化部分的完整定义。

    14.2 延迟实例化

    1.模板实例化存在延迟现象,编译器只会实例化需要的部分。如类模板会只实例化用到的部分成员函数,函数模板如果提供了默认参数,也只会在这个参数会用到的时候实例化它。

    14.3 c++实例化模型

    1.两阶段查找:编译器在模板解析阶段会检测不依赖于模板参数的非依懒型名称,在模板实例化阶段再检查依懒型名称。

    2.Points of Instantiation: 编译器会在需要实例化模板的地方插入实例化点(POI)

    14.4 几种实现方案

    14.5 显式实例化

    十五、模板实参推导

    15.1 推导的过程

    1.函数模板实例化过程中,编译器会根据实参的类型和模板参数 T 定义的形式,推导出函数的各个参数的类型,如果最后推导的结论矛盾,则推导失败

    template<typename T>
    max (T a, T b)

        return b < a ? a : b;

    auto g = max(11.0); // error 根据1推导T为int 根据1.0推导T为double

    template<typename T> void f(T);
    template<typename T> void g(T&amp;);
    double arr[20];
    int const seven = 7;
    f(arr); // T 被decay为double*
    g(arr); //T 推断为 double[20]
    f(seven); // T 被decay为int
    g(seven); // T 推断为 int const

    15.2 推导的上下文

    15.3 特殊的推导情况

    1.函数模板被取地址赋予函数指针时,也会产生实参推导。

    2.类中定义了类型转换的模板函数时,在类型转换时可以产生实参推导。

    template<typename T>
    void f(T, T)
    ;
    void (*pf)(charchar) = &amp;f;  // T 被推断为char

    class S 
    public:
        template<typename T> operator T&amp;();
    ;

    void f(int (&amp;)[20]);
    void g(S s)

        f(s); // 类型转换时T被推导为int[20]

    15.4 初始化列表

    1.模板实参如果是初始化列表时,无法直接完成模板参数类型 T 的推导。若函数参数通过 std::initializer_list 定义,则实参类型需要一致。

    template<typename T> void g(T p);
    template<typename T> void f(std::initializer_list<T>);

    int main() 
        g(’a’, ’e’, ’i’, ’o’, ’u’, 42); // ERROR: T deduced to both char and int
        f(123); // ERROR: cannot deduce T from a braced list
        f(23579); // OK: T is deduced to int

    15.5 参数包

    15.6 右值引用

    1.引用折叠:只有两个右值引用会被折叠为右值引用,其它情形都是左值引用

    undefined
    using RCI = int const&amp;;
    RCI volatile&amp;&amp; r = 42// OK: r has type int const&amp;
    using RRI = int&amp;&amp;;
    RRI const&amp;&amp; rr = 42// OK: rr has type int&amp;&amp;

    15.7 SFINAE

    1.根据 SFINAE 原理,编译器在用实参推导模板参数失败时,会将该模板忽略。

    template<typename T, unsigned N>
    T* begin(T (&amp;array)[N])

        return array;

    template<typename Container>
    typename Container::iterator begin(Container&amp; c)

        return c.begin();

    int main()

        std::vector<int> v;
        int a[10];
        ::begin(v); // OK: only container begin() matches, because the first deduction fails
        ::begin(a); // OK: only array begin() matches, because the second substitution fails

    十六、特化和重载

    16.1 当泛型代码不再适用的时候

    16.2 重载函数模板

    1.函数模板和普通函数一样,是可以被重载的。

    #include<iostream>

    template<typename T>
    int f(T)

        return 1;

    template<typename T>
    int f(T*)

        return 2;


    int main()

        std::cout << f<int*>((int*)0) << std::endl// 更匹配第一个函数calls f<T>(T)
        std::cout << f<int>((int*)0) << std::endl// 只匹配第二个函数 calls f<T>(T*)
        std::cout << f((int*)0) << std::endl// 都匹配,但第二个更特殊,优先选用 calls f<T>(T*)

    1. 如上所示,main 中实例化后的前两个函数完全相同,但是可以同时存在,原因是它们具有不同的签名

    3.函数签名由以下部分构成:

    a. 非受限函数名称

    b. 名称所属的类作用域

    c. 函数的 const volatile 限定符

    d. 函数参数的类型

    e. 如果是函数模板,还包括返回类型、模板参数和模板实参

    // 以下模板的实例化函数可以同时存在
    template<typename T1, typename T2>
    void f1(T1, T2)
    ;
    template<typename T1, typename T2>
    void f1(T2, T1)
    ;
    template<typename T>
    long f2(T)
    ;
    template<typename T>
    char f2(T)
    ;
    1. c++最开始例子中最后一个函数实例化时和两个模板均匹配,此时将选用更特殊的函数模板

    2. 普通函数和模板函数也可以同时重载,此时在匹配程度相同时,优先调用普通函数

    template<typename T>
    std::string f(T)

        return "Template";

    std::string f(int&amp;)

        return "Nontemplate";

    int main()

    int x = 7;
    std::cout << f(x) << std::endl// 调用非模板函数

    16.3 显式特化

    1. 重载只适用于函数模板,对于类模板,可以使用特化的方式使得编译器进行更优的选择。

    2. 类模板若提供了默认参数,特化后的模板可以不加

    template<typename T>
    class Types 
    public:
        using I = int;
    ;
    template<typename T, typename U = typename Types<T>::I>
    class S; // 模板1声明
    template<>
    class S<void>  // 特化后的模板2定义,可以不用默认参数
    public:
        void f();
    ;
    template<> class S<char, char>; // 特化后的模板3声明
    template<> class S<char, 0>; // error 特化失败,0不能当类型
    int main()

        S<int>* pi; // OK: 使用模板1且不需定义
        S<int> e1; // ERROR: 使用模板1且需要定义
        S<void>* pv; // OK:  使用模板2且不需定义
        S<void,int> sv; // OK: 使用模板2且不需定义
        S<void,char> e2; // ERROR: 使用模板1且需要定义
        S<char,char> e3; // ERROR: 使用模板3且需要定义


    template<>
    class S<char, char>  // 特化后的模板3定义,此处定义对main中的实例化调用是不可见的
    ;
    1. 模板全特化之后的类和由相同的特化参数实例化后的类是相同的,不能同时存在
    template<typename T,typename U>
    class Invalid 
    ;
    Invalid<doubleint> x1; // 实例化一个类模板
    template<typename T>
    class Invalid<T,double>; // ok, 部分偏特化可以和实体化实体同时存在

    template<>
    class Invalid<double,int>; // ERROR: 全特化后的模板不能和实例化实体同时存在
    1. 类模板全特化后,若在类外定义函数,则不能在前面加 template<>
    template<typename T>
    class S;
    template<> class S<char**> 
    public:
        void print() const;
    ;

    // 不能出现template<>
    void S<char**>::print() const

        std::cout << "pointer to pointer to char\\n";

    1. 函数模板全特化时,不能含有默认参数值,但可以直接使用
    template<typename T>
    int f(T) // #1

        return 1;

    template<typename T>
    int f(T*) // #2

        return 2;

    template<> int f(int) // OK: specialization of #1

        return 3;

    template<> int f(int*) // OK: specialization of #2

        return 4;


    template<typename T>
    int f(T, T x = 42)

        return x;

    template<> int f(intint = 35) // 特化时不能指定默认参数

        return 0;


    template<typename T>
    int g(T, T x = 42)

        return x;

    template<> int g(intint y)

        return y/2// 返回21

    1. 变量模板也可以进行全特化。
    template<typename T> constexpr std::size_t SZ = sizeof(T);
    template<> constexpr std::size_t SZ<void> = 0;
    1. 除了对类模板进行全特化以外,也可以单独为类模板中的某个成员进行全特化。
    template<typename T>
    class Outer 
    public:
        static int code;
        void print() const 
            std::cout << "generic";
        

        void test();
    ;

    template<typename T>
    int Outer<T>::code = 6// 类外定义

    // 对于Outer<void>类型,code和print将采用特化版本,其余采用普通版本
    template<>
    int Outer<void>::code = 12// 特化静态成员
    template<>
    void Outer<void>::print() const // 特化类模板

        std::cout << "Outer<void>";

    16.4 类模板偏特化

    1. 对于类模板,可以进行偏特化,偏特化也不能提供默认参数,但可以直接使用。
    template<typename T, int I = 3>
    class S; // primary template
    template<typename T>
    class S<int, T>; // ERROR: 参数类型不匹配
    template<typename T = int>
    class S<T, 10>; // ERROR: 不能有默认参数
    template<int I>
    class S<int, I*2>; // ERROR:不能有非类型表达式
    template<typename U, int K>
    class S<U, K>; // ERROR:特化模板和基本模板无区别

    16.5 变量模板偏特化

    1. 对于变量模板,也可以偏特化。
    template<typename T> constexpr std::size_t SZ = sizeof(T);
    template<typename T> constexpr std::size_t SZ<T&amp;> = sizeof(void*);
    十七、未来的方向十八、模板的多态性

    18.1 动态多态

    1. 动态多态:通过继承和虚函数实现,在运行期根据指针或引用的具体类型决定具体调用那一个虚函数。

    18.2 静态多态

    1. 静态多态:通过模板实现,在编译期基于类型调用不同模板。

    18.3 动态多态 vs 静态多态

    18.4 使用 concepts

    1. 使用静态多态时可以采用 6.5 中介绍的 concept 对可传入模板的类型做以限制。

    18.5 新形式的设计模式

    1. 对于桥接模式,若具体实现的类型在编译期间可以确定,则可以使用模板代替传统的桥接模式实现。
    undefined
    undefined

    18.6 泛型编程

    十九、萃取实现

    19.1 序列求和示例

    1. 简单的求和模板函数的定义及使用如下:
    template<typename T>
    accum (T const* beg, T const* end)

        T total; // 初始化为零值
        while (beg != end) 
            total += *beg;
            ++beg;
        
        return total;


    int main() 
        int num[] =  12345 ;
        std::cout << "the average value of the integer values is "
                << accum(num, num+5) / 5;  // 输出3
        char name[] = "templates";
        int length = sizeof(name)-1;
        std::cout << "the average value of the characters in \\""
                    << name << "\\" is "
                    << accum(name, name+length) / length;
                    // 输出-5 因为字符转为对应的八位ASCII编码数字进行累加,累计超过了char表示的最大数字

    1. 为了避免求和溢出,可以通过下面简单萃取的方式重新定义求和函数。
    template<typename T>
    struct AccumulationTraits;
    template<>
    struct AccumulationTraits<char> 
        using AccT = int;
    ;
    template<>
    struct AccumulationTraits<short> 
        using AccT = int;
    ;
    template<>
    struct AccumulationTraits<int> 
        using AccT = long;
    ;
    template<>
    struct AccumulationTraits<unsigned int> 
        using AccT = unsigned long;
    ;
    template<>
    struct AccumulationTraits<float> 
        using AccT = double;
    ;

    template<typename T>
    auto accum (T const* beg, T const* end)

    using AccT = typename AccumulationTraits<T>::AccT;
    AccT total; // assume this actually creates a zero value
    while (beg != end) 
        total += *beg;
        ++beg;
        
    return total;

    1. 上面例子还可能出现问题,就是初始化 total 时,有些类型可能会并不支持默认初始化。为了兼容这种情形,可采用值萃取的形式提供默认初始值。
    template<>
    struct AccumulationTraits<char> 
        using AccT = int;
        static AccT const zero = 0;
    ;
    template<>
    struct AccumulationTraits<short> 
        using AccT = int;
        static AccT const zero = 0;
    ;
    template<>
    struct AccumulationTraits<int> 
        using AccT = long;
        static AccT const zero = 0;
    ;
    template<typename T>
    auto accum (T const* beg, T const* end)

    using AccT = typename AccumulationTraits<T>::AccT;
    AccT total = AccumulationTraits<T>::zero;
    while (beg != end) 
        total += *beg;
        ++beg;
        
        return total;

    1. 上面类内初始化静态数据成员的方式只对整型有效,对于 float 和字面值常量,可以通过 constexpr 定义进行类类初始化,对于非字面值的类型,则可以通过 inline 成员函数提供类内定义。
    template<>
    struct AccumulationTraits<char> 
        using AccT = int;
        static constexpr AccT zero()  // 通过内联函数定义初始值
        return 0;
        
    ;
    1. 也可以将上述的萃取形式参数化,便于特殊情形下指定不同的萃取形式。
    template<typename T, typename AT AccumulationTraits<T>>
      auto accum (T const* beg, T const* end)
      

          typename AT::AccT total = AT::zero();
          while (beg != end) 
              total += *beg;
              ++beg; 
          return total;
      

    19.2 萃取 vs 策略或策略类

    1. 对于 19.1 中的 accum 函数,可以指定不同策略,不止用来求和,可以求乘。
    // 求和策略
    class SumPolicy 
        public:
          template<typename T1, typename T2>
          static void accumulate (T1&amp; total, T2 const&amp; value) 

              total += value;
          
    ;

    // 求乘积策略
    class MultPolicy 
        public:
          template<typename T1, typename T2>
          static void accumulate (T1&amp; total, T2 const&amp; value) 

              total *= value;
          
    ;

    template<typename T,
            typename Policy = SumPolicy,
            typename Traits = AccumulationTraits<T>>
    auto accum (T const* beg, T const* end)

        using AccT = typename Traits::AccT;
        AccT total = Traits::zero();
        while (beg != end) 
            Policy::accumulate(total, *beg);
        ++beg; 
        return total;

    1. 萃取偏向于模板参数本质的特性,策略偏向于泛型可配置的行为。

    19.3 类型函数

    1. 类型函数:接收一些类型作为参数,返回类型或常量值为结果
    #include<vector>
    #include<list>
    #include<iostream>
    #include<typeinfo>
    template<typename T>
    struct ElementT; // primary template
    template<typename T>
    struct ElementT<std::vector<T>>  // partial specialization for std::vector
        using Type = T;
    ;
    template<typename T>
    struct ElementT<std::list<T>> 
        using Type = T;
    ;

    // partial specialization for std::list
    template<typename T, std::size_t N>
    struct ElementT<T[N]> 
        using Type = T;
    ;
    template<typename T>
    struct ElementT<T[]> 
        using Type = T;
    ;

    // 类型函数,打印容器类型T中元素的类型
    template<typename T>
    void printElementType (T const&amp; c)

        std::cout << "Container of "
                << typeid(typename ElementT<T>::Type).name()
                << " elements.\\n";


    int main() 
          std::vector<bool> s;
          printElementType(s); // print b
          int arr[42];
          printElementType(arr); // print i

    1. 萃取还可以用来为类型添加或移除引用,const,volatile 等限定符
    // 移除引用
    template<typename T>
    struct RemoveReferenceT 
        using Type = T;
    ;
    template<typename T>
    struct RemoveReferenceT<T&amp;> 
        using Type = T;
    ;
    template<typename T>
    struct RemoveReferenceT<T&amp;&amp;> 
        using Type = T;
    ;

    // 添加引用
    template<typename T>
    struct AddLValueReferenceT 
        using Type = T&amp;;
    ;

    template<typename T>
    struct AddRValueReferenceT 
        using Type = T&amp;&amp;;
    ;
    1. 除了对单个类型进行萃取,也可以通过萃取对多个类型进行预测。
    // 判断两个类型是否相同
    template<typename T1, typename T2>
    struct IsSameT 
        static constexpr bool value = false;
    ;
    template<typename T>
    struct IsSameT<T, T> 
        static constexpr bool value = true;
    ;
    1. 对 3 中的 same 判断可以进一步优化,以实现编译期多态
    #include <iostream>

    // 定义bool模板类型
    template<bool val>
    struct BoolConstant 
        using Type = BoolConstant<val>;
        static constexpr bool value = val;
    ;

    // 定义两个实例化类型表示true 和 false
    using TrueType  = BoolConstant<true>;
    using FalseType = BoolConstant<false>;

    // 普通IsSameT模板继承FalseType
    template<typename T1, typename T2>
    struct IsSameT : FalseType

    ;

    // 特化的相同类型IsSameT模板继承TrueType
    template<typename T>
    struct IsSameT<T, T> : TrueType

    ;

    // 根据fool的结果调用不同的foolImpl
    template<typename T>
    void foolImpl(T, TrueType)

        std::cout<<"true"<<std::endl;


    template<typename T>
    void foolImpl(T, FalseType)

        std::cout<<"false"<<std::endl;


    template<typename T>
    void fool(T t)

        foolImpl(t, IsSameT<T,int>); // IsSameT<T,int> 类型可能为TrueType或FalseType


    int main()

        fool(5); // print true
        fool(5.0); // print false

    19.4 基于 SFINAE 的萃取

    1. 可以基于 SFINAE 原理排除某些重载的函数模板。
    // 实现一个能判断T是否具有默认构造函数的模板
    template<typename T>
    struct IsDefaultConstructibleT 
    private:
        // 定义两个重载的函数模板,返回类型不同,用于传入IsSameT进行判断
        template<typename U, typename = decltype(U())> // decltype(U()) 中U若不可以默认初始化,则替换失败,这个函数模板被淘汰,注意此处不能为T,否则会编译失败
        static char test(void*);
        template<typename>
        static long test(...)
    // ...参数可以匹配任意实参
    public:
        // 将T传入test,如可以默认构造,则匹配第一个test,test返回类型为char,IsSameT 结果为true,否则为false
        static constexpr bool value = IsSameT<decltype(test<T>(nullptr)), char>::value;
    ;

    IsDefaultConstructibleT<int>::value //结果为 true

     struct S 
       S() = delete;
     ;
     IsDefaultConstructibleT<S>::value // 结果为false
    1. 可以基于 SFINAE 原理排除某些重载的类模板。
     // helper to ignore any number of template parameters:
      template<typename...> using VoidT = void;
    // primary template:
      template<typenametypename = VoidT<>>
      struct IsDefaultConstructibleT : std::false_type
      
      ;

    嵌入式学习笔记以及C++学习笔记更新计划

    后面打算每周写一篇嵌入式学习笔记以及C++学习笔记,只有不断输出,倒逼自己成长。
    嵌入式平台选用IMX6ULL开发板,先学完驱动部分,后学习应用部分,每周都要有进展,时刻最好做好笔记,这些东西太容易忘了。从头学习太浪费时间了。
    C++的学习就前期比较容易一些,选黑客松的,B站资源相当不错。如果时间充裕,可以好好做做C++项目,做个贪吃蛇啥的。
    对于数量,每周各更新两篇就好,本质上还是做笔记和记录学习心得,督促自己努力学习,扎实基础。
    后续的一些计划:
    1. 嵌入式学习笔记(每周一篇)

    2. C++学习笔记(每周一篇)

    3. 项目分享(每月一个)

    4. JAVA学习笔记(C++学完后再说每周一篇)

    5. 实物展示(两个月出一个,淘宝上可以出现)

    6. 力扣刷题

    7. 一些算法介绍以及实操情况

    要求就是技术点讲清楚,知识点说的明了简单,实物制作流程最好做到面面俱到。不求多而范,只求细而精。只是一些计划,要一步一步慢慢去实现,我错过了太多的时间,时不我待。 自信人生二百年 ,会当水击 三千里 加油 ,骚年
    最后送一句我最喜欢的话给自己: 不要怂,就是干