OOP课程笔记:第二章 构造函数与析构函数
第二章 构造函数与析构函数
构造函数是在创建对象时,使用给定的值来将对象初始化。
析构函数是在系统释放对象前,对对象做一些善后工作。
构造函数是类的成员函数,系统约定构造函数名必须与类名相同。
构造函数可以带参数、可以重载,同时没有返回值
class A{
float x, y;
public:
A(float a, float b){
x = a;
y = b;
} //构造函数,初始化对象
float Sum(void) { return x + y; }
void Set(float a, float b){
x = a;
y = b;
}
Print(void) { cout << "x=" << x << '\t' << "y=" << y << endl; }
};
void main(void){
A a1(2.0, 3.0); // 定义时调用构造函数初始化
A a2(1.0, 2.0);
a2.Set(10.0, 20.0); // 利用成员函数重新为对象赋值
a1.Print();
a2.Print();
}注意点:
1.构造函数的函数名必须与类名相同。
2.在定义构造函数时,不能指定函数返回值的类型,也不能指定为 void 类型。
3.一个类可以定义若干个构造函数。当定义多个构造函数时,必须满足函数重载的原则。
4.构造函数可以指定参数的缺省值。
5.若定义的类要说明该类的对象时,构造函数必须是公有的成员函数。如果定义的类仅用于派生其它类时,则可将构造函数定义为保护的成员函数。
6.由于构造函数属于类的成员函数,它对私有数据成员、保护的数据成员和公有的数据成员均能进行初始化
1.缺省构造函数
若没有显式定义构造函数,系统默认缺省的构造函数。
缺省的构造函数并不对所产生对象的数据成员赋初值,即新产生对象的数据成员的值是*不确定的。
class A{
float x, y;
public:
A() {} // 隐含的缺省的构造函数
void Print(void) { cout << x << '\t' << y << endl; }
};
A a1, a2; 2.对局部对象,静态对象,全局对象的初始化
对于局部对象,每次定义对象时,都要调用构造函数。
对于静态对象,是在首次定义对象时,调用构造函数的,且由于对象一直存在,只调用一次构造函数。
对于全局对象,是在 main() 执行之前调用构造函数的。
3.参数初始化列表
通过参数初始化列表来实现对数据成员的初始化,在构造函数首部实现。
Box∷Box(int h,intw,intlen):height(h),width(w),length(len){}注意:
如果数据成员是数组,则应当在构造函数的函数体中用语句对其赋值
class Student{
public:
Student(int n, charchar s, nam[]) : num(n), sex(s) // 定义构造函数
{
strcpy(name, mam); // 数组在函数体完成赋值
} // 函数体
private:
int num;
char sex;
char name[20];
};1.在定义类时,只要显式定义了一个类的构造函数,则编译器就不产生缺省的构造函数
2.所有的对象在定义时,必须调用构造函数,不存在没有构造函数的对象
class A{
float x, y;
public:
A(float a, float b){
x = a;
y = b;
}
void Print(void) { cout << x << '\t' << y << endl; }
};
void main(void){
A a1; // 会发生错误,没有构造函数调用
A a2(3.0, 30.0);
}4.构造函数与 new 运算符
可以使用 new 运算符来动态地建立对象。建立时要自动调用构造函数,以便完成初始化对象的数据成员。最后返回这个动态对象的起始地址。
用 new 运算符产生的动态对象,在不再使用这种对象时,必须用 delete 运算符来释放对象所占用的存储空间。
用 new 建立类的对象时,可以使用参数初始化动态空间。
class A{
float x, y;
public:
A(float a, float b){
x = a;
y = b;
}
A(){
x = 0;
y = 0;
}
void Print(void) { cout << x << '\t' << y << endl; }
};
void main(void){
A *pa1, *pa2;
pa1 = new A(3.0, 5.0); // 用new动态开辟对象空间,初始化
pa2 = new A; // 用new动态开辟空间,调用构造函数初始化
pa1->Print();
pa2->Print();
delete pa1; // 用delete释放空间
delete pa2; // 用delete释放空间
}5.析构函数
1.析构函数是成员函数,函数体可写在类体内,也可写在类体外
2.析构函数是一个特殊的成员函数,函数名必须与类名相同,并在其前面加上字符 ~
3.析构函数不能带有任何参数,不能有返回值,不指定函数类型
4.一个类中,只能定义一个析构函数,析构函数不允许重载。
5、析构函数是在撤消对象时由系统自动调用的。
在程序的执行过程中,当遇到某一对象的生存期结束时,系统自动调用析构函数,然后再收回为对象分配的存储空间。
class A
{
float x, y;
public:
A(float a, float b){
x = a;
y = b;
cout << "调用非缺省的构造函数\n ";
}
A(){
x = 0;
y = 0;
cout << "调用缺省的构造函数\n ";
}
~A() { cout << "调用析构函数\n "; }
void Print(void){
cout << x << '\t' << y << endl;
}
};
void main(void){
A a1;
A a2(3.0, 30.0);
cout << "退出主函数\n ";
}
//运行结果:
//调用缺省的构造函数
//调用非缺省的构造函数
//退出主函数
//调用析构函数
//调用析构函数new 对象的析构
在程序的执行过程中,对象如果用 new 运算符开辟了空间,则在类中应该*定义一个析构函数,并在析构函数中使用 delete 删除由 new 分配的内存空间。因为在撤消对象时,系统自动收回为对象所分配的存储空间,而不能自动收回由 new 分配的动态存储空间。
当使用运算符 delete 删除一个由 new 动态产生的对象时,它首先调用该对象的析构函数,然后再释放这个对象占用的内存空间。
class Str{
char *Sp;
int Length;
public:
Str(char *string){
if (string){
Length = strlen(string);
Sp = new char[Length + 1]; //在构造函数中将成员数据指针指向动态开辟的内存
strcpy(Sp, string); // 用初值为开辟的内存赋值
}
else
Sp = 0;
}
void Show(void) { cout << Sp << endl; }
~Str(){
if (Sp)
delete[] Sp; // 析构函数,当释放对象时收回用new开辟的空间
}
};
void main(void){
Str s1("Study C++");
s1.Show();
}6.不同类型的对象调用构造和析构函数
| 对象类型 | 构造函数 | 析构函数 |
|---|---|---|
| 全局定义对象 | 程序开始运行 | 程序结束 |
| 局部定义对象 | 程序执行到定义对象处 | 退出对象作用域 |
static定义局部对象 | 首次到达对象定义处 | 程序结束 |
new 运算符动态生成对象 | 产生对象时 | delete 时,否则程序结束仍然存在 |
动态构造及析构对象数组
用 new 运算符来动态生成对象数组时,自动调用构造函数,而用 delete 运算符来释放指针所指向的对象数组占用的存储空间时,在指针变量的前面必须加上 [ ],才能将数组元素所占用的空间全部释放。否则,只释放第0个元素所占用的空间。
class A{
float x, y;
public:
A(float a = 0, float b = 0){
x = a;
y = b;
cout << "调用了构造函数\n";
}
void Print(void) { cout << x << '\t' << y << endl; }
~A() { cout << "调用了析构函数\n"; }
};
void main(void){
cout << "进入main()函数\n";
A *pa1;
pa1 = new A[3]; // 开辟数组空间
cout << "\n完成开辟数组空间\n\n";
delete[] pa1; // 必须用[]删除开辟的空间
cout << "退出main()函数\n";
}7.缺省的析构函数
若在类的定义中没有显式地定义析构函数时,则编译器自动地产生一个缺省的析构函数,其格式为
ClassName::~ClassName(){};任何对象都必须有构造函数和析构函数
但在撤消对象时,要释放对象的数据成员用 new 运算符分配的动态空间时,必须显式地定义析构函数。
8.完成拷贝功能的构造函数
可以在定义一个对象的时候用另一个对象为其初始化,即构造函数的参数是另一个对象的引用,这种构造函数常为完成拷贝功能的构造函数
class A{
float x, y;
public;
A(float a = 0, float b = 0){
x = a;
y = b;
}
A(A &a){ // 引用作为形参
x = a.x; // 完成初始化功能
y = a.y;
}
};
void main(void){
A a1(1.0, 2.0);
A a2(a1);
}缺省的拷贝构造函数
如果没有定义完成拷贝功能的构造函数,编译器自动生成一个隐含的完成拷贝功能的构造函数,依次完成类中对应数据成员的拷贝。
A::A(A &a){
x=a.x;
y=a.y;
}new的拷贝构造函数
当类中的数据成员中使用 new 运算符,动态地申请存储空间进行赋初值时,必须在类中显式地定义一个完成拷贝功能的构造函数,以便正确实现数据成员的拷贝
class Str{
int Length;
char *Sp;
public:
Str(char *string){
if (string){
Length = strlen(string);
Sp = new char[Length + 1];
strcpy(Sp, string);
}
else
Sp = 0;
}
void Show(void) { cout << Sp << endl; }
~Str(){
if (Sp)
delete[] Sp; // delete 内存地址
}
};
//隐含的拷贝构造函数为:
Str::Str(Str &s){
Length=s.Length;
Sp=s.Sp; // 新的指针指向的数据是同一个地址
}
void main(void)
{
Str s1("Study C++");
Str s2(s1);
s1.Show();
s2.Show();
// 同一个地址被delete两次会发生错误
}在这种情况下,必须要定义完成拷贝功能的构造函数。
Str::Str(Str &s){
if (s.Sp){
Length = s.Length;
Sp = new char[Length + 1]; // 额外开辟一个新的空间,并非地址赋值
strcpy(Sp, s.Sp);
}
else
Sp = 0;
}9.构造函数与对象成员
在类 A 中 包含类 B 的对象
对类 A 的对象初始化的同时还要对其成员数据类 B 的对象进行初始化,
所以,类 A 的构造函数中要调用类 B 的构造函数
class B{
...// 省略
};
class A{
...//省略
B b1, b2;
};使用构造函数来对成员对象进行初始化
a1(b, c) 调用 A 的构造函数进行初始化
class A{
float x, y;
public:
A(int a, int b){
x = a;
y = b;
}
void Show() { cout << "x=" << x << '\t' << "y=" << y << '\n'; }
};
class C{
float z;
A a1; // 类C的数据成员为类A的对象a1
public:
C(int a, int b, int c) : a1(b, c) { z = a; } // 类C的对象初始化
void Show(){
cout << "z=" << a << '\n';
a1.Show();
}
};
void main(void){
C c1(1, 2, 3); // 对类C的对象初始化
c1.Show();
}对象成员的构造函数顺序
对对象成员的构造函数的调用顺序取决于这些对象成员在类中说明的顺序,与它们在成员初始化列表中的顺序无关。
析构函数的调用顺序与构造函数正好相反。
评论已关闭