深述C++模板类

news2024/11/23 20:01:40

1、前言

函数模板是通用函数的描述,类模板是通用类的描述,使用任意类型来描述类的定义。和函数模板有很多相似的地方,关于函数模板可以看我之前写过的一篇文章:简述C++函数模板。这里就不过多赘述。

2、模板类的基本概念

模板类的基本语法如下:

template <class T>
class MyClass {
private:
    T data;
public:
    MyClass(T value) : data(value) {}
    T getData()  { return data; }
};

和函数模板一样,定义的时候在前面加一个template声明就行。

1、显式调用

不过需要注意的是,和函数模板不一样,在创建具体对象的时候,必须指定具体的数据类型,也就是显式调用,声明具体的类型参数,没有自动转化。比如:

MyClass<int> myIntObj(5);
MyClass<double> myDoubleObj(3.14);

2、模板类缺省数据类型 

模板类可以指定缺省的数据类型,比如:

#include<iostream>

using namespace std;

template <class T1,class T2 = char>
class MyClass {
private:
    T1 data1;
    T2 data2;
public:
    MyClass() : data1(), data2() {}
    MyClass(T1 value1,T2 value2 ) : data1(value1), data2(value2) {}
    void getData() { cout << "data1 is " << data1 << " data2 is " << data2 << endl; }
};

int main()
{
	MyClass<int> text(97,97);
    text.getData();
}

3、数据类型匹配 

我们指定了T2的默认数据类型为char,所以我们显式定义text的时候,传入一个类型参数也不会报错,如果不指定默认的数据类型,仅传入一个类型参数,那么程序会报错:

所以使用类模板的时候,数据类型必须要适应模板类中的代码 。

4、模板类成员函数的类外实现

关于模板类成员函数的类外实现,也和正常的类外实现差不多,只不过需要多写一个模板名,拿上述代码举例:

template <class T1, class T2 >
void MyClass<T1,T2>::getData()
{ cout << "data1 is " << data1 << " data2 is " << data2 << endl; }

在类外实现成员函数的时候,最好不要写默认参数,有的编译器可能会把他当成错误。

5、用new创建模板类对象

看到模板类的代码,大家要改变一下习惯,普通类的类名是标识符,干干净净的,模板类不一样,它是类模板名,不是一种具体的数据类型, 只有当他加上<>符号,<>里面填上具体的数据类型,这个整体才能算是类名,才算是一种具体的数据类型。

比如用new 来创建模板类的指针对象的时候,我们就需要这么写:

MyClass<int, char>* a = new MyClass<int, char>;

MyClass<int, char>这一大块才算是完整的数据类型。

6、模板类的成员函数只有使用的时候才会创建 

 C++ 是一种静态类型语言,编译器在编译阶段需要知道所有类型相关的信息来生成正确的机器代码。对于模板类,其代码并不是在定义模板类的时候就完全生成的。这是因为模板类是一种泛型编程工具,在定义时并不知道最终会使用哪些具体的数据类型来实例化它。

模板类的定义更像是一个蓝图或者模板,它描述了如何根据特定的数据类型来构建一个类。编译器在遇到这个模板定义时,无法为所有可能的类型生成代码,因为可能的类型组合太多了。

 在编译阶段,当模板类的成员函数被使用时,编译器会根据具体的实例化类型生成对应的函数定义。这些生成的函数定义会被放在目标文件中。在链接阶段,如果多个目标文件中都包含了对同一个模板类成员函数(针对相同的实例化类型)的引用,那么链接器会将这些定义合并起来,确保程序的正确性。

 3、模板类具体化

模板类具体化有两种,一种是完全具体化,一种是部分具体化。

具体化程度高的类优先于具体程度低的类,具体化的类优先于没有具体化的类。

例如如下代码:

#include<iostream>

using namespace std;

template <class T1,class T2>
class MyClass {
private:
    T1 data1;
    T2 data2;
public:
    MyClass() : data1(), data2() { cout << "无具体化构造函数调用:" << endl; }
    MyClass(T1 value1, T2 value2) : data1(value1), data2(value2) {}
    void getData();
};

template <class T1, class T2 >
void MyClass<T1,T2>::getData()
{
    cout << "无具体化调用:" << endl;
    //cout << "data1 is " << data1 << " data2 is " << data2 << endl; 
}

template<class T>
class MyClass<T, char>
{
private:
    T data1;
    char data2;
public:
    MyClass() : data1(), data2() { cout << "部分具体化构造函数调用:" << endl; }
    MyClass(T value1, char value2) : data1(value1), data2(value2) {}
    void getData();
};

template<class T>
void MyClass<T,char>::getData()
{
    cout << "部分具体化调用:" << endl;
    //cout << "data1 is " << data1 << " data2 is " << data2 << endl;
}

template <>
class MyClass<int, char>
{
private:
    int data1;
    char data2;
public:
    MyClass() : data1(), data2() { cout << "完全具体化构造函数调用:" << endl; }
    MyClass(int value1, char value2) : data1(value1), data2(value2) {}
    void getData();
};

void MyClass<int, char>::getData()
{
    cout << "完全具体化调用:" << endl;
    //cout << "data1 is " << data1 << " data2 is " << data2 << endl;
}


int main()
{
    MyClass<int, char> text1;
    text1.getData();

    MyClass<char, char>text2;
    text2.getData();

    MyClass<int, int> text3;
    text3.getData();
}

程序运行结果如下: 

我们这里要注意一下写法,例如类外成员函数的实现:

template <class T1, class T2 >
void MyClass<T1, T2>::getData()
{
    cout << "无具体化调用:" << endl;
}

template<class T>
void MyClass<T, char>::getData()
{
    cout << "部分具体化调用:" << endl;
}

void MyClass<int, char>::getData()
{
    cout << "完全具体化调用:" << endl;
}

当你对一个类模板进行完全具体化时,你是在告诉编译器:“对于这些特定的类型,不要使用通用的模板定义,而是使用我提供的这个专门的定义。”这相当于已经是一个实际方案了,类外我们并没有写复杂的模板作为他的名字。

部分具体化则是在保持模板通用性的基础上,对某些特定的类型模式进行优化或定制。 

4、模板类与继承 

模板类太灵活了,所以他的继承可以玩出很多花样。看上去情况很多,但是并不复杂,只是有一点繁琐。

如果对继承的相关知识有所忘却,可以查看:简述C++类继承。

1、模板类继承普通类

模板类继承普通类很简单,只需要把基类的构造函数安排好就行了。继承中有说过用初始化列表的方式来初始化,基类的成员在基类中初始,派生类的成员在派生类中初始。

例如如下代码,我们特意不写基类的默认构造函数,只提供有参构造:

#include<iostream>

using namespace std;

class text
{
private:
    int num;
public:
    //text() { cout << "调用了text的默认构造!" << endl; };
    text(int a) :num(a) { cout << "调用了text的有参构造!" << endl; }
};

template <class T1,class T2>
class MyClass :public text{
private:
    T1 data1;
    T2 data2;
public:
    MyClass() : data1(), data2() { cout << "模板构造函数调用:" << endl; }
    MyClass(T1 value1, T2 value2) : data1(value1), data2(value2) {}   
};

int main()
{
    MyClass<int,char> text1;
}

因为没有初始化的问题,代码并不能运行,派生类中并没有初始化基类的有参构造。要在派生类的构造函数中调用基类的构造函数。代码应该这么改:

#include<iostream>

using namespace std;

class text
{
private:
    int num;
public:
    //text() { cout << "调用了text的默认构造!" << endl; };
    text(int a) :num(a) { cout << "调用了text的有参构造!" << endl; }
};

template <class T1,class T2>
class MyClass :public text{
private:
    T1 data1;
    T2 data2;
public:
    //MyClass() : data1(), data2() { cout << "模板无参构造函数调用:" << endl; }
    MyClass(T1 value1, T2 value2,int a) : data1(value1), data2(value2),text(a)
    { cout << "模板有参构造函数调用!" << endl; }
};

int main()
{
    MyClass<int,char> text1(97,97,97);
}

运行结果如下:

 2、普通类继承模板类的实例化版本

普通类继承模板类的实例化版本和普通继承很相似,例如如下代码:

#include<iostream>

using namespace std;

template <class T1,class T2>
class MyClass {
private:
    T1 data1;
    T2 data2;
public:
    MyClass(T1 value1, T2 value2) : data1(value1), data2(value2)
    { 
        //cout << "模板有参构造函数调用!" << endl;
    }
    void prints()
    {
        cout << "data1 is " << data1 << endl << "data2 is " << data2 << endl;
    }
};

class text:public MyClass<int,char>
{
private:
    int num;
public:
    text(int a,int b,char c) :num(a),MyClass(b,c)
    { 
        //cout << "调用了text的有参构造!" << endl; 
    }
    void print()
    {
        prints();
        cout << "num is " << num;
    }
};

int main()
{
    text a(97, 97, 97);
    a.print();
}

输出结果如下:

3、普通类继承模板类

 这是更常见的作法,通常用普通类去继承一个通用的模板。我们在上述例子的代码基础上做一点改动。

#include<iostream>

using namespace std;

template <class T1,class T2>
class MyClass {
private:
    T1 data1;
    T2 data2;
public:
    MyClass(T1 value1, T2 value2) : data1(value1), data2(value2)
    { 
        //cout << "模板有参构造函数调用!" << endl;
    }
    void prints()
    {
        cout << "data1 is " << data1 << endl << "data2 is " << data2 << endl;
    }
};

template <class T1, class T2>
class text:public MyClass<T1,T2>
{
private:
    int num;
public:
    text(int a,T1 b,T2 c) :MyClass<T1,T2>(b,c),num(a)
    { 
        //cout << "调用了text的有参构造!" << endl; 
    }
    void print()
    {       
        MyClass<T1, T2>::prints();
        cout << "num is " << num;
    }
};

int main()
{
    text<int,char>a(97, 97, 97);
    a.print();
}

这里有几个讲究,不写代码可能会运行不了,但是编译器也不会报错。

1、具体化构造类型

在派生类调用基类模板的构造函数的时候,必须要在类名后面加上<>以及类型参数的名称,例如上面代码是这么写的:

    text(int a,T1 b,T2 c) :MyClass<T1,T2>(b,c),num(a)
    { 
        //cout << "调用了text的有参构造!" << endl; 
    }

如果只写一个MyClass(T1,T2),代码会运行不了,我们之前说过,模板它不是具体的类型,必须加上具体的模板参数才算是一个具体的类型 。

2、初始化构造的顺序

按照 C++ 的规定,成员变量的初始化顺序是按照它们在类中声明的顺序进行的,而不是按照初始化列表中的顺序。在 text 类中,先声明的是 int num,然后才是继承自基类 MyClass<T1, T2> 的部分(虽然在语法上它不是一个普通的成员变量,但在初始化顺序上有类似的考虑)。

所以,如果 num 的初始化依赖于基类 MyClass 的正确初始化(例如,假设 num 的值是通过某种方式基于 data1 或 data2 来确定的),就可能会出现错误。

一种更好的做法是尽量保证成员变量的初始化顺序与它们在类中的声明顺序一致,或者确保它们之间不存在这种相互依赖的初始化关系。这是一个需要注意的细节点。

3、基类模板函数在派生类作用域明确
void print()
{       
    MyClass<T1, T2>::prints();
    cout << "num is " << num;
}

这段代码中,我们加上了基类模板的作用域,如果不加上可能会报错(具体还是要看编译器处理,我的vs 2022就出错了)。

原来的prints调用可能会因为编译器的查找规则而产生混淆。通过使用MyClass<T1, T2>::prints();这种作用域限定符的方式,你明确地告诉编译器,你要调用的是MyClass<T1, T2>这个类模板实例化后的prints函数,这样就避免了潜在的命名冲突和错误的函数调用。

所以普通类继承函数通用模板的核心就在于把普通类也化为模板类。因为他要继承模板的通用特性,所以普通类变成模板类才能继承。

4、模板类继承模板类

 这个也简单,在派生类中扩展通用类型参数就可以了,直接上代码:

#include<iostream>

using namespace std;

template <class T1,class T2>
class MyClass {
private:
    T1 data1;
    T2 data2;
public:
    MyClass(T1 value1, T2 value2) : data1(value1), data2(value2)
    { 
        //cout << "模板有参构造函数调用!" << endl;
    }
    void fun1()
    {
        cout << "data1 is " << data1 << endl << "data2 is " << data2 << endl;
    }
};


template<class T1, class T2,class T3>
class Text :public MyClass<T2,T3>
{
private:
    T1 num;
public:
    Text(T1 a, T2 b, T3 c) :MyClass<T2, T3>(b,c), num(a)
    {

    }
    void fun2()
    {
        MyClass<T2,T3>::fun1();
        cout << "num is " << num;
    }
};

int main()
{    
    Text<int, int, char>a(97, 97, 'a');
    a.fun2();
}

 注意安排好基类的构造函数就行。

5、模板类继承模板参数给出的基类

先上代码:

#include<iostream>

using namespace std;

class AA
{
public:
    AA() :a() { cout << "AA构造函数调用" << endl; }
    AA(int x) :a(x)
    {
        cout << "AA有参构造函数调用" << endl;
    }
private:
    int a;
};

template <class T>
class BB
{
public:
    BB():data() { cout << "BB构造函数调用" << endl; }
    BB(T a) :data(a)
    {
        cout << "BB有参构造函数调用" << endl;
    }
private:
    T data;
};

template <class T,class V>
class CC:public T
{
public:
    CC():T(),data(){ cout << "CC构造函数调用" << endl; }
    CC(V a) :T(), data(a)
    {
        cout << "CC有参构造函数调用" << endl;
    }
private:
    V data;
};

int main()
{
    CC<AA,int> c1;
    CC<BB<int> ,int> c2(10);
}

模板类CC继承了一个模板参数T,也就是说基类是不确定的,可以改变的,可以作为参数来传递,可以用模板参数T调用基类的构造函数。

代码运行结果如下:

 

5、模板类与函数 

在函数中,如果想发挥类模板的通用性的特点,必须结合函数模板。

直接上代码,不过多做解释:

#include<iostream>

using namespace std;

template <class T>
class AA
{
public:
    AA() :a() {}
    AA(T x) :a(x)
    {
        //cout << "AA有参构造函数调用" << endl;
    }
    void show()
    {
        cout << "AA show函数调用" << endl;
        cout << "a = " << a << endl;
    }
private:
    T a;
};

template <typename T>
T func(T& a)
{
    a.show();
    cout << "T func(T& a)调用!" << endl;
    return a;
}

int main()
{
    AA<int> a(10);
    func(a);
}

运行结果如下:

模板类可以作为函数模板的参数传入 ,可以返回模板类类型。

6、模板类与友元

模板类声音也可以有友元。模板的友元分为三类。

1、非模板友元

友元函数不是模板函数,而是利用模板参数生成的函数

直接上代码演示:

#include <iostream>

using namespace std;

template <class T>
class AA {
public:
    AA() : a() {}
    AA(T x) : a(x) {}
    friend void show(AA<T>& A);
private:
    T a;
};

void show(AA<int>& A) {
    cout << "show函数调用" << endl;
    cout << "a = " << A.a << endl;
}

int main() {
    AA<int> a(10);
    show(a);
}

非模板友元函数有一定的局限性,如果模板类AA是char类型的话,编译器就会报错,这样就必须重载模板类的每一个实例化版本,都准备一个友元函数。 

所以C++提供了一种解决方案:利用模板类的参数,自动生成友元函数。

在上述代码的基础上,我们把show函数的定义放到类内进行:

#include <iostream>

using namespace std;

template <class T>
class AA {
public:
    AA() : a() {}
    AA(T x) : a(x) {}
    friend void show(AA<T>& A)
    {
        cout << "show函数调用" << endl;
        cout << "a = " << A.a << endl;
    }
private:
    T a;
};

int main() {
    AA<int> a(10);
    show(a);

    AA<char> b('a');
    show(b);
}

这种方案的本质是:编译器利用模板参数帮我们生成了友元函数。但是这个函数不是模板函数。 我们来证明一下,我们在源代码的基础上加上模板具体化版本:

void show(AA<char>& A) {
    cout << "具体 show函数调用" << endl;
    cout << "a = " << A.a << endl;
}

这时候编译器就会报错:

对于类模板 AA 的任何实例化类型 T,都有一个与之对应的友元函数 show,它能够访问该实例化类型的 AA 类模板对象的私有成员 a

然而,在类模板外部又定义了一个特定类型(AA<char>)的函数 show:这就造成了函数重载的冲突。导致编译器不知道使用哪个函数了。

所以用这种方式生成的友元函数只能用于这个模板类,不能用于其他模板类,且只能在类内实现。

2、约束模板友元

模板实例化时,每一个实例化的类对应每一个友元函数。

代码如下:

#include <iostream>

using namespace std;

template <typename T>
void show(T& A);

template <class T>
class AA {
public:
    AA() : a() {}
    AA(T x) : a(x) {}
    friend void show<>(AA<T>& A);//约束
        
private:
    T a;
};

template <typename T>
void show(T& A)
{
    cout << "通用show函数调用" << endl;
    cout << "a = " << A.a << endl;
}

template <>
void show(AA<char> &A)
{
    cout << "具体show函数调用" << endl;
    cout << "a = " << A.a << endl;
}

int main() {
    AA<int> a(10);
    show(a);

    AA<char> b('a');
    show(b);
}

约束模板友元有两个优点,第一是可以具体化,二是支持多个模板类 ,这种友元方案更有价值,就是语法麻烦了一点。

如果将 friend void show<>(AA<T>& A); 写成 friend void show(AA<T>& A); 编译器上可能不会导致明显的错误,但很可能会出现编译结果不符合预期的情况,特别是在涉及函数模板的通用版本和具体化版本共存的复杂场景下。为了确保代码在不同编译器上的一致性和可预测性,建议按照原写法 friend void show<>(AA<T>& A); 来声明模板友元函数,以便正确地实现模板参数的自动推导和函数调用的正确匹配。

那如果将 friend void show<>(AA<T>& A); 写成 friend void show<>(T& A);这里将存在不匹配的情况。在类模板中声明友元函数时,虽然形式上看起来没问题,但实际上它和全局定义的函数模板 show 并没有建立起正确的关联,导致在友元函数试图访问类模板 AA 中的私有成员 a 时会出现问题。

因为按照当前的声明方式,友元函数 show 的参数 A 只是一个普通的引用类型 T&,它并不知道这个 T 应该和类模板 AA 的模板参数 T 对应起来,也无法直接访问 AA 类模板实例中的私有成员 a

3、非约束模板友元

模板实例化的时候,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数。

代码如下:

#include <iostream>

using namespace std;

template <typename T>
void show(T& A);

template <class T>
class AA {
public:
    AA() : a() {}
    AA(T x) : a(x) {}
    //friend void show<>(AA<T>& A);
    template <typename T> friend void show<>(T& A);

private:
    T a;
};

template <typename T>
void show(T& A)
{
    cout << "通用show函数调用" << endl;
    cout << "a = " << A.a << endl;
}

template <>
void show(AA<char>& A)
{
    cout << "具体show函数调用" << endl;
    cout << "a = " << A.a << endl;
}

int main() {
    AA<int> a(10);
    show(a);

    AA<char> b('a');
    show(b);
}

和约束友元模板相比较 ,这段带码把friend void show<>(AA<T>& A); 写成了template <typename T> friend void show<>(T& A);

这里实际上是在类模板的作用域内重新声明了一个与全局已定义的函数模板 showtemplate <typename T> void show(T& A))相关联的友元函数版本。

关键在于这个声明中的 template <typename T> 部分,它使得编译器能够将类模板 AA 的模板参数 T 与函数模板 show 的模板参数 T 在某种程度上建立起联系。具体来说,当在 main 函数中创建 AA 类模板的实例(如 AA<int> 或 AA<char> 等)并调用 show 函数时,编译器会根据传入的 AA 类模板实例的类型来自动推导函数模板 show 的模板参数 T

7、模板类的成员模板

这个说白了就是套娃,直接放代码吧~

template<class T1, class T2>
class AA              
{
public:
    T1 m_x;
    T2 m_y;

    AA(const T1 x, const T2 y) : m_x(x), m_y(y) {}
    void show() { cout << "m_x=" << m_x << ",m_y=" << m_y << endl; }

    template<class T>
    class BB
    {
    public:
        T m_a;
        T m_b;
        BB() : m_a(), m_b() {}
    };

    BB<T1> m_bb1;
    BB<T2> m_bb2;

    template<typename T>
    void show(T tt);
};

8、将模板类用作参数

把模板类作为参数传递,实际上是在更高层次的模板(通常称为外层模板)中,将一个尚未完全确定类型的类模板作为参数来接收。主要用于数据结构中,部分场景很少用到这么复杂的设计。

应用场景

通用算法实现
  • 可以实现一些通用的算法,这些算法不依赖于特定的容器类型(如数组、链表、向量等),而是通过接受不同的模板类作为参数来处理各种可能的容器。例如,排序算法可以针对不同的容器模板类进行实现,只要这些容器支持必要的元素访问和修改操作(如通过重载 operator[] 等方式)。
代码复用与抽象
  • 有助于代码的复用和抽象。当有多个不同类型的模板类实现了相似的功能(比如都能存储和访问元素),可以通过将模板类作为参数的方式,编写一个通用的外层模板函数或类,来处理这些不同的模板类,避免了为每个具体的模板类重复编写相同功能的代码。

代码演示:

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

template <class T, int len>
class LinkList             // 链表类模板。
{
public:
    LinkList() :m_len(0), m_head(nullptr){}
    LinkList(int x) :m_len(x), m_head(new T(m_len)){}
    
    void insert() { cout << "向链表中插入了一条记录。\n"; }
    void ddelete() { cout << "向链表中删除了一条记录。\n"; }
    void update() { cout << "向链表中更新了一条记录。\n"; }

    ~LinkList() {
        if (m_head) {
            delete m_head;
            m_head = nullptr;
        }
    }
private:
    T* m_head;          // 链表头结点。
    int  m_len;   // 表长。
};

// 线性表模板类:tabletype-线性表类型,datatype-线性表的数据类型。
template<template<class, int >class tabletype, class datatype, int len>//len是非类型参数
class LinearList
{
public:
    tabletype<datatype, len> m_table;     // 创建线性表对象。

    void insert() { m_table.insert(); }         // 线性表插入操作。
    void ddelete() { m_table.ddelete(); }      // 线性表删除操作。
    void update() { m_table.update(); }      // 线性表更新操作。
};

int main()
{
    // 创建线性表对象,容器类型为链表,链表的数据类型为int,表长为20。
    LinearList<LinkList, int, 20>  a;
    a.insert();
    a.ddelete();
    a.update();
}

 这是一个更高层次的类模板 LinearList,它的模板参数比较特殊。

其中 template<class, int >class tabletype 表示它接受一个本身也是模板类的参数,这个模板类需要有两个模板参数(一个类型参数和一个非类型参数),这里就是要接收像 LinkList 这样的模板类作为参数;

具体来说,它表示所定义的模板(在这里是 LinearList 模板类)期望接收一个本身也是模板类的参数

class datatype 是用于指定线性表中数据的类型,它会作为参数传递给 tabletype(也就是类似 LinkList 的模板类);int len 同样是一个非类型参数,用于指定线性表(实际也就是对应的 tabletype 所表示的具体数据结构,如链表)的长度。

9、结语

在对 C++ 类模板进行了深入探讨之后,我们清晰地看到了它在提升代码复用性、灵活性以及实现泛型编程方面所展现出的强大威力。通过类模板,我们能够轻松应对各种不同类型的数据结构和算法需求,将代码从特定类型的束缚中解放出来,使其具备更广泛的适用性。

从类模板的基本概念、具体化、继承、与函数模板的关联、友元函数模板,成员模板以及复杂的将模板类作为数据结构的参数处理等等,它与继承、多态等面向对象特性相结合,能够创造出更加丰富多样且灵活多变的程序结构。

正如我们所了解的,类模板的运用也伴随着一定的复杂性,以及在不同编译器下可能出现的细微差异等。

希望这篇长达一万字的类模板文章能够帮助到你,如有不当还请指教!😎👍.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2244893.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言教程指针笔记整理

https://www.bilibili.com/video/BV1cx4y1d7Ut?spm_id_from333.788.videopod.episodes&vd_sourcee8984989cddeb3ef7b7e9fd89098dbe8&p107 本篇为贺宏宏老师C语言教程指针部分笔记整理 //.c文件 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include …

从二维到一维:动态规划矩阵问题的优化之道

动态规划中的矩阵问题是非常经典的应用场景&#xff0c;比如最小路径和问题。这类问题很自然地可以想到使用二维 dp 数组来求解。 我们定义&#xff1a; dp[i][j] 表示从矩阵的第 i行第 j列到右下角的最小路径和。 基本解法 求解过程从右下角开始&#xff0c;向左上角遍历&am…

git,ssh免密公钥配置,gitee为例,GitHub,gitlab同理

git&#xff0c;ssh免密公钥配置&#xff0c;gitee为例&#xff0c;视频教程在这 git&#xff0c;ssh免密公钥配置&#xff0c;gitee为例&#xff0c;GitHub&#xff0c;gitlab同理_哔哩哔哩_bilibili 一、进入.ssh目录 cd ~/.ssh 二、查看是否有id_rsa.pub这个文件 分为…

Spring Boot整合Kafka,实现单条消费和批量消费,示例教程

如何安装Kafka&#xff0c;可以参考docker搭载Kafka集群&#xff0c;一个文件搞定&#xff0c;超简单&#xff0c;亲试可行-CSDN博客 1、在pom.xml中加入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-sta…

基于Vue+SpringBoot的求职招聘平台

平台概述 本平台是一个高效、便捷的人才与职位匹配系统&#xff0c;旨在为求职者与招聘者提供一站式服务。平台内设三大核心角色&#xff1a;求职者、招聘者以及超级管理员&#xff0c;每个角色拥有独特的功能模块&#xff0c;确保用户能够轻松完成从信息获取到最终录用的整个…

FPGA FIFO系列 - FIFO使用中需要注意的若干问题

FIFO使用中需要注意的若干问题 文章目录 FIFO使用中需要注意的若干问题前言场景1&#xff1a;包数据FIFO设计之冗余法场景2、FIFO数据传输之流控总结 前言 场景1&#xff1a;包数据FIFO设计之冗余法 场景&#xff1a;类似图像、文字等码流数据是不需要重复被访问的&#xff0c…

.NET 9 - BinaryFormatter移除

1.简单介绍 .NET 9 SDK正式版已经发布, 下载地址是.NET9 同时.NET Conf 2024 大会已经从2024-11-13开始了&#xff0c;感觉Aspire和AI的内容相对挺多的&#xff0c;主题分享演示时候打开的网站大部分都是Blazor制作的。 这次.NET Conf 2024老师也再次说明了一下&#xff0c;…

[免费]SpringBoot+Vue毕业设计论文管理系统【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue毕业设计论文管理系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringBootVue毕业设计论文管理系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 现代经济快节奏发展以及不断完善升级的信…

C# 高级--反射 详解

一、反射是什么 1、C#编译运行过程 高级语言->编译->dll/exe文件->CLR/JIT->机器码 2、原理解析metadata&#xff1a;元数据数据清单&#xff0c;记录了dll中包含了哪些东西,是一个描述。IL&#xff1a;中间语言&#xff0c;编译把高级语言编译后得到的C#中最真…

OpenCV与AI深度学习|16个含源码和数据集的计算机视觉实战项目(建议收藏!)

本文来源公众号“OpenCV与AI深度学习”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;分享&#xff5c;16个含源码和数据集的计算机视觉实战项目 本文将分享16个含源码和数据集的计算机视觉实战项目。具体包括&#xff1a; 1. 人…

【软考网工笔记】网络基础理论——应用层

TLv 基本编码规则&#xff08;BER&#xff09;将ASN.1表示的抽象类型值编码为字节串&#xff0c;这种字节串的结构为&#xff1a;类型——长度——值&#xff0c;简称TLv。 其中&#xff0c;值部分还可以递归的在编码为TLv结构&#xff0c;一具有表达复杂结构的能力。 IP地址…

用Python爬虫“偷窥”1688商品详情:一场数据的奇妙冒险

引言&#xff1a;数据的宝藏 在这个信息爆炸的时代&#xff0c;数据就像是一座座等待挖掘的宝藏。而对于我们这些电商界的探险家来说&#xff0c;1688上的商品详情就是那些闪闪发光的金子。今天&#xff0c;我们将化身为数据的海盗&#xff0c;用Python这把锋利的剑&#xff0…

企业网络安全规划建设实践

规划是指较全面或长远的计划。凡事预则立&#xff0c;不预则废&#xff01; 在企业战略规划方面&#xff0c;随着市场环境变化速度的不断加快&#xff0c;人们越来越意识到企业战略规划对企业生存和发展的重要性&#xff0c;战略规划能帮助企业解决影响组织未来发展最重要、最…

QT基本绘图

QT绘图 1.概述 这篇文章介绍如何绘图 2.绘图基本操作 创建一个普通的widget类型的项目 在widget.h 文件中重写绘图事件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : p…

Linux驱动开发(7):使用设备树实现RGB 灯驱动

通过上一小节的学习&#xff0c;我们已经能够编写简单的设备树节点&#xff0c;并且使用常用的of函数从设备树中获取我们想要的节点资源。 这一小节我们带领大家使用设备树编写一个简单的RGB灯驱动程序&#xff0c;加深对设备树的理解。 1. 实验说明 本节实验使用到 EBF6ULL-…

MATLAB实现GARCH(广义自回归条件异方差)模型计算VaR(Value at Risk)

MATLAB实现GARCH(广义自回归条件异方差)模型计算VaR(Value at Risk) 1.计算模型介绍 使用GARCH&#xff08;广义自回归条件异方差&#xff09;模型计算VaR&#xff08;风险价值&#xff09;时&#xff0c;方差法是一个常用的方法。GARCH模型能够捕捉到金融时间序列数据中的波…

Neo4j下载及其Cypher语法介绍

1.部署安装 Neo4j支持众多平台的部署安装&#xff0c;如&#xff1a;Windows、Mac、Linux等系统。Neo4j是基于Java平台的&#xff0c;所以部署安装前先保证已经安装了Java虚拟机。 在神领物流项目中&#xff0c;我们采用docker的方式进行安装。安装命令如下&#xff1a; dock…

【Redis】实现点赞功能

一、实现笔记点赞 使用redis实现点赞功能&#xff0c;对于一个笔记来说&#xff0c;不同用户只能是点赞和没点赞&#xff0c;点赞过的笔记再点击就应该取消点赞&#xff0c;所以实际上根据需求&#xff0c;我们只需要将点赞的数据存到对应的笔记里&#xff0c;查看对应的笔记相…

开源TTS语音克隆神器GPT-SoVITS_V2版本地整合包部署与远程使用生成音频

文章目录 前言1.GPT-SoVITS V2下载2.本地运行GPT-SoVITS V23.简单使用演示4.安装内网穿透工具4.1 创建远程连接公网地址 5. 固定远程访问公网地址 前言 本文主要介绍如何在Windows系统电脑使用整合包一键部署开源TTS语音克隆神器GPT-SoVITS&#xff0c;并结合cpolar内网穿透工…

【Pytorch】torch.utils.data模块

torch.utils.data模块主要用于进行数据集处理&#xff0c;是常用的一个包。在构建数据集的过程中经常会用到。要使用data函数必须先导入&#xff1a; from torch.utils import data 下面介绍几个经常使用到的类。 torch.utils.data.DataLoader DataLoader(dataset, batch_…