对象的构造与析构(一)
程序员文章站
2023-12-24 23:26:33
[TOC] 1. 构造函数的定义 从程序设计的角度,类的对象只是变量,在栈上和堆上创建对象时,成员变量初始为随机值;创建全局对象时,成员变量初始为0值。 C++中可以定义与类名相同的特殊成员函数,叫做构造函数 构造函数没有任何返回类型 类的构造函数在对象定义时自动被调用,进行对象的初始化工作 对象的 ......
目录
1. 构造函数的定义
从程序设计的角度,类的对象只是变量,在栈上和堆上创建对象时,成员变量初始为随机值;创建全局对象时,成员变量初始为0值。
c++中可以定义与类名相同的特殊成员函数,叫做构造函数
- 构造函数没有任何返回类型
- 类的构造函数在对象定义时自动被调用,进行对象的初始化工作
- 对象的定义和声明是不同的
test t1; //定义对象,为对象分配内存空间,并调用构造函数 extern test t2; //声明对象,告诉编译器存在这样一个对象
2. 构造函数的重载
- 一个类中可以根据需要定义多个重载的构造函数
- 构造函数的重载遵循c++重载的规则
#include <stdio.h> class test { public: test() { printf("test()\n"); } test(int v) { printf("test(int v), v = %d\n", v); } test(int v1, int v2) { printf("test(int v1, int v2), v1 = %d, v2 = %d\n", v1, v2); } }; int main() { test t; //调用 test() test t1(1); //调用 test(int v) test t2 = 2; //调用 test(int v),只有一个参数时可以用赋值符号 test t3(1, 2); //调用test(int v1, int v2) //test t4 = ?; //多个参数时,不能用赋值符号 int i(100); printf("i = %d\n", i); return 0; }
对于多个重载的构造函数,在定义对象时c++会自动匹配选择使用哪一个,但在一些特殊情况下,可能需要显式调用构造函数,比如下面的例子。
#include <stdio.h> class test { private: int value; public: test() { value = 0; } test(int v) { value = v; } int getvalue() { return value; } }; int main() { test t1[3]; //自动调用构造函数,所有对象默认用test()进行初始化 test t2[3] = {test(), test(1), test(2)}; //手动调用构造函数,对象使用test(int v)进行初始化 for (int i = 0; i < 3; i++) { printf("t1[%d].value = %d\n", i, t1[i].getvalue()); } for (int i = 0; i < 3; i++) { printf("t2[%d].value = %d\n", i, t2[i].getvalue()); } return 0; }
3. 两个特殊的构造函数
- 有两个特殊的构造函数:无参构造函数和拷贝构造函数(参数为
const classname &
的构造函数) - 当类中没有定义任何构造函数时,编译器默认提供一个函数体为空的无参构造函数
- 当类中没有定义拷贝构造函数时,编译器默认提供一个浅拷贝的拷贝构造函数,进行简单的成员变量值的复制
#include <stdio.h> class test { private: int i; int j; public: test() { i = 1; j = 2; } test(const test &obj) { i = obj.i; j = obj.j; } int geti() { return i; } int getj() { return j; } }; int main() { test t1; //调用无参构造函数 test t2 = t1; //调用拷贝构造函数 printf("t1.i = %d, t1.j = %d\n", t1.geti(), t1.getj()); printf("t2.i = %d, t2.j = %d\n", t2.geti(), t2.getj()); return 0; }
把代码第9-18行两个构造函数注释掉,看以看到运行结果和上面一致,只不过i和j的值变为了随机值。
拷贝构造函数有深拷贝和浅拷贝之分:
- 浅拷贝后对象的物理状态相同,深拷贝后对象的逻辑状态相同
- 编译器提供的拷贝构造函数只进行浅拷贝
当对象中有成员指代了系统中的资源时,就需要进行深拷贝,比如:
- 成员指向了堆空间内存
- 成员打开了系统中的文件
- 成员使用了系统中的网络端口
- ......
作为程序设计的一般性原则,只要自定义拷贝构造函数,必然需要实现深拷贝!!!
#include <stdio.h> class test { private: int i; int j; int *p; public: int geti() { return i; } int getj() { return j; } int *getp() { return p; } test(const test &t) { i = t.i; j = t.j; p = new int; *p = *t.p; } test(int v) { i = 1; j = 2; p = new int; *p = v; } }; int main() { test t1(3); test t2(t1); printf("t1.i = %d, t1.j = %d, t1.p = %p, *t1.p = %d\n", t1.geti(), t1.getj(), t1.getp(), *t1.getp()); printf("t2.i = %d, t2.j = %d, t2.p = %p, *t2.p = %d\n", t2.geti(), t2.getj(), t2.getp(), *t2.getp()); return 0; }
4. 初始化列表的使用
c++提供了初始化列表对成员变量进行初始化,语法规则为:
classname :: classname() : m1(v1), m2(v1, v2), m3(v3) { // some other initialize operation }
关于初始化列表的使用,有以下几条规则:
- const成员变量只能通过初始化列表初始化
- 如果成员变量中有其他类的对象,那么这些对象也只能通过初始化列表初始化
- 成员变量的初始化顺序与成员在类中的声明顺序相同,与初始化列表中的位置无关
- 初始化列表先于构造函数的函数体执行
#include <stdio.h> class value { private: int mi; public: value(int i) { printf("value()::i = %d\n", i); mi = i; } int geti() { return mi; } }; class test { private: const int ci; value m2; value m3; value m1; public: test() : m1(1), m2(2), m3(3), ci(100) { printf("test::ci = %d\n", ci); } int getci() { return ci; } int setci(int v) { int *p = const_cast<int *>(&ci); *p = v; } }; int main() { test t; printf("t.ci = %d\n", t.getci()); t.setci(10); printf("t.ci = %d\n", t.getci()); return 0; }