目录
写在前面
今天,为了这个周末的seminar,博主花了整个白天调case。本来计划今晚继续调case,但我突然意识到今天的cpp还没学。因此,我决定晚上在实验室抽出一些时间给cpp。中午休息时,我简短地学习了一些关于异常处理的知识,但我还没有完全弄清楚如何有效地总结和应用这些知识。然后我想起了昨晚在学习泛型编程时,我只看了视频,却没有实际动手练习。所以本文就是关于昨晚学习的泛型编程的部分琐碎知识以及相应的一个简单的实战练习。
模板类中的静态成员变量
- 我们知道类的静态成员变量只会构造一次,但是模板类中的静态成员变量的生命周期是怎样的呢?
- 我们继续使用昨天用的模板类A,并在其中定义一个静态变量count,并将其生命为public便于直接访问。其头文件代码如下:
/*
* Created by herryao on 1/21/24.
* Email: stevenyao@g.skku.edu
* Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
*/
#ifndef PROJECT02_A_H
#define PROJECT02_A_H
template<typename T>
class A{
public:
A(T t = 0){
this->t_ = t;
}
T& getT(){
return this->t_;
}
A operator+(const A& other);
//static member variables
static int count;
private:
T t_;
//declaration a friend function to achieve the addition operation
//friend function is independent on the class so the template type need to clarify
template<typename TF>
friend A<TF> addA(const A<TF>&a, const A<TF>&b);
};
#endif//PROJECT02_A_H
- 相应的源文件A.hpp的实现也一并放在这里:
/*
* Created by herryao on 1/21/24.
* Email: stevenyao@g.skku.edu
* Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
*/
#include "A.h"
//definition of the function
template<typename T>
//all the class name in the definition need to clarify the template type
A<T> A<T>::operator+(const A<T>& other){
//While within the function body, with or without explict declaration, it is flexible
A temp = A(this->t_ + other.t_);
return temp;
}
template<typename TF>
A<TF> addA(const A<TF>&a, const A<TF>&b){
A temp = A(a.t_ + b.t_);
return temp;
}
- 然后就是在我们的main.cpp我们全局初始化一下我们的静态成员变量
//static member variables
template<typename T> int A<T>::count = 0;
-
让我们来测试我们的静态成员变量在不同类型下的行为。通常,我们认为一旦静态成员变量初始化,任何对象对其的修改都应该是共享的。基于这个理解,我们首先初始化了一个全局静态变量,值为0。接着,我们创建了三种不同类型的对象:int、char和double。每个对象都对这个静态成员变量进行了修改,随后我们打印了每个对象的静态成员变量的值。这样做的目的是为了观察,是否如我们所预期的那样,不同类型的对象共享同一个静态成员变量。
- 下面是我定义的测试用代码。
void test_4_static_template(){ A a(666), b(888); a.count = 80; A c('a'); A d(10.); //static member in template class are independent regarding different type, will within same type it follows the protocol of the static member std::cout << "a: " << a.count << "\tb: " << b.count << "\tc: " << c.count << "\td: " << d.count << std::endl; c.count = 100; std::cout << "a: " << a.count << "\tb: " << b.count << "\tc: " << c.count << "\td: " << d.count << std::endl; }
- 下面是运行结果:
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week02/day07/project02/cmake-build-debug/project02
a: 80 b: 80 c: 0 d: 0
a: 80 b: 80 c: 100 d: 0
Process finished with exit code 0
- 从结果中我们可以看出,相同模板类型的对象确实是公用相同的静态成员变量的,每种不同类型模板对象的静态成员对象是相互独立的。
项目练习:万能容器Vector的实现
- 要求:
- 创建一个模板类Vector,使其能够传入任意类型的数据,并为Vector类重写:
- 构造函数 constructor(有参,无参)
- 析构函数 destructor
- 拷贝构造函数 copy constructor
- 拷贝赋值函数 copy assignment
- 流运算符重载函数 ostream operator <<
- []符号的重载 square bracket operator
- Vector类包含两个私有的成员变量,一个为指针list_,另一个为容器长度length_
- 为Vector容器创建一个Student类,重写相应的函数使得Vector<Student*> 的输出方法正常。这个问题涉及到的成员变量以及方法定义如下:
- 构造函数 constructor(有参,无参)
- 析构函数 destructor
- 拷贝构造函数 copy constructor
- 拷贝赋值函数 copy assignment
- 流运算符重载函数 ostream operator <<(student*, student)
- 一个类似流函数重载的用于获取信息的函数 print()
- 下面我们直接开始实现
- 首先我们直接定义相应的头文件,注意一点就是在流运算符重载时要记得定义模板类型并且避免命名冲突,还有就是最好在类内声明友元之后,将其声明写在头文件类外便于文件间的跳转,直接看代码:
/*
* Created by herryao on 1/22/24.
* Email: stevenyao@g.skku.edu
* Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
*/
#ifndef PROJECT01_VECTOR_H
#define PROJECT01_VECTOR_H
#include<iostream>
template<typename T>
class Vector {
public:
//constructor
Vector(size_t length);
//destructor
~Vector();
Vector(const Vector& vector);
Vector& operator=(const Vector&obj);
T operator[](int idx) const;
T& operator[](int idx);
template<typename TF>
friend std::ostream& operator<< (std::ostream& os, const Vector<TF>& obj);
[[nodiscard]]size_t getLength()const;
private:
T* list_;
size_t length_;
};
std::ostream& operator<< (std::ostream& os, const Vector<TF>& obj);
#endif//PROJECT01_VECTOR_H
- 为了方便链接,我们头源文件分离我们需要把源文件定义成Vector.hpp然后在其余文件中调用源文件。
- 此外我们的程序中有大量的const Vector& 的类型调用,因此对于我们的[]运算符重载我们必须分别给出常量形式(用于复制,和值的获取)和非常量形式(用于赋值)的定义。
- 下面是源文件的实现:
/*
* Created by herryao on 1/22/24.
* Email: stevenyao@g.skku.edu
* Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
*/
#include "Vector.h"
template<typename T>
Vector<T>::Vector(size_t length) {
this->list_ = new T[length];
if (this->list_ != nullptr) {
this->length_ = length;
} else
throw "unable to malloc the heap";
}
template<typename T>
Vector<T>::~Vector() {
if(this->list_!= nullptr){
delete []this->list_;
this->list_ = nullptr;
}
}
template<typename T>
Vector<T>::Vector(const Vector<T> &other) {
this->list_ = new T[other.length_];
if(this->list_ != nullptr){
for(int i=0; i<other.length_; ++i){
this->list_[i] = other.list_[i];
}
this->length_ = other.length_;
}
}
template<typename T>
Vector<T> &Vector<T>::operator=(const Vector<T> &obj) {
if(this != &obj){
if(this->list_ != nullptr){
delete []this->list_;
}
this->list_ = new T[obj.length_];
if(this->list_!= nullptr){
for(int i=0; i<obj.length_; ++i){
this->list_[i] = obj.list_[i];
}
this->length_ = obj.length_;
}
}
return *this;
}
template<typename T>
T &Vector<T>::operator[](int idx) {
if(idx >= 0&& idx<this->length_){
return this->list_[idx];
}
}
template<typename T>
T Vector<T>::operator[](int idx) const{
if(idx >= 0&& idx<this->length_){
return this->list_[idx];
}
}
template<typename TF>
std::ostream& operator<< (std::ostream& os, const Vector<TF>& obj){
for(int i=0; i<obj.length_; ++i){
os << obj.list_[i];
}
os << std::endl;
return os;
}
template<typename T>
size_t Vector<T>::getLength() const {
return this->length_;
}
- 然后就是我们的Student类的设计了,其实这里在自定义类型之前,Martin老师带着我们进行了基本类型变量的测试,由于大量重复操作,这里就不在涉及这部分,大家可以自行测试一下。
- 对与Student类,Martin老师在课上完成了一个简单的操作,在student类内对于名字只是使用了char数组,所以不涉及深浅拷贝的问题,于是他留了两个思考题:
- 对于像字符串这样的动态空间变量,最佳实践是手动分配和释放内存,以减少栈区的压力。此外,在使用像Vector这样的容器时,避免传入复合类型的变量是明智的,因为这可能会降低效率。对于复合类型的变量,使用指针进行操作通常是最高效的方法。
- 因此第一个问题就是将成员变量name_用堆内存存储,然后相应地为其设计好构造函数,析构函数,拷贝构造函数,拷贝赋值函数,并在其中考虑好堆内存的管理以及深浅拷贝的问题。
- 第二个问题就是如果我们调用Student*在Vector 对象中,最后输出的结果会是一段int类型的数据,我们希望的是实现和存储普通类型对象相同的功能。
- 以上的问题其实并不难实现,深浅拷贝已经是一个老生长谈的练习了,而后面的那个指针类型输出,我们只需要定义一个参数类型为Student*的流函数重载的友元函数即可。
- 有了上面的思考我们现在开始实现这个升级版的Student类,还是一样的,友元函数最好在头文件的类外声明一遍,这样方便文件之间的跳转,Student类的头文件定义如下所示:
/*
* Created by herryao on 1/22/24.
* Email: stevenyao@g.skku.edu
* Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
*/
#ifndef PROJECT01_STUDENT_H
#define PROJECT01_STUDENT_H
#include<iostream>
#include<string>
#include<stdio.h>
#include<cstring>
class Student {
public:
Student();
Student(const char*name, int age);
~Student();
void print();
Student(const Student& other);
Student& operator =(const Student&other);
friend std::ostream& operator << (std::ostream& os, const Student* pst);
friend std::ostream& operator <<(std::ostream& os, const Student& st);
private:
char* name_;
int age_;
};
std::ostream& operator << (std::ostream& os, const Student* pst);
std::ostream& operator <<(std::ostream& os, const Student& st);
#endif//PROJECT01_STUDENT_H
- 在源文件中,注意深浅拷贝的部分,还有就是Student* 流函数的重写,相应的源文件的实现如下所示
/*
* Created by herryao on 1/22/24.
* Email: stevenyao@g.skku.edu
* Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
*/
#include "Student.h"
Student::Student() {
this->name_ = new char[strlen("unknown")+1];
if(this->name_){
strcpy(this->name_, "unknown");
this->age_ = 0;
}
}
Student::Student(const char *name, int age) {
this->name_ = new char[strlen(name)+1];
if(this->name_){
strcpy(this->name_, name);
this->age_ = age;
}
}
Student::~Student() {
if(this->name_){
delete[]this->name_;
this->name_ = nullptr;
}
}
Student &Student::operator=(const Student &other) {
if(this != &other){
if(this->name_){
delete[]this->name_;
}
this->name_ = new char[strlen(other.name_)+1];
if(this->name_){
strcpy(this->name_, other.name_);
this->age_ = other.age_;
}
}
return *this;
}
Student::Student(const Student& other){
this->name_ = new char[strlen(other.name_)+1];
if(this->name_){
strcpy(this->name_, other.name_);
this->age_ = other.age_;
}
}
void Student::print() {
std::cout << "student name: " << this->name_ << " age: " << this->age_ << std::endl;
}
std::ostream& operator <<(std::ostream& os, const Student* pst){
os << "student name: " << pst->name_ << " age: " << pst->age_ << std::endl;
return os;
}
std::ostream& operator <<(std::ostream& os, const Student& st){
os << "student name: " << st.name_ << " age: " << st.age_ << std::endl;
return os;
}
- 然后我们定义两个测试函数分别测试一下关于Student以及Student*对象的容器的输出情况
- 在两个测试函数中,我们声明五个相同的对象,然后分别用流函数和其自定义输出方法对其进行测试,先看我们的测试函数:
- Student对象容器的测试函数:
-
void check_4_obj(){ Student st1("gigi", 25); Student st2("bigbean", 25); Student st3("asifmuhammad", 24); Student st4("gulgunbahit", 24); Student st5("panghu",1); Vector<Student> lstu(5); lstu[0] = st1; lstu[1] = st2; lstu[2] = st3; lstu[3] = st4; lstu[4] = st5; std::cout << lstu; std::cout << "here template by obj done" << std::endl; for(int i=0; i<lstu.getLength(); ++i){ lstu[i].print(); } }
- 其调用后的输出如下:
-
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week03/mon/project01/cmake-build-debug/project01 student name: gigi age: 25 student name: bigbean age: 25 student name: asifmuhammad age: 24 student name: gulgunbahit age: 24 student name: panghu age: 1 here template by pointer done student name: gigi age: 25 student name: bigbean age: 25 student name: asifmuhammad age: 24 student name: gulgunbahit age: 24 student name: panghu age: 1 Process finished with exit code 0
- Student*指针容器的测试函数:
-
void check_4_ptr(){ Student st1("gigi", 25); Student st2("bigbean", 25); Student st3("asifmuhammad", 24); Student st4("gulgunbahit", 24); Student st5("panghu",1); Vector<Student*> lpstu(5); lpstu[0] = &st1; lpstu[1] = &st2; lpstu[2] = &st3; lpstu[3] = &st4; lpstu[4] = &st5; std::cout << lpstu; std::cout << "here template by pointer done" << std::endl; for(int i=0; i<lpstu.getLength(); ++i){ lpstu[i]->print(); } }
-
其调用后的输出如下,可以看到终于不再是长长的一串数据输出了,和普通对象的容器实现了相同的功能:
-
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week03/mon/project01/cmake-build-debug/project01 student name: gigi age: 25 student name: bigbean age: 25 student name: asifmuhammad age: 24 student name: gulgunbahit age: 24 student name: panghu age: 1 here template by obj done student name: gigi age: 25 student name: bigbean age: 25 student name: asifmuhammad age: 24 student name: gulgunbahit age: 24 student name: panghu age: 1 Process finished with exit code 0
总结
- 今天的学习很短暂,但是也是一个很有意义的练习,包含了运算符重载,范型编程,友元函数。
- 今天的练习主要是对于昨天学习知识的巩固。由于seminar的原因最近不能花很多时间给cpp的学习,但是博主认为在小的坚持也是一种胜利,无穷小永远大于0,所以希望和博主有相同惰性的读者们一定要坚持下去,祝大家持续变强,共勉。
致谢
- 继续感谢Rock老师和Martin老师
- 感谢我老妈给我充电了2块钱作为支持,我好像只得到了8毛钱?
- 感谢各位的支持,祝大家今天远比昨天强