构造函数与析构函数

  • 1 构造函数

    • 1.1 构造函数具有一些特殊的性质
    • 1.2 定义构造函数的一般形式
    • 1.3 利用构造函数创建对象
  • 2 成员初始化表
  • 3 缺省参数的构造函数
  • 4 重载构造函数
  • 5 拷贝构造函数
    • 5.1 自定义拷贝构造函数
    • 5.2 缺省的拷贝构造函数
    • 5.3 调用拷贝构造函数的三种情况
    • 5.4 浅拷贝和深拷贝
  • 6 析构函数
  • 7 调用构造函数和析构函数的顺序
  • 8 对象的生存期

构造函数和析构函数都是类的成员函数,但它们都是特殊的成员函数,执行特殊的功能,不用调用便自动执行,而且这些函数的名字类的名字有关。

C++语言中有一些成员函数性质是特殊的,这些成员函数负责对象的建立、删除。这些函数的特殊性在于可以由编译器自动地隐含调用,其中一些函数调用格式采用运算符函数重载的语法。

C++引进一个自动完成对象初始化过程的机制,这就是类的构造函数。

对象的初始化

  1. 数据成员是不能在声明类时初始化
  2. 类型对象的初始化方法:
    1. 调用对外接口(public成员函数)实现:声明类→定义对象→调用接口给成员赋值
    2. 应用构造函数(constructor) 实现:声明类→定义对象→同时给成员赋值

1. 构造函数

构造函数是一种特殊的成员函数,它主要用于为对象分配空间,进行初始化。

1.1 构造函数具有一些特殊的性质:

  • (1) 构造函数的名字必须与类名相同
  • (2) 构造函数可以有任意类型的参数,但不能指定返回类型。 它有隐含的返回值,该值由系统内部使用。
  • (3) 构造函数是特殊的成员函数,函数体可写在类体内,也可写在类体外。
  • (4) 构造函数可以重载,即一个类中可以定义多个参数个数或参数类型不同的构造函数。构造函数是不能继承
  • (5) 构造函数被声明为公有函数,但它不能像其他成员函数那样被显式地调用,它是在定义对象的同时被调用的。
  • (6) 在声明类时如果没有定义类的构造函数,编译系统就会在编译时自动生成一个默认形式的构造函数,
  • (7) 默认构造函数是构造对象时不提供参数的构造函数。
  • (8) 除了无参数构造函数是默认构造函数外,带有全部默认参数值的构造函数也是默认构造函数。
  • (9) 自动调用:构造函数在定义类对象时自动调用, 不需用户调用,也不能被用户调用。在对象使用前调用。
  • (10) 调用顺序:在对象进入其作用域时(对象使用前) 调用构造函数。

1.2 定义构造函数的一般形式

  1. class 类名
  2. {
  3. public:
  4. 类名(形参表) ; //构造函数的原型
  5. //类的其它成员
  6. };
  7. 类名::类名(形参表) //构造函数的实现
  8. {
  9. //函数体
  10. }

1.3 利用构造函数创建对象

  • (1) 利用构造函数直接创建对象.其一般形式为:类名 对象名 (实参表);

这里的“类名”与构造函数名相同,“实参表”是为构造函数提供的实际参数。

例2.7 为类Date建立一个构造函数
  1. #include <iostream.h>
  2. class Date {
  3. public:
  4. Date(int y,int m,int d); // 构造函数
  5. void setDate(int y,int m,int d);
  6. void showDate();
  7. private:
  8. int year, month, day;
  9. };
  10. Date::Date(int y,int m,int d) // 构造函数的实现
  11. { year=y; month=m; day=d; }
  12. void Date::setDate(int y,int m,int d)
  13. { year=y; month=m; day=d; }
  14. inline void Date::showDate()
  15. { cout<<year<<"."<<month<<"."<<day<<endl; }
例2.8 利用构造函数直接创建对象
  1. #include <iostream.h>
  2. class Date {
  3. // 省略, 同例2.7
  4. };
  5. // 省略, 同例2.7
  6. void main()
  7. {
  8. Date date1(1998,4,28); // 定义类Date的对象date1,
  9. // 自动调用date1的构造函数,初始化对象date1
  10. cout<<"Date1 output1:"<<endl;
  11. date1.showDate(); // 调用date1的showDate(),显示date1的数据
  12. date1.SetDate(2002,11,14); // 调用date1的setDate(),
  13. // 重新设置date1的数据
  14. cout<<"Date1 output2:"<<endl;
  15. date1.showDate(); // 调用date1的showDate(),显示date1的数据
  16. }
  1. constructing...
  2. Date1 output1:
  3. 1998.4.28
  4. Date1 output2:
  5. 2002.11.14
  • (2) 利用构造函数创建对象时,通过指针和new来实现。其一般语法形式为:

    类名 *指针变量 = new 类名 (实参表);
  1. void main()
  2. {
  3. Date *date1;
  4. date1=new Date(1998,4,28);
  5. //可合写成:Date *date1=new Date(1998,4,28);
  6. cout<<"Date1 output1:"<<endl;
  7. date1->showDate();
  8. date1->setDate(2002,11,14);
  9. cout<<"Date1 output2:"<<endl;
  10. date1->showDate();
  11. delete date1;
  12. }

说明:

  • 构造函数的名字必须与类名相同,否则编译器将把它当作一般的成员函数来处理。
  • 构造函数是不能说明它的返回值类型的,甚至说明为void类型也不行。
  • 构造函数可以是不带参数的。
  1. class A{
  2. public:
  3. A();
  4. //…
  5. private:
  6. int x;
  7. };
  8. AA()
  9. {
  10. cout<<"initialized \n";
  11. x=50;
  12. }
  13. main()
  14. {
  15. A a;

  16. }

有两个长方柱,其长、宽、高分别为:(1)12,25,30;(2)15,30,21。求它们的体积。要求:编一个基于对象的程序,在类中用带参数的构造函数。

  1. class Box{
  2. public:
  3. Box(int,int,int);
  4. int volume( );
  5. private:
  6. int height;
  7. int width;
  8. int length;
  9. };
  10. Box::Box(int h,int w,int len)
  11. {
  12. height = h;
  13. width = w;
  14. length = len;
  15. }
  16. int Box::volume( )
  17. {
  18. return height*width*length;
  19. }
  20. int main( )
  21. {
  22. Box box1(12,25,30);
  23. cout << box1.volume( ) << endl;
  24. Box box2(15,30,21);
  25. cout << box2.volume( ) << endl;
  26. return 0;
  27. }

2. 成员初始化表

对于常量类型引用类型的数据成员,不能在构造函数中用赋值语句直接赋值,C++提供初始化表进行置初值。

带有成员初始化表的构造函数的一般形式如下:

类名::构造函数名(参数表): (成员初始化表){ 构造函数体 }

成员初始化表的一般形式为:

数据成员名1(初始值1),数据成员名2(初始值2),……

例2.9 成员初始化表的使用
  1. #include<iostream.h>
  2. class A{
  3. public:
  4. A(int x1):x(x1),rx(x),pi(3.14) // rx(x)相当于rx=x, pi(3.14)相当于pi=3.14
  5. { }
  6. void print()
  7. {cout<<"x="<<x<<" "<<"rx="<<rx<<" "<<"pi="<<pi;}
  8. private:
  9. int x; int& rx; const float pi;
  10. };
  11. main()
  12. {
  13. A a(10);
  14. a.print();
  15. return 0;
  16. }
  • 构造函数采用成员初始化表对数据成员进行初始化,是一些程序员喜欢使用的方法。
  1. class B{
  2. int i;
  3. char j;
  4. float f;
  5. public
  6. B(int I char J float F)
  7. { i=I; j=J; f=F; };
  8. };
  1. class B{
  2. public
  3. B(int I,char J,float F):i(I),j(J),f(F)
  4. { }
  5. private:
  6. int i;
  7. char j;
  8. float f;
  9. };

说明

如果需要将数据成员存放在堆中或数组中,则应在构造函数中使用赋值语句,即使构造函数有成员初始化表也应如此。

  1. class C{
  2. public:
  3. C(int I,char Ch,float F,char N[]):i(I),ch(Ch),f(F)
  4. { strcpy (name,N);}
  5. private:
  6. int i;
  7. char ch;
  8. float f;
  9. char name[25];
  10. };
  • 类成员是按照它们在类里被声明的顺序初始化的,与它们在初始化表中列出的顺序无关。
【例2.10】
  1. #include<iostream.h>
  2. class D {
  3. public:
  4. D(int i):mem2(i),mem1(mem2+1)
  5. {
  6. cout<<"mem1: "<<mem1<<endl;
  7. cout<<"mem2: "<<mem2<<endl;
  8. }
  9. private:
  10. int mem1;
  11. int mem2;
  12. };
  13. void main()
  14. {
  15. D d(15);
  16. }
  1. mem1: -858993459
  2. mem2: 15

3. 缺省参数的构造函数

例2.11
  1. #include<iostream.h>
  2. class Coord {
  3. public:
  4. Coord(int a=0,int b=0){ x=a; y=b;} // 带有缺省参数的构造函数
  5. int getx(){ return x; }
  6. int gety(){ return y; }
  7. private:
  8. int x,y;
  9. };
  10. void main()
  11. {
  12. Coord op1(5,6); Coord op2(5); Coord op3;
  13. int i,j;
  14. i=op1.getx();j=op1.gety();
  15. cout<<"op1 i= "<<i<<" op1 j= "<<j<<endl;
  16. i=op2.getx();j=op2.gety();
  17. cout<<"op2 i= "<<i<<" op2 j= "<<j<<endl;
  18. i=op3.getx();j=op3.gety();
  19. cout<<"op3 i= "<<i<<" op3 j= "<<j<<endl;
  20. }
  1. class Box{
  2. public:
  3. Box(int h=10,int w=10,int l=10); //在声明构造函数时指定默认参数
  4. int volume( ){ return(height*width*length); }
  5. private:
  6. int height;
  7. int width;
  8. int length;
  9. };
  10. Box:: Box(int h,int w,int l) //在定义函数时可以不指定默认参数
  11. {
  12. height=h;
  13. width=w;
  14. length=l;
  15. }

4. 重载构造函数

构造函数的重载

在一个类中可以定义多个构造函数,以便对类对象提供不同的初始化的方法,供用户选用。这些构造函数具有相同的名字,而参数的个数或参数的类

型不相同(这称为构造函数的重载)

关于构造函数重载的说明

  • (1) 默认构造函数:一个调用构造函数时不必给出实参的构造函数。 显然,无参的构造函数属于默认构造函数。一个类只能有一个默认构造函数。
  • (2) 尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象时只执行其中一个构造函数,并非每个构造函数都被执行。
  1. class Box{
  2. public:
  3. Box(int h, int w, int l): height(h),width(w),length(l) { }
  4. Box();
  5. int volume( );
  6. private:
  7. int height;
  8. int width;
  9. int length;
  10. };
  11. Box::Box()
  12. {
  13. height = 10;
  14. width = 10;
  15. lenght = 10;
  16. }
  17. int Box::volume( )
  18. {
  19. return height*width*length;
  20. }
  21. int main( )
  22. {
  23. Box box1; // 书上为 box1();
  24. cout << box1.volume( ) << endl;
  25. Box box2(15,30,25);
  26. cout << box2.volume( ) << endl;
  27. return 0;
  28. }
例2.17 重载构造函数应用例程。
  1. #include <iostream.h>
  2. class Date{
  3. public:
  4. Date(); // 无参数的构造函数
  5. Date(int y,int m,int d); // 带有参数的构造函数
  6. void showDate();
  7. private:
  8. int year, month, day;
  9. };
  10. Date::Date()
  11. { year=1998; month=4; day=28; }
  12. Date::Date( int y, int m, int d)
  13. { year=y; month=m; day=d; }
  14. inline void Date::showDate()
  15. { cout<<year<<"."<<month<<"."<<day<<endl; }
  16. void main()
  17. {
  18. Date date1; // 声明类Date的对象date1,
  19. // 调用无参数的构造函数
  20. cout<<"Date1 output: "<<endl;
  21. date1.showDate(); // 调用date1的showDate(),显示date1的数据
  22. Date date2(2002 11 14); // 定义类Date的对象date2,
  23. // 调用带参数的构造函数
  24. cout<<"Date2 output: "<<endl;
  25. date2.showDate(); // 调用date2的showDate(),显示date2的数据
  26. }

运行结果:

  1. Date1 output
  2. 1998.4.28
  3. Date2 output
  4. 2002.11.14
例2.18 关于计时器的例子
  1. #include<iostream.h>
  2. #include<stdlib.h>
  3. class timer{
  4. public:
  5. timer() // 无参数构造函数,给seconds清0
  6. { seconds=0; }
  7. timer(char* t) // 含一个数字串参数的构造函数
  8. { seconds=atoi(t); }
  9. timer(int t) // 含一个整型参数的构造函数
  10. { seconds=t; }
  11. timer(int min,int sec) // 含两个整型参数的构造函数
  12. { seconds=min*60+sec; }
  13. int gettime()
  14. { return seconds; }
  15. private
  16. int seconds;
  17. };
  18. main()
  19. {
  20. timer a,b(10),c("20"),d(1,10);
  21. cout<<"seconds1="<<a.gettime()<<endl;
  22. cout<<"seconds2="<<b.gettime()<<endl;
  23. cout<<"seconds3="<<c.gettime()<<endl;
  24. cout<<"seconds4="<<d.gettime()<<endl;
  25. return 0;
  26. }
  1. class x {
  2. public:
  3. x(); // 没有参数的构造函数
  4. x(int i=0); // 带缺省参数的构造函数
  5. };
  6. //…
  7. void main()
  8. {
  9. x one(10); // 正确,调用x(int i=0)
  10. x two; // 存在二义性
  11. //…
  12. }

5. 拷贝构造函数

拷贝构造函数是一种特殊的构造函数,其形参是本类对象的引用。其作用是使用一个已经存在的对象去初始化另一个同类的对象。

  • 通过等于号复制对象时,系统会自动调用拷贝构造函数。
  • 拷贝构造函数与原来的构造函数实现了函数的重载。

拷贝构造函数具有以下特点:

  • (1) 因为该函数也是一种构造函数,所以其函数名与类名相同,并且该函数也没有返回值类型。
  • (2) 该函数只有一个参数,并且是同类对象的引用
  • (3) 每个类都必须有一个拷贝构造函数。程序员可以根据需要定义特定的拷贝构造函数,以实现同类对象之间数据成员的传递。如果程序员没有定义类的拷贝构造函数,系统就会自动生成产生一个缺省的拷贝构造函数。

5.1 自定义拷贝构造函数

自定义拷贝构造函数的一般形式如下:

  1. class 类名{
  2. public :
  3. 类名(形参); //构造函数
  4. 类名(类名 &对象名); //拷贝构造函数
  5. ...
  6. };
  7. 类名:: 类名(类名 &对象名) //拷贝构造函数的实现
  8. { 函数体 }
用户自定义拷贝构造函数
  1. class Coord{
  2. int x,y;
  3. public:
  4. Coord(int a int b) // 构造函数
  5. {
  6. x=a;
  7. y=b;
  8. cout<<"Using normal constructor\n";
  9. }
  10. Coord(const Coord& p) // 拷贝构造函数
  11. {
  12. x=2*p.x;
  13. y=2*p.y;
  14. cout<<"Using copy constructor\n";
  15. }
  16. //…
  17. };

如果p1、 p2为类Coord的两个对象,p1已经存在,则coord p2(p1)调用拷贝构造函数来初始化p2

例2.19 自定义拷贝构造函数的使用
  1. #include<iostream.h>
  2. class Coord {
  3. public:
  4. Coord(int a,int b) // 构造函数
  5. { x=a; y=b; cout<<"Using normal constructor\n";}
  6. Coord(const Coord& p) // 拷贝构造函数
  7. { x=2*p.x; y=2*p.y; cout<<"Using copy constructor\n";}
  8. void print(){ cout<<x<<" "<<y<<endl; }
  9. private:
  10. int x,y;
  11. };
  12. main()
  13. {
  14. Coord p1(30,40); // 定义对象p1,调用了普通的构造函数
  15. Coord p2(p1); // 以“代入” 法调用拷贝构造函数,用对象p1初始化对象p2
  16. p1.print();
  17. p2.print();
  18. return 0;
  19. }

除了用代入法调用拷贝构造函数外,还可以采用赋值法调用拷贝构造函数,如:

  1. main()
  2. {
  3. Coord p1(30,40);
  4. Coord p2=p1; //以"赋值"法调用拷贝构造函数,
  5. 用对象p1初始化对象p2
  6. //…
  7. }

5.2 缺省的拷贝构造函数

如果没有编写自定义的拷贝构造函数,C++会自动地将一个已存在的对象复制给新对象,这种按成员逐一复制的过程由是缺省拷贝构造函数自动完成的。

例2.20 调用缺省的拷贝构造函数
  1. #include<iostream.h>
  2. class Coord{
  3. public:
  4. Coord(int a,int b)
  5. { x=a; y=b; cout<<"Using normal constructor\n"; }
  6. void print(){ cout<<x<<" "<<y<<endl;}
  7. private:
  8. int x,y;
  9. };
  10. main()
  11. {
  12. Coord p1(30,40); // 定义类Coord的对象p1,
  13. // 调用了普通构造函数初始化对象p1
  14. Coord p2(p1); // 以“代入”法调用缺省的拷贝构造函数,
  15. // 用对象p1初始化对象p2
  16. Coord p3=p1; // 以“赋值”法调用缺省的拷贝构造函数,
  17. // 用对象p1初始化对象p3
  18. p1.print(); p2.print(); p3.print();
  19. return 0;
  20. }

5.3 调用拷贝构造函数的三种情况

  • (1) 当用类的一个对象去初始化该类的另一个对象时。
  1. Coord p2(p1); // 用对象p1初始化对象p2, 拷贝构造函数被调用(代入法)
  2. Coord p3=p1; // 用对象p1初始化对象p3, 拷贝构造函数被调用(赋值法)
  • (2) 当函数的形参是类的对象,调用函数,进行形参和实参结合时。
  1. //…
  2. fun1(Coord p) // 函数的形参是类的对象
  3. {
  4. p.print();
  5. }
  6. main()
  7. {
  8. Coord p1(10,20);
  9. fun1(p1); // 当调用函数,进行形参和实参结合时,
  10. 调用拷贝构造函数
  11. return 0;
  12. }
  • (3) 当函数的返回值是对象,函数执行完成,返回调用者时。
  1. // …
  2. Coord fun2()
  3. {
  4. Coord p1(10,30);
  5. return p1;
  6. } // 函数的返回值是对象
  7. main()
  8. {
  9. Coord p2;
  10. P2=fun2(); // 函数执行完成,返回调用者时,调用拷贝构造函数
  11. return 0;
  12. }

5.4 浅拷贝和深拷贝

所谓浅拷贝,就是由缺省的拷贝构造函数所实现的数据成员逐一赋值,若类中含有指针类型数据, 则会产生错误

为了解决浅拷贝出现的错误,必须显示地定义一个自己的拷贝构造函数,使之不但拷贝数据成员,而且为对象1和对象2分配各自的内存空间,这就是所谓的深拷贝

例2.23 浅拷贝例子
  1. #include<iostream.h>
  2. #include<string.h>
  3. class Student {
  4. public:
  5. Student(char *name1,float score1);
  6. ~Student();
  7. private:
  8. char *name; // 学生姓名
  9. float score; // 学生成绩
  10. };
  11. StudentStudent(char *name1,float score1)
  12. {
  13. cout<<"Constructing..."<<name1<<endl;
  14. name=new char[strlen(name1)+1];
  15. if (name !=0)
  16. {
  17. strcpy(name,name1);
  18. score=score1;
  19. }
  20. }
  21. Student∷~Student()
  22. {
  23. cout<<"Destructing..."<<name<<endl;
  24. name[0]='\0';
  25. delete name;
  26. }
  27. void main()
  28. {
  29. Student stu1("liming",90); // 定义类Student的对象stu1
  30. Student stu2=stu1; // 调用缺省的拷贝构造函数
  31. }
  1. Constructing... liming
  2. Destructing... liming
  3. Destructing...

浅拷贝示意图

例2.24 深拷贝例子
  1. #include<iostream.h>
  2. #include<string.h>
  3. class Student {
  4. private:
  5. char *name; // 学生姓名
  6. float score; // 学生成绩
  7. public:
  8. Student(char *name1,float score1);
  9. Student(Student& stu);
  10. ~Student();
  11. };
  12. StudentStudent(char *name1,float score1)
  13. {
  14. cout<<"constructing..."<<name1<<endl;
  15. name=new char[strlen(name1)+1];
  16. if (name !=0)
  17. {
  18. strcpy(name,name1);
  19. score=score1;
  20. }
  21. }
  22. StudentStudent(Student& stu)
  23. {
  24. cout<<"Copy constructing..."<<stu.name<<endl;
  25. name=new char[strlen(stu.name)+1];
  26. if (name !=0)
  27. {
  28. strcpy(name,stu.name);
  29. score=stu.score;
  30. }
  31. }
  32. Student∷~Student()
  33. {
  34. cout<<"Destructing..."<<name<<endl;
  35. name[0]='\0';
  36. delete name;
  37. }
  38. void main()
  39. {
  40. Student stu1("liming" 90); // 定义类Student的对象stu1,
  41. Student stu2=stu1; // 调用自定义的拷贝构造函数
  42. }
  1. Constructingliming
  2. Copy constructingliming
  3. Destructingliming
  4. Destructingliming

深拷贝示意图

6. 析构函数

析构函数也是一种特殊的成员函数。它执行与构造函数相反的操作,通常用于撤消对象时的一些清理任务,如释放分配给对象的内存空间等。

析构函数有以下一些特点:

  • ① 析构函数与构造函数名字相同,但它前面必须加一个波浪号(~);
  • ② 析构函数没有参数,也没有返回值,而且不能重载。因此在一个类中只能有一个析构函数;
  • ③ 当撤消对象时,编译系统会自动地调用析构函数。 如果程序员没有定义析构函数,系统将自动生成和调用一个默认析构函数,默认析构函数只能释放对象的数据成员所占用的空间,但不包括堆内存空间。
例2.25 重新说明类Date
  1. #include <iostream.h>
  2. class Date{
  3. public:
  4. Date(int y,int m,int d); // 构造函数
  5. ~Date(); // 析构函数
  6. void setDate(int y,int m,int d);
  7. void showDate();
  8. private:
  9. int year, month, day;
  10. };
  11. Date::Date(int y,int m,int d) // 构造函数的实现
  12. {
  13. cout<<"constructing..."<<endl;
  14. year=y;month=m; day=d;
  15. }
  16. Date::~Date() // 析构函数的实现
  17. { cout<<"destruting..."<<endl; }
  18. void Date::setDate(int y,int m,int d)
  19. { year=y;month=m;day=d; }
  20. inline void Date::showDate()
  21. { cout<<year<<"."<<month<<"."<<day<<endl; }
  22. void main()
  23. {
  24. Date date1(1998,4,28); // 定义类Date的对象date1,
  25. // 调用date1的构造函数,初始化对象date1
  26. cout<<"Date1 output1:"<<endl;
  27. date1.showDate(); // 调用date1的showDate(),显示date1的数据
  28. date1.setDate(2002,11,14); // 调用date1的setDate(),
  29. // 重新设置date1的数据
  30. cout<<"Date1 output2:"<<endl;
  31. date1.showDate(); // 调用date1的showDate(),显示date1的数据
  32. }

析构函数被调用的两种情况

  1. 若一个对象被定义在一个函数体内,当这个函数结束时,析构函数被自动调用。
  2. 若一个对象是使用new运算符动态创建,在使用delete释放时,自动调用析构函数。
【例2.13】 较完整的学生类例子
  1. #include<iostream.h>
  2. #include<string.h>
  3. class Student {
  4. public:
  5. Student(char *name1,char *stu_no1,float score1); // 构造
  6. 函数
  7. ~Student(); // 析构函数
  8. void modify(float score1); // 修改数据
  9. void show(); // 显示数据
  10. private:
  11. char *name; // 学生姓名
  12. char *stu_no; // 学生学号
  13. float score; // 学生成绩
  14. };
  15. StudentStudent(char *name1,char *stu_no1,float score1)
  16. {
  17. name=new char[strlen(name1)+1];
  18. strcpy(name,name1);
  19. stu_no=new char[strlen(stu_no1)+1];
  20. strcpy(stu_no,stu_no1);
  21. score=score1;
  22. }
  23. Student∷~Student()
  24. {
  25. delete []name;
  26. delete []stu_no;
  27. }
  28. void Studentmodify(float score1)
  29. { score=score1; }
  30. void Studentshow()
  31. {
  32. cout<<"\n name: "<<name;
  33. cout<<"\n stu_no: "<<stu_no;
  34. cout<<"\n score: "<<score;
  35. }
  36. void main()
  37. {
  38. Student stu1("Liming","990201",90); // 定义类Student的对象stu1,
  39. // 调用stu1的构造函数,初始化对象stu1
  40. stu1.show(); // 调用stu1的show(),显示stu1的数据
  41. stu1.modify(88); // 调用stu1的modify(),修改stu1的数据
  42. stu1.show(); // 调用stu1的show(),显示stu1修改后的数据
  43. }
  1. name:Liming
  2. stu_no:990201
  3. score:90
  4. name:Liming
  5. stu_no:990201
  6. score:88

缺省的析构函数

每个类必须有一个析构函数。

若没有显式地为一个类定义析构函数,编译系统会自动地生成一个缺省的析构函数

其格式如下:类名::析构函数名( ){ }

  1. class string_data {
  2. public:
  3. string_data(char *)
  4. { str=new char[max_len];}
  5. ~string_data()
  6. { delete []str;}
  7. void get_info(char *);
  8. void sent_info(char *);
  9. private:
  10. char *str;
  11. int max_len;
  12. };

7. 调用构造函数和析构函数的顺序

1) 一般顺序

调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。

2) 全局对象

在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在所有函数(包括main函数)执行之前调用。在程序的流程离开其作用域时(如main函数结束或调用exit函数)时,调用该全局对象的析构函数。

3) auto局部对象

局部自动对象(例如在函数中定义的对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。

4) static局部对象

如果在函数中定义静态局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。

8. 对象的生存期

对象按生存期的不同分为如下几种:

(1) 局部对象
  • 当对象被定义时,调用构造函数,该对象被创建;当程序退出该对象所在的函数体或程序块时,调用析构函数,对象被释放。
  • 局部对象是被定义在一个函数体或程序块内的,它的作用域限定在函数体或程序块内,生存期较短。
(2) 全局对象
  • 当程序开始运行时,调用构造函数,该对象被创建;当程序结束时,调用析构函数,该对象被释放。
  • 静态对象是被定义在一个文件中,它的作用域从定义是起到文件结束时为止。生存期较长。
(3) 静态对象
  • 当程序中定义静态对象时,调用构造函数,该对象被创建;当整个程序结束时,调用析构函数,对象被释放。
  • 全局对象是被定义在某个文件中,它的作用域包含在该文件的整个程序中,生存期是最长的。
(4) 动态对象
  • 执行new运算符调用构造函数,动态对象被创建;用delete释放对象时,调用析构函数。
  • 动态对象是由程序员掌握的,它的作用域和生存期是由new和delete之间的间隔决定的。
类的应用举例(例)

一圆形游泳池如图所示,现在需在其周围建一圆形过道,并在其四周围上栅栏。栅栏价格为35元/米,过道造价为20元/平方米。过道宽度为3米,游泳池半径由键盘输入。要求编程计算并输出过道和栅栏的造价。

  1. #include <iostream>
  2. using namespace std;
  3. const float PI = 3.14159;
  4. const float FencePrice = 35;
  5. const float ConcretePrice = 20;
  6. //声明类Circle 及其数据和方法
  7. class Circle{
  8. private:
  9. float radius;
  10. public:
  11. Circle(float r); //构造函数
  12. float Circumference() const; //圆周长
  13. /*函数后的修饰符const表示该成员函数的执行不会改变类的状态,也就是说不会修改类的数据成员。 */
  14. float Area() const; //圆面积
  15. };// 类的实现
  16. // 构造函数初始化数据成员radius
  17. Circle::Circle(float r)
  18. {
  19. radius=r;
  20. }
  21. // 计算圆的周长
  22. float Circle::Circumference() const
  23. {
  24. return 2 * PI * radius;
  25. }
  26. // 计算圆的面积
  27. float Circle::Area() const
  28. {
  29. return PI * radius * radius;
  30. }
  31. void main ()
  32. {
  33. float radius;
  34. float FenceCost, ConcreteCost;
  35. // 提示用户输入半径
  36. cout<<"Enter the radius of the pool: ";
  37. cin>>radius;
  38. // 声明 Circle 对象
  39. Circle Pool(radius);
  40. Circle PoolRim(radius + 3);
  41. //计算栅栏造价并输出
  42. FenceCost=PoolRim.Circumference()*FencePrice;
  43. cout<<"Fencing Cost is ¥"<<FenceCost<<endl;
  44. //计算过道造价并输出
  45. ConcreteCost=(PoolRim.Area()-
  46. Pool.Area())*ConcretePrice;
  47. cout<<"Concrete Cost is ¥"<<ConcreteCost<<endl;
  48. }

运行结果

  1. Enter the radius of the pool: 10
  2. Fencing Cost is 2858.85
  3. Concrete Cost is 4335.39

C++_构造函数与析构函数的更多相关文章

  1. .NET 基础 一步步 一幕幕[面向对象之构造函数、析构函数]

    构造函数.析构函数 构造函数: 语法: //无参的构造函数 [访问修饰符] 函数名() :函数名必须与类名相同. //有参的构造函数 [访问修饰符] 函数名(参数列表):函数名必须与类名相同. 作用: ...

  2. 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成员)

    [源码下载] 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成 ...

  3. C++构造函数、析构函数与抛出异常

    [本文链接] http://www.cnblogs.com/hellogiser/p/constructor-destructor-exceptions.html [问题] 构造函数可以抛出异常么?析 ...

  4. python中的构造函数和析构函数

    python中的特殊方法,其中两个,构造函数和析构函数的作用: 比说“__init__”这个构造函数,具有初始化的作用,也就是当该类被实例化的时候就会执行该函数.那么我们就可以把要先初始化的属性放到这 ...

  5. 内存的分配VS回收&构造函数VS析构函数

    之前有一个问题一直困扰着我,就是一个变量出了作用域,我以为这个变量的内存就被回收了,其实不是这样的,昨天问了一个高手,才豁然开朗,自己在看相关代码的反汇编代码,才知道原来真是这样就.这个问题,我想简单 ...

  6. C++C++中构造函数与析构函数的调用顺序

    http://blog.csdn.net/xw13106209/article/details/6899370 1.参考文献 参考1: C++继承中构造函数.析构函数调用顺序及虚函数的动态绑定 参考2 ...

  7. php 的 构造函数 和 析构函数

    构造函数 在C++ java里的应用及其普遍,今天好好研究了一下 php 的 构造函数 和 析构函数 构造函数 和 析构函数 构造函数 void __construct ([ mixed $args ...

  8. C++-理解构造函数、析构函数执行顺序

    先初始化序列中的函数调用,如果基类构造函数为非引用传递,则引起参数的拷贝构造 再: 先类内的成员构造函数(拷贝/默认),再类的构造函数:先基类,再派生类: 本文主要说明对象创建时构造函数的执行顺序,对 ...

  9. C++学习之类的构造函数、析构函数

    在C++的类中,都会有一个或多个构造函数.一个析构函数.一个赋值运算操作符.即使我们自己定义的类中,没有显示定义它们,编译器也会声明一个默认构造函数.一个析构函数和一个赋值运算操作符.例如: //声明 ...

随机推荐

  1. LeetCode赛题391----Perfect Rectangle

    #391. Perfect Rectangle Given N axis-aligned rectangles where N > 0, determine if they all togeth ...

  2. System.Media.Color与System.Drawing.Color转换方法

    public static System.Media.Color GetMediaColorFromDrawingColor(System.Drawing.Color color) {      re ...

  3. 131.003 数据预处理之Dummy Variable & One-Hot Encoding

    @(131 - Machine Learning | 机器学习) Demo 直观来说就是有多少个状态就有多少比特,而且只有一个比特为1,其他全为0的一种码制 {sex:{male, female}}​ ...

  4. Vue小案例(一)

    案例需求: 创建一个品牌展示表格,表头有编号(id),品牌名称(name),创建时间(time)和操作,需要实现的功能是对数据的增删操作,和时间的格式化. 思路分析:在开发之前需要想清楚要用到Vue中 ...

  5. error:将字符串转换为 uniqueidentifier 时失败

    sql server查询中出现 将字符串转换为 uniqueidentifier 时失败异常 原因为id设置为uniqueidentifier 字段,在where查询时需要做转换cast(id as ...

  6. 数据库常用SQL用法

    查找某列数据包含某一字符串: SELECT * FROM table WHERE column LIKE '%string%' 查找某列数据以某些字符串开头: SELECT * FROM table ...

  7. Scratch3.0——克隆代码仓库的正确姿势

    原文地址:https://blog.csdn.net/weiwoyonzhe/article/details/86603450 对Scratch3.0进行二次开发,首先要在github上fock官方代 ...

  8. Storm 实现滑动窗口计数和TopN排序

    计算top N words的topology, 用于比如trending topics or trending images on Twitter. 实现了滑动窗口计数和TopN排序, 比较有意思,  ...

  9. Java学习---异常处理的学习

    基础知识 任何一门计算机程序设计语言都包括有绝对正确和相对正确的语句.绝对正确: 指任何情况下, 程序都会按照流程正确执行:相对正确: 程序的运行受到运行环境的制约, 在这种情况下, 需要附加检测和控 ...

  10. 初始python(三)

    1. 循环 if, while, forbreak : 结束整个循环continue :跳出当前这次循环,但不结束整个循环else :结束整个循环后才执行,不能与break合用,但可以与continu ...