当前位置: 首页 > news >正文

轻松掌握C++的模板与类模板,将Tamplate广泛运用于我们的编程生活

C++提高编程

本阶段主要针对C++泛型编程STL技术做详细讲解,探讨C++更深层的使用

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。

 

模板

1.模板的概念

模板就是建立通用的模具,大大提高复用性

例如:

 

 

2.函数模板

C++另一种编程思想称为泛型编程,主要利用的技术就是模板

C++提供两种模板机制:函数模板类模板

1.函数模板语法

函数模板作用:

建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。

语法

template<typename T>
函数声明或定义

解释

template —— 声明创建模板

typename —— 表面其后面的符号是一种数据类型,可以用class替代

T —— 通过的数据类型,名称可以替换,通常为大写字母,也可以自己定义

示例:

#include<iostream>
using namespace std;
​
//函数案例
​
//两个整型交换的函数
void swapInt(int &a, int &b)//使用C++引用的知识
{int temp = a;a = b;b = temp;
}
​
//两个浮点型交换的函数
void swapDouble(double &a, double &b)//使用C++引用的知识
{double temp = a;a = b;b = temp;
}
​
int main()
{int a = 10;int b = 20;swapInt(a, b);cout << "a = " << a << " " << "b = " << b << endl;double c = 1.1;double d = 2.2;swapDouble(c, d);cout << "c = " << c << " " << "d = " << d << endl;return 0;
}

上面的是函数实现数据交换的方式,接下来展示的是函数模板

#include<iostream>
using namespace std;
​
template<typename T>//声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是通用的数据类型
void mySwap(T &a, T &b)
{T temp = a;a = b;b = temp;
}
​
int main()
{int a = 10;int b = 20;//利用函数模板交换//两种方式使用函数模板//1.自动类型推导mySwap(a, b);cout << "a = " << a << " " << "b = " << b << endl;//2.显示指定类型double c = 1.1;double d = 2.2;mySwap<double>(c, d);cout << "c = " << c << " " << "d = " << d << endl;return 0;
}

总结

  1. 函数模板利用关键字template

  2. 使用函数模板有两种方式:自动类型推导、显示指定类型

  3. 模板的目的是为了提高复用性,将类型参数化

2.函数模板注意事项

注意事项

  1. 自动类型推导,必须推导出一致的数据类型T,才可以使用

  2. 模板必须要确定出T的数据类型,才可以使用

示例:

#include<iostream>
using namespace std;
​
//函数模板注意事项
​
template<typename T>//typename可以替换成class,有的程序员将typename作为函数模板,class作为类模板,作为区分
void mySwap(T &a, T &b)
{T temp = a;a = b;b = temp;
}
​
//1.自动类型推导,必须要推导出一致的数据类型T
void test01()
{int a = 10;int b = 20;char c = 'c';mySwap(a, c);//error,推导不出一致的T类型cout << "a = " << a << " " << "c = " << c << endl;
}
​
//2.模板必须要确定出T的数据类型,才可以使用
template<class T>
void func()
{cout << "func函数的调用" << endl;
}
​
void test02()
{func<int>();
}
​
int main()
{//test01();test02();return 0;
}

3.函数模板案例

案例描述:

  1. 利用函数横板封装一个排序的函数,可以对不同数据类型数组进行排序

  2. 排序规则从大到小,排序算法为选择排序

  3. 分别利用char数组int数组进行测试

示例:

#include<iostream>
using namespace std;
​
//实现通用 对数组进行排序的函数
//规则 从大到小
//算法 选择算法
//测试 char 数组 int
​
//交换函数
template<typename T>
void mySwap(T &a, T &b)
{T temp = a;a = b;b = temp;
}
​
//排序算法
template<typename T>
void mySort(T arr[], int len)
{for(int i = 0;i < len; i++){int max = i;//认定最大值的下标for(int j = i;j < len; j++){//认定的最大值比遍历出的数值要小,说明j下标的元素才是真正的最大值if(arr[max] < arr[j]){max = j;//更新最大值坐标}}if(max != i){//交换max和i元素mySwap(arr[max],arr[i]);}}
}
​
//提供打印数组模板
template<typename T>
void printArray(T arr[], int len)
{for(int i = 0;i < len; i++){cout << arr[i] << " ";}cout << endl;
}
​
void test01()
{//测试char数组char charArr[] = "badcfe";int num = sizeof(charArr) / sizeof(char);mySort(charArr, num);printArray(charArr, num);
}
​
void test02()
{//测试int数组int intArr[] = { 7,5,1,3,9,2,4,6,8 };int num = sizeof(intArr) / sizeof(int);mySort(intArr, num);printArray(intArr, num);
}
​
int main()
{test01();test02();return 0;
}

4.普通函数与函数模板的区别

普通函数与通数模板区别:

  1. 普通函数调用时可以发生自动类型转换隐式类型转换

  2. 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换

  3. 如果利用显示指定类型的方式,可以发生隐式类型转换

示例:

#include<iostream>
using namespace std;
​
//普通函数调用可以发生隐式类型转换
//函数模板调用,如果利用自动类型推导,不会发生隐式类型转换
//函数模板调用,如果利用显示指定类型的方式,可以发生隐式类型转换
​
//普通函数
int myAdd1(int a, int b)
{return a + b;
}
​
void test01()
{int a = 10;int b = 20;char c = 'c';//ASCLL码中a - 97,c - 99int ret = myAdd1(a,c);cout << ret << endl;//109,会隐式地进行计算
}
​
//函数模板
template<typename T>
T myAdd2(T a, T b)
{return a + b;
}
​
void test02()
{int a = 1;char b = 'b';//自动类型推导cout << myAdd2(a,b) << endl;//error,出现了两种类型,无法进行运算//显示指定类型cout << myAdd2<int>(a, b) << endl;//99 可以执行运行
}
​
int main()
{test01();test02();return 0;
}

总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T

5.普通函数与函数模板的调用规则

调用规则如下:

  1. 如果函数模板和普通函数都可以实现,优先调用普通函数

  2. 可以通过空模板参数列表来强制阀用函数模板

  3. 函数模板也可以发生重载

  4. 如果函数模板可以产生更好的匹配优先调用函数模板

示例

#include<iostream>
using namespace std;
​
//普通函数与函数模板调用规则
//1.如果函数模板和普通函数都可以实现,优先调用普通函数
//2.可以通过空模板参数列表来强制阀用函数模板
//3.函数模板也可以发生重载
//4.如果函数模板可以产生更好的匹配优先调用函数模板
​
void myPrint(int a, int b)
{cout << "调用普通函数" << endl;
}
​
template<typename T>
void myPrint(T a, T b)
{cout << "调用模板" << endl;
}
​
//void myPrint(T a, T b, T c)
//{//cout << "调用模板" << endl;
//}
​
void test01()
{int a = 10;int b = 10;int c = 10;myPrint(a, b);//优先调用普通函数,有声名//通过空模板参数列表,强制调用函数模板myPrint<>(a, b);//函数模板也可以发生重载//myPrint(a, b, c);//如果函数模板产生更好的匹配,优先调用模板函数char c1 = 'a';char c2 = 'b';myPrint(c1, c2);
}
​
int main()
{test01();return 0;
}

总结:既然提供提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。

6.模板的局限性

局限性

模板的通用性并不是万能的

例如

template<typename T>
void f(T a, T b)
{a = b;
}

在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现。

template<class T>
void f(T a, T b)
{if(a > b){ ... }
}

在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行

因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

示例

#include<iostream>
#include<string>
using namespace std;
​
//模板局部性
//模板并不是万能的,有些特定数据类型,需要用具体化方法做特殊实现
​
class Person
{
public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}//姓名string m_Name;//年龄int m_Age;
};
​
//对比两个数据是否相等函数
template<typename T>
bool myCompare(T &a, T &b)
{if(a == b){return true;}else{return false;}
}
​
//利用具体化Person的版本实现代码,具体化优先使用
template<> bool myCompare(Person &p1, Person &p2)
{if(p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age){return true;}else{return false;}
}
​
void test01()
{int a = 10;int b = 10;bool ret = myCompare(a, b);if(ret){cout << "a == b" << endl;}else{cout << "a != b" << endl;}
}
​
void test02()
{Person p1("Tom",10);Person p2("Tom",10);bool ret = myCompare(p1,p2);if(ret){cout << "p1 == p2" << endl;}else{cout << "p1 != p2" << endl;}
}
​
int main()
{test01();test02();return 0;
}

总结

  1. 利用具体化的模板,可以解决自定义类型的通用化

  2. 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板

类模板

1.类模板语法

类模板作用: 建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表。

语法

template<typename T>
类

解释: template——声明创建模板 typename——表面其后面的符号是一种数锯类型,可以用class代替 T——通用的数据类型,名称可以替换,通常为大写字母

示例

#include<iostream>
#include<string>
using namespace std;
​
//类模板
template<class NameType, class AgeType>
class Person
{
public:Person(NameType name,AgeType age){this->m_Name = name;this->m_Age = age;}void showPerson(){cout << "name:" << this->m_Name << " " << "age:" << this->m_Age << endl;}NameType m_Name;AgeType m_Age;
};
​
void test01()
{Person<string,int> p1("zhangsan",18);p1.showPerson();
}
​
int main()
{test01();return 0;
}

2.类模板与函数模板区别

类模板与函数模板区别主要有两点:

  1. 类模板没有自动类型推导的使用方式

  2. 类模板在模板参数列表中可以有默认参数

示例

#include<iostream>
#include<string>
using namespace std;
​
//类模板与函数模板区别
template<class NameType, class AgeType = int>//template<class NameType, class AgeType = int>
//类模板在模板参数列表中可以有默认参数,但是函数模板无法调用默认参数
class Person
{
public:Person(NameType name, AgeType age ){this->m_Name = name;this->m_Age =age;}void showPerson(){cout << "name:" << this->m_Name << " " << "age:" << this->m_Age << endl;}NameType m_Name;AgeType m_Age;
};
​
//1.类模板没有自动类型推导使用方式
void test01()
{//Person p("zhangsan",18);error,无法使用自动类型推导Person<string,int>p("zhangsan",18);//只能使用显示指定函数p.showPerson();
}
​
//2.类模板在模板参数列表中可以有默认参数
void test02()
{Person<string,int>p("lisi",2);p.showPerson();
}
​
int main()
{test01();test02();return 0;
}

3.类模板

类模板中成员函数和普通类中成员函数创建时机是有区别的:

  1. 普通类中的成员函数开始就可以创建

  2. 类模板中的成员函数在调用时才创建

示例

#include<iostream>
using namespace std;
​
//类模板中成员函数创建时机
//类模板中成员函数在调用时采取创建
​
class Person1
{
public:void showPerson1(){cout << "Person1 show" << endl;}
};
​
class Person2
{
public:void showPerson2(){cout << "Person2 show" << endl;}
};
​
template<class T>
class MyClass
{
public:T obj;//类模板中的成员函数void func1(){obj.showPerson1();}void func2(){obj.showPerson2();}
};
​
void test01()
{MyClass<Person1> m1;//要确定调用的成员类型m1.func1();MyClass<Person2> m2;m2.func2();
}
​
int main()
{test01();return 0;
}

总结:类模板中的成员函数并不是开始就创建的,在调用时才去创建

4.类模板对象做函数参数

学习目标:

类模板实例化出的对象,向函数传参的方式。

一共有三种传入方式:

  1. 指定传入的类型——直接显示对象的数据类型

  2. 参数模板化——将对象中的参数变为模板进行传递

  3. 整个类模板化——将这个对象类型模板化进行传递

示例

#include<iostream>
#include<string>
using namespace std;
​
//类模板对象做函数参数
​
template<class T1, class T2>
class Person
{
public:Person(T1 name, T2 age){this->m_Name = name;this->m_Age = age;}void showPerson(){cout << "姓名:" << this->m_Name << " " << "年龄:" << this->m_Age << endl;}T1 m_Name;T2 m_Age;
};
​
//1.指定传入形式
void printPerson1(Person<string,int>&p)
{p.showPerson();
}
​
void test01()
{Person<string,int> p("zhangsan",18);//1.指定传入形式printPerson1(p);
}
​
//2.参数模板化
template<class T1, class T2>
void printPerson2(Person<T1,T2>&p)
{p.showPerson();cout << "T1的类型:" << typeid(T1).name() << endl;cout << "T2的类型:" << typeid(T2).name() << endl;
}
​
void test02()
{Person<string,int> p("lisi",20);printPerson2(p);
}
​
//3.整个类模板化
template<class T>
void printPerson(T &p)
{p.showPerson();cout << "T的数据类型:" << typeid(T).name() << endl;
}
​
void test03()
{Person<string,int> p("wangwu",22);printPerson(p);
}
​
int main()
{test01();test02();test03();return 0;
}
姓名:zhangsan 年龄:18
姓名:lisi 年龄:20
T1的类型:NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
T2的类型:i
姓名:wangwu 年龄:22
T的数据类型:6PersonINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEiE

总结

  1. 通过类模板的对象,可以有三种方式向函数中进行传参

  2. 使用比较广泛的是第一种指定传入的类型

typeid的知识补充

在c++中,typeid用于返回指针或引用所指对象的实际类型。

typeid就是C++中查看数据类型的函数,语法一般为:typeid(A).name()

由第二的T1返回值知string的原名是很长的。

示例

#include<iostream> 
using namespace std;
​
class  Base {};
class  Derived
{
public:
};
​
int  main()
{Base b, *pb;pb = NULL;Derived d;
​cout  <<  typeid(int).name()  <<  endl<<  typeid(unsigned).name()  <<  endl<<  typeid(long).name()  <<  endl<<  typeid(unsigned long).name()  <<  endl<<  typeid(char).name()  <<  endl<<  typeid(unsigned char).name()  <<  endl<<  typeid(float).name()  <<  endl<<  typeid(double).name()  <<  endl<<  typeid(string).name()  <<  endl<<  typeid(Base).name()  <<  endl<<  typeid(b).name()<<endl<<  typeid(pb).name()<<endl<<  typeid(Derived).name() << endl<<  typeid(d).name()<<endl <<  typeid(type_info).name()  <<  endl;return   0 ;
}

vscode上面输出类型就是如下可见:

i
j
l
m
c
h
f
d
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
4Base
4Base
P4Base
7Derived
7Derived
St9type_info

5.类模板与继承

当类模板碰到继承时,需要注意一下几点:

  1. 当子类继承的类是一个类模板时,子类在声明的时候,要指定出父类中T的类型

  2. 如果不指定,编译无法给子类分配内存

  3. 如果想灵活指定出父类中T的类型,子类也需变为类模板

示例

#include<iostream>
#include<string>
using namespace std;
​
//类模板与继承
template<class T>
class Base
{T m;
};
​
class S1 :public Base<int>//必须要知道父类中的T类型,才能继承给子类
{
​
};
​
void test01()
{S1 s1;
}
​
//想灵活指定父类中T类型,子类也需要变类模板
template<class T1, class T2>
class S2 :public Base<T2>
{
public:S(){cout << "T1类型:" << typeid(T1).name() << endl;cout << "T2类型:" << typeid(T2).name() << endl;}T1 obj;
};
​
void test02()
{S2<int,char> s2;s2.S();
}
​
int main()
{test01();test02();return 0;
}

总结:如果父类是类模板,子类需要指定出父类中的数据类型

6.类模板成员函数类外实现

学习目标:够掌控类模板中的成员函数类外实现

示例

#include<iostream>
using namespace std;
​
//类模板成员函数类外实现
template<class T1, class T2>
class Person
{
public:Person(T1 name, T2 age);void showPerson();T1 m_Name;T2 m_Age;
};
​
//构造函数类外实现
template<class T1, class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{this->m_Name = name;this->m_Age = age;
}
​
//成员函数类外实现
template<class T1, class T2>
void Person<T1,T2>::showPerson()//体现是类模板
{cout << "姓名:" << this->m_Name << " " << "年龄:" << this->m_Age << endl;
}
​
void test01()
{Person<string,int> p("Tom",20);p.showPerson();
}
​
int main()
{test01();return 0;
}

总结:类模板中成员函数类外实现时,需要加上模板参数列表

7.类模板分文件编写

学习目标:

掌握类模板成员函数分文件编写产生的问题以及解决方式

问题:

类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决:

解决方法一:直接包含.cpp文件

解决方法二:将声明和实现写到同一文件中,并更改后缀为.hpphpp是约定的名称,并不是强制

示例:

#include<iostream>
#include<string>
using namespace std;
​
//类模板分文件编写问题以及解决
template<class T1, class T2>
class Person
{
public:Person(T1 name, T2 age);void showPerson();T1 m_Name;T2 m_Age;
};
​
//类外实现
template<class T1, class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{this->m_Name = name;this->m_Age = age;
}
​
template<class T1, class T2>
void Person<T1,T2>::showPerson()
{cout << "姓名:" << this->m_Name << " " << "年龄:" << this->m_Age << endl;
}
​
void test01()
{Person<string,int>p("zhangsan",18);p.showPerson();
}
​
int main()
{test01();return 0;
}

分文件方法一:

person.h

#pragma once
#include<iostream>
#include<string>
using namespace std;
​
template<class T1, class T2>
class Person
{
public:Person(T1 name, T2 age);void showPerson();T1 m_Name;T2 m_Age;
};

person.cpp

#include"person.h"
template<class T1, class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{this->m_Name = name;this->m_Age = age;
}
​
template<class T1, class T2>
void Person<T1,T2>::showPerson()
{cout << "姓名:" << this->m_Name << " " << "年龄:" << this->m_Age << endl;
}

main.cpp

#include<iostream>
using namespace std;
//第一种方式
#include"person.cpp"//person.h调用的话是不会创建里面的东西
​
//第二种方式,将.h和.cpp中的内容写到一起,将后缀名改为.hpp
​
​
void test01()
{Person<string,int>p("zhangsan",18);p.showPerson();
}
​
int main()
{test01();return 0;
}

分文件方法二:

person.hpp

#pragma once
#include<iostream>
#include<string>
using namespace std;
​
template<class T1, class T2>
class Person
{
public:Person(T1 name, T2 age);void showPerson();T1 m_Name;T2 m_Age;
};
​
template<class T1, class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{this->m_Name = name;this->m_Age = age;
}
​
template<class T1, class T2>
void Person<T1,T2>::showPerson()
{cout << "姓名:" << this->m_Name << " " << "年龄:" << this->m_Age << endl;
}

main.cpp

#include<iostream>
using namespace std;
//第一种方式
#include"person.hpp"//person.h调用的话是不会创建里面的东西
​
//第二种方式,将.h和.cpp中的内容写到一起,将后缀名改为.hpp
​
​
void test01()
{Person<string,int>p("zhangsan",18);p.showPerson();
}
​
int main()
{test01();return 0;
}

8.类模板与友元

学习目标:掌握类模板配合友元函数的类内和类外实现

全局函数类内实现——直接在类内声明友元即可

全局函数类外实现——需要提前让编译器知道全局函数的存在

//提前让编译器知道有Person类的存在
template<class T1, class T2>
class Person;
​
//类外实现,先让编译器看到,了解到有这么一个代码
template<class T1, class T2>
void printPerson2(Person<T1,T2> p)
{cout << "类外实现的内容 " << "姓名:" << p.m_Name << " " << "年龄:" << p.m_Age << endl;
}

示例

#include<iostream>
#include<string>
using namespace std;
//通过全局函数打印Person的信息
​
//提前让编译器知道有Person类的存在
template<class T1, class T2>
class Person;
​
//类外实现,先让编译器看到,了解到有这么一个代码
template<class T1, class T2>
void printPerson2(Person<T1,T2> p)
{cout << "类外实现的内容 " << "姓名:" << p.m_Name << " " << "年龄:" << p.m_Age << endl;
}
​
template<class T1, class T2>
class Person
{//全局函数 类内实现friend void printPerson(Person<T1,T2> p){cout << "类内实现的内容 " << "姓名:" << p.m_Name << " " << "年龄:" << p.m_Age << endl;}//全局函数 类外实现//加一个空模板参数列表//如果全局函数 是类外实现 需要让编译器提前知道这个函数的存在。friend void printPerson2<>(Person<T1,T2> p);public:Person(T1 name, T2 age){this->m_Name = name;this->m_Age = age;}private:T1 m_Name;T2 m_Age;
};
​
void test01()
{Person<string,int>p("Tom",20);printPerson(p);
}
​
void test02()
{Person<string,int>p("Jerry",18);printPerson2(p);
}
​
int main()
{test01();test02();return 0;
}

总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别。

类模板案例

案例描述:实现一个通用的数组类,要求如下:

  1. 可以对内置数据类型以及自定义数据类型的数据进行存储

  2. 将数组中的数据存储到堆区

  3. 构造函数中可以传入数组的容量

  4. 提供对应的拷贝构造函数以及operator=防止浅拷贝问题

  5. 提供尾插法和尾删除法对数组中的数据进行增加和删除

  6. 可以通过下标的方式访问数组中的元素

  7. 可以获取数组中当前元素个数和数组的容量

函数实现

MyArray.hpp

#pragma
#include<iostream>
#include<string>
using namespace std;
​
template<class T>
class MyArray
{
public://有参构造 参数 容量MyArray(int capacity){//cout << "MyArray有参构造的调用" << endl;this->m_Capacity = capacity;this->m_Size = 0;this->pAddress = new T[this->m_Capacity];}//拷贝构造MyArray(const MyArray& arr){//cout << "MyArray拷贝构造的调用" << endl;this->m_Capacity = arr.m_Capacity;this->m_Size = arr.m_Size;//this->pAddress = arr.pAddress//深拷贝this->pAddress = new T[arr.m_Capacity];//将arr中的数据都拷贝过来for(int i = 0;i < this->m_Size; i++){this->pAddress[i] = arr.pAddress[i];}}//operator= 防止浅拷贝问题 a = b = cMyArray& operator=(const MyArray& arr){//cout << "MyArray的operator=的调用" << endl;//先判断原来堆区是否有数据,如果有先释放if(this->pAddress != NULL){delete[] this->pAddress;this->pAddress = NULL;this->m_Capacity = 0;this->m_Size = 0;}//深拷贝this->m_Capacity = arr.m_Capacity;this->m_Size = arr.m_Size;this->pAddress = new T[arr.m_Capacity];for(int i = 0;i < this->m_Size; i++){this->pAddress[i] = arr.pAddress[i];}return *this;}//尾插法void Push_Back(const T & val){//判断容量是否等于大小if(this->m_Capacity == this->m_Size){return;}this->pAddress[this->m_Size] = val;//在数组末尾插入数据this->m_Size++;//更新数组大小}//尾删法void Pop_Back(){//让用户访问不到最后一个元素,即为尾删,逻辑删除if(this->m_Size == 0){return;}this->m_Size--;//更新数组大小}//通过下标方式访问数组中的元素 arr[0] = 100 赋值操作T& operator[](int index){return this->pAddress[index];}//返回数组容量int getCapacity(){return this->m_Capacity;}//返回数组大小int getSize(){return this->m_Size;}//析构函数~MyArray(){//cout << "MyArray析构函数的调用" << endl;if(this->pAddress != NULL){delete[] this->pAddress;this->pAddress = NULL;}}
​
private:T * pAddress;//指针指向堆区开辟的真实数组int m_Capacity;//数组容量int m_Size;//数组大小
};

main.cpp

#include<iostream>
#include<string>
using namespace std;
#include"MyArray.hpp"
​
void test01()
{//测试构造函数、析构函数、operator的调用,使用完课注释掉MyArray<int>arr1(5);MyArray<int>arr2(arr1);MyArray<int>arr3(100);arr3 = arr1;
}
​
void printIntArray(MyArray<int>&arr)
{for(int i = 0;i < arr.getSize(); i++){cout << arr[i] << " ";}cout << endl;
}
​
void test02()
{//测试尾插法、尾删法和其余函数调用MyArray<int>arr1(5);for(int i = 0;i < 5; i++){//利用尾插法arr1.Push_Back(i);}cout << "arr1的打印输出为:" << endl;printIntArray(arr1);cout << "arr1的容量为:" << arr1.getCapacity() << endl;cout << "arr1的大小为:" << arr1.getSize() << endl;MyArray<int>arr2(arr1);//拷贝构造函数cout << "arr2的打印输出为:" << endl;printIntArray(arr2);cout << "arr2的容量为:" << arr2.getCapacity() << endl;cout << "arr2的大小为:" << arr2.getSize() << endl;
}
​
//测试自定义数据类型
class Person
{
public:Person();Person(string name, int age){this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age;
};
​
void printPersonArray(MyArray<Person>& arr)
{for(int i = 0;i < arr.getSize(); i++){cout << "姓名:" << arr[i].m_Name << " " << "年龄:" << arr[i].m_Age << endl;}
}
​
void test03()
{MyArray<Person>arr(10);Person p1("zhangsan",18);Person p2("lisi",19);Person p3("wangwu",20);Person p4("zhaoliu",21);Person p5("tangqi",22);//将数据插入到数组中arr.Push_Back(p1);arr.Push_Back(p2);arr.Push_Back(p3);arr.Push_Back(p4);arr.Push_Back(p5);//打印数组printPersonArray(arr);//打印数组容量cout << "数组容量:" << arr.getCapacity() << endl;//打印数组大小cout << "数组大小:" << arr.getSize() << endl;
}
​
int main()
{test01();test02();test03();return 0;
}

        今天我们全面学习的模板和类模板这一块的知识,就到此为止啦。掌握和运用,还需要持续不断的训练和磨合,加深对模板板块的理解,这在未来,对我们的提升自己编码水平和能力,都是非常重要的。通过学长参加招聘会,我了解到今年春招情况惨淡,大部分公司都对软件开发部门进行了比较具有规模性的裁员。应对这样残酷的现实,我们只能不断提高自己,踏实掌握技术才是硬道理!

码字不易,希望大家多多支持!!

 

相关文章:

轻松掌握C++的模板与类模板,将Tamplate广泛运用于我们的编程生活

C提高编程 本阶段主要针对C泛型编程和STL技术做详细讲解&#xff0c;探讨C更深层的使用 泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。 模板 1.模板的概念 模板就是建立通用的模具&#xff0c;大大提高复用性 例如&#xff1a; 2.函数模板 C另一种编程思想称…...

pandas 数据预处理+数据概览 处理技巧整理(持续更新版)

这篇文章主要是整理下使用pandas的一些技巧&#xff0c;因为经常不用它&#xff0c;这些指令忘得真的很快。前段时间在数模美赛中已经栽过跟头了&#xff0c;不希望以后遇到相关问题的时候还去网上查&#xff08;主要是太杂了&#xff09;。可能读者跟我有一样的问题&#xff0…...

mmdetectionV2.x版本 训练自己的VOC数据集

mmdetection目录下创建data文件夹&#xff0c;路劲如图所示&#xff0c;不带yololabels 修改配置文件 mmdet/datasets/voc.py 配置图片格式 mmdet/datasets/xml_style.py 如果图片是jpg则改成jpg&#xff0c;是png格式就改成png&#xff0c;这里我不需要改&#xff0c;本…...

Shell - crontab 定时 git 拉取并执行 maven 打包

目录 一.引言 二.踩坑与实践 1.原始代码 2.mvn package 未执行与解决 [导入环境变量] 3.git pull 未执行与解决 [添加绝对路径] 三.总结 一.引言 git 任务部署在通道机&#xff0c;每天6点需要定时更新 jar 包并打包上线&#xff0c;所以需要在 linux 服务器上&#xff…...

408考研计算机之计算机组成与设计——知识点及其做题经验篇目3:指令的寻址方式

上篇文章我们讲到&#xff0c;指令的基本格式&#xff0c;一条指令通常包括操作码字段和地址码字段两部分&#xff1a; 操作码字段地址码字段并且我们还讲到根据操作数地址码的数目不同&#xff0c;可将指令分为零一二三四地址指令。感兴趣的小伙伴们可以看看小编的上一篇文章…...

前端包管理工具:npm,yarn、cnpm、npx、pnpm

包管理工具npm Node Package Manager&#xff0c;也就是Node包管理器&#xff1b; 但是目前已经不仅仅是Node包管理器了&#xff0c;在前端项目中我们也在使用它来管理依赖的包&#xff1b; 比如vue、vue-router、vuex、express、koa、react、react-dom、axios、babel、webpack…...

推荐系统 FM因式分解

reference&#xff1a;知乎 FM算法解析 LR算法没有二阶交叉 如果是id类特征&#xff0c;这里的x是0/1&#xff0c;raw的特征输入就是float&#xff0c;当然&#xff0c;在我的理解里&#xff0c;一般会把raw的特征进行分桶&#xff0c;还是映射到0/1特征&#xff0c;不然这个w…...

Maven基础入门

文章目录Maven简介Maven 工作模式1.仓库2.坐标Maven的基本使用1.常用命令2.生命周期依赖管理1.依赖配置2.依赖传递3.可选依赖4.排除依赖5.依赖范围IDEA配置MavenMaven简介 Apache Maven 是一个项目管理和构建工具&#xff0c;它基于项目对象模型(POM)的概念&#xff0c;通过一…...

传输层协议 TCP UDP

目录 协议前菜 端口号 ​编辑端口号范围划分 认识知名端口号(Well-Know Port Number) netstat pidof 传输层协议 UDP协议 UDP协议端格式 UDP的特点 面向数据报 UDP的缓冲区 UDP使用注意事项 基于UDP的应用层协议 TCP协议 TCP协议概念 TCP协议段格式 标志…...

一点就分享系列(实践篇6——上篇)【迟到补发】Yolo-High_level系列算法开源项目融入V8 旨在研究和兼容使用【持续更新】

一点就分享系列&#xff08;实践篇5-补更篇&#xff09;[迟到补发]—Yolo系列算法开源项目融入V8旨在研究和兼容使用[持续更新] 题外话 去年我一直复读机式强调High-level在工业界已经饱和的情况&#xff0c;目的是呼吁更多人看准自己&#xff0c;不管是数字孪生交叉领域&#…...

buu RSA 1 (Crypto 第一页)

题目描述&#xff1a; 两个文件&#xff0c;都用记事本打开&#xff0c;记住用记事本打开 pub.key: -----BEGIN PUBLIC KEY----- MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMAzLFxkrkcYL2wch21CM2kQVFpY97 /AvKr1rzQczdAgMBAAE -----END PUBLIC KEY-----flag.enc: A柪YJ^ 柛x秥?y…...

Python 二分查找:bisect库的使用

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…...

性能优化之HBase性能调优

HBase是Hadoop生态系统中的一个组件&#xff0c;是一个分布式、面向列存储的内存型开源数据库&#xff0c;可以支持数百万列&#xff08;MySQL4张表在HBase中对应1个表&#xff0c;4个列&#xff09;、超过10亿行的数据存储。可用作&#xff1a;冷热数据分离HBase适合作为冷数据…...

图像金字塔,原理、实现及应用

什么是图像金字塔 图像金字塔是对图像的一种多尺度表达&#xff0c;将各个尺度的图像按照分辨率从小到大&#xff0c;依次从上到下排列&#xff0c;就会形成类似金字塔的结构&#xff0c;因此称为图像金字塔。 常见的图像金字塔有两类&#xff0c;一种是高斯金字塔&#xff0…...

08-Oracle游标管理(定义,打开、获取数据及关闭游标)

目标 1.确定何时需要显示游标2.声明、打开和关闭显示游标3.从显示游标中提取数据4.了解与游标有关的属性5.使用游标FOR循环检索游标中的数据6.在游标FOR循环的子查询中声明游标7.评估使用逻辑运算符结合在一起的布尔条件游标 1、在使用一个PL/SQL块来执行DML语句或只返回一行结…...

Python判断字符串是否包含特定子串的7种方法

目录1、使用 in 和 not in2、使用 find 方法3、使用 index 方法4、使用 count 方法5、通过魔法方法6、借助 operator7、使用正则匹配转自&#xff1a;https://cloud.tencent.com/developer/article/1699719我们经常会遇这样一个需求&#xff1a;判断字符串中是否包含某个关键词…...

aop实现接口访问频率限制

引言 项目开发中我们有时会用到一些第三方付费的接口&#xff0c;这些接口的每次调用都会产生一些费用&#xff0c;有时会有别有用心之人恶意调用我们的接口&#xff0c;造成经济损失&#xff1b;或者有时需要对一些执行时间比较长的的接口进行频率限制&#xff0c;这里我就简…...

Hive---窗口函数

Hive窗口函数 其他函数: Hive—Hive函数 文章目录Hive窗口函数开窗数据准备建表导入数据聚合函数window子句LAG(col,n,default_val) 往前第 n 行数据LEAD(col,n, default_val) 往后第 n 行数据ROW_NUMBER() 会根据顺序计算RANK() 排序相同时会重复&#xff0c;总数不会变DENSE…...

JavaSe第7次笔记

1. C语言里面&#xff0c;NULL是0地址。Java中null和0地址没关系。 2.数组可以做方法的返回值。 3.可以使用变量作为数组的个数开辟空间。 4.断言assert&#xff0c;需要设置。 5.排序&#xff1a;Arrays. sort(array); 6.查找&#xff1a; int index Arrays. binarySea…...

什么是 Service 以及描述下它的生命周期。Service 有哪些启动方法,有 什么区别,怎样停用 Service?

在 Service 的生命周期中,被回调的方法比 Activity 少一些,只有 onCreate, onStart, onDestroy, onBind 和 onUnbind。 通常有两种方式启动一个 Service,他们对 Service 生命周期的影响是不一样的。 1. 通过 startService Service 会经历 onCreate 到 onStart,然后处于运行…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

Java 二维码

Java 二维码 **技术&#xff1a;**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

android13 app的触摸问题定位分析流程

一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

uniapp 实现腾讯云IM群文件上传下载功能

UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中&#xff0c;群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS&#xff0c;在uniapp中实现&#xff1a; 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...