C++核心编程——详解函数模板

news2024/11/17 13:47:49

在这里插入图片描述
纵有疾风起,人生不言弃。本文篇幅较长,如有错误请不吝赐教,感谢支持。

💬文章目录

    • 一.模板的基础知识
      • ①为什么有模板?
      • ②初识模板
    • 二.函数模板
      • ①函数模板的定义
      • ②函数模板的使用
      • ③函数模板实例化
        • 1️⃣隐式实例化
        • 2️⃣显示实例化
      • ④函数模板的具体化(特化、特例化)
        • 1️⃣模板的局限性及解决方法
        • 2️⃣函数模板具体化(特化、特例化)
        • 3️⃣实例化和具体化的相同点
      • ⑤普通函数和函数模板调用规则
      • ⑥函数模板的注意事项
      • ⑦函数模板的分文件编写
    • 三.函数模板高级
      • ①decltype关键字

一.模板的基础知识

①为什么有模板?

在C++程序中,声明变量、函数、对象等实体时,程序设计者需要指定数据类型,让编译器在程序运行之前进行类型检查并分配内存,以提高程序运行的安全性和效率。但是这种强类型的编程方式往往会导致程序设计者为逻辑结构相同而具体数据类型不同的对象编写模式一致的代码
🃏案例:

//交换int数据
void SwapInt(int& a,int& b)
{
    int temp = a;
    a = b;
    b = temp;
}
//交换char数据
void SwapChar(char& a,char& b)
{
    char temp = a;
    a = b;
    b = temp;
}

问题:如果我要交换double类型数据,那么还需要些一个double类型数据交换的函数,繁琐,写的函数越多,当交换逻辑发生变化的时候,所有的函数都需要修改,无形当中增加了代码的维护难度,如果能把类型作为参数传递进来就好了,传递int就是int类型交换,传递double类型就是double类型交换。

②初识模板

C++标准提供了模板机制,用于定义数据类型不同但逻辑结构相同的数据对象的通用行为。在模板中,运算对象的类型不是实际的数据类型,而是一种参数化的类型。带参数类型的函数称为函数模板,带参数类型的类称为类模板

例如上面的案例,我们可以定义函数Swap(),交换两个参数,我们可以将类型参数化,如Swap(T,T),其中,T就是参数化的类型,在调用Swap()函数时,可以传入任意类型的数据,函数可以根据传入的数据推导出T的值是哪种数据类型,从而进行相应的操作。这样程序设计者就可以专注于逻辑代码的编写,而不用关心实际具体的数据类型。

模板就像生产模具,例如,中秋生产月饼,生产月饼的模具就是模板,在做模具时,只关心做出什么样式的月饼,而不用关心月饼具体的原料是什么(如水果、五仁、豆沙等)。
在这里插入图片描述
程序运行时,模板的参数由实际参数的数据类型决定,编译器会根据实际参数的数据类型生成相应的一段可运行代码,这个过程称为模板实例化。函数模板生成的实例称为模板函数,类模板生成的实例称为模板类

二.函数模板

函数模板是函数的抽象,它与普通函数相似,唯一的区别就是函数参数的类型是不确定的,函数参数的类型只有在调用过程中才被确定。

①函数模板的定义

C++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定(泛型),用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。 在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能
定义函数模板的语法格式如下所示:在这里插入图片描述
上述语法格式中,template是声明模板的关键字,<>中的参数称为模板参数;typename关键字用于标识模板参数,可以用class关键字代替,class和typename并没有区别。

  • 模板参数< >中不能为空,一个函数模板中可以有多个模板参数,模板参数和普通函数参数相似,只是不确定数据类型。
  • template下面是定义的函数模板,函数模板定义方式与普通函数定义方式相同只是参数列表中的数据类型要使用<>中的模板参数名表示 。

②函数模板的使用

template<class T>//T是模板参数
void mySwap(T &a,T &b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
//使用函数模版,和正常调用函数没区别
void test()
{
	int a = 10,b = 20;
    double c=3.1415,d=9.26;
	//1.编译器会根据实参来自动推导数据类型,并生成相应的函数
	mySwap(a,b);//mySwap中的T会变成int类型
	cout << "a=" << a <<"b=" << b << endl;
	mySwap(c,d);//mySwap中的T会变成double类型
    cout << "c=" << c << "d=" << d << endl;
}

在这里插入图片描述

🖲解释:

  • 第一行指出,要建立一个模板,并将类型命名为T。关键字template和class是必需的,也可以使用关键字typename代替class.另外,必须使用尖括号<>。类型名可以任意选择(这里为T)。
  • 余下的代码交换两个T值的算法。模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换int的函数时,编译器将按模板模式创建这样的函数,并用int代替T。同样,需要交换double的函数时,编译器将按模板模式创建这样的函数,并用double代替T.

③函数模板实例化

所谓函数模板实例化,就是用类型参数替换函数模板中的模板参数,生成具体类型的函数定义。
实例化可分为:

  • 隐式实例化
  • 显式实例化

下面分别介绍这两种实例化方式。

1️⃣隐式实例化

隐式实例化是根据函数调用时传入的参数的数据类型确定模板参数T的类型,模板参数的类型是隐式确定的。
例如下例调用mySwap()函数模板时,传入的是int类型数据a和b,编译器根据传入的实参推演出模板参数T的类型是int,就会根据函数模板将T全部替换成int,实例化出一个int类型的函数,如下所示:

//交换int数据
void mySwap(int& a,int& b)
{
    int temp = a;
    a = b;
    b = temp;
}
int main()
{
    int a=20,b=30;
    mySwap(a,b);
    return 0;
}

编译器生成具体类型函数的这一过程就称为实例化,生成的函数称为模板函数
需要注意的是:

①不能在函数调用的参数中指定模板参数的类型,对函数模板的调用应使用实参推演。例如,只能进行Swap(2,3)这样的调用,或者定义整型变量int a=20,b=30,再将变量a、b作为参数,进行Swap(a,b)这样的调用,编译器会根据传入的实参推演出T为int类型,而不能使用Swap(int,int)方式,直接将类型传入进行调用。
②函数模板不会减少可执行程序的大小,因为编译器会根据调用时的参数类型进行相应的实例化。

2️⃣显示实例化

隐式实例化不能为同一个模板参数指定两种不同的类型。
例如:

template<class T>//T是模板参数
void mySwap(T &a,T &b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
//就一个模板参数T,你隐式的给传两个数据类型
Swap(10,3.14)//编译器搞不清楚T是什么数据类型,二义性。
//那就让我们显示的告诉它

函数参数类型不一致,编译器便会报错。这就需要显式实例化解决类型不一致的问题。显式实例化需要指定函数模板中的模板参数的数据类型,语法格式如下所示:
在这里插入图片描述
< >中是显式实例化的数据类型,即要实例化出一个什么类型的函数。相当于主动告诉编译器,我希望你将用我传的参数的数据类型去将模板参数显示转换为该类型,并按照模版规定的逻辑生成函数定义。
<实例化类型>尖括号中实例化类型数量要与你定义模板时定义模板参数数量一致,可以理解用你显示具体化的数据类型去替换你定义模板时模板参数。
🃏案例:

template<class T>
void mySwap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
template void mySwap<int>(int& a, int& b);//int去替换定义函数模板时的T
void test()
{
	int a = 10,b= 3;
	mySwap<int>(a, b);//显示告诉模板mySwap中的T会变成int类型
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
}

🖲代码运行结果如下:
在这里插入图片描述

④函数模板的具体化(特化、特例化)

1️⃣模板的局限性及解决方法

假设有如下模板函数:

template<class T>
void func(T a, T b)
{}

如果代码实现时定义了赋值操作 a = b,但是T为数组,这种假设就不成立了同样,如果里面的语句为判断语句 if(a>b),但T如果是结构体,该假设也不成立,另外如果是传入的数组,数组名为地址,因此它比较的是地址,而这也不是我们所希望的操作。总之,编写的模板函数很可能无法处理某些类型,另一方面,有时候通用化是有意义的,但C++语法不允许这样做。为了解决这种问题,可以提供模板的重载,为这些特定的类型提供显示具体化的模板

2️⃣函数模板具体化(特化、特例化)

函数模板的特点是通用性,可以解决某个方面的普遍性问题,但是这个世界上的事情从来不是绝对的,有普遍的,就有特殊的。

可以提供一个具体化的函数定义,当编译器找到与函数调用匹配的具体化定义时,将使用该定义,不再寻找模板,函数模板是对函数体重新定义。

具体化(特例化、特化)的语法:
在这里插入图片描述
注意显示实例化的前缀是template,没有尖括号<>。
举例:交换两个学生类的成员变量。

#include <iostream>
using namespace std;
//函数模板
template<class T>
void mySwap(T &a,T &b)
{
    T tmp = a;
    a = b;
    b = tmp;
    cout<<"调用了mySwap(T &a,T &b)"<<endl;
}
class Student
{
public:
    Student(int num):score(num){}
    int score;
};
//具体化函数模板,为Student这一数据类型单独写一份函数定义
template <>void mySwap<Student>(Student &a,Student &b)
{
     int tmp = a.score;
     a.score = b.score;
     b.score = tmp;
     cout<<"具体化函数模板:template <>void mySwap<Student>(Student &a,Student &b)调用"<<endl;
 }
int main()
{
    int a = 10,b = 20;
    mySwap(a,b);//调用函数模板
    cout << "a=" << a << "b=" << b << endl;
    Student s1(150),s2(90);
    mySwap(s1,s2);//调用具体化函数模板
    cout << "s1.score" << s1.score<< "s2.score" << s2.score << endl;
    return 0;
}

注意:具体化函数的返回值、函数名和形参列表与函数模板相同。但是对于具体化函数模板,不管是函数声明还是定义中,都是具体的数据类型,没有通用的数据类型了。
在这里插入图片描述

3️⃣实例化和具体化的相同点

实例化和具体化都是用于生成具体的定义,实例化是编译器借助你提供的数据类型来隐式或显示替换模板中的模板参数来实例化出函数定义,而具体化则是你专门为一个具体的数据类型写函数定义。
例如:
显示实例化

template void Swap<int>(int&a,int&b)//显示具体化
//使用Swap()模板生成int类型的函数定义
//就是编译器借助你提供的int去显示替换模板中的模板参数
//生成int函数定义类型。

显示具体化

template<>void Swap <int>(int&a,int&b)//显示具体化
//不要使用Swap( )模板来生成函数定义
//而应使用专门为int类型显式地定义的函数定义

⑤普通函数和函数模板调用规则

  • ✅如果函数有多个原型,则编译器在选择函数调用时,普通函数优先于模板函数,显式具体化模板优先于函数模板。
  • ✅如果希望使用函数模板,可以用空模板参数强制使用函数模板。
  • ✅如果函数模板可以产生一个更好的匹配,那么选择模板。
#include <iostream>
using namespace std;
//普通函数
void mySwap(int a, int b)
{
    int tmp = a;
    a = b;
    b = tmp;
    cout << "调用了普通函数mySwap(int a,int b)" << endl;
}
//函数模板
template<class T>
void mySwap(T& a, T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
    cout << "调用了函数模板mySwap(T &a,T &b)" << endl;
}
class Student
{
public:
    Student(int num) :score(num) {}
    int score;
};
//显示具体化
template <>void mySwap<Student>(Student& a, Student& b)
{
    int tmp = a.score;
    a.score = b.score;
    b.score = tmp;
    cout << "具体化函数模板:template <>void mySwap<Student>(Student &a,Student &b)调用" << endl;
}
int main()
{
    int a = 10, b = 20;
    //如果函数模板和普通函数都能匹配,c++编译器优先考虑普通函数
    mySwap(a, b);
    //如果我必须要调用函数模板,那么怎么办?
    //可以通过空模板实参列表的语法限定编译器只能通过模板匹配
    mySwap<>(a, b);
    cout << "a=" << a << "b=" << b << endl;
    Student s1(150), s2(90);
    //调用显示具体化函数模板
    mySwap(s1, s2);
    cout << "s1.score:" << s1.score << "s2.score:" << s2.score << endl;
    //此时普通函数和显示具体化模板都没有很好的匹配
    //所以使用函数模板可以产生一个更好的匹配,那么选择模板
    char d = 'd', e = 'e';
    mySwap(d, e);
    return 0;
}

在这里插入图片描述

⑥函数模板的注意事项

1️⃣可以为类的成员函数创建模板,但不能是虚函数和析构函数。

#include <iostream> 
#include<string> 
using namespace std;
class Maker
{
public:
template<typename T>
Maker(T a)
{
    cout<<a<<endl;
}
template<typename T>
void show(T b)
{
    cout<<b<<endl;
}
};
int main()
{
    Maker m1("感谢支持强风吹拂king的博客");
    m1.show(666);
}

上例将Maker的构造函数和成员函数show声明成函数模板,可以接受任意的数据类型。
在这里插入图片描述

如果将show声明为虚函数会怎么样呢?
在这里插入图片描述
也不能把析构函数声明成函数模板,一个类的析构函数只有一个,根本没有参数,不需要模板。

2️⃣只要使用函数模板,就必须明确数据类型(不论是显示实例化还是隐式实例化),让编译器知道模板参数到底是个什么数据类型,确保实参与函数模板能匹配上,生成具体的函数定义。

举例:

template<class T>//T是模板参数
void mySwap(T &a,T &b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
//使用函数模版
int main()
{
    int a = 10,b = 20;
	mySwap(a,b);
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
}

现在mySwap函数描述了一种通用类型T,函数这两个形参都是T定义的,也就是说这两个形参的数据类型必须相同,在main函数中,a和b都是int类型没有任何问题。
在这里插入图片描述

int main()
{
    int a = 10
    char b = 20;
	mySwap(a,b);
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
}

如果将b改为字符,编译器报错了,模板参数T不明确,未能找到匹配的重载函数。
在这里插入图片描述
实参与函数模板的匹配,和函数重载道理一致,如果匹配不上就会报错。
把函数模板的形参都删掉,函数里面的代码也删掉。现在mySwap函数没有参数,那么编译还能通过吗?

template<class T>//T是模板参数
void mySwap()
{
	cout<<"show函数被调用"<<endl;
}
//使用函数模版
int main()
{
	mySwap();
}

放图:

未能推导模板参数。
也就是说模板参数很死板,一定要明确数据类型,上面无参show函数,不能通过参数隐式实例化函数模板,那就只能显示实例化。

mySwap<int>();

在这里插入图片描述

3️⃣使用函数模板时,如果是隐式实例化函数模板,不会发生隐式类型转换,如果显式实例化函数模板,则可以发生隐式类型转换。

举例:

template<class T>//T是模板参数
T Add (T a,T b)
{
	return a+b;
}
//使用函数模版
int main()
{
    int a = 10,b = 20;
    cout<<Add(a,b)<<endl;
}

现在a和b都是整型,把他们作为函数模板的实参,编译器推导出来的就是整型,与函数模板的定义是匹配的。
在这里插入图片描述

我们将b改为字符,在C++中字符是可以隐式转换成整型的。

int main()
{
    int a = 10;
    char b = 20;
    cout<<Add(a,b)<<endl;
}

在这里插入图片描述
模板参数不明确,看来隐式实例化没有将字符b转换成整型。
那我们显示实例化试一下:

int main()
{
    int a = 10;
    char b = 'a';
    cout<<Add<int>(a,b)<<endl;
}

在这里插入图片描述

编译成功。也就是说如果显式实例化函数模板,则可以发生隐式类型转换。

4️⃣如果函数模板有多个模板参数,则每个模板参数前都必须使用关键字class或typename修饰。

例如:

template<typename T, typename U>//两个关键字可以混
void func(T t, U u){}
template<typename T,U>//错误,每一个模板参数前都必须有关键字修饰
void func(T t, U u){}

5️⃣全局作用域中声明的与模板参数同名的对象、函数或类型,在函数模板中将被隐藏。

例如:

int num=9000; 
template<typename T> 
void func(T t)
{
    T num;   
    cout<<num<<endl;//输出的是局部变量num,全局int类型的num被屏蔽
}

在函数体内访问的num是T类型的变量num,而不是全局int类型的变量num。

6️⃣函数模板中声明的对象或类型不能与模板参数同名。

例如:

template<typename T>
void func(T t){
    typedef float T;//错误,定义的类型与模板参数名相同 } ​​

7️⃣模板参数名在同一模板参数列表中只能使用一次,但可在多个函数模板声明或定义之间重复使用。

例如:

template<typename T, typename T>//错误,在同一个模板中重复定义模板参数 
void func1(T x, T y){}template<typename T>void func2(T z){}template<typename T>//在不同函数模板中可重复使用相同的模板参数名 void func3(T w){}​​

⑦函数模板的分文件编写

函数模板只是函数的描述,没有实体,创建函数模板的代码放在头文件中。
函数模板的具体化有实体,编译的原理和普通函数一样,所以,声明放在头文件中,定义放在源文件中。
例如把下面这段代码分文件编写:

#include <iostream>
using namespace std; 
void Swap(int a, int b) // 普通函数。
{
cout << "使用了普通函数。\n";
}
template <typename T>
void Swap(T a, T b) // 函数模板。
{
cout << "使用了函数模板。\n";
}
template <>
void Swap(double a, double b) // 函数模板的具体化版本。
{
cout << "使用了具体化的函数模板。\n";
}
int main()
{
    Swap(1,2); //将使用普通函数。
    Swap(1.3, 3.5); //将使用具体化的函数模板。
    Swap('c', 'd'); //将使用函数模板。
}

在这里插入图片描述

头文件public.h,存放函数的声明

#pragma once
#include <iostream>
using namespace std; 
void Swap(int a, int b); //普通函数的声明
template <typename T>
void Swap(T a, T b) // 函数模板的定义
{
  cout << "使用了函数模板。\n";
}
template <>
void Swap(double a, double b); // 函数模板的具体化的声明

public.cpp,存放函数的实现

#include "public.h"
void Swap(int a, int b) //普通函数的实现
{
    cout << "使用了普通函数。\n";
}
template <>
void Swap(double a, double b) // 函数模板的具体化的实现
{
    cout << "使用了具体化的函数模板。\n";
}

demo01.cpp 测试文件

#include "public.h"
int main()
{
    Swap(1,2); // 将使用普通函数。
    Swap(1.3, 3.5); // 将使用具体化的函数模板。
    Swap('c', 'd'); // 将使用函数模板。
}

三.函数模板高级

①decltype关键字

先来看一个示例:

template<typename T1,typename T2>
void func(T1 a, T2 b)
{
    //其他代码
    ??? tmp=a+b;
    //其他代码
}

func函数模板描述了两个通用类型T1和T2,形参a和b分别用T1和T2定义,在函数体内声明tmp保存a+b的结果,那么tmp怎么声明,用什么数据类型,T1还是T2,还是其他?

如果T1是double,T2是int,那么最终结果是double类型;如果T1是short,T2是int,那么最终结果是int类型;还有除了C++的内置数据类型,结构体和类,还可以重载+号运算符,这样情况就更复杂了,这样的话tmp又将定义成什么类型呢?

在C++11中,decltype操作符,用于查询表达式的数据类型。
语法:

decltype(表达式) var;
decltype分析表达式并返回它的类型,
用这个类型定义var变量。

decltype分析表达式并返回它的类型,不会计算执行表达式。函数调用也一种表达式,因此不必担心在使用decltype时执行了函数。
decltype推导规则(按步骤):

  • 1)如果表达式是一个没有用括号括起来的标识符,decltype本身自带的()不算,则var的类型与该标识符的类型相同,包括const等限定符。
int main()
{
    const short a = 100;
    decltype(a) var=666;
    //var的类型将和a保持一致。
    //注意:如果decltype分析表达式的类型是引用
    //那么必须初始化这个引用
    short b = 10;
    short& pa = b;
    decltype(pa) var2 = b;//var2的类型是short&,初始化var2的引用指向b;、
    cout << "var:" << var <<"var2:" << var2 << endl;
}

在这里插入图片描述
在这里插入图片描述

  • 2)如果表达式是一个函数调用,则var的类型与函数的返回值类型相同(函数不能返回void,但可以返回void * ,因为void * 可以声明变量)。
int func()
{
    cout<<"调用了func函数"<<endl;
    return 666;
}
int main()
{
    int b=100;
    decltype(func()) val=b;
}

大家看,val的类型,是func函数的返回类型int。
在这里插入图片描述

,我们运行代码,发现并没有调用func函数,这也说明decltype不会计算执行表达式。
在这里插入图片描述

✅还有一点:
在decltype中填函数名和函数调用是两回事。

int main()
{
    decltype(func()) val=b;
    //填函数调用,得到的是函数的返回类型
    decltype(func) *val2=func;
    //只填函数名得到的是函数的类型
    //再加*号就是函数指针类型,我们让val2指向func函数
    val2();//借助函数指针val2调用func函数
}

在这里插入图片描述
在这里插入图片描述

  • 3)如果表达式是一个左值(能取地址)(要排除第一种情况)、或者用括号括起来的标识符,那么var的类型是表达式的引用。
int func()
{
    cout << "调用了func函数" << endl;
    return 666;
}
int main()
{
    short a = 100;
    decltype(a) var;
    //var的类型将和a保持一致。
    decltype((a)) var2=a;//此时的a用括号括起来了,那么var2就是short&类型
    decltype((func)) val3 = func;
    //如果func不加(),那val3就是函数类型
    //但加了()就是函数类型的引用
    val3();//函数类型引用也可以调用函数。
}

在这里插入图片描述
在这里插入图片描述

  • 4)如果上面的条件都不满足,则var的类型与表达式的类型相同。
    如果需要多次使用decltype,可以结合typedef和using,给数据类型起别名。
    那么我们现在用decltype关键字去解决最初的问题
template<typename T1,typename T2>
void func(T1 a, T2 b)
{
    //其他代码
    decltype(a+b) tmp=a+b;
    cout<<"tmp="<<tmp<<endl;
    //其他代码
}
int main()
{
    func(3.14,600);
}

在这里插入图片描述

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

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

相关文章

QAC用户使用手册

文章目录 1 QAC介绍1.1 QAC简介1.2 QAC dashboard简介 2 QAC使用&#xff08;基本操作&#xff09;2.1 创建QAC工程2.2 创建QAC工程2.3 添加代码到QAC工程2.4 添加代码到QAC工程2.5 上传分析报告及结果 1 QAC介绍 1.1 QAC简介 Helix QAC是Perforce公司(原PRQA公司)产品,主要用…

「Java核心技术大会 2023」——小解送书第三期

目录 共同深入探讨 Java 生态&#xff01;直播预约&#xff1a;视频号“IT阅读排行榜” 抽奖 大会简介 人工智能在22年、23年的再次爆发让Python成为编程语言里最大的赢家&#xff1b;云原生的持续普及令Go、Rust等新生的语言有了进一步叫板传统技术体系的资本与底气。我们必…

Android studio项目编译进安卓源码中

最近要做一个Android 8.1 的launcher &#xff0c;在Androidstudio上开发好基本功能后&#xff0c;移到Android源码中编译 1.在源码中创建代码目录 我开发基于展讯9820e平台&#xff0c;在如下目录创建好对应名字的文件夹 \vendor\sprd\platform\packages\apps\xxxLauncher创…

回收站清空了怎么恢复?3个妙招恢复数据

回收站被人为清空&#xff0c;被放入回收站的文件因时间过久而被电脑自动删除时&#xff0c;回收站里的数据清空了还能找到吗&#xff1f;是可以的这3个小妙招可以帮你还原回收站的数据&#xff01; 妙招一&#xff1a;借助注册表还原回收站清空的数据 可以尝试借助注册表还原…

Bootstrap 环境安装

文章目录 Bootstrap 环境安装下载 Bootstrap 文件结构预编译的 BootstrapBootstrap 源代码 HTML 模板实例Bootstrap CDN 推荐 Bootstrap 环境安装 Bootstrap 安装是非常容易的。本章将讲解如何下载并安装 Bootstrap&#xff0c;讨论 Bootstrap 文件结构&#xff0c;并通过一个实…

常见的Jmeter参数化方式总结

目录 前言&#xff1a; 参数化概念 参数化方式 二、用户变量 三、CSV数据文件 四、函数助手 前言&#xff1a; 在进行接口性能测试时&#xff0c;我们通常需要针对不同的场景进行参数化操作。JMeter是一款强大的性能测试工具&#xff0c;它提供了多种参数化方式&#xff0c;方便…

Idea在JavaSE项目中配置JavaEE

新建模块&#xff08;File --> new --> Module...&#xff09;javase项目 选择了这个webapp的支持之后&#xff0c;IDEA会自动给你生成一个符合Servlet规范的webpp目录结构。 如果说我们现在需要使用servlet的和JSP 那么需要servlet和JSP的jar包 也可以选择添加库,但是…

qt udp通信

udp不分客户端和服务器&#xff0c;只需要使用一个类 QUdpSocket 这里写目录标题 界面设计qudpsocketthis按钮 打开按钮 发送 关闭 界面设计 接收框设置为 只读 为ui界面各个模块改名字 本低端口和目标ip框对齐&#xff0c;可以对目标ip 宽度设置 为一样 水平策略 qudpsocke…

OpenHarmony端云一体化应用开发快速入门练习(下)登出销户等

一、登出 前提条件&#xff1a;需要在AGC控制台开通认证服务。需要先在您的应用中集成认证服务SDK。 开发步骤 当用户不再使用应用&#xff0c;或者需要使用其他帐号登录时&#xff0c;需要调用AGConnectAuth.signOut登出当前用户。用户一旦被登出&#xff0c;端侧的用户信息和…

Vivado 下按键控制 LED 实验

目录 Vivado 下按键控制 LED 实验 1、简介 2、实验环境 3、实验任务 4、硬件设计 5、程序设计 5.1、按键控制 led 模块代码 5.2、Vivado 仿真验证 5.2.1、Testbench 模块代码如下&#xff1a; 5.2.2、Vivado 仿真验证 6、下载验证 6.1、添加约束文件.xdc 6.2、板上…

SPSSPRO数据分析之——CSI数据预处理、降维

目录 一、前言 二、数据准备 三、进行预处理 四、进行降维任务 五、正态性检测 六、代码功能 一、前言 SPSSPRO是一款全新的在线数据分析平台&#xff0c;可以用于科研数据的分析、数学建模等&#xff0c;对于那些不会编程或者刚进入科研的新人来说&#xff0c;这款工…

CDGA/CDGP——第八章 数据集成和互操作

加gzh“大数据食铁兽”&#xff0c; 回复“知识点” 获取《DMBOK知识梳理for CDGA/CDGP》常考知识点&#xff08;第八章 数据集成与互操作&#xff09; 第八章 数据集成和互操作 第八章在CDGA分值占比较少&#xff0c;CDGP不考核&#xff0c;主要考点包括&#xff1a;定义、…

电商数据分析方案:丰富经验护航,分析一步到位

如果做电商数据分析的每一步都从零开始&#xff0c;摸着石头过河&#xff0c;反复测试修改。一通忙活下来&#xff0c;成果没见多少&#xff0c;人力物力成本倒是节节攀升&#xff0c;试问又有多少企业承受得住&#xff1f;如果有一套一步到位的数据分析方案&#xff0c;是不是…

Excel根据颜色求和与计数

文章目录 一、需求二、实现方法1.代码2.创建自定义函数3.使用函数 三、参考资料 一、需求 一个Excel中有不同颜色标记的单元格&#xff0c;统计的时候&#xff0c;需要按照颜色进行统计。 人工来做肯定是不可能了&#xff0c;借助Excel的功能好像也没有思路&#xff0c;其实这…

【三维重建】【深度学习】【数据集】基于COLMAP制作自己的NeRF(LLFF格式)数据集

【三维重建】【深度学习】【数据集】基于COLMAP制作自己的NeRF(LLFF格式)数据集 提示:最近开始在【三维重建】方面进行研究,记录相关知识点,分享学习中遇到的问题已经解决的方法。 文章目录 【三维重建】【深度学习】【数据集】基于COLMAP制作自己的NeRF(LLFF格式)数据集前言下…

python复习第一章

什么是 Python&#xff1f; Python 是一门流行的编程语言。它由 Guido van Rossum 创建&#xff0c;于 1991 年发布。 它用于&#xff1a; Web 开发&#xff08;服务器端&#xff09;软件开发数学系统脚本 Python 可以做什么&#xff1f; 可以在服务器上使用 Python 来创建…

图解路由器处理报文全过程,值得一看!

你们好啊&#xff0c;我的网工朋友 只要有网络的地方&#xff0c;你很难不看到路由器的身影&#xff0c;各种低、中、高端的&#xff0c;种类繁多&#xff0c;所具备的功能和内部实现不完全一样。 要知道&#xff0c;路由器不断的在吞吐通信数据&#xff0c;就像鱼吐泡泡一样…

【有奖征文 】AI编程:华为云CodeArts Snap入门体验

了不起的开发者们&#xff0c;当你听到“编程”一词时&#xff0c;可能想到的是一行行复杂的代码和漫长的坐姿。但是&#xff0c;随着人工智能的飞速发展&#xff0c;AI编程正在成为一种全新的编程方式&#xff0c;使得编程变得更加简单和直观。现在&#xff0c;是时候跟大家分…

如何在ALPS系统上模拟MODBUS协议

Modbus协议介绍 请想象一下你有一个工厂&#xff0c;里面有许多机器和设备&#xff0c;比如传感器、电机、控制器等&#xff0c;这些设备需要相互通信以便共享数据和执行任务。Modbus协议就像是这些设备之间的一种语言或规则&#xff0c;确保机器和设备能够互相理解和交流。 在…

python数据可视化Mito安装配置

目录 遇见 Mito如何启动 Mito数据透视表Mito 令人印象深刻的功能可视化数据自动代码生成Mito 安装 JupyterLab 是 Jupyter 主打的最新数据科学生产工具&#xff0c;某种意义上&#xff0c;它的出现是为了取代Jupyter Notebook。 它作为一种基于 web 的集成开发环境&#xff…