来总结一下C++中的构造函数 析构函数,强化一下记忆:
构造函数:
构造函数一种特殊的成员函数,名字与类名相同,没有返回值,可以有参数。
构造函数的作用是在创在对象时,对对象内的成员进行赋初值操作。
构造函数必须是 public 属性的,否则创建对象时无法调用。当然,设置为 private、protected 属性也不会报错,但是没有意义。
构造函数没有返回值,因为没有变量来接收返回值,即使有也毫无用处,这意味着:
- 不管是声明还是定义,函数名前面都不能出现返回值类型,即使是 void 也不允许;
- 函数体中不能有 return 语句。
如果定义类的时候,没有定义构造函数,系统会自动生成一个默认的无参构造函数。
//假如A是一个类
A(){ } //这是系统生成的默认无参构造函数 可以认为其不做任何事
调用没有参数的构造函数可以省略括号。比如,在栈上创建对象可以写作A a1()
或A a
,在堆上创建对象可以写作A *pa = new A()
或A *pa = new A
,它们都会调用构造函数 A()。
一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。
对象生成时,自动调用构造函数,一旦对象生成完毕,则无法在其上执行构造函数。
一个类可以有多种构造函数:构造函数的重载。
和普通成员函数一样,构造函数是允许重载的。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。
构造函数在实际开发中会大量使用,它往往用来做一些初始化工作,例如对成员变量赋值、预先打开文件等。
>构造函数的初始化列表:
构造函数的一项重要功能是对成员变量进行初始化,为了达到这个目的,可以在构造函数的函数体中对成员变量一一赋值,还可以采用参数初始化表。
#include <iostream>
using namespace std;
class Student{
private:
char *m_name;
int m_age;
float m_score;
public:
Student(char *name, int age, float score);
};
//采用参数初始化表
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
定义构造函数时并没有在函数体中对成员变量一一赋值,其函数体为空(当然也可以有其他语句),而是在函数首部与函数体之间添加了一个冒号:
,后面紧跟m_name(name), m_age(age), m_score(score)
语句,这个语句的意思相当于函数体内部的m_name = name; m_age = age; m_score = score;
语句,也是赋值的意思。
使用参数初始化表并没有效率上的优势,仅仅是书写方便,尤其是成员变量较多时,这种写法非常简明明了。
参数初始化表可以用于全部成员变量,也可以只用于部分成员变量。下面的示例只对 m_name 使用参数初始化表,其他成员变量还是一一赋值:
Student::Student(char *name, int age, float score): m_name(name){
m_age = age;
m_score = score;
}
注意,参数初始化顺序与初始化表列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。
>参数初始化表还有一个很重要的作用,那就是初始化 const 成员变量。初始化 const 成员变量的唯一方法就是使用参数初始化表。
class VLA{
private:
const int m_len;
int *m_arr;
public:
VLA(int len);
};
VLA::VLA(int len){
m_len = len; //错误的
m_arr = new int[len];
}
//必须使用参数初始化表来初始化 m_len
VLA::VLA(int len): m_len(len){
m_arr = new int[len];
}
>构造函数和数组:
有这样一个类:
class csample{
int x;
public:
csample(){ //构造函数1
cout<<"constructor 1 called"<<endl;
}
csample(int n){//构造函数2
x = n;
cout<<"constructor 2 called"<<endl;
}
};
csample a[2];
//constructor 1 called constructor 1 called
csample a[2] = {1,2};
//constructor 2 called constructor 2 called
csample a[2] = {1};
//constructor 2 called constructor 1 called
csample * arr = new csample[2];
//constructor 1 called constructor 1 called
csample * parr[3] = {new csample(1),new csample()};
//constructor 2 called constructor 1 called //注意只有两个元素
//因为这里是创建了一个对象指针数组,指向还未明确,因为只生成了两个对象 前两个指针分别指向了生成的两个对象 最后一个指针没有指向这两个对象
复制构造函数:
只有一个参数:同类对象的引用(X & x) 假设X为一个类
比如:X::X(X & x){ } X::X(const X & x){ }这样的形式
如果没有定义复制构造函数,则系统自动生成默认复制构造函数,默认复制构造函数完成复制的功能。
class csample {
csample(csample c){//这样的复制构造函数是错误的 不允许这样写!!
}
csample(csample & c){
}
csample(const csample & c){
}
}
复制构造函数被调用的三种情况:
1.用一个对象初始化另一个对象时。
csample a1;
csample a2(a1);
csample a3 = a1; //注意这是初始化语句 非赋值语句
2.对象作为函数的参数时。
void test (csample c){ }
//函数被调用时,该类的复制构造函数被调用。
3.对象作为函数的返回值时。
csample test (){ }
//函数返回时 该类的复制构造函数被调用
需要注意的是:对象之间的赋值时不会调用复制构造函数的
csample c1;
csample c2;
c1 = c2;
在用对象作为函数参数时,我们可以用常量引用:
void func (const csample & c){ }
//这样会减少复制构造函数的调用 减少对象的生成
//加上const保证对象不会被改变
类型转化构造函数:
只有一个参数且不是复制构造函数的构造函数就可以认为是类型转化构造函数。
类型转化构造函数的目的是实现类型的自动转化。
当需要的时候,系统会根据类型转化构造函数自动生成一个无名临时对象。
class csample{
int x;
public:
csample(){ //构造函数1
cout<<"constructor 1 called"<<endl;
}
csample(int n){//构造函数2 类型转化构造函数
x = n;
cout<<"constructor 2 called"<<endl;
}
};
csample c1();
csamople c2 = 12; //使用 类型转化构造函数 初始化
c1 = 10; //10 被自动转化为一个临时对象(通过调用类型转化构造函数) 为c1赋值
析构函数:
创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。
析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个 ~
符号。
函数名是标识符的一种,原则上标识符的命名中不允许出现
~
符号,在析构函数的名字中出现的~
可以认为是一种特殊情况,目的是为了和构造函数的名字加以对比和区分。
C++ 中的 new 和 delete 分别用来分配和释放内存,它们与C语言中 malloc()、free() 最大的一个不同之处在于:用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数。构造函数和析构函数对于类来说是不可或缺的,所以在C++中非常鼓励使用 new 和 delete。
注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。
调用时机:
在所有函数之外创建的对象是全局对象,它和全局变量类似,位于内存分区中的全局数据区,程序在结束执行时会调用这些对象的析构函数。
在函数内部创建的对象是局部对象,它和局部变量类似,位于栈区,函数执行结束时会调用这些对象的析构函数。
new 创建的对象位于堆区,通过 delete 删除时才会调用析构函数;如果没有 delete,析构函数就不会被执行。
资料:http://c.biancheng.net/cpp/biancheng/view/196.html C语言中文网