C++类模板的总结之《万能择优器》
这篇文章是围绕类模板展开写的一个项目总结文件。分享了关于类模板的使用和类在使用时一些容易忽略的细节问题。如类的成员变量在是指针变量时在构造函数和析构函数中的处理方法。
- 在上代码之前先看看我们定义这个模板类要干啥事:我们知道C++ STL标准库中有一个vector动态的容器 ,它可以动态的存储不同类型的数据,包括我们自定义的类型也是没有问题的。这个容器很强大,那今天我们只是借助这个模型来展开我要设计的这个动态万能数组,简单来说就是借助C++类模板的应用来实现可以保存 int ,char ,double 以及自定义的类型的万能数组。话不多说直接上代码:
#pragma once
//自定义数组模板类
#include <iostream>
using namespace std;
template <typename T>
class Vector
{
public:
Vector(int size =128); //构造函数
Vector(const Vector &object); //拷贝构造函数
~Vector(void); //析构函数
int getSize() const; //获取内部存储的元素个数
//vector<int> a1 ,a2,a1[0]
T& operator[](int index) ;
//a1=a2=a3
//【1】种解决方案
template <typename T>
friend ostream & operator << (ostream & ostr, const Vector<T> &object);
//【2】种解决方案
//friend ostream & operator << <T>(ostream & ostr, const Vector<T>
Vector & operator=(const Vector &object);
private:
T *m_base;
int m_len;
};
//template <typename T>
//ostream & operator <<(ostream & ostr,Vector<T> &object);
上面代码是自定义Vector 类模板的声明部分。
#include "Vector.h"
#include <iostream>
using namespace std;
template <typename T>
Vector<T>::Vector(int size){
if (size>0){
this->m_len=size;
this->m_base=new T[m_len]; //分配内存
}
}
template <typename T>
Vector<T>::Vector (const Vector &object){
this->m_len=object.m_len;
this->m_base =new T [m_len]; //【1】分配内存空间
//数据拷贝
for (int i=0;i<m_len;i++){
this->m_base[i]=object.m_base[i];
}
} //拷贝构造函数
template <typename T>
Vector<T>::~Vector(void){
if (m_base!=NULL){
delete [] m_base;
m_base=NULL;
m_len=0;
}
}
template <typename T>
T& Vector<T>:: operator[](int index) {
return m_base[index];
}
template <typename T>
Vector<T> & Vector<T>:: operator=(const Vector &object){
if (m_base!=NULL){
delete [] m_base;
m_base=NULL;
m_len=0;
}
this->m_len=object.m_len;
this->m_base =new T [m_len]; //【1】分配内存空间
//this->m_base=(T*)malloc(m_len); //[2]第二中c语言的内存分配
//数据拷贝
for (int i=0;i<m_len;i++){
this->m_base[i]=object.m_base[i];
}
return *this;
}
template <typename T>
int Vector<T>::getSize() const{
return m_len;
}
//使用全局的友元函数来实现<<输出流运算符的重载
template <typename T>
ostream & operator <<(ostream & ostr, const Vector<T> &object){
for (int i=0;i<object.m_len;i++){
ostr<<object.m_base[i]<<"\t";
}
ostr<<endl;
return ostr;
}
这就是模板的实现代码,其实原理很简单,在模板类建立一个动态的内存来保存来自不同类型的数据,然后再从这块内存中取出数据。我们实现输出看一下就差不多可以理解了。
#include <Windows.h>
#include <iostream>
#include "Vector.hpp"
#include "Vector.h"
using namespace std;
int main(){
Vector <float> myVetor(10);
for (int i =0;i<myVetor.getSize();i++){
myVetor[i]=i*0.1f;
cout<<myVetor[i]<<endl;
}
Vector <float> aVetor;
aVetor=myVetor;
cout<<"重载=符号的输出"<<endl;
for (int i =0;i<aVetor.getSize();i++){
cout<<aVetor[i]<<endl;
}
Vector <float> mty(myVetor);
cout<<"拷贝构造函数的输出"<<endl;
for (int i =0;i<mty.getSize();i++){
cout<<mty[i]<<endl;
}
cout<<myVetor<<mty;
system("pause");
return 0;
}
输出结果:
其实这已经完成了这个万能数组的50%了,最重要的就是怎样自定义的类型也带到这里面呢!
我们不妨自定义创建一个类Student 类如下:
#pragma once
#include <string>
using namespace std;
class Student{
public:
Student();
Student(const char* name ,int age=0);
Student (const Student &object); //自定义拷贝构造函数
Student& operator= (const Student &other ); //重载赋值构造函数
string getStr() const;
~Student(void);
private:
int age; //年龄
char *name; //姓名
//string name;
};
Student类的实现文件
#include "Student.h"
#include <sstream>
using namespace std;
Student::Student(){
this->age=0;
this->name="未命名";
const char * defaultName="未命名";
this->name=(char*)malloc((strlen(defaultName)+1)*sizeof(char));
strcpy_s(this->name,strlen(defaultName)+1,defaultName);
}
Student& Student:: operator= (const Student & other ){
this->name=(char*)malloc((strlen(other.name)+1)*sizeof(char));
strcpy_s(this->name,strlen(other.name)+1,other.name);
this->age=other.age;
return *this;
}
Student:: Student (const Student &object){
if (name){
delete name;
}
this->name=(char*)malloc((strlen(object.name)+1)*sizeof(char));
strcpy_s(this->name,strlen(object.name)+1,object.name);
this->age=object.age;
} //自定义拷贝构造函数
Student:: Student(const char* name ,int age){
int len=strlen(name)+1 ;
this->name=(char*)malloc(len*sizeof(char)); //动态内存分配
strcpy_s(this->name,len,name);
//this->name=name;
this->age=age;
}
Student::~Student(void){
if (name){
delete name;
/*free(name);*/
}
}
string Student:: getStr() const{
stringstream sstr;
sstr<<"姓名:"<<name<<"\t年龄:"<<age<<endl;
return sstr.str();
}
可以看到在类的几个构造函数中都是用了malloc()函数(当然也可以使用new 关键字来申请内存空间)来创建新的内存空间来保存name的信息为什么呢?
这里有两点需要注意:
- 为什么要自定义拷贝构造函数和赋值构造函数,我们知道在拷贝、对对象赋值时系统是会自动为我们生成“合成赋值构造函数”,“合成拷贝构造函数”的,当然了我刚开始也没有在意直接运行会发现系统根本无法正常的退出。反复调试才发现根本问题也就是调用了默认的构造函数导致的,是不是听起来像句废话。
- 那有为什么我们不用指针变量就不会有这样的问题呢?
那想知道这个其实也不难,那就是Debug了,就是这么简单呀,我们将自定义的赋值构造函数注释了来看会发生什么。退出后触发了一个中断,我们点击【重试】,各位可别点击【中止】呦,那样你将啥都看不到!记住点击【重试】来触发这个中断
这个是测试的代码:
Student stu1("王晓华",30);
Student stu2("王大伟",21);
//将类放在自定义的容器内
Vector <Student> stuList(2);
stuList[0]=stu1;
stuList[1]=stu2;
for(int i=0;i<stuList.getSize();i++){
cout<<stuList[i].getStr();
}
vector<Student>stuList1;
stuList1.push_back(stu1);
stuList1.push_back(stu2);
for(unsigned int i=0; i<stuList1.size();i++){
cout<< stuList1[i].getStr();
}
点击【重试】会跳出对话框:点击【中断】来查看是哪里的问题。
啥都别看,因为可能断点的位置你看不懂,就看调用堆栈中的内容,当然不是全看点击跳转到我们自己的代码位置看看是哪里出了问题?可以看到Student::~Student()这样一个位置。查看一下
乱码了,不是原来的姓名了。 对象在析构时找到要释放的这块内存了已经不是原来的东西了?这很危险的操作,系统又怎么能让你这样简简单单的就去释放掉它呢?
其实问题就是在 stuList[0]=stu1。
在这句话这里就调用了类的赋值构造函数,问题就在默认的赋值构造函数是简单的位拷贝,就是所谓的浅拷贝,简单的浅拷贝这对一般的数据没有大的影响,但是对于指针变量那就是毁天灭地,想象一下你把另一个对象的中指针拿来用,我们且不说对他可以修改,更严重的是你给别人东西。他释放掉自己的内存后你上哪找这块东西,一去不回的操作。天涯海角你也找不到的。找到的那块内存已经被别人在使用,就算没人用也不能让你随便支配啦!是不是尴尬?为了避免这种毁天灭地的现象,我们要像上面代码一样进行深拷贝,为赋值的对象创建新的内存来保存这个指针变量,这样就不会出现上面的问题了。
Student::~Student(void){
if (name){
delete name;
/free(name);/
}
}
与上面的问题相似:在使用 vector 容器时 stuList1.push_back(stu1); 在这里会使用拷贝构造函数 看push_back()函数的原型就是到了。处理方法也和上面相似了。
说到这里也就差不多了。