第10课 - 构造与析构 - 下
1. C++中的对象组合
C++中提供了初始化列表对成员变量进行初始化
语法规则:
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}
注意:
成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关。初始化列表先于构造函数的函数体执行。
#include <stdio.h>
class M
{
private:
int mI;
public:
M(int i)
{
printf("M(int i), i = %d ", i);
mI = i;
}
int getI()
{
return mI;
}
};
class Test
{
private:
const int c; //类定义里面的成员变量不允许赋初值。
M m1;
M m2;
public:
Test() : c(1), m2(3), m1(2)
{
printf("Test() ");
}
void print()
{
printf("c = %d, m1.mI = %d, m2.mI = %d ", c, m1.getI(), m2.getI());
}
};
//这个程序若不Test() : c(1), m2(3), m1(2)这样写,最初的编译是会出错的。原因是没有对c初始化,m1和m2的初始化也是有参数的,按该题的方式来写是不妥的。
void run()
{
Test t1;
t1.print();
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
运行结果:
M<int i>, i = 2
M<int i>, i = 3
Test<>
c = 1, m1.mI = 2, m2.mI =3
由程序可以看出来,程序是先执行初始化列表中的内容然后再执行构造函数结构体中的内容。
2. const常量
类中的const成员是肯定会被分配空间的。
类中的const成员变量只是一个只读变量。
编译器无法直接得到const成员变量的初始值,因此无法进入符号表成为真正意义上的常量。
l 初始化与赋值不同
初始化是用已存在的对象或值对正在创建的对象进行初值设置。
赋值是用已存在的对象或值对已经存在的对象进行值设置。
初始化:被初始化的对象正在创建。赋值:被赋值的对象已经存在。
const常量能被初始化但是不能被赋值。
3. 对象的销毁
生活中存在的对象都是被初始化后才上市的。生活中的对象被销毁前会做一些清理工作。
一般而言所有被销毁的对象都需要做清理。为每个类都提供一个public的destroy函数
对象不再被需要时立即调用destroy函数进行清理。
destroy只是一个普通的函数,必须显示的调用。如果对象销毁前没有做清理,那么很可能造成资源泄漏。在构造函数中申请的资源,需要在对象销毁前释放。
4. 析构函数
C++中的类可以定义一个特殊的成员函数清理对象。
这个特殊的成员函数叫做析构函数。
定义:~ClassName()。
析构函数没有参数也没有任何返回类型的声明。
析构函数在对象销毁时自动被调用。
5. Array的进化
Array.h
#ifndef _ARRAY_H_
#define _ARRAY_H_
class Array
{
private:
int mLength;
int* mSpace;
public:
Array(int length);
Array(const Array& obj);
int length();
void setData(int index, int value);
int getData(int index);
~Array();
};
#endif
Array.cpp
#include "Array.h"
Array::Array(int length)
{
if( length < 0 )
{
length = 0;
}
mLength = length;
mSpace = new int[mLength];
}
Array::Array(const Array& obj)
{
mLength = obj.mLength;
mSpace = new int[mLength];
for(int i=0; i<mLength; i++)
{
mSpace[i] = obj.mSpace[i];
}
}
int Array::length()
{
return mLength;
}
void Array::setData(int index, int value)
{
mSpace[index] = value;
}
int Array::getData(int index)
{
return mSpace[index];
}
Array::~Array()
{
mLength = -1;
delete[] mSpace;
}
main.c
#include <stdio.h>
#include "Array.h"
int main()
{
Array a1(10);
for(int i=0; i<a1.length(); i++)
{
a1.setData(i, i);
}
for(int i=0; i<a1.length(); i++)
{
printf("Element %d: %d ", i, a1.getData(i));
}
Array a2 = a1;
for(int i=0; i<a2.length(); i++)
{
printf("Element %d: %d ", i, a2.getData(i));
}
printf("Press any key to continue...");
getchar();
return 0;
}
6. 构造与析构
构造函数与析构函数的调用秩序。
当类中有成员变量是其它类的对象时,首先调用成员变量的构造函数,调用顺序与声明顺序相同;之后调用自身类的构造函数。析构函数的调用秩序与对应的构造函数调用秩序相反。
#include <stdio.h>
class Test
{
private:
int mI;
public:
Test()
{
printf("Test() ");
mI = -1;
}
Test(int i)
{
printf("Test(int i), i = %d ", i);
mI = i;
}
Test(const Test& obj)
{
printf("Test(const Test& obj), i = %d ", obj.mI);
mI = obj.mI;
}
~Test()
{
printf("~Test(), i = %d ", mI);
}
};
void func(Test t)
{
Test r(1);
}
void run()
{
Test t(0);
func(t);
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
运行结果:
Test<int i>, i = 0
Test<const Test& obj>,i = 0
Test<int i>, i = 1
~Test<>, i = 1
~Test<>, i = 0
~Test<>, i = 0
分析:
我们程序第一步先运行到 Test t(0),会执行的语句是 printf("Test(int i), i = %d ", i);
第二步是运行到func(t),执行的语句是拷贝构造函数 printf("Test(const Test& obj), i = %d ", obj.mI)。第三步运行到Test r(1),执行的程序是printf("Test(int i), i = %d ", i)。
之后是反着执行析构函数。
小结:
析构函数是C++中对象销毁时做清理工作的特殊函数。
析构函数在对象销毁时自动被调用。
析构函数是对象所使用的资源及时释放的保障。
析构函数的调用秩序与构造函数相反。
问:
可以直接调用构造函数吗?
如果可以,直接调用构造函数会有什么情况发生呢?
答:
可以直接调用构造函数,直接调用构造函数将得到一个临时对象。
#include <stdio.h>
class Test
{
private:
int mI;
int mJ;
const char* mS;
public:
Test()
{
printf("Test() ");
mI = 0;
mJ = 0;
}
Test(const char* s)
{
printf("Test(const char* s) ");
Test();
mS = s;
}
~Test()
{
printf("~Test() ");
}
void print()
{
printf("mI = %d, mJ = %d, mS = %s ", mI, mJ, mS);
}
};
void run()
{
Test t = Test("Delphi Tang"); // Test t("Delphi Tang");
t.print();
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
运行结果:
Test<const char* s>
Test<>
~Test<>
mI = -142459858, mJ = -2, mS = Delphi Tang
~Test<>
分析:构造函数只可以手工调用或者被编译器自动的调用。若上面两种情况都不属于,就会在调用对象的时候产生一个临时对象,临时对象调用结束后马上就会被销毁(析构函数自动调用)。