C++学习笔记:构造函数和析构函数

1.一个基础的C++面向对象编程

一般一个.cpp和一个.hpp文件配对来描述一个classclass的名字和文件名相同的。

①在头文件person.hpp:中定义类:

#ifndef __PERSON_HPP__
#define __PERSON_HPP__

#include<iostream>
#include<string>

namespace xx{
    
    
//声明类
class person
{
    
    
//访问权限
public:
	//属性
	std::string name;
	int age;
	bool male;
	
	//方法
	void eat(void);
	void work(void);
	void sleep(void);
	
private:
protected:
};
};
#endif

②在源文件person.cpp中实现类(构造函数,析构函数,方法等):

#include"person.hpp"

void xx::person::eat(void){
    
    
	std::cout<<"eating..."<<std::endl;
}

void xx::person::work(void){
    
    
	std::cout<<"working..."<<std::endl;
}

void xx::person::sleep(void){
    
    
	std::cout<<"sleeping..."<<std::endl;
}

③在主程序main.cpp中调用之前实现的类和方法:

#include"person.hpp"

//using namespace xx;
int main(int argc,char**argv)
{
    
    
	xx::person tom;
	tom.name="Tom";
	tom.age=22;
	tom.male=true;
	
	//tom的一天
	tom.eat();
	tom.work();
	tom.eat();
	tom.work();
	tom.sleep();
	
	return 0;
}

2.构造函数和析构函数

2.1 什么是构造函数

构造函数字面意思就是用来构造对象的函数;析构函数字面意思是用来析构对象的函数。构造函数和析构函数可以理解为语言自带的一种回调函数(写完之后不用手动调用,满足一定条件后会自动执行)。

在上面第一部分的代码中,首先创建了一个对象,然后依次给对象的属性赋值。这是比较山寨的做法,跟结构体比较像了。典型的C++做法是使用new关键字来创建对象并且使用构造函数来完成初始化操作。

当使用new关键字来产生对象时构造函数会自动被调用,一般用于初始化对象的属性、分配对象内部需要的动态内存。

对象消亡时析构函数会自动被调用,一般用于回收析构函数中分配的动态内存,避免内存丢失。

2.2 构造函数和析构函数一般用法

如果在实现一个类的时候不写构造函数和析构函数,C++会自动提供默认的构造函数和析构函数,它们都是空的,没有返回值没有参数(对于第一部分中的代码就啥都没写,但是不影响运行)。

当然也可以显式提供默认构造和析构函数:对于构造函数来说,它不需要返回值类型,并且可以带参数也可以不带参数,函数名同类名。析构函数也不需要返回值类型,同时不带参数。构造函数可以实现多种方式初始会对象,因此可以重载,根据实际创建对象时传递的参数来决定具体调用哪个构造函数,但是析构函数不需要重载。

#ifndef __PERSON_HPP__
#define __PERSON_HPP__

#include<iostream>
#include<string>

namespace xx{
    
    
//声明类
class person
{
    
    
public:
	//属性
	std::string name;
	int age;
	bool male;
	
	person();//默认构造函数 
	person(std::string name,int age,bool male);//自定义构造函数
	~person();//默认析构函数
};
};
#endif
#include"person.hpp"

//C++提供的默认构造函数和析构函数都是空的,没有返回类型也没有参数
xx::person::person(){
    
    };
xx::person::~person(){
    
    };

//自定义构造函数
xx::person::person(std::string name,int age,bool male){
    
    
	this->name=name;
	this->age=age;
	this->male=male;
}
#include"person.hpp"

using namespace xx;

int main(int argc,char**argv)
{
    
    
	//通过自定义构造函数创建并初始化对象
	person* tom = new person("tom",15,true);
	std::cout<<tom->name<<";"<<tom->age<<";"<<tom->male<<std::endl;
	
	return 0;
}

关于默认构造函数的一些细节:

  • 1.当有了自定义构造函数后,编译器不会自动创建默认构造函数,需要手动去写默认构造函数,因为默认构造函数一般是空的,所以一般直接在类内写出来,类似于内联函数,比如person(){};
  • 2.当有了自定义构造函数后,在栈上分配对象时想使用默认构造函数,语法是person tom;,而不是person tom();

2.3 为什么需要构造函数和析构函数

构造函数可以看作是对象的初始化式,构造函数可以为对象完成动态内存申请,同时在析构函数中再释放,形成动态内存的完整使用循环。C语言中的结构体没有构造函数概念,所以结构体中需要用到动态内存时必须在定义struct变量后再次单独申请和释放,而这些操作都需要程序员手工完成。C++ class的构造和析构特性,是C++支持面向对象编程的一大语言特性。

2.4 在构造和析构函数中使用动态内存

析构函数在对象对销毁时自动调用,一般有2种情况:

  • 1.用new创建的对象,必须呀用delete显式析构才会去调用析构函数。
  • 2.分配在栈上的对象,当栈释放时自动调用析构函数。

可以用实验证明以上两点,为了方便调试,可以在函数中加上打印信息:

xx::person::person()
{
    
    
	std::cout<<"默认构造函数"<<std::endl;
};
xx::person::person(std::string name,int age,bool male){
    
    
	std::cout<<"自定义构造函数"<<std::endl;
	this->name=name;
	this->age=age;
	this->male=male;
}
xx::person::~person()
{
    
    
	std::cout<<"默认析构函数"<<std::endl;
};

对于第一种,用new创建的对象,必须用delete显式析构对象才会去调用析构函数:

int main(int argc,char**argv)
{
    
    
	person* tom = new person("tom",15,true);
	delete tom;
	return 0;
}

输出:

自定义构造函数
默认析构函数

当去掉delete时:

int main(int argc,char**argv)
{
    
    
	person* tom = new person("tom",15,true);
	return 0;
}

输出:

自定义构造函数

对于第二种情况:

int main(int argc,char**argv)
{
    
    
	person tom("tom",15,true);
	return 0;
}

输出:

自定义构造函数
默认析构函数

上述的两种方式也是创建对象的两种方式,普通情况下析构函数都是空的(上面添加的cout只是为了看到函数是否执行了),因为不必做什么特别的事情

class中使用动态内存变量:
需要大块内存,且需要按需灵活的申请和释放,用栈怕爆、用全局怕浪费和死板时,这个情况下就适合用动态内存。
①在class person中增加一个int *指针,用于指向一个int类型元素的内存空间
②在构造函数中分配动态内存
③在析构函数中回收动态内存
④将动态内存从int变量升级到int数组变量

实际中C++常用的动态内存往往是容器vector那些。

对象分配到栈上不是一个好方法,C++中大量使用动态内存

构造函数与类的成员初始化
2.2.6.1、构造函数初始化成员变量
(1)默认构造函数不带参,无初始化功能
(2)若无其他构造函数,则默认构造函数可以省略。但若有哪怕1个其他构造函数,则默认构造函数不能省,必须写上。
(3)栈上分配对象时,若使用默认构造函数,则对象变量后面不加空的(),若用带参构造才需要加(初始化参数)
2.2.6.2、C++的成员初始化列表
(1)一般用于带参构造函数中,用来给属性传参赋值
(2)成员初始化列表和构造函数之间用冒号间隔,多个列表项之间用逗号间隔
(3)初始化列表可以替代构造函数内的赋值语句,达到同样效果

xx::person::person(std::string Name,int Age,bool Male):name(Name),age(Age),male(Male)
{
    
    }
xx::person::person(td::string Name,int Age,bool Male)
{
    
    
	this->name=Name;
	this->age=Age;
	this->male=Male;
}
person* tom=new person("tom",15,true);

构造函数使用参数默认值
在一个class声明的时候,可以给构造函数的形参赋值默认值,实际调用时如果不传参那就使用默认值
(2)方法实现时形参可以不写默认值,但是实际是按照声明时的默认值规则的
(3)有默认值情况,要注意实际调用不能有重载歧义,否则编译不能通过
(4)所有参数都带默认值的构造函数,1个可以顶多个构造函数(举例说明)

猜你喜欢

转载自blog.csdn.net/PecoHe/article/details/113027896