构造函数:在定义对象的时候对 对象 进行初始化的函数
构造函数的特点:
1、函数名和类名相同
2、构造函数没有返回值
3、构造函数不需要手动调用,在创建对象的时候编译器自己会调用
4、构造函数可以被重载
注意事项:如果类中没有写构造函数,在创建对象的时候编译器会自动创建一个无参构造函数(形式:类名() 函数参数列表中没有参数)是一个空的函数,如果你写了构造函数(编译器默认的无参构造消失),并且形式不跟编译器默认的无参函数形式一样,则在定义对象时要初始化对象的成员(初始化形式:类名 对象名(参数列表),即括号法,下面有介绍)参数要和希望使用的构造函数的参数一致,否则系统报错:没有合适的默认构造函数进行初始化。
eg:
class Student
{
public:
Student() //无参构造函数
{
_id = 0;
_name = NULL;
cout << "无参构造函数 Student() 被调用" << endl;
}
Student(int id)
{
_id = id;
_name = NULL;
cout << "构造函数 Student(int id) 被调用" << endl;
}
Student(char *name)
{
_id = 0;
_name = name;
cout << "构造函数 Student(char *name) 被调用" << endl;
}
Student(int id, char *name) //以上4种都是自定义的构造函数
{
_id =id;
_name = name;
cout << "构造函数 Student(int id, char *name) 被调用" << endl;
}
private:
char *_name;
};
构造函数的使用形式
int main()
{
Student s; //无参构造函数 Student() 被调用
1.括号法:
Student s1(1, "笑笑"); //构造函数 Student(int id, char *name) 被调用
Student s2(2); //构造函数 Student(int id) 被调用
2.等号法 = 注意只能调用有且仅有一个参数的构造函数
//2、等号法 = 只能调用有一个参数的构造函数
Student s3 = 10; //相当于=======> Student s3(10);
Student s4 = "西卡"; //相当于=======> Student s4("西卡");
Student s5 = (3, "爱纳米"); //后面扩号中的内容相当于逗号表达式,取最后的值==> Student s5("爱纳米")
3.显示调用的构造函数
Student s6 = Student(4, "杰杰杰"); //相当于===> Student s6(4, "杰杰杰");
}
拷贝构造函数:用另一个对象 对当前对象进行初始化
固定形式: 类名(const 类名& 变量名)
析构函数: 在对象被释放的时候编译器自动调来释放对象资源
形式:~类名
特点:1.没有返回值 2.没有参数 3. 不能被重载 4.先构造的函数后析构
这两个和构造函数一样,如果没有自定义的拷贝构造/析构函数,编译器会自动生成默认的拷贝构造/析构函数
eg:
class Student
{
public:
Student() //无参构造函数
{
_id = 0;
_name = NULL;
cout << "无参构造函数 Student() 被调用" << endl;
}
Student(int id)
{
_id = id;
_name = NULL;
cout << "构造函数 Student(int id) 被调用" << endl;
}
Student(char *name)
{
_id = 0;
_name = name;
cout << "构造函数 Student(char *name) 被调用" << endl;
}
Student(int id, char *name) //以上4种都是自定义的构造函数
{
_id =id;
_name = name;
cout << "构造函数 Student(int id, char *name) 被调用" << endl;
}
Student (const Student &obj)//拷贝构造函数
{
_id = obj._id;
_name = obj._name;
cout << "拷贝构造函数被调用" << endl;
}
~Student() //析构函数
{
cout << "析构函数被调用" << endl;
}
private:
int _id;
char *_name;
};
int main()
{
cout << "111111111" << endl;//输出 111111111
Student s; //无参构造函数 Student() 被调用
Student s2(s); //拷贝构造函数被调用
cout << "22222222222" << endl;//22222222222
//析构函数被调用(析构s2)
//析构函数被调用(析构s)
return 0;
}
特殊情形:在构造函数中调用构造函数
eg:
class A
{
public:
A(int a, int b)
{
_a = a;
_b = b;
A(a, b, 30);//构造函数中调用构造函数不会达到预期的效果,运行时产生一个匿名对象(也叫临时对象,不用构造函数进行初始化),为这个匿名对象赋值,结束后析构了匿名函数
}
A(int a, int b, int c)
{
_a = a;
_b =b;
_c = c;
}
void print()
{
printf("_a = %d, _b = %d, _c = %d\n", _a, _b, _c);
}
~A()
{
printf("调用析构时,_a = %d, _b = %d, _c = %d\n", _a, _b, _c);
}
};
int main()
{
A a(10, 20);
a.print();//最后a=10,b=20,c是一个垃圾值
A(10, 20, 30);//也是一个临时对象,不调用构造函数,在a之前被析构
//最后一个析构是,a被析构(先构造的后析构);
return 0;
}
当对象做函数参数传递的时候,会调用拷贝构造函数
void func(Student s1)
{
; /*原因:直接传变量名,在外函数func中会定义s1,而在参数传递的时候,会有一个值传递,即用s给s1赋值,满足初始化s1的条件,也就满足了调用拷贝构造函数的条件(用另一个对象去 给当前对象进行初始化)*/
}
void func2(Student &s1)
{
;/*传引用的可以避免拷贝构造函数的调用
原因:传参的时候是将变量代表的空间传了过来,函数形参为这个空间取了别名s1,并不存在*/
}
int main()
{
func1(s);//调用无参构造/拷贝构造/析构函数(析构函数func1里的s1)
cout << "*******" << endl;
func2(s);//没有调用构造/拷贝构造/析构函数
//最后析构了s
return 0;
}
调用拷贝构造函数的特殊情况:函数的返回值是对象
eg:延用上面的Student类
Student func1()
{
Student s;
return s;
}
//处理函数返回值的三种情形
/*1、不使用函数的返回值
函数返回的时候会产生一个匿名对象,用返回的对象 对匿名对象 进行初始化,从而达成调用拷贝构造函数的条件调用,调用拷贝构造函数。因为这个匿名对象没人使用,所以被立马释放掉了*/
int mian()
{
func1();/*无参构造函数 Student() 被调用
拷贝构造函数被调用
析构函数被调用(析构s)
析构函数被调用(析构匿名)*/
}
/*2、用新的对象去收返回值(相当于用匿名对象给新的对象s1 初始化)
1:函数返回的时候会产生一个匿名对象,用返回的对象 对匿名对象初始化,调用拷贝构造函数
2 : 然后将变量名s1 给这个匿名对象使用,匿名对象变成了有名对象*/
int mian()
{
Student s1 = func1(); /*无参构造函数 Student() 被调用
拷贝构造函数被调用
析构函数被调用(析构s)
析构函数被调用(析构s1即匿名),main中并没有为s1单独的开辟空间,
所以不会调用三个析构函数*/
}
/*3、用旧的对象去收返回值
1: 函数返回的时候会产生一个匿名对象,用返回的对象 对匿名对象初始化,调用拷贝构造函数
2 : 用匿名对象 对s1 进行赋值(不会调用构造函数)
3:匿名对象还是匿名的 所以释放了
*/
int main()
{
Student s1(10);//构造函数 Student(int id) 被调用
s1 = func1();/*赋值!不调用构造函数
无参构造函数 Student() 被调用
拷贝构造函数被调用
析构函数被调用(析构s)
析构函数被调用(析构匿名)
析构函数被调用(析构s1)
*/
return 0;
}
浅拷贝与深拷贝
浅拷贝的原因:编译器在调用默认拷贝构造函数时,只能复制值,不能复制空间。下面举例说明
eg:
class Student
{
public:
Student(int id = 0, char *name = NULL)
{
_id = id;
//给_name分配空间
_name = (char*)malloc(sizeof(char)*20);
strcpy(_name, name);
}
void show()
{
printf ("id = %d, name = %s\n", _id, _name);
}
~Student()
{
if (_name != NULL)
free(_name);
_name = NULL;
}
private:
int _id;
char *_name;
};
int main()
{
Student s(1, "小明");
s.show();
Student s1 = s;//调用默认的拷贝构造函数(不调用构造函数),不复制空间,
/*将s的_id 复制给 s1的_id,将s的_name指针指向的地址
复制给s1的_name指针,两个指针指向了同一块空间
在s,s1分别使用完后调用析构函数进行释放空间时,
对同一块空间(定义s时开辟的空间)释放了两次,运行时出错。
*/
return 0;
}
解决方案:深拷贝(自定义一个拷贝构造函数,进行堆上空间的复制)
class Student
{
public:
Student(int id = 0, char *name = NULL)
{
_id = id;
//给_name分配空间
_name = (char*)malloc(sizeof(char)*20);
strcpy(_name, name);
}
//自定义拷贝构造函数
Student(const Student &obj)
{
_id = obj._id;
//给当前对象的_name分配空间
_name = (char*)malloc(sizeof(char)*20);
strcpy(_name, obj._name);
}
void show()
{
printf ("id = %d, name = %s\n", _id, _name);
}
~Student()
{
if (_name != NULL)
free(_name);
_name = NULL;
}
private:
int _id;
char *_name;
};
int main()
{
Student s(1, "小明");
s.show();
Student s1 = s;/*调用自定义的拷贝构造函数,s1的_name指针指向自己开辟的空间,析构自己开辟的空间,避免了浅拷贝的错误*/
s1.show();
return 0;
}
Tip:
如果不是特别需要,不要用一个对象去初始化另一个对象(避免 拷贝构造的操作)可以将 拷贝构造设成私有的,(不让外部使用),声明就可以,不需要实现。
eg:
class B
{
private:
B(const B &);//私有拷贝构造函数声明
public:
B(){}
};