1.构造函数
1.1功能:
在实例化对象时,完成资源的申请及对成员变量的初始化。
栈区实例化对象:
类名 类对象名(构造函数的实参表)
堆区:
类名 *类指针名 = new 类名(构造函数的实参表)
1.2格式要求
1.构造函数的名字必须与类同名
2.构造函数没有返回值
3.构造函数访问控制权限一般是public;(不一定必须,看使用场景)
1.3调用时机;
实例化对象的过程中自动调用。且只能调用一次,且不可以手动调用。
例:
#include <iostream>
using namespace std;
class Student{
private:
string name;
int age;
public:
//构造函数
Student(string n, int a){
cout<<"我是构造函数"<<endl;
name = n;
age = a;
}
void print_info(){
cout<<this->name<<" "<<this->age<<endl;
}
};
int main(int argc, const char *argv[])
{
//实参表是用来给构造函数传参的
//从编译器的角度----->
//s1.Student("qianxy",28);
Student s1("qianxy",28);
s1.print_info();
Student *s2 = new Student("qinxy",19);
s2->print_info();
delete s2;
return 0;
}
1.4构造函数可以用冒号引出 初始化表
#include <iostream>
using namespace std;
class Student{
private:
string name;
int age;
int *score;
public:
//构造函数用冒号引出初始化表
//一定要注意 :成员变量名(形参名)
//score(new int(s)) //初始化表中完成对指针成员的初始化
Student(string n, int a, int s):name(n),age(a),score(new int(s)){
cout<<"我是构造函数"<<endl;
//score = new int(s);//对于指针成员 需要先new空间再赋值
}
void print_info(){
cout<<this->name<<" "<<this->age<<endl;
}
};
int main(int argc, const char *argv[])
{
//实参表是用来给构造函数传参的
//从编译器的角度----->
//s1.Student("qianxy",28);
Student s1("qianxy",28,99);
s1.print_info();
Student *s2 = new Student("qinxy",19,98);
s2->print_info();
delete s2;
return 0;
}
必须使用初始化表的场景:
1.成员变量名字和构造函数形参名字冲突时;---也可以用this解决
2.类中包含 const 成员变量时
3.类中包含 引用 成员变量
4.类中包含成员子对象(类中包含其他类对象时)时,
//必须使用初始化表调用子对象的构造函数 完成对成员子对象的初始化
例1:
#include <iostream>
using namespace std;
class Student{
private:
string name;
int age;
int * const score;
int &sex;//0 女生 1男
public:
Student(string n, int a, int s, int sex):name(n),age(a),score(new int(s)),sex(sex){
cout<<"我是构造函数"<<endl;
//score = new int(s);//score 时被const 修饰的指针
//只能初始化 不能赋值
//this->sex = sex;//引用必须初始化
}
void print_info(){
cout<<this->name<<" "<<this->age<<endl;
}
};
int main(int argc, const char *argv[])
{
Student s1("qianxy",28,99,1);
s1.print_info();
return 0;
}
例2:
#include <iostream>
using namespace std;
class Teacher{
private:
//string name = "马云";//新版本的编译器允许 类内 直接对成员初始化
//但是一般不使用这种方式
string name;
public:
Teacher(string n);
string get_name(){
return name;
}
};
//构造函数的类内声明 类外定义
Teacher::Teacher(string n):name(n){
cout<<"Teacher 构造"<<endl;
}
class Person{
private:
Teacher t1;//成员子对象
int age;
public:
//必须使用初始化表,在初始化表中调用Teacher的构造函数,完成对成员t1的初始化
//如果没有在初始化表中显性的调用 Teacher
//的构造函数,会默认调用Teacher类的
//无参构造函数,如果没有无参构造函数 会报错
Person(string n,int a):t1(n),age(a){
cout<<"Person 构造"<<endl;
//t1.Teacher(n);//错误的 构造函数不能被手动调用
}
void print_info(){
//在Person类中无法直接访问 Teacher类的私有成员name
//需要 Teacher类 提供public权限的函数访问
cout<<t1.get_name()<<" "<<age<<endl;
}
};
int main(int argc, const char *argv[])
{
Person p1("yangfs",28);
p1.print_info();
return 0;
}
1.5构造函数可以重载
我们前面用的都是有参的构造函数
构造函数是可以定义无参数版本。
如果类中没有显性给定构造函数,编译器会默认提供一个无参数的,函数体为空的构造函数,用来实例化对象。
如果类中显性的给定了构造函数,编译器就不再提供无参数的版本了
此时,如果需要使用无参构造函数,需要自己在类中手动给定
例:
#include <iostream>
using namespace std;
class Student{
private:
string name;
int age;
public:
Student(){//手动给定无参构造函数,否则实例化对象s1时,会报错
cout<<"无参构造"<<endl;
}
Student(string n,int a):name(n),age(a){
cout<<"有参构造"<<endl;
}
//构造函数可以有多个版本,只要符合重载要求就ok
Student(string n):name(n){}
};
int main(int argc, const char *argv[])
{
Student s1;//调用无参的
Student s2("马云",50);//调用有参的
Student s2("马化腾");
return 0;
}
2.析构函数
2.1功能:
释放构造函数new出来的空间以及其他善后工作。
2.2格式:
1.与类同名,需要在名字前加上 ~ 如 ~Student(){}
2.析构函数没有返回值
3.析构函数没有参数 所以不能重载
2.3调用时机:
对象消亡时,自动调用,且只调用一次。
1.栈区
生命周期结束就消亡,调用析构函数
2.堆区
调用 delete 时,对象消亡,调用析构函数(进程结束,操作系统回收时,不会调用)
例:
#include <iostream>
using namespace std;
class Student{
private:
string name;
int age;
int *score;
public:
Student(){
cout<<"无参构造"<<endl;
}
Student(string n, int a,int s):name(n),age(a),score(new int(s)){
cout<<"有参构造"<<endl;
}
~Student();//析构函数
};
Student::~Student(){
cout<<"析构函数"<<endl;
delete score;//析构函数应该的干的活
score = NULL;
}
int main(int argc, const char *argv[])
{
//Student s1;
Student s2("zhangsan",18,60);
//堆区
Student *p1 = new Student("lisi",20,70);
delete p1;
return 0;
}
当类中没有显性给定析构函数时,编译器会默认提供一个
函数体为空的析构函数,用来对象消亡时使用。
如果显性的给定了析构函数,编译器就不再提供默认的了。
3.构造函数和析构函数调用的顺序
//堆区的情况不考虑 因为取决于 什么时候调用 delete
//我们只考虑栈区的情况
构造函数是顺序调用,析构函数是逆序调用,
也就是说,先构造的,后析构。
例:
#include <iostream>
using namespace std;
class Student{
private:
string name;
int age;
public:
Student(string n,int a):name(n),age(a){
cout<<this->name<<" 无参构造函数"<<endl;
}
~Student(){
cout<<this->name<<" 析构函数"<<endl;
}
};
int main(int argc, const char *argv[])
{
Student s1("s1",10);
Student s2("s2",20);
Student s3("s3",25);
/*
Student *p1 = new Student("p1",10);
Student *p2 = new Student("p2",20);
Student *p3 = new Student("p3",30);
delete p1;
delete p2;
delete p3;
*/
return 0;
}
4.拷贝构造函数
4.1功能:
拷贝构造函数也是构造函数,使用来完成类对象初始化的。
4.2格式:
类名(const 类名 &other):初始化表{
//函数体
}
4.3调用时机:
用一个已经初始化的对象去初始化新对象时,对自动调用拷贝构造函数。
string s1 = "hello";
string s2(s1);
string s2 = s1;
从编译器的角度----> string s2(s1) <===> s2.string(s1);
例:
#include <iostream>
using namespace std;
class Person{
private:
string name;
int age;
public:
Person(){
cout<<this<<" "<<name<<" 无参构造函数"<<endl;
}
Person(string n, int a):name(n),age(a){
cout<<this<<" "<<name<<" 有参构造函数"<<endl;
}
Person(const Person &other):name(other.name),age(other.age){
cout<<this<<" "<<name<<" 拷贝构造函数"<<endl;
}
~Person(){
cout<<this<<" "<<name<<" 析构函数"<<endl;
}
};
int main(int argc, const char *argv[])
{
Person p1;//无参构造
Person p2("小明",18);//有参构造
Person p3(p2);//拷贝构造
Person p4 = p3;//拷贝构造
//从编译器的角度 左调右参
//p3.Person(p2);
cout<<"&p1="<<&p1<<" &p2="<<&p2<<" &p3="<<&p3<<" &p4="<<&p4<<endl;
return 0;
}
5.深拷贝与浅拷贝
浅拷贝:
当类中没有显性的给定拷贝构造函数时,编译器会默认提供一个拷贝构造函数
这个拷贝构造函数只完成成员之间的简单赋值,叫做浅拷贝。
如果类中没有指针成员,使用浅拷贝是没有问题的,但是如果类中有指针成员
就会出现 double free 的问题。
深拷贝:
当类中有指针成员时,在类中显性的给定拷贝构造函数,对类中的指针成员,
重新分配资源,只完成数据的复制,这种叫做深拷贝。
参考:https://blog.csdn.net/weixin_44767670/article/details/113687780
示意图:
例:
#include <iostream>
using namespace std;
class Student{
private:
string name;
int *age;
public:
Student(){
cout<<"无参构造"<<endl;
}
Student(string n,int a):name(n),age(new int(a)){
cout<<"有参构造"<<endl;
}
//自己写的拷贝构造函数,是个深拷贝
Student(const Student &other):
name(other.name),age(new int(*(other.age))){
cout<<"拷贝构造"<<endl;
}
~Student(){
cout<<"析构函数"<<endl;
delete age;
}
void show(){
cout<<name<<" "<<*age<<endl;
}
};
int main(int argc, const char *argv[])
{
Student t1("小明",18);
t1.show();
Student t2 = t1;
t2.show();
return 0;
}
6.拷贝赋值函数
6.1格式
类名& operator=(const 类名 &other){
if(this != &other){//防止自身给自身赋值
//成员之间相互赋值语句
}
return *this;//返回自身的引用
}
6.2调用时机
两个已经初始化的对象之间相互赋值时自动调用拷贝赋值函数。
当类中没有显性的给定拷贝赋值函数时,编译器会默认提供一个拷贝赋值函数
这个拷贝赋值函数只完成成员之间的简单赋值。
拷贝赋值函数也涉及 深浅拷贝的问题。
例:
#include <iostream>
using namespace std;
class Student{
private:
string name;
int age;
public:
Student(){
cout<<"无参构造"<<endl;
}
Student(string n,int a):name(n),age(a){
cout<<"有参构造"<<endl;
}
Student(const Student &other):name(other.name),age(other.age){
cout<<"拷贝构造"<<endl;
}
//返回自身的引用是为了 可以 级联调用
Student &operator=(const Student &other){
cout<<"拷贝赋值"<<endl;
if(this != &other){
this->name = other.name;
age = other.age;
}
return *this;
}
~Student(){
cout<<"析构函数"<<endl;
}
void show(){
cout<<name<<" "<<age<<endl;
}
};
int main(int argc, const char *argv[])
{
Student t1("小明",18);//有参构造
t1.show();
Student t2;//无参构造
Student t3;//无参构造
t2 = t1;//拷贝赋值
//从编译器的角度:
// t2.operator=(s1);
t3 = t2 = t1;//级联使用 =
//从编译器的角度:
// t3.operator=(t2.operator=(s1));
t2.show();
return 0;
}
例2:拷贝赋值函数,深拷贝的例子
#include <iostream>
using namespace std;
class Student{
private:
string name;
int *age;
public:
Student(){
cout<<"无参构造"<<endl;
}
Student(string n,int a):name(n),age(new int(a)){
cout<<"有参构造"<<endl;
}
Student(const Student &other):name(other.name),age(new int(*(other.age))){
cout<<"拷贝构造"<<endl;
}
//返回自身的引用是为了 可以 级联调用
Student &operator=(const Student &other){
cout<<"拷贝赋值"<<endl;
if(this != &other){
this->name = other.name;
//给指针成员重新分配空间
age = new int(*(other.age));
}
return *this;
}
~Student(){
cout<<"析构函数"<<endl;
delete age;
}
void show(){
cout<<name<<" "<<*age<<endl;
}
};
int main(int argc, const char *argv[])
{
Student t1("小明",18);//有参构造
t1.show();
Student t2;//无参构造
t2 = t1;
t2.show();
return 0;
}