[黑马程序员课程记录]C++提高部分2

本文详细介绍了类模板的原理、语法,包括类模板与函数模板的区别,成员函数创建时机,对象传参方式,继承问题,友元函数实现,以及一个通用数组类的实战案例。深入理解并掌握了如何创建和使用类模板以实现灵活的数据类型支持和高效编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.3 类模板

1.3.1 类模板语法

类模板作用:

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

语法:

template<typename T>

解释:

template — 声明创建模板

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

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

示例:

#include <string>
//类模板
template<class NameType, class AgeType> 
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

void test01()
{
	// 指定NameType 为string类型,AgeType 为 int类型
	Person<string, int>P1("孙悟空", 999);
	P1.showPerson();
}

int main() {

	test01();

	system("pause");

	return 0;
}

总结:类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板

1.3.2 类模板与函数模板区别

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

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数

示例:

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

//类模板与函数模板区别

template <class nametype,class agetype=int>  //默认参数
class person
{
public:

	person(nametype name, agetype age)
	{
		this->name = name;
		this->age = age;
	}

	void showperson()
	{
		cout << "name: " << name << "  age:  " << age << endl;
	}

	nametype name;
	agetype age;
};

//1.类模板没有自动类型推导的使用方式
void test1()
{
	//person p("swk", 100);//错误 类模板使用时候,不可以用自动类型推导

	person<string, int>p1("孙悟空", 100); //必须使用显示指定类型的方式,使用类模板
	p1.showperson();

}

//2、类模板在模板参数列表中可以有默认参数
void test2()
{
	person<string>p1("猪八戒", 100); 类模板中的模板参数列表 可以指定默认参数
	p1.showperson();
}


int main()
{
	test1();
	test2();

	system("pause");
	return 0;
}

总结:

  • 类模板使用只能用显示指定类型方式
  • 类模板中的模板参数列表可以有默认参数
1.3.3 类模板中成员函数创建时机

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

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建

示例:

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

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

class person1
{
public:
	void showperson1()
	{
		cout << "showperson1" << endl;
	}
	
};

class person2
{
public:
	void showperson2()
	{
		cout << "showperson2" << endl;
	}

};

template <class T>
class myclass
{
public:
	T obj;

	//类模板的成员函数
	void func1()
	{
		obj.showperson1();类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成
	}

	void func2()
	{
		obj.showperson2();
	}
};

void test1()
{
	myclass<person1>p;
	p.func1();
	//p.func2();编译会出错,说明函数调用才会去创建成员函数

}


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

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

1.3.4 类模板对象做函数参数

学习目标:

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

一共有三种传入方式:

  1. 指定传入的类型 — 直接显示对象的数据类型
  2. 参数模板化 — 将对象中的参数变为模板进行传递
  3. 整个类模板化 — 将这个对象类型 模板化进行传递

示例:

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

//类模板对象做函数参数

template<class T1,class T2>
class person
{
public:

	person(T1 name, T2 age)
	{
		this->name = name;
		this->age = age;
	}

	void showperson()
	{
		cout << "name: " << name << " age: " << age << endl;
	}

private:
	T1 name;
	T2 age;
};

//1.指定传入的类型
void print1(person<string, int>&p)
{
	p.showperson();
}


void test1()
{
	person<string, int>p1("孙悟空", 100);
	print1(p1);
}


//2.参数模板化
template<class T1, class T2>
void print2(person<T1, T2>& p)
{
	p.showperson();

	cout << "T1的类型为: " << typeid(T1).name() << endl;//可以看t1,t2的数据类型
	cout << "T2的类型为: " << typeid(T2).name() << endl;

}

void test2()
{
	person<string, int>p1("猪八戒", 100);
	print2(p1);

}


//3.整个类模板化、
template<class T>
void print3(T& p)
{
	p.showperson();
	cout << "T的数据类型" << typeid(T).name() << endl;

}

void test3()
{
	person<string, int>p1("沙悟净", 100);
	print3(p1);
}

int main()
{
	test1();
	test2();
	test3();

	system("pause");
	return 0;
}

总结:

  • 通过类模板创建的对象,可以有三种方式向函数中进行传参
  • 使用比较广泛是第一种:指定传入的类型
1.3.5 类模板与继承

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

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需变为类模板

示例:

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

//类模板与继承

template<class T>
class base
{
public:
	T m;
};

//class son :public base//错误,必须要知道父类中T类型,才能继承给子类
class son:public base<int>
{

};

void test1()
{
	son s1;
}

//如果想灵活指定父类中T类型,子类也需要变为模板
template<class T1,class T2>
class son2 :public base<T2>
{
public:
	son2()
	{
		cout << "T1类型" << typeid(T1).name() << endl;
		cout << "T2类型" << typeid(T2).name() << endl;

	}
	T1 obj;
};

void test2()
{
	son2<int, char>s2;
}

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

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

1.3.6 类模板成员函数类外实现

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

示例:

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

//类模板成员函数类外实现

template<class T1, class T2>
class person
{
public:

	person(T1 name, T2 age);
	/*{
		this->name = name;
		this->age = age;
	}*/

	void showperson();
	/*{
		cout << "name: " << name << " age: " << age << endl;
	}*/

	T1 name;
	T2 age;
};

//构造函数类外实现
template<class T1, class T2>
person<T1,T2>::person(T1 name, T2 age)
{
	
	this->name = name;
	this->age = age;
	
}

//成员函数的类外实现
template<class T1, class T2>
void person<T1, T2>::showperson()
{
	
	cout << "name: " << name << " age: " << age << endl;

}

void test1()
{
	person<string, int>p("zzz", 10);
	p.showperson();
}



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

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

1.3.7 类模板分文件编写

学习目标:

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

问题:

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

解决:

  • 解决方式1:直接包含.cpp源文件
  • 解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

示例:

person.hpp中代码:

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

//类模板分文件编写问题以及解决

template<class T1, class T2>
class person
{
public:

	person(T1 name, T2 age);

	void showperson();

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

template<class T1, class T2>
void person<T1, T2>::showperson()
{
	cout << "name: " << name << " age: " << age << endl;
}

类模板分文件编写.cpp中代码

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

//第一种解决方法,直接包含源文件
//#include"person.cpp"

//第二种解决方式,将.h和.cpp中内容写到一起,将后缀名改为.hpp文件
#include"person.hpp"


//类模板分文件编写问题以及解决

//template<class T1, class T2>
//class person
//{
//public:
//
//	person(T1 name, T2 age);
//	
//	void showperson();
//	
//	T1 name;
//	T2 age;
//};

//template<class T1, class T2>
//person<T1,T2>::person(T1 name, T2 age)
//{
//	this->name = name;
//	this->age = age;
//}
//
//template<class T1, class T2>
//void person<T1,T2>::showperson()
//{
//	cout << "name: " << name << " age: " << age << endl;
//}

void test1()
{
	person<string, int>p("zzz", 10);
	p.showperson();
}


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

总结:主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp

1.3.8 类模板与友元

学习目标:

  • 掌握类模板配合友元函数的类内和类外实现

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

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

示例:

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

template <class T1, class T2>
class person;

//如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到
//template<class T1, class T2> void printperson2(person<T1, T2> & p); 


//2.全局函数类外实现
template <class T1, class T2>
void printperson2(person<T1, T2>&p)
{
	cout << "外姓名: " << p.name << "  外年龄:  " << p.age << endl;
}



template <class T1,class T2>
class person
{
	//全局函数 类内实现
	friend void printperson(person<T1,T2>&p)
	{
		cout << "姓名: " << p.name << "  年龄:  " << p.age << endl;
	}

	//全局函数类外实现
	//加空模板的参数列表
	//如果全局函数 是类外实现 需要让编译器提前知道这个函数的存在
	friend void printperson2<>(person<T1, T2>&p);
	

public:
	person(T1 name,T2 age)
	{
		this->name = name;
		this->age = age;
	}


private:
	T1 name;
	T2 age;
};

//1.全局函数在类内实现
void test1()
{
	person<string, int>p("tom", 19);
	printperson(p);
}



//类外实现,需要将类外函数代码提前放到最上面
void test2()
{
	person<string, int>p2("joy", 99);
	printperson2(p2);
}


int main()
{
	test1();
	test2();

	system("pause");
	return 0;
}

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

1.3.9 类模板案例

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

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

在这里插入图片描述

示例:

myarry.hpp

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


template <class T>
class myarry
{
public:
	myarry(int capacity)
	{ 
		this->m_capacity = capacity;
		this->m_size = 0;
		this->m_arr = new T[this->m_capacity];
	}

	myarry(const myarry &arr)
	{
		this->m_capacity = arr.m_capacity;
		this->m_size = arr.m_size;
		//this->m_arr = arr.m_arr;//浅拷贝
		
		this->m_arr = new T[arr.m_capacity];//深拷贝
		
		//将数据拷贝过来
		for (int i = 0; i < this->m_size; i++)
		{
			this->m_arr[i] = arr.m_arr[i];
		}
	}

	myarry &operator=(const myarry &arr)
	{
		//判断是否堆区有数据
		if (this->m_capacity != NULL)
		{
			delete[]this->m_arr;
			this->m_arr = NULL;
		}

		//深拷贝
		this->m_capacity = arr.m_capacity;
		this->m_size = arr.m_size;
		//this->m_arr = arr.m_arr;//浅拷贝 

		this->m_arr = new T[arr.m_capacity];//深拷贝

		//将数据拷贝过来
		for (int i = 0; i < this->m_size; i++)
		{
			this->m_arr[i] = arr.m_arr[i];
		}

		return *this;
	}

	//尾插
	void weicha(T& a)
	{
		if (m_capacity == m_size)
		{
			return;
		}

		m_arr[this->m_size] = a;
		m_size++;
	}

	//尾删法
	void weishan()
	{
		if (m_size == 0)
		{
			return;
		}

		m_size--;
	}

	//显示容量
	int getcapacity()
	{
		return m_capacity;
	}

	//显示大小
	int getsize()
	{
		return m_size;
	}

	//重载[]
	T &operator[](int a)
	{
		return this->m_arr[a];
	}

	~myarry()
	{
		if (this->m_capacity != NULL)
		{
			delete[]this->m_arr;
			this->m_arr = NULL;
		}
	}

private:
	T *m_arr;//数组 指针指向开辟真实数组
	int m_capacity;//容量
	int m_size;//大小
};


项目.cpp

#include<iostream>
using namespace std;
#include"myarry.hpp"
#include<string>


void test1()
{
	myarry<int>m1(10);
	for (int i = 0; i < 10; i++)
	{
		m1.weicha(i);
	}
	cout << "普通数据尾删前" << endl;
	for (int j = 0; j < m1.getsize(); j++)
	{
		cout << m1[j] << endl;
	}
	cout << "普通数据尾删前的容量:" << m1.getcapacity() << endl;
	cout << "普通数据尾删前的大小:" << m1.getsize() << endl;

	m1.weishan();
	cout << "普通数据尾删后的容量:" << m1.getcapacity() << endl;
	cout << "普通数据尾删后的大小:" << m1.getsize() << endl;
}

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

	string name;
	int age;
};

void test2()
{
	myarry<person>m2(10);

	person p1("张三", 10);
	person p2("李四", 11);
	person p3("王五", 12);
	person p4("赵六", 13);

	m2.weicha(p1);
	m2.weicha(p2);
	m2.weicha(p3);
	m2.weicha(p4);

	cout << "非普通数据尾删前" << endl;
	for (int j = 0; j < m2.getsize(); j++)
	{
		cout << "姓名: " << m2[j].name << " 年龄:" << m2[j].age << endl;
	}
	cout << "非普通数据尾删前的容量:" << m2.getcapacity() << endl;
	cout << "非普通数据尾删前的大小:" << m2.getsize() << endl;
}
int main()
{	            
	test1();
	test2();
	system("pause");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值