一、构造函数
1、定义:类中与类名相同的特殊成员函数
2、调用规则:
(1)在对象创建时自动调用,完成初始化相关工作
(2)无返回值,与类同名,默认无参,可以重载,可默认参数
(3)默认构造函数。 就是一个无参数的构造函数,如果不显示提供构造函数,系统就是调用默认的构造函数。如果我们提供了一个显示的构造函数,那么默认的构造函数就被隐藏掉了。构造函数一旦手动提供, 默认将不复存在。
3、拷贝构造函数:由已存在的对象,创建新的对象。形式如下:
class 类名
{
类名(const 类名 &another)
{
拷贝构造体
}
}
3.1 拷贝构造函数应用场景
class Test
{
public:
/*
Test() {} 默认的构造函数,已经手动提供,默认就被隐藏
*/
Test(int x, int y)
{
m_x = x;
m_y = y;
cout << “调用了有参数的构造函数” << endl;
}
//无参数的构造函数
Test(){
m_x = 0;
m_y = 0;
cout << “调用了无参数的构造函数” << endl;
}
//拷贝构造函数 ,想通过另一个Test对象 another 将本对象进行拷贝
Test(const Test & another)
{
m_x = another.m_x;
m_y = another.m_y;
cout << “调用了拷贝构造函数” << endl;
}
//等号操作符
void operator = (const Test &t)
{
cout << “调用了=号操作符” << endl;
m_x = t.m_x;
m_y = t.m_y;
}
void printT()
{
cout << "x : " << m_x << ", y : " << m_y << endl;
}
//提供一个析构函数
~Test()
{
cout << “~Test()析构函数被执行了” << endl;
cout << “(” << m_x << ", " << m_y << “)” << “被析构了” << endl;
}
private:
int m_x;
int m_y;
};
//拷贝构造函数的第一个场景
void test1()
{
Test t1(1, 2);
Test t2(t1);
//通过t1 给t2 进行赋值
t2.printT();
}
//拷贝构造函数的第二场景
void test2()
{
Test t1(1, 2);
Test t3=t1;//创建对象并完成初始化操作,因此调用拷贝构造函数
Test t2;//创建对象但没有初始化操作
t2 = t1; //调用的不是拷贝构造函数,调用的是 =号操作符,也能够完成将t1的值给t2 但不是调用t2的拷贝构造函数。
}
void func(Test t) //调用该函数时会调用局部变量t的拷贝构造函数
{
cout << “func begin…” << endl;
t.printT();
cout << “func end…” << endl;
}
//场景三
void test3()
{
cout << “test3 begin …” << endl;
Test t1(10, 20); //创建了一个t1的对象。通过t1的有参数的构造函数
func(t1);//调用拷贝构造函数
cout << “test3 end…” << endl;
}
//场景四
Test func2()
{
cout << “func2 begin…” << endl;
Test temp(10, 20); //调用temp的带参数构造函数
cout << “func2 end…” << endl;
return temp; // 返回一个类的对象时,会创建一个临时的匿名对象 = temp ,把temp的数据给到了临时的匿名对象,并调用这个临时匿名对象的拷贝构造函数, 将temp穿进去。
}
void test4()
{
cout << "test4 begin " << endl;
func2();//匿名对象在此被析构了, 如果一个临时的匿名对象,没有任何变量去接收它,编译器认为这个临时匿名对象没有用处。编译器会立刻销毁这个临时的匿名对象。
cout << “test4 end” << endl;
}
void test5()
{
cout << “test5 begin …” << endl;
Test t1 = func2();//如果有一个变量去接收这个临时的匿名对象, 编译器认为这个匿名对象转正了,就不会立刻给他销毁。
//t1 = 匿名的临时对象 为什么不会发生拷贝构造
// 此时的t1 去接收这个匿名的临时对象不是 重新创建一个t1 而是给这个匿名对象起个名字就叫t1
//一旦这个匿名对象有了自己的名字,编译器就不会立刻给这个匿名对象销毁了,
//就当普通局部变量处理了
cout << “test5 end…” << endl;
//在此时析构的t1
}
void test6()
{
cout << “test6 begin…” << endl;
Test t1; //调用t1的无参数构造函数
t1 = func2(); //调用的=号操作符 ,t1 = 匿名对象。 调用了t1的=号操作符
//此时匿名没有被转正,匿名没有自己的名字, 匿名对象这个内存没有自己的别名, 编译器就会立刻销毁。
cout << “test6 end…” << endl;
}
3.2默认构造函数与默认的拷贝构造函数
class B
{
public:
/*
默认提供的函数
B() {
//默认的无惨构造函数
}
B(const B& b)
{
m_b = b.m_b; //默认的拷贝构造函数
p = b.p;
}
~B()
{
//默认的析构函数
}
*/
//如果显示的写了一个普通构造函数, 会隐藏默认的无惨构造函数
//如果显示的写了一个拷贝构造函数 ,会隐藏默认的无参构造函数和默认的拷贝构造函数
//如果显示的写了一个析构函数, 会隐藏默认的析构函数
B(const B& b)
{
}
private:
int m_b;
char p;
};
class A
{
public:
A(int a)
{
m_a = a;
}
/
默认的拷贝构造函数
A(const A & a)
{
m_a = a;
}
/
//显示的提供一个拷贝构造的函数的时候,默认的拷贝构造函数就会被隐藏
A(const A &a) {
cout << “显示提供的拷贝构造函数” << endl;
m_a = a.m_a;
}
/
默认的析构函数
~A()
{
}
*/
//只有提供一个显示的析构函数,才会将默认的析构覆盖点
~A()
{
cout << "A的析构函数 调用 " << endl;
}
private:
int m_a;
};
3.3 深拷贝和浅拷贝
如果类中包含的数据元素全部在栈上,浅拷贝可以满足需求的。但如果堆上的数据,则会发生多次析构行为,即显示的提供一个拷贝构造函数,来完成深拷贝操作,防止内存泄漏或者多次释放。
class Teacher
{
public:
//有参数的构造函数
Teacher(int id, char name)
{
cout << “调用了Teacher 的构造函数” << endl;
//是给id 赋值
m_id = id;
//给姓名赋值
int len = strlen(name);
m_name = (char)malloc(len + 1);
strcpy(m_name, name);
}
//显示写一个拷贝构造函数
//通过显示拷贝构造函数提供了深拷贝的动作,若进行浅拷贝,对象的指针成员指向堆上同一个内存空间,对象销毁时在析构函数中会对此调用内存释放函数,造成段错误。
Teacher(const Teacher &another)
{
m_id = another.m_id; //给id赋值
int len = strlen(another.m_name);
m_name = (char*)malloc(len + 1);
strcpy(m_name, another.m_name);
}
~Teacher() {
//在构造函数中, 已经开辟了内存 所以为了防止泄露
//在析构函数中,在对象销毁之前,把m_name的内存释放掉
if (m_name != NULL) {
free(m_name);
m_name = NULL;
cout << “释放掉了m_name” << endl;
}
}
private:
int m_id;
char *m_name;
};
3.4 构造函数初始化列表
1、如果类中的一个成员对象,其本身就是一个类或者一个结构,而且这个成员它有带参数的构造函数,这时需要通过构造函数的初始化列表对该成员变量进行初始化。
2、当类成员中有一个const对象或者一个引用时,他们必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的负载操作,这样是不被允许的。
3、初始化列表中的初始化顺序,与声明顺序有关,与前后赋值顺序无关
class A
{
public:
//显示提供一个带参数的构造函数
A(int a)
{
m_a = a;
cout << "a = " << m_a << “调用了构造函数” << endl;
}
~A()
{
cout << "a = " << m_a << “被析构了” << endl;
}
private:
int m_a;
};
class B
{
public:
B(int b) :a1(10), a2(100) //在初始化B的时候通过初始化列表给内部对象a1 和a2 进行了初始化
{
m_b = b;
cout << "b = " << m_b << “调用了构造函数” << endl;
}
B(int aa1, int aa2, int b) : a1(aa1), a2(aa2), m_b(b) //通过初始化列表不仅能够初始化成员对象, 还可以初始化成员变量
{
}
~B()
{
cout <<“b = " <<m_b << " 调用了析构函数” << endl;
}
private:
int m_b;
A a2;
A a1;
};
class ABC
{
public:
ABC()
{
}
ABC(int a, int b, int c) :m_a(a), m_b(b), m_c© {}
ABC(const ABC &abc)
{
m_a = abc.m_a;
m_b = abc.m_b;
m_c = abc.m_c;
}
void printABC()
{
cout << m_a << “,” << m_b << “,” << m_c << “,” << endl;
}
private:
int m_a;
int m_b;
int m_c;
};
class ABCD
{
public:
ABCD(int a, int b, int c, int d, int m) :m_abc(a, b, c), m_m(m)//常量成员变量不能够赋值,只能通过初始化列表进行初始化
{
m_d = d;
}
ABCD(int d, ABC abc, int m) :m_abc(abc), m_m(m)
{
m_d = d;
}
ABCD(int d, int m) :m_m(m)
{
}
private:
ABC m_abc;
int m_d;
const int m_m; //常量
};
二、析构函数:
规则:
(1)对象销毁时,自动调用,完成销毁的善后工作
(2)无返回值,与类同名,无参。不可以重载
(3)析构函数的作用并不是删除对象,而在对象销毁前完成一些清理工作
(4)构函数的调用顺序, 跟对象的构造顺序相反, 谁先构造,谁最后一个被析构。