函数的构造与析构

构造函数

构造函数(constructor)是与类同名的特殊成员函数,主要用来初始化对象的数据成员。我们前面在初始化类中的变量时,实现了一个init函数对其进行初始化。
构造函数的特点:
(1)与类同名
(2)没有返回类型
(3)可以被重载
(4)由系统自动调用,不允许在程序中显示调用
我们这里还是用以前的例子(这样可以与前面的进行一个对比,加深我们对构造函数的印象)

#include <iostream>
using namespace std;
class Student{
 private:
     string m_name;
     int m_age;
     int m_no;
public:
Student(const string& name, int age, int no){
    cout << "Student constructor" <<endl;
    m_name = name;
    m_age = age;
    m_no = no;
}
void setName(const string& name){
   m_name = name;
}
void setAge(int age){
if(age < 0)
    cout << "无效年龄" << endl;
else
    m_age = age;
}
void setNo(int no){
    m_no = no;
}
void sleep(int hour){
     cout << "我睡了"<< hour <<"小时"<< endl;
}
   void eat(const string &food){
     cout << "我正在吃" <<food << endl;
  }
   void learn(const string &course){
     cout << "我正在学习" << course << endl;
  }
  void who(){
   cout << "我叫: "<<m_name << ", 我今年" << m_age << endl;
  }
};
int main(void){
  Student s1("张飞", 21, 10003);
  s1.who();
  s1.eat("烧烤");
  
  Student s2("刘备", 28, 10000);
  s2.who();
  s2.learn("C++");
  return 0;
}

与前面我们使用类里面定义的函数相比,我们这里更简洁,提高了开发效率

缺省构造函数

缺省这一释义我想已经很熟悉啦,缺省构造函数也称无参构造函数,但其未必真的没有任何参数,为一个有参构造函数的每个参数都提供一个缺省值,同样可以达到无参构造函数的效果。
注意:
1.如果一个类没有定义任何构造函数,那么编译器会为其提供一个缺省构造函数
(1)对基本类型的成员变量,不做初始化;
(2)对类类型的成员变量(成员子对象),将自动调用相应类的无参构造函数来初始化;
2.如果一个类定义了构造函数,无论是否有参数,那么编译器都不会再提供缺省构造函数
我们看下面这个例子

#include <iostream>
using namespace std;
class A{
public:
    A(void){
    cout << "A的无参构造" << endl;
    m_i = 0;
    }
public:
    int m_i;
};
class B{
public:
     int m_j; //基本类型成员变量
     A m_a; //类类型成员变量(成员子对象)
};
int main(void){
     B b; //调用成员对象m_a的无参构造函数 调用B的缺省构造函数
     cout << b.m_j << endl; //未知
     cout << b.m_a.m_i <<endl; //0
return 0;
}

在这里插入图片描述

构造函数的重载

我们先看代码

#include <iostream>
using namespace std;
struct param{
    int l;
    int w;
    int h;
    int ww;
};
class Desk{
private:
int length, width, height, weight;
public:
 Desk(int l, int w, int h, int ww);
#if 1
Desk(void){
cout << "Desk(void)" << endl;
     length = 0;
     width = 0;
     height = 0;
     weight = 0;
}
#endif
    Desk(param & p){
    cout << "Desk(parm &)" << endl;
    length = p.l;
    width = p.w;
    height = p.h;
    weight = p.ww;
   }
};
#if 1
  Desk::Desk(int l, int w, int h, int ww){  //函数类内声明,类外实现
  cout << "Desk(int, int, int, int)" << endl;
  length = l;
  width = w;
  height = h;
  weight = ww;
}
#endif
int main(void){
    Desk d1(1,2,3,4);
    Desk d2; //调用无参构造函数
    param pm;
    pm.l = 1;
    pm.w = 2;
    pm.h = 3;
    pm.ww =4;
   Desk d3(pm);
   return 0;
}

些重载的构造函数具有特殊的含义
(1)缺省构造函数:按缺省方式构造;
(2)类型转换构造函数:从不同类型的对象构造;
(3)拷贝构造函数:从相同类型的对象构造;

类型转换构造函数

将其它类型转换为当前类类型需要借助转换构造函数(Conversion constructor) , 转换构造函数只有一个参数 。

#include <iostream>
#include <cstring>
using namespace std;
class Integer{
private:
    int m_i ;
public:
    Integer(void){
    cout << "Integer(void)" << endl;
    m_i = 0;
}
    explicit Integer(int n){ //类型转换构造函数
    cout << "Integer(int)" <<endl;
    m_i = n;
}
explicit Integer(const char *str){
    cout << "Integer(const char *)" << endl;
    m_i = strlen(str);
}
void print(void){
    cout << m_i << endl;
}
};
int main(void){
    Integer a;
    a.print();
   //Integer b = 1; //编译器会找参数为int类型的构造函数
   Integer b = Integer(1); //编译器会找参数为int类型的构造函数
   b.print();
   //Integer c = "abc"; //编译器会找参数为const char *类型的构造函数
   Integer c = Integer("abc"); //编译器会找参数为const char *类型的构造函数
   c.print();
   return 0;
}

explicit关键字,就是告诉编译需要类型转换时,强制要求写成如下形式

Integer b = Integer(1);
//Integer b = 1; //error

拷贝构造函数

1.用一个已定义的对象构造同类型的副本对象,将调用该类的拷贝构造构造函数;形式:

class A{
   A(const A &that){ //拷贝构造函数 注意参数必须是常引用
   ...
   }
};
A a;
A b(a); //调用拷贝构造
A c = a; //调用拷贝构造

咱们来看一个实例

#include <iostream>
using namespace std;
class A{
public:
   int m_data;
   A(int data = 0){
   cout << "A(int)" << endl;
   m_data = data;
}
   A(const A& that){ //拷贝构造函数
   cout << "A(const A&)" << endl;
   m_data = that.m_data;
   }
};
int main(void){
A a1;
A a2(a1); //编译器会调用拷贝构造函数
A a3 = a1; //调用拷贝构造
return 0;
}

2.如果一个类没有显式定义拷贝构造函数,那么编译器会为其提供一个缺省拷贝构造函数;
(1)对基本类型成员变量,按字节复制
(2)对类类型成员变量(成员子对象),调用相应类的拷贝构造函数

class User {
string m_name; // 调用string类的拷贝构造函数
int m_age; // 按字节复制
};

3.注意事项:
(1)拷贝函数的调用时机
用已定义对象作为同类型对象的构造实参;
以对象的形式向函数传递参数;
从函数中返回对象。
(2)拷贝构造过程风险高而且效率低,设计时应尽可能避免
避免或减少对象的拷贝;
传递对象形式的参数时,使用引用型参数;
从函数中返回对象时,使用引用函数返回值。

初始化列表

构造函数对数据成员进行初始化还可以通过成员初始化列表的方式完成。语法格式:
构造函数名(参数表): 成员1(初始值参数),成员2(初始值参数){
}

我们这里还是借用前面的例子:

#include <iostream>
using namespace std;
class Student{
private:
   string m_name;
   int m_age;
   int m_no;
public:
Student(const string& name, int age, int no):m_name(name),m_age(age), m_no(no){
   cout << "Student constructor" <<endl;
}
void setName(const string& name){
   m_name = name;
}
void setAge(int age){
if(age < 0)
    cout << "无效年龄" << endl;
else
    m_age = age;
   } 
   void setNo(int no){
   m_no = no;
   }
   void sleep(int hour){
   cout << "我睡了"<< hour <<"小时"<< endl;
   }
   void eat(const string &food){
   cout << "我正在吃" <<food << endl;
   }
   void learn(const string &course){
   cout << "我正在学习" << course << endl;
  }
  void who(){
  cout << "我叫: "<<m_name << ", 我今年" << m_age << endl;
  }
};
int main(void){
Student s1("张飞", 21, 10003);
s1.who();
s1.eat("烧烤");
Student s2("刘备", 28, 10000);
s2.who();
s2.learn("C++");
return 0;
}

一般而言,使用初始化列表和在构造函数体对成员变量进行赋初值,两者区别不大,可以任选一种,但是下面几种场景必须要使用初始化列表:
(1)如果有类类型的成员变量(成员子对象),而该类又没有无参构造函数,则必须要通过初始化列表显式指明其初始化方式;

#include <iostream>
using namespace std;
class A{
private:
    int m_data;
public:
    A(int data){
    cout <<"A(int)" << endl;
    m_data = data;
    }
};
class B{
private:
    A m_a;
public:
    B(void):m_a(123){
    cout <<"B(void)" << endl;
    }
};
int main(void){
B b; //一定会去构造成员对象m_a , 未指定如何构造,系统去调用m_a的无参构造函数
return 0;
}

(2)“const”修饰的成员变量(常成员变量)必须要在初始化列表中初始化;
(3)“引用型”成员变量必须要在初始化列表中初始化。

#include <iostream>
using namespace std;
int num = 12;
class A{
public:
     int& m_r;
     const int m_c;
/*
* error
A(void){
m_r = num;
m_c = 100;
}
*/
      A(void):m_r(num), m_c(100){
     }
};
int main(void){
     A a;
     cout << a.m_r << " " <<a.m_c <<endl;
     return 0;
}

初始化的顺序问题:类中成员变量按声明顺序依次被初始化,而与初始化表中的顺序无关

#include <iostream>
using namespace std;
class A{
public:
     A(int a){
     cout <<"A constuctor" << endl;
     }
};
class B{
public:
    B(int b){
     cout <<"B constuctor" << endl;
    }
};
class C{
private:
   A m_a;
   B m_b;
public:
   C(int a, int b):m_b(b), m_a(a){
    }
};
int main(void){
    C c(1,2);
return 0;
}

this指针

大家先想一想我们在c++里如果定义了两个不同的类对象,我们在使用同一份代码的时候,编译器是怎么区别去调用的呢?
官方点就是这样子问你:不同的对象各自拥有独立的成员变量,但它们共享同一份成员函数代码,那么在成员函数中如何区分所访问的成员变量隶属于哪个对象?
在这里我们就引入了this指针这一概念,this是一个用于标识对象自身的隐式指针,代表对象自身的地址。
在编译类成员函数时,C++编译器会自动将this指针添加到成员函数的参数表中。在用类的成员函数时,调用对象会把自己的地址通过this指针
传递给成员函数。
我们来看我们之前的一个代码:

#include <iostream>
using namespace std;
class Student{
public:
   Student(int age, const string &name){
   m_age = age;
   m_name = name;
   }
   void print(){
   cout << m_name << ":" << m_age << endl;
}
private:
   int m_age;
   string m_name;
};
int main(void){
   Student zs(12, "zhangsan");
   Student ls = Student(13, "lisi");
   zs.print();
   ls.print();
   return 0;
}

我们在经过编译器编译后就是下面这样子:

#include <iostream>
using namespace std;
class Student{
public:
   Student(Student *this, int age, const string &name){
   this->m_age = age;
   this->m_name = name;
   }
void print(Student *this){
   cout << this->m_name << ":" << this->m_age << endl;
}
private:
   int m_age;
   string m_name;
};
int main(void){
  Student zs(12, "zhangsan"); // (&zs, 12, "zhangsan")
  Student ls = Student(13, "lisi");
  zs.print(); //print(&zs)
  ls.print();//print(&ls)
  return 0;
}

我们引入this指针,有什么应用呢?一般用于:
(1)类中的成员变量和参数变量名字一样,可以通过this指针区分
(2)从成员函数中返回调用对象自身(返回自引用),支持链式调用
(3)在成员函数中销毁对象自身(对象自销毁)
下面我们分别来验证一下:

#include <iostream>
using namespace std;
class Counter{
private:
   int count;
public:
   Counter(int count = 0){
   this->count = count;  //这里我们通过this指针来区分形参和我们类里面定义的变量
   }
   Counter &add(void){  // 这里就是通过this指针来返回自己,这样子可以链式调用自己
   ++count;
    return *this;
   }
void print(void){
cout << count << endl;
}
void destroy(void){
cout <<"this : " << this << endl;
delete this; //销毁对象本身
}
};
int main(void){
   Counter cnt;
   cnt.print();
   cnt.add().add().add();
   cnt.print();
   Counter *pcn = new Counter;
   pcn->add();
   pcn->print();
   pcn->destroy();
   cout<<"pcn: " <<pcn <<endl;
   return 0;
}

我们看一下结果:
在这里插入图片描述
这里最后一个pcn对象里,pcn和this的地址值相等;可见c++是多么高效(在代码编写方面)

常成员函数

在C++中,为了禁止成员函数修改成员数据的值,可以将它设置为常成员函数。设置方法就是在函数体之前加上const关键字。

class X{
  void func(参数1, 参数2...) const{
  }
}

来看个实例:

#include <iostream>

using namespace std;

class Student{
private:
	int age;
	string name;
public:
	Student(int age,const string &name){
	    this->age = age;
		this->name = name;
	}
	void who(void) const{
		age++;
	    cout << "我是: "  << name << "我的年纪: " << age << endl;
	}
};

int main(void){
    Student s1(23,"曹操");
	s1.who();

	return 0;
}

在这里插入图片描述
我们这里尝试修改Student类里的age,编译器是不允许的
那么析构函数的实现本质是什么呢?
常函数中的this指针是常指针,所以不能在常函数中修改成员变量。

class A{
public:
void print (void) const { ... }//编译前
void print (const A* this) { ... }//编译后
};

1.常函数的使用注意事项:
2.常对象只能调用常函数,非常对象既可以调用非常函数 也可以调用常函数
(1)函数名和形参表相同的成员函数,常版本和非常版本可以构成重载
(2)常对象只能选择常版本
(3)非常对象优先选择非常版本
3.被mutable修饰的成员可以常函数中修改(了解)

#include <iostream>
using namespace std;
class A{
public:
    A(int mm=0, int nn=0):m(mm),n(nn){}
void fun(void){ //void func(A * this)
    cout <<__func__<< endl;
}
void bar(void) const{
    cout << __func__ << endl;
}
void fun(void) const { //void func(const A *this)
    cout <<__func__<< "const" <<endl;
     m++;
    //n++;//语法错误
}
private:
    mutable int m;
    int n;
};
int main(void){
    A a;
    a.fun();
    a.bar();
    const A b;
    //b.fun(); //语法错误
    b.bar();
    return 0;
}

析构函数

析构函数是与类同名的另外一个特殊成员函数,作用与构造函数相反,用于对象生存期结束时,完成对象的清理工作。析构函数的名字是: ~
类名

class X{
public:
X(){}
~X(){} //析构函数
};

析构函数的特点:
1.无参无返回值
2.不能重载
3.只能由系统调用,不能显示调用
(1)栈对象,离开其作用域时析构函数自动调用
(2)堆对象,执行delete操作时析构函数自动调用

#include <iostream>
using namespace std;
class Integer{
public:
     Integer(int i=0){
     m_pi = new int(i);
     }
    ~Integer(void){
    cout << "析构函数" << endl;
    delete m_pi;
    }
    void print(void) const{
    cout <<*m_pi << endl;
    }
 private:
     int *m_pi;
};
int main(void){
    if(1){
    Integer i(100);
    i.print();
    cout << "test1" << endl;
    Integer *pi = new Integer(200);
    pi->print();
    delete pi; //析构函数被调用
    cout << "test2 " << endl;
}// i 的生命周期结束 对应的析构函数被调用
    cout << "test3" << endl;
return 0;
}

在这里插入图片描述
由结果看来,析构函数的执行时间是在变量作用域结束或者使用delete回收;
我们这里,如果类没有显式定义析构函数,那么编译器会为其提供一个缺省析构函数,缺省析构的函数体为空,在空析构函数体执行完毕后,类中的成员会被自动销毁,也就是缺省虚构函数,其效果可以比缺省构造函数

1.对基本类型成员变量,什么也不做
2.对类类型成员变量(成员子对象),将会自动调用相应类的析构函数
我们看以下代码:

#include <iostream>
using namespace std;
class A{
public:
    A(void){
    cout << "A(void)" << endl;
   }
   ~A(void){
    cout <<"~A(void)" << endl;
   }
};
class B{
public:
    B(void){
    cout << "B(void)" << endl;
    }
    ~B(void){
    cout << "~B(void)" << endl;
    }
    A m_a; // 成员子对象
};
int main(void){
     B b;
    return 0;
}

在这里插入图片描述
可以看出,我们在初始化定义了一个B类型的对象后,先去调用(A类)成员子对象的构造函数,然后再调用自身的构造函数;在函数结束后,我们先清理自身的类函数,再清理(A类)成员子对象
那么,我们可以得出以下结论:
对象的创建和销毁的过程:
1.对象创建
(1)分配内存
(2)构造成员对象
(3)调用构造函数
对象销毁
(1)调用析构函数
(2)析构成员对象
(3)释放内存
可见对象的销毁是对象的创建的逆过程(从大的方向来看)
好了,关于c++中函数的构造与析构就说到这里啦!如有勘误还请大家指出来哦!感谢观看!希望对你们有用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值