【C++第三阶段】模板类模板通用数组实现案例

以下内容仅为当前认识,可能有不足之处,欢迎讨论!


文章目录

  • 模板
    • 怎么使用模板
    • 函数模板注意事项
    • 普通函数与函数模板的区别
    • 普通函数与函数模板调用规则
    • 函数模板限制
  • 类模板
    • 类模板语法
    • 类模板与函数模板区别
    • 类模板中成员函数创建时机
    • 类模板对象做函数参数
      • 打印类别
    • 类模板与继承
    • 类模板成员函数类外实现
    • 类模板分文件编写
    • 类模板与友元
    • 类模板案例-通用数组实现


本教程针对泛型编程和STL做技术讲解,探讨更深层的使用。

模板

什么是模板,模板的目的是什么?怎么用模板,优缺点,能用在哪里。

C++提供两种模板机制,函数模板和类模板。

什么是模板?

建立通用的模具,提高复用性。

模板的目的是什么?

提高代码的复用性,同时解决函数重载的问题:不能根据情况指定不同的返回值类型。以及其他函数重载的问题(2024年3月26日20点44分暂时想不到了)

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

怎么使用模板

首先,明确模板的语法如下:

template<typename T>
//后写方法

其中

template 是创建模板的声明,说明下面接着的方法用到了模板。

typename是传入不同类型的参数,这个类型是typename。

黑马:typename-表明其后符号是一种数据类型,可以用class类型。

T是通用数据类型,名称可以替换,通常为大写字母。

接着是模板使用具体案例:

#include<iostream>
#include<string>
using namespace std;

//以下是模板的简单举例
//说明返回值是temp。
template <typename T>
T MySwap(T& param1, T& param2) {
	T temp = param1;
	param1 = param2;
	param2 = temp;
	return temp;
}

void test0326a() {
	int a = 10;
	int b = 20;
	cout << "now , a = " << a << " , b = " << b << " . " << endl;
	//隐式调用模板函数
	MySwap(a, b);
	cout << "After implicitly utilizing swap func , a= " << a << " , b = " << b << " . " << endl;

	//显式调用模板函数
	MySwap<int>(a, b);
	cout << "After explicitly utilizing swap func , a = " << a << " , b = " << b << " . " << endl;
}

int main() {

	test0326a();

	system("pause");
	return 0;
}

可以看到,有两种使用模板方式,隐式及显式。隐式直接传入参数即可,由编译器自行判断参数是何数据类型。显式需要在调用时在函数名与参数列表中用<>说明是何数据类型。

运行结果:

image-20240326231120001

函数模板注意事项

隐式:自动类型推导,必须推导出一致的数据类型T,才可以使用。

显式:模板必须要确定T的数据类型,才可以使用。

注意事项一:必须推导出一致的数据类型T才可以使用。比如下面的将C作为参数传入,识别出两个数据类型不一致。

image-20240326214133982

注意事项二:既然用了函数模板,就需要确定T的数据类型。

像下面的函数,如果隐式地让编译器自动确定数据类型,编译器不知道要确定哪个数据类型。就会报错。所以需要显式地指定数据类型。

image-20240326214501054

image-20240326214454022

所以,解决办法就是显式地指定数据类型。

void test0326c() {
	print<float>();
}

对应代码:

#include<iostream>
#include<string>
using namespace std;

//以下是模板的简单举例
//说明返回值是temp。
template <typename T>
T MySwap(T& param1, T& param2) {
	T temp = param1;
	param1 = param2;
	param2 = temp;
	return temp;
}

template <typename T>
void print() {
	cout << "print 函数运行." << endl;
}

void test0326b() {
	int a = 10;
	int b = 20;
	char c = 'c';
	cout << "原来的a = " << a << " , b = " << b << " . " << endl;
	//MySwap(a, c);
}

void test0326c() {
	print<float>();
}

int main() {

	test0326c();

	system("pause");
	return 0;
}

运行结果:

image-20240326231048933

一个通用模板排序数组的案例代码:

#include<iostream>
#include<string>
using namespace std;

template<typename T>
void MySwap(T &param1 , T &param2){
	T temp = param1;
	param1 = param2;
	param2 = temp;
}

template<typename T>
void print(T& array, int number) {
	//cout << "当前数组为:";
	for (int i = 0; i < number; i++) {
		if (array[i] != '\0') {

			cout << array[i] << "\t";
		}
	}
	cout << endl;
}

template <typename T>
void sort(T& array) {
	int member_number = sizeof(array) / sizeof(array[0]);
	print(array, member_number);
	for (int i = 0; i < member_number; i++) {
		for (int j = i; j < member_number-1; j++) {
			if (array[i] > array[j + 1]) {
				MySwap(array[i], array[j + 1]);
			}
		}
	}
	print(array, member_number);
}



int main() {
	int int_array[] = { 2,9 ,3, 5 , 1 };
	float float_array[] = { 3.1 , 9.1 , 3.6 , 5.2 };
	char char_array[] = "fezcda";
	sort(char_array);
	sort(int_array);
	sort(float_array);
	system("pause");
	return 0;
	}

运行结果:

image-20240326230951333

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

区别:是否会发生隐式类型转换。

A—普通函数传入参数时,会发生隐式类型转换。

B—a函数模板传入参数,如果是隐式地自动类型推导,不会发生隐式类型转换,会发生报错。

B—b函数模板传入参数,如果是显式地指定类型方式,会发生隐式类型转换。

根据以上三个编号,分别设计代码验证。

#include<iostream>
#include<string>
using namespace std;

//设计简单的加法函数与加法重载
int MyAdd(int a, int b) {
	//如果是引用方式就不可以
	return a + b;
}

template<typename T>
T MyTempAdd(T a, T b) {//如果是引用方式就不可以
	return a + b;
}


int main() {
	int int_array[] = { 2,9 ,3, 5 , 1 };
	float float_array[] = { 3.1 , 9.1 , 3.6 , 5.2 };
	char char_array[] = "fezcda";

	int a = 10;
	int b = 13;
	char c = 'c'; //ASCII = 99
	cout <<"myadd a+b = " << MyAdd(a, b) << endl;
	cout << "myadd a+c = " << MyAdd(a, c) << endl;
	cout << "MyTempAdd a+b = " << MyTempAdd(a, b) << endl;
	cout << "MyTempAdd a+c = " << MyTempAdd<int>(a, c) << endl;//必须显式指定数据类型,否则会报错。
	system("pause");
	return 0;
}

image-20240326230719245

运行结果:

image-20240326230823336

问题:这里就不能再传入引用,如果引用,必须是对应的数据类型,无论是函数还是模板。

普通函数与函数模板调用规则

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

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

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

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

1.优先调用普通函数

#include<iostream>
#include<string>
using namespace std;

void print( int a) {
	cout << "调用普通函数" << endl;
}

template<typename T>
void print(T a) {
	cout << "调用函数模板" << endl;
}

void test0326e() {
	int a = 1;
	print(a);
}

int main() {
	test0326e();

	system("pause");
	return 0;
}

通过结果可以看出,优先调用了普通函数。

image-20240327000647436

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

在前面加上空的英文书名号就可以。

更改test0326e()函数中代码为:

void test0326e() {
	int a = 1;
	print<>(a);
}

即可优先调用函数模板

image-20240327000801084

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

参数个数不同,参数类型不同。

#include<iostream>
#include<string>
using namespace std;


template<typename T>
void print(T a) {
	cout << "调用函数模板" << endl;
}

template<typename T>
void print(T a, T b) {
	cout << "调用重载的函数模板" << endl;
}

void test0326e() {
	int a = 1;
	int b = 2;
	print<>(a ,b);
}

int main() {
	test0326e();

	system("pause");
	return 0;
}

可以看到,调用了重载的函数模板。

image-20240327000930124

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

怎么理解?比如下面代码,传入的是字符,在普通函数中,可以隐式转换数据类型到int,但是函数模板可以不用隐式转换直接到char。所以编译器会执行进函数重载中。

#include<iostream>
#include<string>
using namespace std;

void print( int a) {
	cout << "调用普通函数" << endl;
}

template<typename T>
void print(T a) {
	cout << "调用函数模板" << endl;
}


void test0326e() {
	int a = 1;
	int b = 2;
	char c = 'c';
	print<>(c);
}

int main() {
	test0326e();

	system("pause");
	return 0;
}

运行结果:

image-20240327001147736

函数模板限制

普通的数据类型,当然可以运算。

#include<iostream>
#include<string>
using namespace std;

template<typename T>
bool MyCompare(T a, T b) {
	if (a == b) {
		return true;
	}
	else {
		return false;
	}
}

void test0326b() {
	int a = 10;
	int b = 10;
	bool res = MyCompare(a, b);
	if (res) {
		cout << "a == b" << endl;
	}
	else {
		cout << "a != b" << endl;
	}
}

int main() {
	test0326b();


	system("pause");
	return 0;
}

运行结果:

image-20240326235833087

如果是自定义的数据类型,也会出现无法运算的时候。

如果是自定义数据类型,就如果想要走特殊的通道到达对应的函数模板,则需要特殊操作。

#include<iostream>
#include<string>
using namespace std;

class Person {
public:
	Person(int age, string name) {
		this->p_age = age;
		this->p_name = name;
	}

public:
	int p_age;
	string p_name;
};


template<typename T>
bool MyCompare(T a, T b) {
	if (a == b) {
		return true;
	}
	else {
		return false;
	}
}

template<> bool MyCompare(Person p1, Person p2) {
	if (p1.p_age == p2.p_age && p1.p_name == p2.p_name) {
		return true;
	}
	else {
		return false;
	}
}

void test0326b() {
	int a = 10;
	int b = 10;
	bool res = MyCompare(a, b);
	if (res) {
		cout << "a == b" << endl;
	}
	else {
		cout << "a != b" << endl;
	}
}

void test0326d() {
	Person tom(12, "Cooper");
	Person Jack(12, "Cooper");
	bool res = MyCompare(tom, Jack);

	if (res) {
		cout << "a == b" << endl;
	}
	else {
		cout << "a != b" << endl;
	}
}

int main() {
	test0326d();
	//test0326b();


	system("pause");
	return 0;
}

运行结果:

image-20240327000237165

类模板

类模板语法

目的/作用:

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

我理解:望名思义,以为是通用的类,类名也是通用的。但其实是类里面的属性或者方法,可以通用。

语法:

template <typename T1 ,typename T2 , typename ,Tn>
class{}

template 声明创建模板

typename 表明后面的符号是一种数据类型,具体是什么数据类型可以自己指定。

T 通用的数据类型,名称可以替换,通常为大写字母。

案例代码:

#include<iostream>
#include<string>
using namespace std;

template<typename name_type , typename age_type>
class Person {
public:
	Person(name_type name, age_type age) :p_name(name), p_age(age) {}

public:
	name_type p_name;
	age_type p_age;

public:
	void print() {
		cout << "name = " << p_name << " , age = " << p_age << " . " << endl;
	}
};

void test0327a() {
	Person <string, int>p("Json", 23);
	p.print();
}

int main() {
	//test0326e();
	test0327a();

	system("pause");
	return 0;
}

运行结果:

image-20240327194736825

类模板与函数模板区别

区别①类模板不能自动推导类型。

函数模板可以通过传入参数,由编译器判断谁是什么数据类型,而类模板没有这个功能。

区别②类模板可以对类型赋默认值。

如果类中成员属性≥1,可以对成员属性的类型赋默认值。

以下是两点具体代码体现。

void test0327b() {
	Person p("docker", 99);
	p.print();
}

区别①会报错:

image-20240327200148738

区别②:

#include<iostream>
#include<string>
using namespace std;

template<typename name_type , typename age_type = int>//体现区别2,可以对类型赋默认值。
class Person {
public:
	Person(name_type name, age_type age) :p_name(name), p_age(age) {}

public:
	name_type p_name;
	age_type p_age;

public:
	void print() {
		cout << "name = " << p_name << " , age = " << p_age << " . " << endl;
	}
};
}

void test0327b() {
	//Person p("docker", 99);
	//p.print();
}

void test0327c() {
	Person <string>p("Coco", 33);//体现2,可以对类型赋默认值,使得string数据类型是参数1,默认数据类型是参数2
	p.print();
}

int main() {
	test0327c();

	system("pause");
	return 0;
}

运行结果:

image-20240327200302791

类模板中成员函数创建时机

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

  • 普通类成员函数一开始创建。
  • 类模板成员函数调用时创建。

问题:什么叫调用时才创建?

因为具体的类有明确的数据类型,而类模板没有指定的数据类型,所以一开始时不会创建,在实际调用时才会创建。

代码示例:

#include<iostream>
#include<string>
using namespace std;

class Per {
public:
	void print() {
		cout << "Per类调用print函数" << endl;
	}
};

class Son {
public:
	void show() {
		cout << "Son类调用show函数" << endl;
	}
};

template<class T>
class Person {
public:
	T obj;//这一点在自己编写时忘记了
public:
	void p_print(){
		obj.print();
	}
	void p_show() {
		obj.show();
	}
};

void test0327d() {
	Person <Per> per;
	per.p_print();
	//per.p_show();
	Person <Son> son;
	//son.p_print();
	son.p_show();
}
int main() {
	test0327d();

	system("pause");
	return 0;
}

如果不注释per.p_print();son.p_print();,就会报错:image-20240327202523442

注释掉之后,能有运行结果:

image-20240327202555263

类模板对象做函数参数

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

什么意思?就是将对象作为参数传入函数中。但此时的对象是类模板对象,所以又有模板数据类型不确定的问题在里面。就会有对应三种类模板对象做函数参数的方法。

三种传入方式:

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

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

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

第一种方法,指定传入类型,就是在将类模板对象作为参数传入普通函数时,直截了当的在函数定义时就说明类模板对象的模板数据类型是什么。

第二种,参数模板化——将对象中的参数作为模板传递。

此时函数不再是普通函数,而是模板函数。类模板对象中的模板数据类型需要与模板函数中的模板数据类型保持一致。

第三种,整个类模板化——将这个对象类型作为模板进行传递。

示例代码:

#include<iostream>
#include<string>
using namespace std;

template<class T1 , class T2>
class Person {
public:
	Person(T1 name, T2 age):p_name(name),p_age(age) {}

public:
	T1 p_name;
	T2 p_age;

public:
	void print() {
		cout << "name = " << p_name << " , age = " << p_age << " . " << endl;
	}
};

//方式1:指定类模板对象模板数据类型,把类模板对象作为参数传入函数中。
void CommonPrintPerson(Person<string , int> &p) {
	//现在这是个普通函数
	p.print();
}

//方式2:参数模板化,使用模板函数,把类模板对象中的成员属性类型作为模板数据类型传入。
template<typename T1 , typename T2>
void TemplatePrintPerson(Person <T1, T2>& p) {
	p.print();
}

//方式3:对象模板化,将类模板对象也作为模板数据类型传入函数中
template <class T3>
void TemplateClassPrintPerson(T3 p) {
	p.print();
}


void test0327e() {
	Person<string, int>p("Jack", 22);
	Person<string, int>e("Eoa", 32);
	Person<string, int>r("Ross", 32);
	//普通函数调用时,指定模板数据类型将类模板对象作为参数传入函数中。
	CommonPrintPerson(p);
	TemplatePrintPerson<string, int>(e);
	TemplateClassPrintPerson(r);
}

int main() {

	test0327e();

	system("pause");
	return 0;
}


运行结果:

image-20240327210559275

打印类别

补充的一点是可以通过typeid(要查看的数据类型).name()获得是什么类别。

比如

类模板与继承

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

如果子类继承的父类是一个类模板时,子类在声明时,需要指定父类中T的类型。如果不指定,编译器无法给子类分配内存。

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

为什么不指定父类T中的类型,编译器无法给子类分配内存呢?因为不确定父类有什么数据类型。

所以,对于第一点显式指定父类数据类型,指定的模板数据类型需要写在父类的右边。

image-20240327213010633

如果想要灵活指定父类中的模板数据类型,子类也需要变为类模板。

代码示例如下:

#include<iostream>
#include<string>
using namespace std;

template <class T>
class Base {
public:
	T member;
};

//显式指定父类模板数据类型
class Under :public Base<int> {
public:
	Under() {
		cout << "父类继承子类,显式指定类模板数据类型" << endl;
		cout << "类模板数据类型为:" << typeid(member).name() << "."<<endl;
	}

};

//灵活指定父类模板,需要子类也是类模板
template <class T1 , class T2>
class Bottom :public Base<T2> {
public:
	Bottom() {
		cout << "灵活指定父类模板,需要子类也是类模板" << endl;
		//cout << "父类类模板数据类型为:" << typeid(member).name() << "." << endl;
		//需要注释掉才能运行,说明子类先构造,父类才构造。而显式指定的已经指定了。
		cout << "子类类模板数据类型为:" << typeid(B_member).name() << "." << endl;
	}
public:
	T2 B_member;
};

void test0327f() {
	Under u;
	Bottom<int, char>bo;
}

int main() {
	test0327f();
	system("pause");
	return 0;
}

运行结果:

image-20240327213707604

类模板成员函数类外实现

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

成员函数类外实现,需要加上类模板的说明,同时加上类模板参数。

代码说明:

#include<iostream>
#include<string>
using namespace std;

template<class T1 , class T2 >
class Person {
public:
	Person(T1 name , T2 age);
public:
	T1 p_name;
	T2 p_age;
public:
	void show();
};

template<class T1 , class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->p_name = name;
	this->p_age = age;
}

template<class T1 , class T2>
void Person<T1 , T2>::show() {
	cout << "name = " << this->p_name << " , age = " << this->p_age << " . " << endl;
}

void test0327g() {
	Person <string, int>p("Ross", 33);
	p.show();
}

int main() {
	test0327g();


	system("pause");
	return 0;
}

运行结果:

image-20240327221140056

类模板分文件编写

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

解决:

①直接包含cpp源文件。

②将声明和实现写入同一个文件中,并改后缀名为.hpp,约定名称,不强制。

类模板中的成员函数创建时机是在调用阶段,调用才会创建,一开始不创建。所以要么包含源码—cpp文件,要么写入同一个文件中,后缀名是’hpp’。

防止头文件重复包含

类模板与友元

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

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

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

我的理解:加了友元标识符之后,类内的函数就变成了全局函数。所以可以在类外直接调用。

弹幕:printPerson函数不加friend时是一个私有的成员函数,但加了friend之后就变成了一个全局函数,因为自身的成员函数默认就可以调用自身的成员属性不需要友元,反之需要友元的都不是成员函数。

全局函数类内实现就一步到位。

代码示例:

#include<iostream>
#include<string>
using namespace std;

template<class T1 , class T2>
class Person {
	//类内全局函数
	friend void printPerson(Person<T1,T2> p) {
		cout << "全局函数类内实现" << endl;
		cout << "name = " << p.p_name << " , age = " << p.p_age << " . " << endl;
	}
public:
	Person(T1 name, T2 age) :p_name(name), p_age(age) {}
private:
	T1 p_name;
	T2 p_age;
};

void test0327h() {
	Person <string, int> p("张三", 34);
	printPerson(p);
}

int main() {
	test0327h();


	system("pause");
	return 0;
}

运行结果:

image-20240327231909161

全局函数类外实现,由于模板函数的特殊,若将类外模板函数的实现写于声明后,编译器不认识。并且,类内的函数声明是普通的函数声明,需要将其转换成模板函数的声明,

image-20240327232647811

如图,是全局函数类外实现写在了声明后,编译器不认识。

如果写在声明前,由于传入了Person类,编译器也不认识Person类,还需要把Person类的声明也写在前面。

image-20240327232355135

接下来,还是不行,因为这样相当于全局函数是普通函数,需要加上空模板参数列表让编译器认为它是函数模板。

最后,终于成功生成。

image-20240327232714203

完整代码:

#include<iostream>
#include<string>
using namespace std;

template<class T1, class T2>
class Person;

template<class T1, class T2>
void showPerson(Person<T1, T2> p) {
	cout << "全局函数类外实现" << endl;
	cout << "name = " << p.p_name << " , age = " << p.p_age << " . " << endl;
}

template<class T1 , class T2>
class Person {
	//类内全局函数
	friend void printPerson(Person<T1,T2> p) {
		cout << "全局函数类内实现" << endl;
		cout << "name = " << p.p_name << " , age = " << p.p_age << " . " << endl;
	}

	friend void showPerson<>(Person <T1, T2> p);
public:
	Person(T1 name, T2 age) :p_name(name), p_age(age) {}
private:
	T1 p_name;
	T2 p_age;
};


void test0327h() {
	Person <string, int> p("张三", 34);
	//printPerson(p);
	showPerson(p);
}

int main() {
	test0327h();


	system("pause");
	return 0;
}

运行结果:

image-20240327232732006

看弹幕:友元不是类的成员,也不接受它所在区域访问控制级别的约束。

所以,一般都是全局函数类内实现。当然,也可以直接在类里面写上函数模板。

template<class T1 , class T2>
friend void showPerson(Person <T1, T2> p);

一样可以运行。

类模板案例-通用数组实现

制作一个通用的数组类,可以满足如下要求:

  1. 对内置数据类型以及自定义数据类型的数据进行存储。
  2. 将数组中的数据存储到堆区。
  3. 构造函数中可以传入数组的容量。
  4. 提供对应的拷贝构造函数以及operator=防止浅拷贝问题。
  5. 提供尾插法和尾删法对数组中的数据进行增加和删除。
  6. 可以通过下标的方式访问数组中的元素。
  7. 可以获取当前数组中的元素个数和数组的容量。

自己的分析:

①,可以用类模板存储元素。

②,在每个数据存储时,也就是构造函数,需要new一个数据到堆区。同时析构函数需要释放内存。

③,怎么理解?——传入数组容量,才能确定开辟多少内存空间。

④,这个拷贝构造函数,说的是类的拷贝。重写=,就是在类中重写=号运算符。

⑤,尾插法,尾删法。提供两种方法,参数是什么?对于插入,参数就是新的数据元素;对于删除,可以没有参数。内存空间的扩充怎么做?参考职工管理系统。内存空间的删除,就是直接将最后一个元素内存地址释放,并将数组个数-1。

⑥传入对应的数字,获取其东西。

CommonArray.hpp

#include<iostream>
#include<string>
using namespace std;

当前元素个数和数组容量。
*/
template <class T>
class CommonArray;




template<class T>
class CommonArray {
public:
	CommonArray(int num);
	CommonArray(CommonArray& ca);
	~CommonArray();
public:
	
	//这个代表什么意思?为什么要是创建一个T类型的东西?这个东西再指向一个数组?
	T* array;
private:
	int ca_num;
	int ca_size;
public:
	void tail_insert(const T &element);
	void tail_delete();
	int get_array_size();
	int get_element_number();
	int get_common_array_cost();
	CommonArray& operator=(const CommonArray& ca);
	T& operator[](int index);
};

template<class T>
CommonArray<T>::CommonArray(int size) {

	//cout << "有参构造函数调用" << endl;
	//容量:数组内一共可以放ca_num个元素。
	this->ca_size = size;
	//新建一个T类型的数组指针,并把地址设为null
	//this->array = NULL;
	//开辟这么大一个空间的数组。
	this->array = new T[this->ca_size];
	this->ca_num = 0;
};

template<class T>
CommonArray<T>::CommonArray(CommonArray& ca) {

	//cout << "拷贝构造函数调用" << endl;
	this->ca_size = ca.ca_size;
	this->ca_num = ca.ca_num;
	this->array = new T[ca.ca_size];
	for (int i = 0; i < ca.ca_size; i++) {
		this->array[i] = ca.array[i];
	}
}

//拷贝构造函数和重写=运算符防止编译器提供的两个函数造成浅拷贝现象。
//如果浅拷贝,删除时会导致堆区数据重复释放。

template<class T>//返回值左值存在,应该要返回引用。
CommonArray<T>& CommonArray<T>::operator=(const CommonArray<T>& ca) {

	//cout << "operator=函数调用" << endl;
	this->ca_size = ca.ca_size;
	this->ca_num = ca.ca_num;
	this->array = new T[ca.ca_size];
	for (int i = 0; i < ca.ca_size; i++) {
		this->array[i] = ca.array[i];
	}
	return *this;
}

template<class T>
CommonArray<T>::~CommonArray() {
	if (this->array) {

		delete[] this->array;
		this->array = 0;
		this->ca_size = 0;
		this->ca_num = 0;
	}
}

//为了防止T被修改,一般用引用的方式传入,使用const
template<class T>
void CommonArray<T>::tail_insert(const T &element) {
	if (this->ca_num != this->ca_size) {
		this->array[this->ca_num] = element;
		this->ca_num += 1;
	}
	else {
		cout << "数组已满,无法新增。" << endl;
		system("pause");
		return;
	}
}

template<class T>
void CommonArray<T>::tail_delete() {
	this->ca_num--;
}

template<class T>
int CommonArray<T>::get_array_size() {
	return this->ca_size;
}


template<class T>
int CommonArray<T>::get_element_number() {
	return this->ca_num;
}

template<class T>
int CommonArray<T>::get_common_array_cost() {
	int cost;
	cost = sizeof(T) * this->ca_size;
	return cost;
}

template<class T>
//返回值是T是因为要作为返回值,又因为想让它作为左值存在,比如int i = 0 ,就需要让他放回一个引用。。
T& CommonArray<T>::operator[](int index) {
	return this->array[index];
}

测试文件.cpp

#include<iostream>
#include<string>
using namespace std;

#include"CommonArray.hpp"

class Person {
public:
	Person() {}
	Person(string name, int age) :p_name(name),p_age(age) {}
public:
	string p_name;
	int p_age;
};

void Print_Array(CommonArray<int>& common_array) {
	for (int i =0 ; i< common_array.get_element_number(); i++){
		cout << common_array.array[i] << " ";
	}
}

void Print_Person_Array(CommonArray<Person>& common_array_persons) {
	for (int i = 0; i < common_array_persons.get_element_number(); i++) {
		cout << "数组成员" << i + 1 << ": 姓名:" << common_array_persons.array[i].p_name<<"\t,";
		cout << "\t年龄:" << common_array_persons.array[i].p_age << " \t. " << endl;
	}

}

void test0328() {
	CommonArray<int> ca(5);
	for (int i = 0; i < ca.get_array_size(); i++) {
		ca.tail_insert(i);
	}
	cout << "ca原先为:" ;
	Print_Array(ca);

	ca.tail_delete();
	cout << "现在的ca容量大小为:" << ca.get_array_size() << endl;
	cout << "现在的ca 元素个数为:" << ca.get_element_number() << endl;
	cout << "ca现在为:";
	Print_Array(ca);
	cout << endl;
	CommonArray<Person> persons(5);
	Person p1("孙悟空", 999);
	Person p2("韩信", 19);
	Person p3("爪云", 39);
	Person p4("张飞", 29);
	Person p5("安其拉", 18);

	persons.tail_insert(p1);
	persons.tail_insert(p2);
	persons.tail_insert(p3);
	persons.tail_insert(p4);
	persons.tail_insert(p5);
	
	Print_Person_Array(persons);

}

int main() {
	test0328();


	system("pause");
	return 0;
}

image-20240402203442273


以上是我的学习笔记,希望对你有所帮助!
如有不当之处欢迎指出!谢谢!

学吧,学无止境,太深了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

文心一言指令词宝典之旅行篇

作者&#xff1a;哈哥撩编程&#xff08;视频号、抖音、公众号同名&#xff09; 新星计划全栈领域优秀创作者博客专家全国博客之星第四名超级个体COC上海社区主理人特约讲师谷歌亚马逊演讲嘉宾科技博主极星会首批签约作者 &#x1f3c6; 推荐专栏&#xff1a; &#x1f3c5;…

创业者的三大法宝:自我进化、自我激励与诚信坚守

一、摘要&#xff1a; 在创业的道路上&#xff0c;每一位创业者都如同航海家&#xff0c;驾驶着自己的船只&#xff0c;在波涛汹涌的大海中探寻成功的彼岸。而在这条充满未知与挑战的旅程中&#xff0c;创业者们需要具备哪些关键的品质和能力呢&#xff1f;京东集团创始人刘强…

Codigger Desktop:开发者的利器,每个人的好帮手(二)

昨日&#xff0c;我们为大家揭开了Codigger Desktop开发者利器的三种特性&#xff0c;展现了其独特的亮点。今日&#xff0c;我们将继续为大家呈现另外三项引人注目的特性&#xff0c;以展现这款工具的全面实力。 一、AI辅助&#xff1a;智能识别Module&#xff0c;环境配置一步…

小米造车为什么能够成功?

#小米汽车 #小米su7交付 引言 小米官方公告&#xff0c;今天(4月3日)小米SU7将正式交付&#xff0c;预示着我们将在道路上见到越来越多的小米汽车。 3月28日&#xff0c;小米汽车在官方微博发文宣布&#xff0c;小米SU7开启大定4分钟订单突破1万台&#xff0c;7分钟订单突破2万…

内存管理是如何影响系统的性能的

大家好&#xff0c;今天给大家介绍内存管理是如何影响系统的性能的&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 内存管理对系统性能的影响至关重要&#xff0c;主要体现在以下…

ADG优化之多实例redo应用

最近接到客户电话&#xff0c;咨询如何对ADG备库进行优化的问题&#xff0c;这个客户也是我们的老客户了&#xff0c;数据量庞大&#xff08;100TB以上&#xff09;&#xff0c;数据库版本为19C RAC &#xff0c;客户现在遇到备库性能瓶颈&#xff0c;经常发生数据延迟同步的情…

VLAN基础讲解+不同VLAN间通信(实验)

第一章 VLAN基础 1.1 什么是VLAN 随着网络中计算机的数量越来越多&#xff0c;传统的以太网络开始面临广播泛滥以及安全性无法保证等各种问题。 VLAN即虚拟局域网&#xff0c;是将一个物理的局域网在逻辑上划分成多个广播域的技术。通过在交换机上配置VLAN&a…

汉诺塔问题的递归算法解析

文章目录 汉诺塔问题的递归算法解析问题描述递归算法思路代码实现算法复杂度分析总结 汉诺塔问题的递归算法解析 问题描述 汉诺塔问题是一个经典的递归算法问题。问题描述如下&#xff1a; 在经典汉诺塔问题中&#xff0c;有 3 根柱子及 N 个不同大小的穿孔圆盘&#xff0c;盘…

jupyter notebook 配置默认文件路径

Jupyter是一种基于Web的交互式计算环境&#xff0c;支持多种编程语言&#xff0c;如Python、R、Julia等。使用Jupyter可以在浏览器中编写和运行代码&#xff0c;同时还可以添加Markdown文本、数学公式、图片等多种元素&#xff0c;非常适合于数据分析、机器学习等领域。 安装 …

【业务风控安全】如何做好业务反欺诈避免黄牛抢走你的保时米?

全文共计2500字&#xff0c;阅读时间大概30分钟。 【前言】 小米SU7&#xff0c;最近频频霸榜微博热搜榜前三的热词。纵观任意一家火爆产品的生产商&#xff0c;都必须时刻将业务反欺诈放在首位&#xff0c;简单的说就是把黄牛干死或者被黄牛干死。 雷老板3月31日发博说小米已…

用友U8 Cloud ExportUfoFormatAction SQL注入漏洞复现(XVE-2024-4626)

0x01 产品简介 用友U8 Cloud是用友推出的新一代云ERP,主要聚焦成长型、创新型企业,提供企业级云ERP整体解决方案。 0x02 漏洞概述 用友U8 Cloud ExportUfoFormatAction接口处存在SQL注入漏洞,未授权的攻击者可通过此漏洞获取数据库权限,从而盗取用户数据,造成用户信息泄…

SVD图像处理(MATLAB)

使用SVD处理图像模拟演示 参考文献 https://github.com/matzewolf/Image_compression_SVD/blob/master/svd_compress.m MATLAB代码 clc; clearvars; close all;A_orgimread("lena256.bmp"); compr20; A_orgdouble(A_org);A_red svd_compress( A_org, compr ); s…

C语言终篇--基于epoll ET模式 的 非阻塞IO服务器模型

使用技术: 1 epoll事件驱动机制&#xff1a;使用epoll作为IO多路复用的技术&#xff0c;以高效地管理多个socket上的事件。 2 边缘触发&#xff08;Edge Triggered, ET&#xff09;模式&#xff1a;epoll事件以边缘触发模式运行&#xff0c;这要求代码必须负责消费所有可用的…

LM4890S 音频功率放大器 2.2-5.5V 防倒充电路

LM4890S是一款音频功率放大器&#xff0c;适用于蓝牙耳机和其他便携式设备。它的作业电压为2.2V至5.5V&#xff0c;输出功率为1W x 1 8Ω。该产品具有恒定电流/恒定电压线性操控&#xff0c;热反应可对充电电流进行自动调理&#xff0c;以限制芯片温度。此外&#xff0c;LM489…

【C++入门】初识C++

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

5.动态规划

1.背包问题 (1)0/1背包问题 01背包问题即每个物品只能选1个 考虑第i件物品&#xff0c;当j<w[i]时&#xff0c;f[i][j]f[i-1][j]&#xff0c;当j>w[i]时&#xff0c;此时有两种选择&#xff0c;选择第i件物品和不选第i件物品。此时f[i][j]max(f[i-1][j],f[i-1][j-w[i]]v…

硬件工程师职责与核心技能有哪些?

作为一个优秀的硬件工程师&#xff0c;必须要具备优秀的职业技能。那么&#xff0c;有些刚入行的工程师及在校的学生经常会问到&#xff1a;硬件工程师需要哪些核心技能&#xff1f;要回答这个问题&#xff0c;首先要明白硬件工程师的职责&#xff0c;然后才能知道核心技能要求…

c语言游戏实战(7):扫雷(下)

前言&#xff1a; 扫雷是一款经典的单人益智游戏&#xff0c;它的目标是在一个方格矩阵中找出所有的地雷&#xff0c;而不触碰到任何一颗地雷。在计算机编程领域&#xff0c;扫雷也是一个非常受欢迎的项目&#xff0c;因为它涉及到许多重要的编程概念&#xff0c;如数组、循环…

E-魔法猫咪(遇到过的题,做个笔记)

题解&#xff1a; 来自学长们思路&#xff1a; 其中一种正解是写单调队列。限制队列内的数单调递增&#xff0c;方法为每当新来的数据比当前队尾数据小时队 尾出列&#xff0c;直到能够插入当前值&#xff0c;这保证了队头永远是最小值。因此总体思路是队尾不断插入新值的同时 …

C++函数匹配机制

函数匹配 在大多数情况下&#xff0c;我们容易确定某次调用应该选用哪个重载函数。 然而&#xff0c;当几个重载函数的形参数量相等以及某些形参的类型可以由其他类型转换得来时&#xff0c;这项工作就不那么容易了。 以下面这组函数及其调用为例&#xff1a; void f(); vo…