C++学习笔记:构造函数和析构函数
1.一个基础的C++面向对象编程
一般一个
.cpp
和一个.hpp
文件配对来描述一个class
,class
的名字和文件名相同的。
①在头文件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个可以顶多个构造函数(举例说明)