C++进阶—第六章 类的封装性(补)

6.6析构函数

知识点1:构造函数和new

在前面的构造函数中,name和curriculum是长度不确定的字符串,而系统在栈中分配的是一块固定大小的内存,若name或curriculum长度过长可能导致内存不够的情况。因此,name和curriculum应该在堆存储区中动态分配内存。

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0);

void printinfo(void);

};  //需要使用分号结尾

 

//student.cpp

#include "student.h"

 

Student::Student():name("no name"),age(0),sch(school::primary_school),curriculum("no curriculum"),achievement(0)

{

cout << "Student()" << endl;

}

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement) : age(age), sch(sch), achievement(achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

//main.cpp

#include "student.h"

int main(int argc, char **argv) {

 

Student stu1;

stu1.printinfo();

Student stu2("zhangsan",12);

stu2.printinfo();

 

return 0;

}

运行结果:

Student()

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:zhangsan

年龄:12

学校:primary_school

课程:no curriculum

成绩:0

知识点2:使用new来创建对象

在前面我们使用Student stu1(不是Student stu1())和Student stu2("zhangsan",12)来实例化对象,除了这种方法,还可以使用new在堆空间创建对象。

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0);

void printinfo(void);

};  //需要使用分号结尾

 

//student.cpp

#include "student.h"

 

Student::Student():age(0),sch(school::primary_school),achievement(0)

{

cout << "Student()" << endl;

const char * name = "no name";

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

const char * curriculum = "no curriculum";

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement) : age(age), sch(sch), achievement(achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

//main.cpp

#include "student.h"

int main(int argc, char **argv) {

 

Student stu1;

stu1.printinfo();

Student stu2("zhangsan",12);

stu2.printinfo();

 

cout << "Student * stu3 = new Student" << endl;

Student * stu3 = new Student;  //调用无参默认构造函数

stu3->printinfo();

cout << "Student * stu4 = new Student()" << endl;

Student * stu4 = new Student(); //调用无参默认构造函数

stu4->printinfo();

cout << "Student * stu5 = new Student[2]" << endl;

Student * stu5 = new Student[2]; //调用2个无参默认构造函数

stu5->printinfo();    //或stu5[0].printinfo();

(stu5+1)->printinfo();//或stu5[1].printinfo();

cout << "Student * stu6 = new Student(lisi,17,school::middle_school)" << endl;

Student * stu6 = new Student("lisi",17,school::middle_school);  //调用有参构造函数

stu6->printinfo();

 

delete stu3;

delete stu4;

delete []stu5;

delete stu6;

 

return 0;

}

运行结果:

Student()

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:zhangsan

年龄:12

学校:primary_school

课程:no curriculum

成绩:0

Student * stu3 = new Student

Student()

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student * stu4 = new Student()

Student()

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student * stu5 = new Student[2]

Student()

Student()

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student * stu6 = new Student(lisi,17,school::middle_school)

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:lisi

年龄:17

学校:middle_school

课程:no curriculum

成绩:0

使用new创建对象,其本质也是调用类的构造函数,不过对象数据存放的位置是堆空间中。在main()函数执行结束后,会自动释放new开辟的堆空间。若在main()的子函数中,使用new来创建对象,而不delete,必定会造成内存泄漏,因此即使在main()函数中使用new,在不用时也要使用delete来释放对象指针,这是一个良好的习惯。

但是,我们在Student类的构造函数中使用new来创建数据成员name和curriculum,该在哪里释放呢?当main()函数执行结束时,所创建的局部对象会被销毁,在对象销毁前的一瞬间会主动调用一个函数来完成对象的清理工作,这个函数就是析构函数。

知识点3:析构函数

当对象在销毁前的一瞬间,会调用析构函数。若代码中没有析构函数,则系统会提供一个默认的析构函数。析构函数主要作用是完成对象的清理工作,比如在构造函数中使用new,那么在析构函数中就必须delete。析构函数的格式是~加上类名,无返回值无参数。析构函数的调用顺序和构造函数相反。

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0);

~Student();

void printinfo(void);

};  //需要使用分号结尾

 

//student.cpp

#include "student.h"

 

Student::Student():age(0),sch(school::primary_school),achievement(0)

{

cout << "Student()" << endl;

const char * name = "no name";

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

const char * curriculum = "no curriculum";

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement) : age(age), sch(sch), achievement(achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::~Student()

{

cout << "~Student()" << endl;

if (this->name)

{

cout << this->name << endl;

delete this->name;

}

if (this->curriculum)

delete this->curriculum;

}

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

//main.cpp

#include "student.h"

int main(int argc, char **argv)

{

Student stu1;

stu1.printinfo();

Student stu2("zhangsan", 12);

stu2.printinfo();

Student stu3("lisi", 15,school::middle_school,"math",90);

stu3.printinfo();

 

return 0;

}

运行结果:

Student()

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:zhangsan

年龄:12

学校:primary_school

课程:no curriculum

成绩:0

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:lisi

年龄:15

学校:middle_school

课程:math

成绩:90

~Student()

lisi

~Student()

zhangsan

~Student()

no name

6.7拷贝构造函数

在类定义中,若没有构造函数和析构函数,系统会提供一个默认的构造函数和析构函数,除了这2个函数外,还有什么函数系统会默认提供呢?这一小节要讲的就是这个函数—拷贝构造函数。

拷贝构造函数没有返回值,其参数是类对象引用,函数名就是类名,如Student(Student &stu)。对于Student类,当执行Student stu2(stu1)时会调用拷贝构造函数;当执行Student stu2 = stu1会调用拷贝构造函数(相同于Student stu2(stu1));当函数返回值类型为Student对象(不是Student对象引用),执行return stu时(stu对象是局部变量),会调用拷贝构造函数。

Student类中的name和curriculum数据成员是使用new开辟的动态内存,因此需要我们提供一个自己的拷贝构造函数,在拷贝构造函数中name和curriculum使用new来创建。

拷贝构造函数会创建对象,因此一个拷贝构造函数对应一个析构函数。

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;             //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0);

~Student();

Student(const Student &stu);

friend Student copy_age(Student &stu1, Student &stu2);

void printinfo(void);

};  //需要使用分号结尾

 

//student.cpp

#include "student.h"

Student::Student():age(0),sch(school::primary_school),achievement(0)

{

cout << "Student()" << endl;

const char * name = "no name";

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

const char * curriculum = "no curriculum";

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement) : age(age), sch(sch), achievement(achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::~Student()

{

cout << "~Student()" << endl;

if (this->name)

{

cout<<this->name<<endl;

delete this->name;

}

if (this->curriculum)

delete this->curriculum;

}

Student::Student(const Student &stu)

{

cout << "Student(const Student &stu)" << endl;

this->name = new char[strlen(stu.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(stu.name) + 1, stu.name);  //深拷贝

this->age = stu.age;

this->sch = stu.sch;

this->curriculum = new char[strlen(stu.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(stu.curriculum) + 1, stu.curriculum);  //深拷贝

this->achievement = stu.achievement;

}

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

Student copy_age(Student &stu1, Student &stu2)

{

if (stu1.age > stu2.age)

return stu1;

else

return stu2;

}

 

//main.cpp

#include "student.h"

int main(int argc, char **argv)

{

Student stu1;

stu1.printinfo();

Student stu2("zhangsan", 12);

stu2.printinfo();

Student stu3("lisi", 15,school::middle_school,"math",90);

stu3.printinfo();

 

Student stu4(stu3);

stu4.printinfo();

copy_age(stu2, stu3).printinfo();

return 0;

}

运行结果:

Student()

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:zhangsan

年龄:12

学校:primary_school

课程:no curriculum

成绩:0

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:lisi

年龄:15

学校:middle_school

课程:math

成绩:90

Student(const Student &stu)

学生姓名:lisi

年龄:15

学校:middle_school

课程:math

成绩:90

Student(const Student &stu)

学生姓名:lisi

年龄:15

学校:middle_school

课程:math

成绩:90

~Student()

lisi

~Student()

lisi

~Student()

lisi

~Student()

zhangsan

~Student()

no name

代码中Student stu4(stu3)调用了一次拷贝构造函数,copy_age(stu2, stu3)调用了一次拷贝构造函数,因此析构函数总共会被调用5次。

6.8赋值运算符和运算符重载

运算符本质上就是函数,在C++库中提供基础数据类型的+、-、*、/、=、...等运算的函数,但是对于类的对象,就没有定义。这就需要我们自己来编写类对象的运算符函数,因此这一小节称为运算符重载。运算符函数的格式是operator运算符的形式,如赋值运算符:Class_name & operator=(const Class_name & cls)或Class_name operator=(const Class_name & cls)。

知识点1:赋值运算符重载

除了构造函数、析构函数、拷贝构造函数,系统还会提供默认的赋值运算符函数,由于类中的name和curriculum数据成员是使用new来创建的,因此需要我们提供一个自己的赋值运算符函数。

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0);

~Student();

Student(const Student &stu);

Student & operator=(const Student & stu);

//Student operator=(const Student & stu);

void printinfo(void);

};  //需要使用分号结尾

 

//student.cpp

#include "student.h"

 

Student::Student():age(0),sch(school::primary_school),achievement(0)

{

cout << "Student()" << endl;

const char * name = "no name";

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

const char * curriculum = "no curriculum";

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement) : age(age), sch(sch), achievement(achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::~Student()

{

cout << "~Student()" << endl;

if (this->name)

{

cout<<this->name<<endl;

delete this->name;

}

if (this->curriculum)

delete this->curriculum;

}

Student::Student(const Student &stu)

{

cout << "Student(const Student &stu)" << endl;

this->name = new char[strlen(stu.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(stu.name) + 1, stu.name);  //深拷贝

this->age = stu.age;

this->sch = stu.sch;

this->curriculum = new char[strlen(stu.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(stu.curriculum) + 1, stu.curriculum);  //深拷贝

this->achievement = stu.achievement;

}

Student & Student::operator=(const Student & stu)

{

cout << "Student & operator=(const Student & stu)" << endl;

if (this == &stu)

return *this;

if (this->name)

delete this->name;

if (this->curriculum)

delete this->curriculum;

 

this->name = new char[strlen(stu.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(stu.name) + 1, stu.name);  //深拷贝

this->age = stu.age;

this->sch = stu.sch;

this->curriculum = new char[strlen(stu.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(stu.curriculum) + 1, stu.curriculum);  //深拷贝

this->achievement = stu.achievement;

 

return *this;

}

/*Student Student::operator=(const Student & stu)

{

cout << "Student operator=(const Student & stu)" << endl;

Student temp;

if (temp.name)

delete temp.name;

if (temp.curriculum)

delete temp.curriculum;

temp.name = new char[strlen(stu.name) + 1];

strcpy_s(const_cast<char *>(temp.name), strlen(stu.name) + 1, stu.name);  //深拷贝

temp.age = stu.age;

temp.sch = stu.sch;

temp.curriculum = new char[strlen(stu.curriculum) + 1];

strcpy_s(const_cast<char *>(temp.curriculum), strlen(stu.curriculum) + 1, stu.curriculum);  //深拷贝

temp.achievement = stu.achievement;

 

return temp;

}*/

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

//main.cpp

#include "student.h"

 

int main(int argc, char **argv)

{

 

Student stu1("lisi", 15, school::middle_school, "math", 90);

Student stu2;

stu2 = stu1; //相当于stu2.operator=(stu1);

 

return 0;

}

运行结果:

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

Student()

Student & operator=(const Student stu)

~Student()

lisi

~Student()

lisi

代码中执行stu2 = stu1时,会调用赋值运算符函数;但是执行Student stu2 = stu1时,调用的是拷贝构造函数。在前面的代码中实现了2种赋值运算符函数,一种是返回值为引用,return *this;一种是返回值是局部变量。第一种方法比较好用。

执行stu2 = stu1相当于执行的是stu2.operator=(stu1)。因此执行赋值运算符函数,其左侧操作数表示函数的对象,右侧操作数表示函数的参数。若要把赋值运算符函数改成类外的友元函数,则是friend Student & operator=(Student & stu2,Student & stu1),其中左侧操作数表示第一个参数,右侧操作数表示第二个参数。

知识点2:<<运算符重载

在前面的代码中,一直使用printinfo()来打印Student对象的数据成员,现在学习了赋值运算符重载,能不能使用cout<<的方式打印Student对象呢?可以的,在头文件iostream中,cout实际上是类ostream的对象,因此执行cout<<stu1,可以写成Student & operator<<(ostream & os,Student & stu)。若要完成连续打印的功能,如cout<<stu1<<stu2,把函数改成ostream & operator<<(ostream & os,Student & stu)即可。

若要表示为类的成员函数,则运算符的左侧操作数必须是类对象,cout<<stu1的左侧操作数是ostream对象,因此<<运算符函数,只能表示为类的友元函数。至于什么是友元,会在下面讲到,这里了解就行了。

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0);

~Student();

Student(const Student &stu);

Student & operator=(const Student & stu);

friend ostream & operator<<(ostream & os, Student& stu);

};  //需要使用分号结尾

 

//student.cpp

#include "student.h"

 

Student::Student():age(0),sch(school::primary_school),achievement(0)

{

cout << "Student()" << endl;

const char * name = "no name";

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

const char * curriculum = "no curriculum";

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement) : age(age), sch(sch), achievement(achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::~Student()

{

cout << "~Student()" << endl;

if (this->name)

{

cout<<this->name<<endl;

delete this->name;

}

if (this->curriculum)

delete this->curriculum;

}

Student::Student(const Student &stu)

{

cout << "Student(const Student &stu)" << endl;

this->name = new char[strlen(stu.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(stu.name) + 1, stu.name);  //深拷贝

this->age = stu.age;

this->sch = stu.sch;

this->curriculum = new char[strlen(stu.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(stu.curriculum) + 1, stu.curriculum);  //深拷贝

this->achievement = stu.achievement;

}

Student & Student::operator=(const Student & stu)

{

cout << "Student & operator=(const Student & stu)" << endl;

if (this == &stu)

return *this;

if (this->name)

delete this->name;

if (this->curriculum)

delete this->curriculum;

 

this->name = new char[strlen(stu.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(stu.name) + 1, stu.name);  //深拷贝

this->age = stu.age;

this->sch = stu.sch;

this->curriculum = new char[strlen(stu.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(stu.curriculum) + 1, stu.curriculum);  //深拷贝

this->achievement = stu.achievement;

 

return *this;

}

 

ostream & operator<<(ostream & os, Student& stu)

{

os << "学生姓名:" << stu.name << endl;

os << "年龄:" << stu.age << endl;

switch (int(stu.sch))

{

case 0:

os << "学校:" << "primary_school" << endl;

break;

case 1:

os << "学校:" << "middle_school" << endl;

break;

case 2:

os << "学校:" << "collge" << endl;

break;

default:

os << "学校:" << "..." << endl;

}

os << "课程:" << stu.curriculum << endl;

os << "成绩:" << stu.achievement << endl;

return os;  //返回cout

}

 

//main.cpp

#include "student.h"

int main(int argc, char **argv)

{

 

Student stu1("lisi", 15, school::middle_school, "math", 90);

Student stu2;

stu2 = stu1;

cout <<stu1<<stu2;

return 0;

}

运行结果:

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

Student()

Student & operator=(const Student stu)

学生姓名:lisi

年龄:15

学校:middle_school

课程:math

成绩:90

学生姓名:lisi

年龄:15

学校:middle_school

课程:math

成绩:90

~Student()

lisi

~Student()

lisi

知识点3:+运算符和*运算符重载

经过几次优化,Student类不再是那么简陋。这小节为了简单体现+运算符和*运算符的含义,下面将重新定义一个Time类来讲解。

//time.h

#include <iostream>

using namespace std;

 

class Time {

private:

int hours;   //时,范围0~23

int minutes; //分,范围0~59

public:

Time() :hours(0), minutes(0) { cout << "Time()" << endl; };

Time(int hours, int minutes = 0) :hours(hours), minutes(minutes)

{

cout << "Time(int hours, int minutes = 0)" << endl;

};

~Time() { cout << "~Time()" << endl; };

Time(const Time & t)

{

cout << "Time(const Time & t)" << endl; hours = t.hours; minutes = t.minutes;

};

Time & operator=(const Time & t);

friend ostream & operator<<(ostream & os, Time & t);

Time operator+(const Time & t) const;

//相当于friend Time operator+(const Time & t1,const Time & t2);

Time operator*(int i) const;  

//minutes * i,相当于friend Time operator*(const Time & t,int i)

};

 

//time.cpp

# include "time.h"

Time & Time::operator=(const Time & t)

{

cout << "Time & operator=(const Time & t)" << endl;

hours = t.hours;

minutes = t.minutes;

return *this;

};

Time Time::operator+(const Time & t) const

{

cout << "Time operator+(const Time & t) const" << endl;

Time temp;

temp.minutes = minutes + t.minutes;

int h = temp.minutes / 60;

temp.minutes = temp.minutes % 60;

 

temp.hours = hours + t.hours + h;

temp.hours = temp.hours % 24;

return temp;

}

Time Time::operator*(int i) const

{

cout << "Time operator*(int i) const" << endl;

Time temp;

temp.minutes = minutes * i;

int h = temp.minutes / 60;

temp.minutes = temp.minutes % 60;

 

temp.hours = hours + h;

temp.hours = temp.hours % 24;

return temp;

}

ostream & operator<<(ostream & os, Time & t)

{

cout << "ostream & operator<<(ostream & os, Time & t)" << endl;

os << t.hours << " : " << t.minutes << endl;

return os;

};

 

//main.cpp

#include "time.h"

int main(int argc, char** argv)

{

Time t1;

Time t2(10, 35);

Time t3(13, 36);

t1 = t2 + t3;

cout <<t1;

Time t4;

t4 = t1 * 6;

cout << t4 << endl;

return 0;

}

运行结果:

Time()

Time(int hours, int minutes = 0)

Time(int hours, int minutes = 0)

Time operator+(const Time & t) const

Time()

Time(const Time & t)

~Time()

Time & operator=(const Time & t)

~Time()

ostream & operator<<(ostream & os, Time & t)

0 : 11

Time()

Time operator*(int i) const

Time()

Time(const Time & t)

~Time()

Time & operator=(const Time & t)

~Time()

ostream & operator<<(ostream & os, Time & t)

1 : 6

 

~Time()

~Time()

~Time()

~Time()

代码解释:当执行t2 + t3时,调用Time operator+(const Time & t) const,它等价于t2.operator+(t3)。函数后面的const表示对象t2内的数据成员只读,其只能用于成员函数,函数声明和函数定义都需要加const。

为什么Time & operator=(const Time & t)返回值类型可以是引用,而Time operator+(const Time & t) const返回值必须是临时变量的对象。因为执行t1 = t2,传入的参数分别是t1对象和t2对象,需要修改的是t1对象,因此返回值可以引用t1对象。而t1 = t2 + t3实际上执行2个运算符函数,先计算t2 + t3,再将返回对象赋值给t1,即分别执行+运算符和赋值运算符;执行t1 = t2 + t3时,传入的是t1、t2和t3对象,需要修改的是t1对象,而t2、t3对象不修改;因此执行t2 + t3时,t2和t3不能修改,+运算符返回一个中间变量的对象temp,最后将temp赋值给t1。

类对象执行双重运算时,如 A = B + C、 A = B - C、 A = B * C...等,第一重运算符函数如+、-、*、/等,其返回值类型不能是对象引用,必须返回临时变量。其中A = ++B例外,因为执行前++运算符函数时,B就需要修改,再将修改后的返回对象赋值给A,因此前++运算符函数的返回值类型为引用。而A = B++,是先执行A = B,再执行B++,因此后++运算符函数的返回值为临时变量。

现在复习下类成员函数和普通函数之间的区别。1.默认形参只能设置在类定义的函数声明中,此函数必须是构造函数;2.默认数据成员只能设置在类成员函数实现的cpp文件中,此函数必须是构造函数;3.友元函数的friend只能设置在类定义的函数声明中,此函数是类外函数;4.const函数中,在类定义的成员函数声明和cpp文件的类成员函数实现都需要设置const,此函数必须是成员函数。

问题:对于上面的代码还是有问题的,当执行t1 * 6,实际上执行的是t1.operator(6);但是执行6 * t1,该怎么办呢?总不能执行6.operator(t1)吧。因此,我们还需要一个类外函数,其参数1是int,参数2是Time对象。

//time.h

#include <iostream>

using namespace std;

 

class Time {

private:

int hours;   //时,范围0~23

int minutes; //分,范围0~59

public:

Time() :hours(0), minutes(0) { cout << "Time()" << endl; };

Time(int hours, int minutes = 0) :hours(hours), minutes(minutes)

{

cout << "Time(int hours, int minutes = 0)" << endl;

};

~Time() { cout << "~Time()" << endl; };

Time(const Time & t)

{

cout << "Time(const Time & t)" << endl; hours = t.hours; minutes = t.minutes;

};

Time & operator=(const Time & t);

friend ostream & operator<<(ostream & os, Time & t);

Time operator+(const Time & t) const; //相当于friend Time operator+(const Time & t1,const Time & t2);

Time operator*(int i) const;  //minutes * i,相当于friend Time operator*(const Time & t,int i)

friend Time operator*(int i,const Time & t);

};

 

//time.cpp

# include "time.h"

Time & Time::operator=(const Time & t)

{

cout << "Time & operator=(const Time & t)" << endl;

hours = t.hours;

minutes = t.minutes;

return *this;

};

Time Time::operator+(const Time & t) const

{

cout << "Time operator+(const Time & t) const" << endl;

Time temp;

temp.minutes = minutes + t.minutes;

int h = temp.minutes / 60;

temp.minutes = temp.minutes % 60;

 

temp.hours = hours + t.hours + h;

temp.hours = temp.hours % 24;

return temp;

}

Time Time::operator*(int i) const

{

cout << "Time operator*(int i) const" << endl;

Time temp;

temp.minutes = minutes * i;

int h = temp.minutes / 60;

temp.minutes = temp.minutes % 60;

 

temp.hours = hours + h;

temp.hours = temp.hours % 24;

return temp;

}

ostream & operator<<(ostream & os, Time & t)

{

cout << "ostream & operator<<(ostream & os, Time & t)" << endl;

os << t.hours << " : " << t.minutes << endl;

return os;

};

Time operator*(int i, const Time & t)

{

cout << "Time operator*(int i, const Time & t)" << endl;

return t * i;  //执行类Time的成员函数operator*

}

 

//main.cpp

#include "time.h"

int main(int argc, char** argv)

{

Time t1;

Time t2(10, 35);

Time t3(13, 36);

t1 = t2 + t3;

cout <<t1;

Time t4;

t4 = 6 * t1;//t4 = t1 * 6;

cout << t4 << endl;

return 0;

}

运行结果:

Time()

Time(int hours, int minutes = 0)

Time(int hours, int minutes = 0)

Time operator+(const Time & t) const

Time()

Time(const Time & t)

~Time()

Time & operator=(const Time & t)

~Time()

ostream & operator<<(ostream & os, Time & t)

0 : 11

Time()

Time operator*(int i, const Time & t)

Time operator*(int i) const

Time()

Time(const Time & t)

~Time()

Time & operator=(const Time & t)

~Time()

ostream & operator<<(ostream & os, Time & t)

1 : 6

 

~Time()

~Time()

~Time()

~Time()

问题:解决了乘法的问题,还有加法呢?在加法运算中,有++i和i++运算符函数,分别称为前++运算符和后++运算符。针对Time类,++t可以用Time & operator++(void)来表示;t++用Time operator++(int a)表示。由于t1 = ++t是t先加1,再赋值,因此执行前++运算时,可以返回引用t,因为传入的参数t在执行++运算时就需要修改,并将修改后的对象返回给t1。而t1 = t++是t先执行t1 = t,再将t加1,因此后++运算符函数返回的是临时变量。默认约定:前++运算符和后++运算符函数通过传入参数int a来区分,a可以是任意值,不加int a的函数是前++运算符函数,加int a的函数是后++运算符函数。

//time.h

#include <iostream>

using namespace std;

 

class Time {

private:

int hours;   //时,范围0~23

int minutes; //分,范围0~59

public:

Time() :hours(0), minutes(0) { cout << "Time()" << endl; };

Time(int hours, int minutes = 0) :hours(hours), minutes(minutes)

{

cout << "Time(int hours, int minutes = 0)" << endl;

};

~Time() { cout << "~Time()" << endl; };

Time(const Time & t)

{

cout << "Time(const Time & t)" << endl; hours = t.hours; minutes = t.minutes;

};

Time & operator=(const Time & t);

friend ostream & operator<<(ostream & os, Time & t);

Time operator+(const Time & t) const; //相当于friend Time operator+(const Time & t1,const Time & t2);

Time operator*(int i) const;  //minutes * i,相当于friend Time operator*(const Time & t,int i)

friend Time operator*(int i,const Time & t);

Time & operator++(void);//前++运算符函数

Time operator++(int a); //后++运算符函数

};

 

//time.cpp

# include "time.h"

Time & Time::operator=(const Time & t)

{

cout << "Time & operator=(const Time & t)" << endl;

hours = t.hours;

minutes = t.minutes;

return *this;

};

Time Time::operator+(const Time & t) const

{

cout << "Time operator+(const Time & t) const" << endl;

Time temp;

temp.minutes = minutes + t.minutes;

int h = temp.minutes / 60;

temp.minutes = temp.minutes % 60;

 

temp.hours = hours + t.hours + h;

temp.hours = temp.hours % 24;

return temp;

}

Time Time::operator*(int i) const

{

cout << "Time operator*(int i) const" << endl;

Time temp;

temp.minutes = minutes * i;

int h = temp.minutes / 60;

temp.minutes = temp.minutes % 60;

 

temp.hours = hours + h;

temp.hours = temp.hours % 24;

return temp;

}

Time & Time::operator++(void)

{

cout << "Time & operator++(void)" << endl;

minutes += 1;

int h = minutes / 60;

minutes = minutes % 60;

hours = hours + 1 + h;

hours = hours % 24;

return *this;

}

Time Time::operator++(int a)

{

cout << "Time operator++(int a)" << endl;

Time temp;

temp = *this;

minutes += 1;

int h = minutes / 60;

minutes = minutes % 60;

hours = hours + 1 + h;

hours = hours % 24;

return temp;

}

ostream & operator<<(ostream & os, Time & t)

{

cout << "ostream & operator<<(ostream & os, Time & t)" << endl;

os << t.hours << " : " << t.minutes << endl;

return os;

};

Time operator*(int i, const Time & t)

{

cout << "Time operator*(int i, const Time & t)" << endl;

return t * i;  //执行类Time的成员函数operator*

}

 

//main.cpp

#include "time.h"

int main(int argc, char** argv)

{

Time t1;

Time t2;

Time t3(10, 59);

t1 = ++t3;

cout <<t1<<t3;

t2 = t3++;

cout << t2 << t3;

return 0;

}

运行结果:

Time()

Time()

Time(int hours, int minutes = 0)

Time & operator++(void)

Time & operator=(const Time & t)

ostream & operator<<(ostream & os, Time & t)

12 : 0

ostream & operator<<(ostream & os, Time & t)

12 : 0

Time operator++(int a)

Time()

Time & operator=(const Time & t)

Time(const Time & t)

~Time()

Time & operator=(const Time & t)

~Time()

ostream & operator<<(ostream & os, Time & t)

12 : 0

ostream & operator<<(ostream & os, Time & t)

13 : 1

~Time()

~Time()

~Time()

6.9友元

6.9.1友元函数

友元主要分为友元函数和友元类,这一小节先讲友元函数。在前面的类Student和类Time中都用到了友元函数,也就是在类定义中加friend的函数。那么友元函数和成员函数到底有什么区别呢?

类外函数是无法访问类的数据成员,当函数名前加上friend,写入类定义中,表示这个函数是类的友元函数,从而得到访问类数据成员的权限。友元函数格式:函数声明写在类定义中,函数名前加上friend;函数定义写在类成员函数实现的cpp文件中,不能加friend和类名。

成员函数可以转换成友元函数。以Time类来举例,当执行t1 * 3时,其调用Time成员函数是t1.operator*(3),若将其转换成普通形式的函数是operator(t1,3),但是普通形式的函数无法访问类对象的数据成员,因此在Time类的定义中,在函数前加friend,表示这个函数是Time类的友元函数,可以访问Time类的数据成员。因此Time operator*(int n) const转换成类外友元函数是friend Time operator(Time & t,int n)。

第一形参非对象的友元函数无法转换为成员函数。同样以Time举例,当执行3 * t1时,其调用友元函数是friend Time operator*(int n,Time & t)。由于无法执行3.operator(t1),无法构造成员函数,因为只有类对象才可以调用成员函数。结论:当类对象调用的成员函数转换为普通函数时,对象本身会默认为是普通函数的第一形参;因此当普通函数的第一形参不是类对象时,此函数若要访问对象数据成员,只能写成友元函数。

//time.h

#include <iostream>

using namespace std;

 

class Time {

private:

int hours;   //时,范围0~23

int minutes; //分,范围0~59

public:

Time() :hours(0), minutes(0) { cout << "Time()" << endl; };

Time(int hours, int minutes = 0) :hours(hours), minutes(minutes)

{

cout << "Time(int hours, int minutes = 0)" << endl;

};

~Time()

{

cout << "~Time()" << endl;

};

Time(const Time & t)

{

cout << "Time(const Time & t)" << endl; hours = t.hours; minutes = t.minutes;

};

Time & operator=(const Time & t);

friend ostream & operator<<(ostream & os, Time & t);

friend Time operator+(const Time & t1, const Time & t2); //相当于Time operator+(const Time & t) const

friend Time operator*(const Time & t, int i);  //minutes * i,相当于Time operator*(int i) const

friend Time operator*(int i,const Time & t);

Time & operator++(void);//前++运算符函数

Time operator++(int a); //后++运算符函数

};

 

//time.cpp

# include "time.h"

Time & Time::operator=(const Time & t)

{

cout << "Time & operator=(const Time & t)" << endl;

hours = t.hours;

minutes = t.minutes;

return *this;

};

Time operator+(const Time & t1, const Time & t2)

{

cout << "friend Time operator+(const Time & t1, const Time & t2)" << endl;

Time temp;

temp.minutes = t1.minutes + t2.minutes;

int h = temp.minutes / 60;

temp.minutes = temp.minutes % 60;

 

temp.hours = t1.hours + t2.hours + h;

temp.hours = temp.hours % 24;

return temp;

}

Time operator*(const Time & t, int i)

{

cout << "friend Time operator*(const Time & t, int i)" << endl;

Time temp;

temp.minutes =t. minutes * i;

int h = temp.minutes / 60;

temp.minutes = temp.minutes % 60;

 

temp.hours = t.hours + h;

temp.hours = temp.hours % 24;

return temp;

}

Time & Time::operator++(void)

{

cout << "Time & operator++(void)" << endl;

minutes += 1;

int h = minutes / 60;

minutes = minutes % 60;

hours = hours + 1 + h;

hours = hours % 24;

return *this;

}

Time Time::operator++(int a)

{

cout << "Time operator++(int a)" << endl;

Time temp;

temp = *this;

minutes += 1;

int h = minutes / 60;

minutes = minutes % 60;

hours = hours + 1 + h;

hours = hours % 24;

return temp;

}

ostream & operator<<(ostream & os, Time & t)

{

cout << "ostream & operator<<(ostream & os, Time & t)" << endl;

os << t.hours << " : " << t.minutes << endl;

return os;

};

Time operator*(int i, const Time & t)

{

cout << "Time operator*(int i, const Time & t)" << endl;

return t * i;  //执行类Time的成员函数operator*

}

 

//main.cpp

#include "time.h"

 

int main(int argc, char** argv)

{

Time t1(10,35);

Time t2(14, 35);

Time t3 = t1 + t2;

cout << t3;

Time t4 = t3 * 3;

cout << t4;

return 0;

}

运行结果:

Time(int hours, int minutes = 0)

Time(int hours, int minutes = 0)

friend Time operator+(const Time & t1, const Time & t2)

Time()

Time(const Time & t)

~Time()

ostream & operator<<(ostream & os, Time & t)

1 : 10

friend Time operator*(const Time & t, int i)

Time()

Time(const Time & t)

~Time()

ostream & operator<<(ostream & os, Time & t)

1 : 30

~Time()

~Time()

~Time()

~Time()

6.9.2友元类

友元类顾名思义,在类Class_name1的定义中,使用“friend class Class_name2”的形式,来声明Class_name2是类Class_name1的友元类,因此Class_name2可以访问Class_name1的所有数据成员。

友元类的声明可以放在原始类定义的公有、私有、保护部分,其与位置无关,通常情况下回放在公有部分。在友元类的头文件中一定要包含原始类的头文件,不然无法访问;同时为了防止头文件被重复包含,使用#ifndef、#define、#endif。

//student.h

#ifndef _STUDENT_H_

#define _STUDENT_H_

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0);

~Student();

Student(const Student &stu);

Student & operator=(const Student & stu);

friend ostream & operator<<(ostream & os, Student& stu);

friend class Teacher;

};  //需要使用分号结尾

#endif

 

//teacher.h

#ifndef _TEACHER_H_

#define _TEACHER_H_

#include "student.h"

 

class Teacher {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

public:

Teacher();

Teacher(const char * name, int age = 30, school sch = school::primary_school, const char * curriculum = "no curriculum");

~Teacher();

Teacher(const Teacher & tech);

Teacher & operator=(const Teacher & tech);

friend ostream & operator<<(ostream & os, Teacher & tech);

void look_stu_achiv(Student & stu);

};

#endif

 

//student.cpp

#include "student.h"

 

Student::Student():age(0),sch(school::primary_school),achievement(0)

{

cout << "Student()" << endl;

const char * name = "no name";

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

const char * curriculum = "no curriculum";

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement) : age(age), sch(sch), achievement(achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::~Student()

{

cout << "~Student()" << endl;

if (this->name)

{

delete this->name;

}

if (this->curriculum)

delete this->curriculum;

}

Student::Student(const Student &stu)

{

cout << "Student(const Student &stu)" << endl;

this->name = new char[strlen(stu.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(stu.name) + 1, stu.name);  //深拷贝

this->age = stu.age;

this->sch = stu.sch;

this->curriculum = new char[strlen(stu.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(stu.curriculum) + 1, stu.curriculum);  //深拷贝

this->achievement = stu.achievement;

}

Student & Student::operator=(const Student & stu)

{

cout << "Student & operator=(const Student & stu)" << endl;

if (this == &stu)

return *this;

if (this->name)

delete this->name;

if (this->curriculum)

delete this->curriculum;

 

this->name = new char[strlen(stu.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(stu.name) + 1, stu.name);  //深拷贝

this->age = stu.age;

this->sch = stu.sch;

this->curriculum = new char[strlen(stu.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(stu.curriculum) + 1, stu.curriculum);  //深拷贝

this->achievement = stu.achievement;

 

return *this;

}

 

ostream & operator<<(ostream & os, Student& stu)

{

os << "学生姓名:" << stu.name << endl;

os << "年龄:" << stu.age << endl;

switch (int(stu.sch))

{

case 0:

os << "学校:" << "primary_school" << endl;

break;

case 1:

os << "学校:" << "middle_school" << endl;

break;

case 2:

os << "学校:" << "collge" << endl;

break;

default:

os << "学校:" << "..." << endl;

}

os << "课程:" << stu.curriculum << endl;

os << "成绩:" << stu.achievement << endl;

return os;  //返回cout

}

 

//teacher.cpp

#include "teacher.h"

 

Teacher::Teacher() :age(30), sch(school::primary_school)

{

cout << "Teacher()" << endl;

const char * name = "no name";

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(name) + 1, name);  //深拷贝

const char * curriculum = "no curriculum";

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

 

Teacher::Teacher(const char* name, int age, school sch, const char* curriculum) : age(age), sch(sch)

{

cout << "Teacher(const char * name, int age, school sch, const char * curriculum)" << endl;

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(name) + 1, name);  //深拷贝

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

 

Teacher::~Teacher()

{

cout << "~Teacher()" << endl;

if (this->name)

{

delete this->name;

}

if (this->curriculum)

delete this->curriculum;

}

Teacher::Teacher(const Teacher &tech)

{

cout << "Teacher(const Teacher &tech)" << endl;

this->name = new char[strlen(tech.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(tech.name) + 1, tech.name);  //深拷贝

this->age = tech.age;

this->sch = tech.sch;

this->curriculum = new char[strlen(tech.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(tech.curriculum) + 1, tech.curriculum);  //深拷贝

}

 

Teacher & Teacher::operator=(const Teacher & tech)

{

cout << "Teacher & operator=(const Teacher & tech)" << endl;

if (this == &tech)

return *this;

if (this->name)

delete this->name;

if (this->curriculum)

delete this->curriculum;

 

this->name = new char[strlen(tech.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(tech.name) + 1, tech.name);  //深拷贝

this->age = tech.age;

this->sch = tech.sch;

this->curriculum = new char[strlen(tech.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(tech.curriculum) + 1, tech.curriculum);  //深拷贝

 

return *this;

}

 

ostream & operator<<(ostream & os, Teacher& tech)

{

os << "老师姓名:" << tech.name << endl;

os << "年龄:" << tech.age << endl;

switch (int(tech.sch))

{

case 0:

os << "学校:" << "primary_school" << endl;

break;

case 1:

os << "学校:" << "middle_school" << endl;

break;

case 2:

os << "学校:" << "collge" << endl;

break;

default:

os << "学校:" << "..." << endl;

}

os << "课程:" << tech.curriculum << endl;

return os;  //返回cout

}

 

void Teacher::look_stu_achiv(Student & stu)//同学校同课程的老师可以查阅学生成绩

{

if (this->sch != stu.sch)

return;

if (0 != _stricmp(this->curriculum, stu.curriculum))  //使用_stricmp()无视字符大小写

return;

cout <<"老师开始查阅学生成绩..."<<endl<< stu<<"查阅结束!"<<endl;

 

}

 

//main.cpp

#include "student.h"

#include "teacher.h"

 

int main(int argc, char **argv)

{

 

Student stu1("lisi", 15, school::middle_school, "Math", 90);

cout <<stu1;

Teacher tech("wanglaoshi", 32, school::middle_school, "math");

tech.look_stu_achiv(stu1);

cout << tech;

return 0;

}

运行结果:

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:lisi

年龄:15

学校:middle_school

课程:Math

成绩:90

Teacher(const char * name, int age, school sch, const char * curriculum)

老师开始查阅学生成绩...

学生姓名:lisi

年龄:15

学校:middle_school

课程:Math

成绩:90

查阅结束!

老师姓名:wanglaoshi

年龄:32

学校:middle_school

课程:math

~Teacher()

~Student()

6.10静态成员和嵌套类

6.10.1静态成员

在类定义中,每个实例化对象都有自己的一套数据成员。那么当所有实例化对象需要共用一个数据时,该如何表示呢?这就是本小节所说的知识点—静态成员。

静态成员分为静态数据成员和静态成员函数。静态数据成员由于其脱离对象而存在,必须在对象创建前就应该初始化;同时静态数据成员具有唯一性,其不能放入类定义的头文件中。因此,将静态成员放在类成员函数实现的cpp文件中最为合适。

在类定义的成员函数声明前加上static(cpp文件中,函数定义不能加static),表示此函数为静态成员函数。由于静态成员函数不和实例化对象相关联,因此其只能访问类的静态数据成员。静态成员函数可以由任何一个对象来访问,也可以通过类名和作用域运算符来访问。

以类Student为例,现在在类定义中加入静态数据成员count来表示学生的个数。在调用构造函数时,count加1;调用析构函数时,count减1。

//student.h

#ifndef _STUDENT_H_

#define _STUDENT_H_

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

static int count;

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0);

~Student();

Student(const Student &stu);

Student & operator=(const Student & stu);

friend ostream & operator<<(ostream & os, Student& stu);

friend class Teacher;

static int get_count(void);

};  //需要使用分号结尾

#endif

 

//teacher.h

#ifndef _TEACHER_H_

#define _TEACHER_H_

#include "student.h"

 

class Teacher {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

public:

Teacher();

Teacher(const char * name, int age = 30, school sch = school::primary_school, const char * curriculum = "no curriculum");

~Teacher();

Teacher(const Teacher & tech);

Teacher & operator=(const Teacher & tech);

friend ostream & operator<<(ostream & os, Teacher & tech);

void look_stu_achiv(Student & stu);

};

#endif

 

//student.cpp

#include "student.h"

int Student::count = 0; //静态数据成员初始化

 

Student::Student():age(0),sch(school::primary_school),achievement(0)

{

cout << "Student()" << endl;

++count;

const char * name = "no name";

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

const char * curriculum = "no curriculum";

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement) : age(age), sch(sch), achievement(achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

++count;

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name),strlen(name)+1, name);  //深拷贝

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

Student::~Student()

{

cout << "~Student()" << endl;

--count;

if (this->name)

delete this->name;

if (this->curriculum)

delete this->curriculum;

}

Student::Student(const Student &stu)

{

cout << "Student(const Student &stu)" << endl;

++count;

this->name = new char[strlen(stu.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(stu.name) + 1, stu.name);  //深拷贝

this->age = stu.age;

this->sch = stu.sch;

this->curriculum = new char[strlen(stu.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(stu.curriculum) + 1, stu.curriculum);  //深拷贝

this->achievement = stu.achievement;

}

Student & Student::operator=(const Student & stu)

{

cout << "Student & operator=(const Student & stu)" << endl;

if (this == &stu)

return *this;

if (this->name)

delete this->name;

if (this->curriculum)

delete this->curriculum;

 

this->name = new char[strlen(stu.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(stu.name) + 1, stu.name);  //深拷贝

this->age = stu.age;

this->sch = stu.sch;

this->curriculum = new char[strlen(stu.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(stu.curriculum) + 1, stu.curriculum);  //深拷贝

this->achievement = stu.achievement;

 

return *this;

}

int Student::get_count(void)

{

return count;

}

 

ostream & operator<<(ostream & os, Student& stu)

{

os << "学生姓名:" << stu.name << endl;

os << "年龄:" << stu.age << endl;

switch (int(stu.sch))

{

case 0:

os << "学校:" << "primary_school" << endl;

break;

case 1:

os << "学校:" << "middle_school" << endl;

break;

case 2:

os << "学校:" << "collge" << endl;

break;

default:

os << "学校:" << "..." << endl;

}

os << "课程:" << stu.curriculum << endl;

os << "成绩:" << stu.achievement << endl;

return os;  //返回cout

}

 

//teacher.cpp

#include "teacher.h"

 

Teacher::Teacher() :age(30), sch(school::primary_school)

{

cout << "Teacher()" << endl;

const char * name = "no name";

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(name) + 1, name);  //深拷贝

const char * curriculum = "no curriculum";

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

 

Teacher::Teacher(const char* name, int age, school sch, const char* curriculum) : age(age), sch(sch)

{

cout << "Teacher(const char * name, int age, school sch, const char * curriculum)" << endl;

this->name = new char[strlen(name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(name) + 1, name);  //深拷贝

this->curriculum = new char[strlen(curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(curriculum) + 1, curriculum);  //深拷贝

}

 

Teacher::~Teacher()

{

cout << "~Teacher()" << endl;

if (this->name)

{

delete this->name;

}

if (this->curriculum)

delete this->curriculum;

}

Teacher::Teacher(const Teacher &tech)

{

cout << "Teacher(const Teacher &tech)" << endl;

this->name = new char[strlen(tech.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(tech.name) + 1, tech.name);  //深拷贝

this->age = tech.age;

this->sch = tech.sch;

this->curriculum = new char[strlen(tech.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(tech.curriculum) + 1, tech.curriculum);  //深拷贝

}

 

Teacher & Teacher::operator=(const Teacher & tech)

{

cout << "Teacher & operator=(const Teacher & tech)" << endl;

if (this == &tech)

return *this;

if (this->name)

delete this->name;

if (this->curriculum)

delete this->curriculum;

 

this->name = new char[strlen(tech.name) + 1];

strcpy_s(const_cast<char *>(this->name), strlen(tech.name) + 1, tech.name);  //深拷贝

this->age = tech.age;

this->sch = tech.sch;

this->curriculum = new char[strlen(tech.curriculum) + 1];

strcpy_s(const_cast<char *>(this->curriculum), strlen(tech.curriculum) + 1, tech.curriculum);  //深拷贝

 

return *this;

}

 

ostream & operator<<(ostream & os, Teacher& tech)

{

os << "老师姓名:" << tech.name << endl;

os << "年龄:" << tech.age << endl;

switch (int(tech.sch))

{

case 0:

os << "学校:" << "primary_school" << endl;

break;

case 1:

os << "学校:" << "middle_school" << endl;

break;

case 2:

os << "学校:" << "collge" << endl;

break;

default:

os << "学校:" << "..." << endl;

}

os << "课程:" << tech.curriculum << endl;

return os;  //返回cout

}

 

void Teacher::look_stu_achiv(Student & stu)

{

if (this->sch != stu.sch)

return;

if (0 != _stricmp(this->curriculum, stu.curriculum))  //使用_stricmp()无视字符大小写

return;

cout <<"老师开始查阅学生成绩..."<<endl<< stu<<"查阅结束!"<<endl;

 

}

 

//main.cpp

#include "student.h"

#include "teacher.h"

 

int main(int argc, char **argv)

{

 

Student stu1("lisi", 15, school::middle_school, "Math", 90);

cout <<stu1;

Teacher tech("wanglaoshi", 32, school::middle_school, "math");

tech.look_stu_achiv(stu1);

cout << tech;

Student stu2;

cout << "the number of student is " << stu1.get_count() << endl;

cout << "the number of student is " << stu2.get_count() << endl;

cout << "the number of student is " << Student::get_count() << endl;

return 0;

}

运行结果:

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:lisi

年龄:15

学校:middle_school

课程:Math

成绩:90

Teacher(const char * name, int age, school sch, const char * curriculum)

老师开始查阅学生成绩...

学生姓名:lisi

年龄:15

学校:middle_school

课程:Math

成绩:90

查阅结束!

老师姓名:wanglaoshi

年龄:32

学校:middle_school

课程:math

Student()

the number of student is 2

the number of student is 2

the number of student is 2

~Student()

~Teacher()

~Student()

6.10.2嵌套类

在C++中,可以将类的定义放在另一个类中,这样的类称为嵌套类。嵌套类中的成员一般为public属性,方便原始类中的成员函数去访问它。嵌套类的作用是为了帮助实现原始类;一般存放在原始类的private内,这样还可以避免名称冲突。

还记得前面结构体章节中的数据结构吗?其中有队列、链表等,但是它们都是用结构体实现的,现在我们用类来实现其中的单向队列。

//queue.h

#include <iostream>

using namespace std;

 

class Queue

{

private:

//定义队列,由节点、队首指针和队尾指针组成

class Node    //定义队列结点的数据结构

{

public:

double data;  //数据域,存储队列信息

Node *next; //指针域,指向下一个结点

};

Node *front;      //队首指针,指向头节点

Node *rear;       //队尾指针,指向尾节点

int size;         //节点数

public:

Queue();

Queue(double data);

~Queue();

void InQueue(double data);

bool OutQueue(void);

friend ostream & operator<<(ostream & os, Queue & Q);

};

 

//queue.cpp

#include "queue.h"

//队列的成员函数

Queue::Queue()

{

Node *q = nullptr;

q = new Node;         //申请一个结点的空间

q->data = 0;

q->next = nullptr;   //q为头结点,由于只有一个节点,因此q也是尾节点,因此next为null   

front = q;          //队首与队尾指针都指向这个结点

rear = q;

size = 1;

}

Queue::Queue(double data)

{

Node *q = nullptr;

q = new Node;         //申请一个结点的空间,队列太大,放入堆空间

q->data = data;

q->next = nullptr;   //q为头结点,由于只有一个节点,因此q也是尾节点,因此next为null   

front = q;          //队首与队尾指针都指向这个结点

rear = q;

size = 1;

}

Queue::~Queue()

{

Node *p = nullptr;

while(size)               //如果只有一个节点,Q.front->next == null

{

p = front;           //将原来的头结点存放在p中

front = front->next; //第二个节点设置为头结点

delete p;            //释放堆空间,不需要指定p为野指针,因为函数结束,p销毁

--size;

}

}

void Queue::InQueue(double data)

{

Node *p = nullptr;     //新创建一个结点

p = new Node;

p->data = data;        //输入数据信息

p->next = nullptr;

rear->next = p;        //将新结点插入队列尾部,因此前面一个节点的next指向p

rear = p;              //将新结点插入队列尾部后,前面节点不再是尾节点,将p设置为新的尾结点

++size;

}

bool Queue::OutQueue(void)

{

Node *p = nullptr;

if (size)

{

p = front;           //将原来的头结点存放在p中

front = front->next; //第二个节点设置为头结点

delete p;            //释放堆空间,不需要指定p为野指针,因为函数结束,p销毁

--size;

return 1;

}

else

return 0;

}

ostream & operator<<(ostream & os, Queue & Q)

{

if (Q.size)

{

os << "size = " << Q.size << endl;

os << "第1个数据:" << Q.front->data << endl;

//os << "第2个数据:" << Q.front->next->data << endl;

os << "..." << endl;

os << "最后一个数据:" << Q.rear->data << endl;

}

else

os << "没有任何数据" << endl;

return os;

}

 

//main.cpp

#include "queue.h"

 

int main(void)

{

Queue q1;

q1.InQueue(3);

cout << q1;

Queue q2(3.5);

q2.InQueue(4.5);

cout << q2;

q2.OutQueue();

cout << q2;

return 0;

}

运行结果:

size = 2

第1个数据:0

...

最后一个数据:3

size = 2

第1个数据:3.5

...

最后一个数据:4.5

size = 1

第1个数据:4.5

...

最后一个数据:4.5

代码中的Node类就是Queue的嵌套类,它是为了帮助实现Queue而诞生的。

6.11类模板

函数有函数模板,类也有类模板。在类定义前加上template <typename T>,即表示这是个类模板,模板中可以用T来表示一个数据类型。类模板不是类,在编译过程中,会根据T的不同生成不同的类。

在前面的Queue类中,其中存储的数据只能是double,若要存储一个string数据该怎么办呢?

6.11.1类模板和成员函数

//queue.h

#include <iostream>

using namespace std;

 

template <typename T>

class Queue

{

private:

//定义队列,由节点、队首指针和队尾指针组成

class Node    //定义队列结点的数据结构

{

public:

T  data;  //数据域,存储队列信息

Node * next; //指针域,指向下一个结点

};

Node *front;      //队首指针,指向头节点

Node *rear;       //队尾指针,指向尾节点

int size;

public:

Queue();

Queue(T data);

~Queue();

void InQueue(T data);

bool OutQueue(void);

void Test_Member(Queue<T> & Q); //成员函数的声明,形参可以使用Queue & Q,也可以使用Queue<T> & Q

friend ostream & operator<<<>(ostream & os, Queue<T> & Q);

};

 

template <typename T>

Queue<T>::Queue()

{

Node *q = nullptr;

q = new Node;         //申请一个结点的空间

q->next = nullptr;   //q为头结点,由于只有一个节点,因此q也是尾节点,因此next为null   

front = q;          //队首与队尾指针都指向这个结点

rear = q;

size = 1;

}

template <typename T>

Queue<T>::Queue(T data)

{

Node *q = nullptr;

q = new Node;         //申请一个结点的空间,队列太大,放入堆空间

q->data = data;

q->next = nullptr;   //q为头结点,由于只有一个节点,因此q也是尾节点,因此next为null   

front = q;          //队首与队尾指针都指向这个结点

rear = q;

size = 1;

}

template <typename T>

Queue<T>::~Queue()

{

Node *p = nullptr;

while (size)               //如果只有一个节点,Q.front->next == null

{

p = front;           //将原来的头结点存放在p中

front = front->next; //第二个节点设置为头结点

delete p;            //释放堆空间,不需要指定p为野指针,因为函数结束,p销毁

--size;

}

}

template <typename T>

void Queue<T>::InQueue(T data)

{

Node *p = nullptr;     //新创建一个结点

p = new Node;

p->data = data;        //输入数据信息

p->next = nullptr;

rear->next = p;        //将新结点插入队列尾部,因此前面一个节点的next指向p

rear = p;              //将新结点插入队列尾部后,前面节点不再是尾节点,将p设置为新的尾结点

++size;

}

template <typename T>

bool Queue<T>::OutQueue(void)

{

Node *p = nullptr;

if (size)

{

p = front;           //将原来的头结点存放在p中

front = front->next; //第二个节点设置为头结点

delete p;            //释放堆空间,不需要指定p为野指针,因为函数结束,p销毁

--size;

return 1;

}

else

return 0;

}

template <typename T>

ostream & operator<<<>(ostream & os, Queue<T> & Q)//友元函数的定义,形参必须使用Queue<T> & Q

{

if (Q.size)

{

os << "size = " << Q.size << endl;

os << "第1个数据:" << Q.front->data << endl;

//os << "第2个数据:" << Q.front->next->data << endl;

os << "..." << endl;

os << "最后一个数据:" << Q.rear->data << endl;

}

else

os << "没有任何数据" << endl;

return os;

}

template <typename T>

void Queue<T>::Test_Member(Queue<T> & Q) //成员函数的定义,形参可以使用Queue & Q,也可以使用Queue<T> & Q

{

cout << "test1......" << endl;

}

 

//main.cpp

#include "queue.h"

#include <string>

 

int main(void)

{

Queue<int> q1;

q1.InQueue(1);

cout << q1;

Queue<string> qs("hello");

qs.InQueue("world");

cout << qs;

 

return 0;

}

运行结果:

size = 2

第1个数据:-842150451

...

最后一个数据:1

size = 2

第1个数据:hello

...

最后一个数据:world

类模板的定义和函数模板类似,只需要在类定义前加上template <typename T>,再将类定义中的数据类型换成T即可,其中可以使用class来代替typename。

类模板中的成员函数都是函数模板,因此其不能再cpp中实现(编译器只能搜索具体的函数,不能搜索函数模板),将成员函数模板的实现也必须放入头文件中。成员函数前都需要加template <typename T>,同时将Queue::改为Queue<T>::。

模板类Queue定义中的成员函数Test_Member(Queue & data),其函数声明和定义中的函数形参可以用Queue & data或Queue<T> & data。

函数模板可以具体化,类模板也可以具体化。当对Queue<T>的模板实例化Queue<string>不满意时,可以通过template <> class Queue<string>{};来重新定做一个Queue<string>,当2种发生选择冲突时,会优先选择重做的Queue<string>。

6.11.2类模板和友元函数

在前面的类Queue模板中,友元函数—运算符<<重载函数,其声明、定义和成员函数不同。由于友元函数和友元类在类模板中的表现形式相同,因此本小节只对类模板中的友元函数进行讲解。

(1)友元函数是普通的类外函数

//queue.h

#include <iostream>

using namespace std;

 

template <typename T>

class Queue

{

private:

//定义队列,由节点、队首指针和队尾指针组成

class Node    //定义队列结点的数据结构

{

public:

T  data;  //数据域,存储队列信息

Node * next; //指针域,指向下一个结点

};

Node *front;      //队首指针,指向头节点

Node *rear;       //队尾指针,指向尾节点

int size;

public:

Queue();

Queue(T data);

~Queue();

void InQueue(T data);

bool OutQueue(void);

friend ostream & operator<<<>(ostream & os, Queue<T> & Q);//友元函数的声明,形参可以使用Queue & Q,也可以使用Queue<T> & Q

void Test_Member(Queue<T> & Q); //成员函数的声明,形参可以使用Queue & Q,也可以使用Queue<T> & Q

friend void Test1_Friend(void);

friend void Test2_Friend(Queue<T> & Q);

};

 

template <typename T>

Queue<T>::Queue()

{

Node *q = nullptr;

q = new Node;         //申请一个结点的空间

q->next = nullptr;   //q为头结点,由于只有一个节点,因此q也是尾节点,因此next为null   

front = q;          //队首与队尾指针都指向这个结点

rear = q;

size = 1;

}

template <typename T>

Queue<T>::Queue(T data)

{

Node *q = nullptr;

q = new Node;         //申请一个结点的空间,队列太大,放入堆空间

q->data = data;

q->next = nullptr;   //q为头结点,由于只有一个节点,因此q也是尾节点,因此next为null   

front = q;          //队首与队尾指针都指向这个结点

rear = q;

size = 1;

}

template <typename T>

Queue<T>::~Queue()

{

Node *p = nullptr;

while (size)               //如果只有一个节点,Q.front->next == null

{

p = front;           //将原来的头结点存放在p中

front = front->next; //第二个节点设置为头结点

delete p;            //释放堆空间,不需要指定p为野指针,因为函数结束,p销毁

--size;

}

}

template <typename T>

void Queue<T>::InQueue(T data)

{

Node *p = nullptr;     //新创建一个结点

p = new Node;

p->data = data;        //输入数据信息

p->next = nullptr;

rear->next = p;        //将新结点插入队列尾部,因此前面一个节点的next指向p

rear = p;              //将新结点插入队列尾部后,前面节点不再是尾节点,将p设置为新的尾结点

++size;

}

template <typename T>

bool Queue<T>::OutQueue(void)

{

Node *p = nullptr;

if (size)

{

p = front;           //将原来的头结点存放在p中

front = front->next; //第二个节点设置为头结点

delete p;            //释放堆空间,不需要指定p为野指针,因为函数结束,p销毁

--size;

return 1;

}

else

return 0;

}

template <typename T>

ostream & operator<<<>(ostream & os, Queue<T> & Q)//友元函数的定义,形参必须使用Queue<T> & Q

{

if (Q.size)

{

os << "size = " << Q.size << endl;

os << "第1个数据:" << Q.front->data << endl;

//os << "第2个数据:" << Q.front->next->data << endl;

os << "..." << endl;

os << "最后一个数据:" << Q.rear->data << endl;

}

else

os << "没有任何数据" << endl;

return os;

}

 

template <typename T>

void Queue<T>::Test_Member(Queue<T> & Q) //成员函数的定义,形参可以使用Queue & Q,也可以使用Queue<T> & Q

{

cout << "Test_Member......" << endl;

}

 

void Test1_Friend(void)

{

cout << "Test1_Friend......" << endl;

}

 

 

void Test2_Friend(Queue<int> & Q)

{

cout << "Test2_Friend......" << endl;

}

void Test2_Friend(Queue<double> & Q)

{

cout << "Test2_Friend......" << endl;

}

 

 

//main.cpp

#include "queue.h"

#include <string>

 

int main(void)

{

Queue<int> q1;

q1.InQueue(1);

cout << q1;

Queue<string> qs("hello");

qs.InQueue("world");

cout << qs;

Test1_Friend();

Test2_Friend(q1); //Test2_Friend(qs)无法编译,因为没有Test2_Friend(Queue<string> & Q)函数

return 0;

}

运行结果:

size = 2

第1个数据:-842150451

...

最后一个数据:1

size = 2

第1个数据:hello

...

最后一个数据:world

Test1_Friend......

Test2_Friend......

类Queue模板中的void Test1_Friend(void)和void Test2_Friend(Queue<T> & Q)函数都是其友元函数,一个有参数一个没有参数,它们不是成员函数,也不是函数模板,因此函数定义时不能加template <typename T>。

void Test2_Friend(Queue<T> & Q)由于不是函数模板,因此必须根据T的不同来实现Test2_Friend()函数。在代码中,分别实现了void Test2_Friend(Queue<int> & Q)和void Test2_Friend(Queue<double> & Q)。当执行Queue<int> q1时,Queue<T>会具体化Queue<int>类,并使用void Test2_Friend(Queue<int> & Q)友元函数。针对不同的Queue<T>具体化,需要提供不同的Test2_Friend()函数;并且所有的模板具体化共用一套Test2_Friend()函数。

void Test2_Friend(Queue<int> & Q)在类定义的声明中,形参可以使用Queue<int> & Q,也可以使用Queue & Q。在老编译器中,形参只能使用Queue<int> & Q,当然也建议大家这么使用。

(2)友元函数是类外声明的函数模板

还记得前面的ostream & operator<<<>(ostream & os, Queue<T> & Q)函数吗?它为什么在函数名的后面加<>呢?头文件ostream中提供了一个输出流模板basic_ostream,而operator<<函数就是一个函数模板,在头文件iostream中声明。

//queue.h

#include <iostream>

using namespace std;

 

template <typename TT>

void Test1_Friend(void);

template <typename TT>

void Test2_Friend(TT & Q);

 

template <typename T>

class Queue

{

private:

//定义队列,由节点、队首指针和队尾指针组成

class Node    //定义队列结点的数据结构

{

public:

T  data;  //数据域,存储队列信息

Node * next; //指针域,指向下一个结点

};

Node *front;      //队首指针,指向头节点

Node *rear;       //队尾指针,指向尾节点

int size;

public:

Queue();

Queue(T data);

~Queue();

void InQueue(T data);

bool OutQueue(void);

friend ostream & operator<<<>(ostream & os, Queue<T> & Q);//友元函数的声明,形参可以使用Queue & Q,也可以使用Queue<T> & Q

void Test_Member(Queue<T> & Q); //成员函数的声明,形参可以使用Queue & Q,也可以使用Queue<T> & Q

friend void Test1_Friend<T>(void);

friend void Test2_Friend<>(Queue<T> & Q);

};

 

template <typename T>

Queue<T>::Queue()

{

Node *q = nullptr;

q = new Node;         //申请一个结点的空间

q->next = nullptr;   //q为头结点,由于只有一个节点,因此q也是尾节点,因此next为null   

front = q;          //队首与队尾指针都指向这个结点

rear = q;

size = 1;

}

template <typename T>

Queue<T>::Queue(T data)

{

Node *q = nullptr;

q = new Node;         //申请一个结点的空间,队列太大,放入堆空间

q->data = data;

q->next = nullptr;   //q为头结点,由于只有一个节点,因此q也是尾节点,因此next为null   

front = q;          //队首与队尾指针都指向这个结点

rear = q;

size = 1;

}

template <typename T>

Queue<T>::~Queue()

{

Node *p = nullptr;

while (size)               //如果只有一个节点,Q.front->next == null

{

p = front;           //将原来的头结点存放在p中

front = front->next; //第二个节点设置为头结点

delete p;            //释放堆空间,不需要指定p为野指针,因为函数结束,p销毁

--size;

}

}

template <typename T>

void Queue<T>::InQueue(T data)

{

Node *p = nullptr;     //新创建一个结点

p = new Node;

p->data = data;        //输入数据信息

p->next = nullptr;

rear->next = p;        //将新结点插入队列尾部,因此前面一个节点的next指向p

rear = p;              //将新结点插入队列尾部后,前面节点不再是尾节点,将p设置为新的尾结点

++size;

}

template <typename T>

bool Queue<T>::OutQueue(void)

{

Node *p = nullptr;

if (size)

{

p = front;           //将原来的头结点存放在p中

front = front->next; //第二个节点设置为头结点

delete p;            //释放堆空间,不需要指定p为野指针,因为函数结束,p销毁

--size;

return 1;

}

else

return 0;

}

template <typename T>

ostream & operator<<<>(ostream & os, Queue<T> & Q)//友元函数的定义,形参必须使用Queue<T> & Q

{

if (Q.size)

{

os << "size = " << Q.size << endl;

os << "第1个数据:" << Q.front->data << endl;

//os << "第2个数据:" << Q.front->next->data << endl;

os << "..." << endl;

os << "最后一个数据:" << Q.rear->data << endl;

}

else

os << "没有任何数据" << endl;

return os;

}

 

template <typename T>

void Queue<T>::Test_Member(Queue<T> & Q) //成员函数的定义,形参可以使用Queue & Q,也可以使用Queue<T> & Q

{

cout << "Test_Member......" << endl;

}

 

template <typename TT>

void Test1_Friend(void)

{

cout << "Test1_Friend......" << endl;

}

 

template <typename TT>

void Test2_Friend(TT & Q)

{

cout << "Test2_Friend......" << endl;

}

 

//main.cpp

#include "queue.h"

#include <string>

 

int main(void)

{

Queue<int> q1;

q1.InQueue(1);

cout << q1;

Queue<string> qs("hello");

qs.InQueue("world");

cout << qs;

Test1_Friend<int>();

Test2_Friend(q1);

Test2_Friend(qs);

return 0;

}

运行结果:

size = 2

第1个数据:-842150451

...

最后一个数据:1

size = 2

第1个数据:hello

...

最后一个数据:world

Test1_Friend......

Test2_Friend......

函数void Test1_Friend(void)和void Test2_Friend(TT & Q)分别是类外的函数模板,由于它在类中使用了,因此需要在类Queue<T>定义前进行函数模板声明;其函数模板的实现放在类Queue<T>定义的后面。为了区分函数模板和类模板,其表示的数据类型分别是TT和T。

函数void Test1_Friend(void)和void Test2_Friend(TT & Q)是在类外声明和定义的,在类内需要函数模板具体化。因此在类Queue<T>中,friend void Test1_Friend<T>(void)表示TT = T;friend void Test2_Friend<>(Queue<T> & Q)表示TT = Queue<T>,因此可以写成friend void Test2_Friend<Queue<T>>(Queue<T> & Q)。

当执行Queue<int> q1时,类模板具体化时,会创建友元函数friend void Test1_Friend<int>(void)和friend void Test2_Friend<Queue<int>>(Queue<int> & Q)。因此每个类模板实例化都会有一套自己的友元函数;但是同类型的对象共用同一个友元函数,如Queue<int> q1何Queue<int> q2共用的是相同的友元函数。

在main()函数中,执行Test2_Friend(q1)会调用friend void Test2_Friend<Queue<int>>(Queue<int> & Q)函数;而执行Test1_Friend()却无法找到对应的具体函数,因此需要写成Test1_Friend<int>()。

(3)友元函数是类内定义的函数模板

当出现template <typename T1,typename T2> fun(T1 a,T2 b)时该怎么办?在类Queue<T>模板中只能具体化其中的T1或T2,那另一个数据类型该怎么办呢?我们可以在类内定义一个函数模板。

//queue.h

#include <iostream>

using namespace std;

 

template <typename T>

class Queue

{

private:

//定义队列,由节点、队首指针和队尾指针组成

class Node    //定义队列结点的数据结构

{

public:

T  data;  //数据域,存储队列信息

Node * next; //指针域,指向下一个结点

};

Node *front;      //队首指针,指向头节点

Node *rear;       //队尾指针,指向尾节点

int size;

public:

Queue();

Queue(T data);

~Queue();

void InQueue(T data);

bool OutQueue(void);

friend ostream & operator<<<>(ostream & os, Queue<T> & Q);//友元函数的声明,形参可以使用Queue & Q,也可以使用Queue<T> & Q

void Test_Member(Queue<T> & Q); //成员函数的声明,形参可以使用Queue & Q,也可以使用Queue<T> & Q

template <typename T1, typename T2> friend void Test_Friend(T1 &a, T2 &b);

};

 

template <typename T>

Queue<T>::Queue()

{

Node *q = nullptr;

q = new Node;         //申请一个结点的空间

q->next = nullptr;   //q为头结点,由于只有一个节点,因此q也是尾节点,因此next为null   

front = q;          //队首与队尾指针都指向这个结点

rear = q;

size = 1;

}

template <typename T>

Queue<T>::Queue(T data)

{

Node *q = nullptr;

q = new Node;         //申请一个结点的空间,队列太大,放入堆空间

q->data = data;

q->next = nullptr;   //q为头结点,由于只有一个节点,因此q也是尾节点,因此next为null   

front = q;          //队首与队尾指针都指向这个结点

rear = q;

size = 1;

}

template <typename T>

Queue<T>::~Queue()

{

Node *p = nullptr;

while (size)               //如果只有一个节点,Q.front->next == null

{

p = front;           //将原来的头结点存放在p中

front = front->next; //第二个节点设置为头结点

delete p;            //释放堆空间,不需要指定p为野指针,因为函数结束,p销毁

--size;

}

}

template <typename T>

void Queue<T>::InQueue(T data)

{

Node *p = nullptr;     //新创建一个结点

p = new Node;

p->data = data;        //输入数据信息

p->next = nullptr;

rear->next = p;        //将新结点插入队列尾部,因此前面一个节点的next指向p

rear = p;              //将新结点插入队列尾部后,前面节点不再是尾节点,将p设置为新的尾结点

++size;

}

template <typename T>

bool Queue<T>::OutQueue(void)

{

Node *p = nullptr;

if (size)

{

p = front;           //将原来的头结点存放在p中

front = front->next; //第二个节点设置为头结点

delete p;            //释放堆空间,不需要指定p为野指针,因为函数结束,p销毁

--size;

return 1;

}

else

return 0;

}

template <typename T>

ostream & operator<<<>(ostream & os, Queue<T> & Q)//友元函数的定义,形参必须使用Queue<T> & Q

{

if (Q.size)

{

os << "size = " << Q.size << endl;

os << "第1个数据:" << Q.front->data << endl;

//os << "第2个数据:" << Q.front->next->data << endl;

os << "..." << endl;

os << "最后一个数据:" << Q.rear->data << endl;

}

else

os << "没有任何数据" << endl;

return os;

}

 

template <typename T>

void Queue<T>::Test_Member(Queue<T> & Q) //成员函数的定义,形参可以使用Queue & Q,也可以使用Queue<T> & Q

{

cout << "Test_Member......" << endl;

}

 

template <typename T1, typename T2>

void Test_Friend(T1 &a, T2 &b)

{

cout << "Test_Friend......" << endl;

}

 

//main.cpp

#include "queue.h"

#include <string>

 

int main(void)

{

Queue<int> q1;

q1.InQueue(1);

cout << q1;

Queue<string> qs("hello");

qs.InQueue("world");

cout << qs;

Test_Friend(q1, qs);

return 0;

}

运行结果:

size = 2

第1个数据:-842150451

...

最后一个数据:1

size = 2

第1个数据:hello

...

最后一个数据:world

Test_Friend......

在类中定义一个函数模板,当类Queue<T>模板具体化时,会产生一套函数模板,此函数模板一般在main()中调用时具体化。当执行Test_Friend(q1, qs),实际执行的函数是void Test_Friend(Queue<int> &a, Queue<string> &b)。

在前面友元函数是类外定义的函数模板中,函数模板是在类Queue<T>模板定义中确定了TT的数据类型;而友元函数是类内定义的函数模板中,函数模板是在main()函数中确定T1和T2的数据类型。什么时候在类外定义函数模板,什么时候在类内定义函数模板呢?当在类模板中无法对函数模板具体化时,使用类内定义函数模板;否则使用类外定义函数模板。

友元函数是类外定义的函数模板,又称为约束模板友元函数;友元函数是类内定义的函数模板时,又称为非约束模板友元函数。

3种不同的方式,分别表示了类模板具体化共用一套友元函数;类模板具体化都有自己的一套友元函数;类模板具体化会产生一套友元函数模板。

6.12名称空间

为了解决名称冲突的问题,在C++中将不同的代码库放入不同的名称空间中。名称空间又称命名空间。

//person.h

#include <iostream>

#include <string>

//using namespace std;

namespace A{

#ifndef _PERSON_H_

#define _PERSON_H_

class Person {

private:

std::string name;

int age;

public:

Person() {};

Person(std::string name,int age)

{

this->name = name;

this->age = age;

}

~Person(){};

void printinfo(void)

{

std::cout << this->name << ": " << this->age <<std::endl;

}

};

#endif

}

 

//student.h

#include <iostream>

#include <string>

//using namespace std;

namespace B{

#ifndef _STUDENT_H_

#define _STUDENT_H_

class Student {

private:

std::string name;

int age;

public:

Student() {};

Student(std::string name, int age)

{

this->name = name;

this->age = age;

}

~Student() {};

void printinfo(void)

{

std::cout << this->name << ": " << this->age << std::endl;

}

};

#endif

}

 

//teacher.h

#include <iostream>

#include <string>

//using namespace std;

namespace C {

#ifndef _TEACHER_H_

#define _TEACHER_H_

class Teacher {

private:

std::string name;

int age;

public:

Teacher() {};

Teacher(std::string name, int age)

{

this->name = name;

this->age = age;

}

~Teacher() {};

void printinfo(void)

{

std::cout << this->name << ": " << this->age << std::endl;

}

};

#endif

}

 

//main.cpp

#include "person.h"

#include "student.h"

#include "teacher.h"

using namespace A;

using B::Student;  //也可以放入main()中

int main(int argc, char **argv)

{

Person per("zhangsan", 25);

per.printinfo();

Student stu("lisi", 12);

stu.printinfo();

C::Teacher tech("wanglaoshi", 32);

tech.printinfo();

return 0;

}

运行结果:

zhangsan: 25

lisi: 12

wanglaoshi: 32

名称空间由3种用法:using namespace A;using B::Student;C::Teacher tech("wanglaoshi", 32)。第一种用法在main()函数中可以使用名称空间A中所有的类和变量;第二种用法在main()函数中可以使用名称空间B的类Student;第三种用法是直接在main()函数中使用名称空间C::Teacher。

若名称空间中的变量或类名相同时,必须使用第三种方法,即在调用类名或变量时加上所属名称空间。

//person1.h

#include <iostream>

#include <string>

//using namespace std;

namespace A{

#ifndef _PERSON1_H_

#define _PERSON1_H_

class Person {

private:

std::string name;

int age;

public:

Person() {};

Person(std::string name,int age)

{

this->name = name;

this->age = age;

}

~Person(){};

void printinfo(void)

{

std::cout << this->name << ": " << this->age <<std::endl;

}

};

#endif

}

 

//person2.h

#include <iostream>

#include <string>

//using namespace std;

namespace B{

#ifndef _PERSON2_H_

#define _PERSON2_H_

class Person {

private:

std::string name;

int age;

public:

Person() {};

Person(std::string name, int age)

{

this->name = name;

this->age = age;

}

~Person() {};

void printinfo(void)

{

std::cout << this->name << ": " << this->age << std::endl;

}

};

#endif

}

 

//main.cpp

#include "person1.h"

#include "person2.h"

 

int main(int argc, char **argv)

{

A::Person per1("zhangsan", 16);

per1.printinfo();

B::Person per2("lisi", 18);

per2.printinfo();

return 0;

}

运行结果:

zhangsan: 16

lisi: 18

类的三大特性本是封装性、继承性、多态性,但是个人认为多态性是由继承性产生的,类定义中虚的用法,完全可以看做抽象性。因此个人认为类的3大特性是封装性、继承性(多态性)、抽象性,本书也是按照这3个特性,分三大章节来讲解类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值