1.构造函数
在c++中,有一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显示调用(也不能够显示调用),而是在创建对象时自动执行,这种特殊的成员函数就是构造函数。
#include <iostream>
using namespace std ;
class Student
{
private: char *m_name ;
int m_age ;
public:
//声明构造函数
Student(char *name,int age)
{
m_name = name ;
m_age = age ;
}
~Student(){}
//声明普通成员函数
void print()
{
cout << m_name << "的年龄是" << m_age << endl ;
}
};
int main()
{
Student stu1("小明",16);
stu1.print();
return 0 ;
}
关于构造函数的几点说明:
1.构造函数的名称必须和类名相同
2.构造函数不能有返回值,即不能有return语句
3.构造函数在定义对象时会自动执行,不需要手动调用
2.构造函数的重载和调用
#include <iostream>
using namespace std ;
class Test
{
private: int m_a ;
int m_b ;
public:
Test()//无参构造函数
{
m_a = 1 ;
m_b = 2 ;
cout << "无参构造函数被调用" << endl ;
}
Test(int a)//有一个参数构造函数
{
m_a = a ;
m_b = 0 ;
cout << "有一个参数的构造函数被调用" << endl ;
}
Test(int a, int b)//有两个参数构造函数
{
m_a = a ;
m_b = b ;
cout << "有一个参数的构造函数被调用" << endl ;
}
void print()
{
cout << "a=" << m_a << ",b=" << m_b <<endl ;
}
};
int main()
{
Test t ;
t.print();
Test t1(10);
t1.print();
Test t2(10,20);
t2.print();
return 0 ;
}
运行结果
无参构造函数被调用
a=1,b=2
有一个参数的构造函数被调用
a=10,b=0
有一个参数的构造函数被调用
a=10,b=20
3.拷贝构造函数
形式:className(const className &obj)
作用:用一个现有的对象去初始化另一个对象
调用时机:(对于上面的代码)
Test的拷贝构造函数定义如下:
Test(const Test &obj)
{
m_a = obj.m_a ;
m_b = obj.m_b ;
cout << "拷贝构造函数被调用" << endl ;
}
(1)用一个对象去初始化另一个对象
Test t(10) ;
Test t1(t) ;
Test t2 = t1 ;
Test t3 = Test(t1) ;
当创建对象t1 、t2、t3时都是用t进行初始化的,都会调用拷贝构造函数
(2) 当函数形参是一个对象时,例如:
void printT()
{
t.print() ;
}
通常以下面的方式调用:
Test t1(1,2)
printT(t1);
t1在被构造的时候会调用2个参数的构造函数,之后调用printT,实参到形参的传递,实际就是一个复制的过程,这里等价于“Test t = t1” ,会调用形参的拷贝构造函数 。
一般情况下,对象作为函数形参时最好使用引用或者对象指针,以避免调用拷贝构造函数时进行对象的复制。
(3)函数返回值是一个对象
Test func()
{
Test t1 ;
return t1 ;
}
一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不在自动生成
4.析构函数
创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,如释放分配的内存,关闭打开的文件等,这个函数就是析构函数。
析构函数也是一种特殊的成员函数,没有返回值,不需要程序员显示调用,而是在销毁对象时自动执行,析构函数的名字是在类名前加一个“~”,注意:析构偶函数没有参数,不能被重载,一个类只有一个析构函数,如果用户没有定义一个析构函数,编译器会自动生成一个默认的析构函数。
例:
#include <iostream>
using namespace std ;
class Test
{
private: int m_a ;
int m_b ;
public:
Test(int a,int b)
{
m_a = a ;
m_b = b ;
cout << "构造函数被调用" << endl ;
}
~Test()
{
cout << "析构函数被调用" << endl ;
}
};
int main()
{
Test t(1,2) ;
return 0 ;
}
运行结果:
函数被调用
析构函数被调用
析构函数在对象被销毁时调用,而对象的销毁时机与它所在的内存区域有关,在所有函数之外创建的对象是全局对象,它和全局变量类似,位于内存分区中的全局数据库区,程序在结束执行时会调用这些对象的析构函数
在函数内部或者代码块中创建的对象是局部对象,位于栈区 ,出了当前作用域会调用这些对象的析构函数
5.构造函数的参数初始化列表
如果有一个类成员,它本身是一个类或者一个结构,而且这个成员只有一个带参数的构造函数,没有默认构造函数,这时如果要对这个类成员进行初始化,就必须调用 这个类成员的带参数的构造函数,比如:
Class A
{
public:
A(int a,int b)
{
m_a = a ;
m_b = b ;
printf("A 构造函数被调用,a = %d, b = %d\n",a,b);
}
private:
int m_a ;
int m_b ;
};
Class B
{
private:
A m1 ;
int m_c ;
int m_d ;
};
类B中有类A的一个对象,但类B没有定义构造函数,所以类B中有一个默认的无参构造函数,即使如此,我们也不能按以下方式直接定义一个类B的对象。直接定义对象在编译时会报错,“类A中没有合适的默认构造函数可用”,原因是类B中有一个类A的对象m1 ,而类A不支持无参构造,所以类B无法以默认的方式去初始化m1,必须显示得调用调用类A的构造函数对m1进行初始化。
为了有效地对m1初始化,c++引用了新的初始化成员变量的方法,就是对象初始化列表
使用方法:在函数首部和函数体之间加一个:,后面紧跟要初始化的参数,如:
B(int a ,int b ,int c,int d): m1(a,b),m_c(c)
{
m_d = d ;
printf("B 的构造函数被调用\n") ;
}
初始化列表可用于全部成员的初始化,也可用于部分变量的初始化
说明:1.初始化列表要优先于当前对象的构造函数先执行;
2.子对象的初始化顺序和其在初始化列表的排列顺序无关,但和在类中的声明顺序有关,先声明的先初始化。
3.析构函数的调用顺序和构造函数相反;
4.参数初始化列表可以初始化const成员变量,初始化const成员变量的唯一方法就是初始化列表。
完整代码:
#include <iostream>
using namespace std ;
class A
{
public :
A(int a,int b)
{
m_a = a ;
m_b = b ;
printf("A 构造函数被调用,a = %d ,b = %d\n",a,b);
}
~A()
{
printf("A 析构函数被调用,a = %d ,b = %d\n",m_a,m_b);
}
privateiii :
int m_a ;
int m_b ;
};
class B
{
private:
A m1 ;
A m2 ;
A m3 ;
int m_c;
int m_d;
const int f ;
public:
B(int a,int b,int c,int d):m3(a,b),m2(c,d),m1(b,c),f(0)
{
m_c = c ;
m_d = d ;
cout << "B 构造函数被调用,c =" << c <<",d = " << d << endl ;
}
~B()
{
printf("B 析构函数被调用,c = %d ,d = %d\n",m_c,m_d);
}
my_copy(const my_copy &p)
{
m_a = p.m_a ;
m_b = p.m_b ;
m_c = p.m_c ;
m_d = p.m_d ;
}
};
int main()
{
B b(1,2,3,4);
return 0;
}
运行结果:
[root@localhost 201907]# ./lesson3
A 构造函数被调用,a = 2 ,b = 3
A 构造函数被调用,a = 3 ,b = 4
A 构造函数被调用,a = 1 ,b = 2
B 构造函数被调用,c =3,d = 4
B 析构函数被调用,c = 3 ,d = 4
A 析构函数被调用,a = 1 ,b = 2
A 析构函数被调用,a = 3 ,b = 4
A 析构函数被调用,a = 2 ,b = 3