郑州微科网站建设,克拉玛依建设局网站,哈尔滨网页制作,如何简单制作自己的网站设计好MyString类后#xff0c;就可以像使用普通类型一样使用它了。例如#xff0c;类的对象可以像普通的变量一样作为另一个类的数据成员。【例1】 MyString类的对象作为CStudent类的数据成员。1. //MyString类的定义省略
2. //注意#xff1a;保留其构造函数、析构函数、…设计好MyString类后就可以像使用普通类型一样使用它了。例如类的对象可以像普通的变量一样作为另一个类的数据成员。【例1】 MyString类的对象作为CStudent类的数据成员。1. //MyString类的定义省略
2. //注意保留其构造函数、析构函数、复制构造函数和赋值运算符中的输出内容3. //file: student.h
4. #pragma once
5. #includeMyString.h
6. class CStudent
7. {
8. public:
9. CStudent() { cout CStudent的默认构造函数被调用 endl; }
10. CStudent(int num, const MyString name,const MyString major, double score);
11. ~CStudent() { cout CStudent的析构函数被调用 endl; }
12. void set_number(int num) { number num; }
13. int get_number(){ return number; }
14. MyString set_name(const MyString name);
15. MyString get_name() { return name; }
16. MyString set_major(const MyString major);
17. MyString get_major() { return major; }
18. void set_score(double score) { this-score score; }
19. double get_score() { return score; }
20. private:
21. int number;
22. MyString name;
23. MyString major;
24. double score;
25. };26. //file: student.cpp
27. #includestudent.h28. CStudent::CStudent(int num, const MyString name,const MyString major, double score)
29. {
30. number num;
31. this-name name;
32. this-major major;
33. this-score score;
34. cout CStudent的有参构造函数被调用 endl;
35. }36. MyString CStudent::set_name(const MyString name)
37. {
38. this-name name;
39. return this-name;
40. }41. //file: main.cpp
42. #includestudent.h
43. #includeiostream
44. using namespace std;
45. int main()
46. {
47. MyString name(zhangsan), major(computer);
48. CStudent stu(1, name, major, 100), stu2;
49. CStudent stu3(stu);
50. stu2 stu3;51. cout stu.get_name().get_string() endl;
52. cout stu2.get_name().get_string() endl;53. return 0;
54. }其输出如下1. MyString的有参构造函数被调用
2. MyString的有参构造函数被调用
3. MyString的默认构造函数被调用
4. MyString的默认构造函数被调用
5. MyString的赋值运算符函数被调用
6. MyString的赋值运算符函数被调用
7. CStudent的有参构造函数被调用
8. MyString的默认构造函数被调用
9. MyString的默认构造函数被调用
10. CStudent的默认构造函数被调用
11. MyString的复制构造函数被调用
12. MyString的复制构造函数被调用
13. MyString的赋值运算符函数被调用
14. MyString的赋值运算符函数被调用
15. zhangsan
16. zhangsan
17. CStudent的析构函数被调用
18. MyString的析构函数被调用
19. MyString的析构函数被调用
20. CStudent的析构函数被调用
21. MyString的析构函数被调用
22. MyString的析构函数被调用
23. CStudent的析构函数被调用
24. MyString的析构函数被调用
25. MyString的析构函数被调用
26. MyString的析构函数被调用
27. MyString的析构函数被调用对于例1在定义CStudent类时使用了MyString类比如其数据成员name是MyString类型的也就是说MyString类的对象name作为CStudent的数据成员。这样对于编写CStudent类的程序员来说只需要知道MyString类的用法就行了而不需要再去考虑如动态内存分配等细节因而大大减轻了程序员的工作量。不过类毕竟与普通的数据类型不同因而就带来了一些问题。下面结合程序的输出分析程序的运行过程如下1输出的第1、2行是程序第47行中构造name和major时产生的。2程序第48行会调用CStudent类的有参构造函数构造对象stu、调用CStudent的默认构造函数构造stu2,而输出中的第7行才是CStudent的有参构造函数中输出的信息、第10行才是CStudent的默认构造函数输出的信息因此输出的第3行至第10行都是因程序第48行产生的输出。这些输出表明在CStudent的有参构造函数执行之前先调用了两次MyString类的默认构造函数然后调用了两次MyString类的赋值运算符函数在执行CStudent的默认构造函数之前先调用了两次MyString类的默认构造函数。然而在CStudent类的有参构造函数中没有看到调用MyString类的默认构造函数初始化内嵌对象name和major的地方那么两次调用MyString类的默认构造函数是怎么发生的同理在CStudent类的默认构造函数的实现中也没有显式调用MyString类的默认构造函数初始化内嵌对象name和major的地方那么两次调用MyString类的默认构造函数是怎么发生的这就需要介绍构造函数的初始化列表了。另外在CStudent类的有参构造函数中的语句“this-name name; this-major major;”中直接使用了类的内嵌对象name和major由此两次调用MyString类的赋值运算符函数产生输出的第5和第6行这说明这两个对象在进入该构造函数之前就已经构造完毕。既然在进入CStudent类的构造函数之前就能调用MyString类的构造函数初始化name和major那么能不能通过传递CStudent类的有参构造函数中的参数name和major来调用MyString类的复制构造函数初始化CStudent类的成员对象name和major呢这样做还可以省去在CStudent类的有参构造函数中对它们的赋值即省去两次调用MyString类的赋值运算符函数的过程。3程序第49行是调用CStudent类的复制构造函数但例1中没有设计该函数因此执行的是编译器自动提供的复制构造函数程序第50行是一个赋值运算由于例1中也没有为CStudent设计赋值运算符函数因此编译器自动提供了默认的赋值运算符函数。显然程序第51行和第52行产生的输出为输出中的第15行和第16行因此程序第49行和第50行产生的输出为输出中的第11行至第14行显示调用了两次MyString类的复制构造函数和两次赋值运算符函数。根据输出的第15行和第16行的内容相同且程序运行正常可以判断编译器自动提供的复制构造函数和赋值运算符是正确的。那么编译器自动提供的复制构造函数和赋值运算符函数是什么样的4例1中设计了CStudent类的析构函数但在其函数体中只有一条输出语句。这是因为CStudent类中没有涉及动态内存分配因此不涉及回收堆内存的问题。注意MyString类型的对象name和major涉及了堆内存不过回收其堆内存的工作由MyString类的析构函数完成。从程序中可以看出对象的析构顺序为依次析构对象stu3、stu2和stu然后析构对象major和name。整个析构过程产生了输出中的第17行至第27行其中析构stu3产生了输出中的第17行至第19行。那么析构组合类对象stu3为什么是这样的一个过程下面会解释上面提出的问题。不过在此之前先介绍一下类的前向引用声明问题因为这个问题在定义组合类时经常会用到。在C语言中使用基本数据类型的变量时需要遵循先声明后引用的规则。与此类似在定义新的类型时也要遵循这一规则。例如在例1中在定义CStudent类之前先通过预编译指令引入了MyString类的定义例1的第5行。在声明一个类之前就试图使用这个类则会出现编译错误如例2所示。【例2】 在声明一个类之前就试图使用这个类则会出现编译错误。1. class A
2. {
3. public:
4. void A_fun(B b); //因之前没有声明类型B故这里试图引用B会造成编译错误
5. int i;
6. };7. class B
8. {
9. public:
10. void B_fun(A a);
11. int j;
12. };在例2中在类A的定义中引用了类B。然而B类还没有被声明所以会造成编译错误。解决办法是进行前向类型声明比如在声明A之前加入声明语句“class B;”。进行了类的前向声明之后仅能保证声明的符号可见但在给出类的具体定义之前并不能涉及类的具体内容如下面的程序。class B;
class A
{
public:int A_fun(B b){ return b.j; } //在给出B的具体定义之前涉及了其//具体内容所以会出现编译错误int i;
};class B
{
public:int B_fun(A a);int j;
};在上面的程序中类A的函数A_fun()试图访问对象b的数据成员j即试图引用B类的具体内容。然而在此之前类B的具体定义尚未给出所以会出现编译错误。解决办法是将该函数的实现写在类外并且在类B的完整定义之后。类似地在给出类的完整定义之前不能定义类的对象因为定义类的对象就会涉及对象的构造从而会涉及类的具体内容如下面的程序。class B;
class A
{
public:int A_fun(B b);B m_b; //在给出类B的完整定义之前定义B的对象会造成编译错误A m_a; //在类A的定义内部定义A的对象会造成编译错误
};class B
{
public:int B_fun(A a);int j;
};在上面的程序中类A试图定义B的对象m_b和A的对象m_a然而此时类B和类A的定义都不完整因而会造成编译错误。解决办法是首先把类B的完整定义放到类A的定义之前其次在类A中不能定义类A的对象只能定义类A的指针如下面的程序。class A; //因为定义类B时引用了类A所以需要做前向声明
class B
{
public:
int B_fun(A a);int j;
};class A
{
public:int A_fun(B b){ return b.j; } //前面已有类B的完整定义故该语句正确B m_b; //前面已有类B的完整声明故此处能够定义类B的对象A* m_pa; //永远不能在类定义中定义自身的对象可以定义自身的指针
};01、组合类的构造函数如前所述在CStudent类的有参构造函数中可以直接使用内嵌的对象name这就意味着该对象在程序执行CStudent类的有参构造函数之前就已经调用了MyString的构造函数完成了初始化。为了解释这个问题就需要介绍初始化列表的概念了。类的构造函数都带有一个初始化列表主要作用是为初始化类的数据成员提供一个机会。如果在设计构造函数时没有在初始化列表中给出数据成员的初始化方式则编译器会采用数据成员的默认的初始化方式——对于类的对象来说就是调用其默认的构造函数——进行初始化且初始化列表中的内容会在执行构造函数之前执行。这就是在上面例1中的CStudent类的有参构造函数中可以使用其成员对象name的原因。一般地带初始化列表的构造函数的形式如下仅以写在类的声明内部为例写在类的声明外部与此相似只是需要在函数名前加上类名和域作用符class 类名
{
public类名(): 初始化数据成员1, 初始化数据成员2, ...{}...
};以写在类的声明外部为例CStudent类的有参构造函数可以写成如下形式。CStudent::CStudent(int num, const MyString name,const MyString major, double score): number(num), name(name), major(major), score(score)
{cout CStudent的有参构造函数被调用 endl;
}其中初始化列表中的第一个name是CStudent的数据成员第二个name是构造函数中的参数。在这个实现中由于在初始化列表中使用复制构造函数初始化了name和major所以在CStudent的构造函数内部就不需要再次为成员name和major赋值了。另外基本数据类型number和score也可以在初始化列表中初始化但要注意不能写成类似于“number num”的形式。另外需要说明的是构造函数的调用顺序。由于初始化列表的存在在调用组合类的构造函数之前会先调用其成员对象的构造函数且当有多个成员对象时C语言规定按照成员对象在组合类声明中出现的顺序依次构造而与它们在初始化列表中出现的顺序无关。例如虽然name和major在上述构造函数的初始化列表中出现的顺序与在下面构造函数的初始化列表中出现的顺序不同但在执行时都是先初始化name再初始化major程序如下CStudent::CStudent(int num, const MyString name,const MyString major, double score): number(num), major(major), name(name), score(score)
{cout CStudent的有参构造函数被调用 endl;
}最后要强调的是初始化列表可以省去——此时使用数据成员的默认方式初始化但不意味着没有初始化列表。例如例1中CStudent的默认构造函数实际的实现形式为在初始化列表中调用MyString的默认构造函数初始化name和major但基本数据类型的成员number和score没有初始化程序如下CStudent() : name(), major()
{cout CStudent的默认构造函数被调用 endl;
}例1中CStudent的有参构造函数实际的实现形式中的初始化列表与上面的类似仅在初始化列表中使用MyString类的默认构造函数初始化数据成员name和major没有初始化number和score程序如下CStudent::CStudent(int num, const MyString name,const MyString major, double score) : name(), major()
{number num;this-name name;this-major major;this-score score;cout CStudent的有参构造函数被调用 endl;
}显然这个实现中为初始化name和major需要调用两次MyString类的默认构造函数和两次赋值运算符函数。因此充分利用初始化列表还可以减少函数调用的次数提高程序的运行效率。02、组合类的析构函数对于CStudent类来说其析构函数没有多少特殊的地方其要完成的功能主要是负责该类数据成员的清理。在CStudent类中由于数据成员没有用到堆内存对象name和major用到了但它们由MyString类负责处理所以不需要专门为它编写析构函数。不过对于组合类的析构函数也有需要说明的地方那就是当组合类的对象超出生存期时析构函数的调用顺序问题。这里只需要遵循一个原则析构函数的调用顺序与构造函数的调用顺序完全相反。如果把对象的初始化过程比喻为按照严格规程生产一台机器的过程那么显然需要先按照设定的规程生产各个零部件相当于调用作为数据成员的对象的构造函数然后调试整台机器相当于调用组合类的构造函数当需要拆卸机器时需要按照完全相反的顺序拆卸相当于调用各部分的析构函数否则就无法拆卸开来。对于CStudent类的对象调用析构函数的顺序是调用CStudent类的析构函数析构CStudent类的对象然后调用MyString类的析构函数析构对象major最后调用MyString类的析构函数析构对象name。03、组合类的复制构造函数正象普通的复制构造函数一样如果没有编写它编译器就会自动提供一个并且其完成的功能就是实现对应数据成员的复制。比如在例1中没有给出CStudent类的复制构造函数因此编译器会自动提供一个如下形式的复制构造函数——注意在初始化列表中调用了MyString类的复制构造函数来初始化name和major。class CStudent
{
public:CStudent(const CStudent stu);...
};CStudent::CStudent(const CStudent stu) : number(stu.number),name(stu.name), major(stu.major), score(stu.score)
{
}如果明确给出了复制构造函数的定义则编译器就不再提供默认的实现因此关于复制构造函数的一切都需要程序员负责——一定要在初始化列表中使用复制构造函数初始化对象成员比如下面这个实现就不太好。CStudent::CStudent(const CStudent stu)
{number stu.number;name stu.name;major stu.major;score stu.score;
}这个实现没有明确给出初始化列表但这并不意味着没有初始化列表而是意味着在初始化列表中采用默认的形式对数据成员初始化即name和major的初始化是通过调用MyString的默认构造函数——而不是复制构造函数——实现的而基本数据类型的成员number和score没有初始化。也正因为如此在上面的实现中需要分别为各数据成员赋值否则将不能正确完成CStudent对象的复制。04、组合类的赋值运算符当没有为类提供赋值运算符函数时编译器会自动提供一个赋值运算符函数其完成的功能就是对数据成员逐一赋值对于基本数据类型就是按位赋值对于对象成员就是调用其赋值运算符函数进行赋值。在CStudent类中虽然其对象成员name和major使用了堆内存但因为已经为MyString类提供了实现深复制的赋值运算符函数因此编译器为CStudent类自动提供的赋值运算符函数能够正确运行其实现形式如下CStudent CStudent::operator(const CStudent stu)
{if (this ! stu) //防止自赋值{number stu.number;name stu.name; //调用MyString类的赋值运算符函数major stu.major; //调用MyString类的赋值运算符函数score stu.score;}return *this;
}在例1中第50行调用了CStudent类的赋值运算符函数。根据上述编译器为CStudent类自动提供的赋值运算符函数的形式在函数实现中两次调用MyString类的赋值运算符函数。这正是第50行的程序产生了输出中的第13行和第14行的原因。