<C++>搞明白构造函数和析构函数有这一篇就够了

简介: <C++>搞明白构造函数和析构函数有这一篇就够了

构造函数和析构函数


语法


构造函数语法: 类名(){}


1
、没有返回值也不写void
2
、函数名称与类名相同
3
、构造函数可以有参数,因此可以发生重载
4
、程序在调用对象时会自动调用,无需手动调用且只会调用一次

析造函数语法: ~类名(){}
1
、没有返回值也不写void
2
、函数名称与类名相同,在名称前加上符号~
3
、构造函数不可以有参数,因此不可以发生重载
4
、程序在对象销毁前会自动调用析构,无需手动调用且只会调用一次


作用


构造函数  主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
析构函数  主要作用于对象销毁前系统自动调用,执行一些清理工作


代码实现


#include<iostream>
using namespace std;
class Person
{
public:
  Person()
  {
    cout << "Person构造函数的调用" << endl;
  }
  ~Person()
  {
    cout << "~Person析构函数的调用" << endl;
  }
};
void test01()
{
  Person p;//栈上的对象运行完毕后,编译器自动释放
}
int main()
{
  test01();
}


image.png


test01中创建了Personp,主函数只是调用了一下创建的Personp,编译器就自动调用了类的构造函数和析构函数,析构函数是程序运行完毕后,编译器自动清理内存空间的时候调用的。


两大分类方式


按参数分为 有参构造 和 无参构造
按类型分为 普通构造 和 拷贝构造

无参和有参构造很好理解,就是有无参数的区别,这里讲一下拷贝构造函数:


//拷贝构造函数
  Person(const Person &p) //格式: const 类名 引用(&)变量名
  {
    //讲传入的人身上的所有属性,拷贝到我身上
    age = p.age;
    cout << "Person的拷贝构造函数调用" << endl;
  }

Person()的括号中是const Person &p,这是拷贝构造的函数格式,他需要传入相同类的对象,会产生一个具有相同属性的类,比如p1的年龄为20,经过拷贝构造p2的年龄也会是20,但是两个类对象的地址并不相同,这个到后面会具体解释


三种调用方式


class Person
{
public:
  //构造函数
  Person()
  {
    cout << "Person的无参构造函数调用" << endl;
  }
  Person(int a)
  {
    age = a;
    cout << "Person的有参构造函数调用" << endl;
  }
  //拷贝构造函数
  Person(const Person &p) //格式: const 类名 引用(&)变量名
  {
    //讲传入的人身上的所有属性,拷贝到我身上
    age = p.age;
    cout << "Person的拷贝构造函数调用" << endl;
  }
  ~Person()
  {
    cout << "~Person的析构函数调用" << endl;
  }
  int age;
};


括号法


 Person p;//默认构造函数调用
  Person p2(10);//有参构造函数
  Person p3(p2);//拷贝构造函数
  cout << "p2 age=" << p2.age << endl;
  cout << "p3 age=" << p3.age << endl;


注意事项:调用默认构造函数的时候,不要加()Person p1() 编译器会认为是函数的声明,不认为在创建对象,等同于 void func()


显示法


 Person p;
  Person p2=Person(10);//有参构造函数
  Person p3=Person(p2);//拷贝构造函数
  Person(100);//匿名对象,特点:当前执行完毕后,系统会立即回收掉匿名对象
  cout << "AAAAA" << endl;

注意事项2:拷贝构造初始化匿名对象等同于去掉括号,导致重定义,不要用拷贝构造初始化匿名对象,如果利用匿名对象的话,会和Peron p2=Person(10),重复,出现重定义错误;也不要用拷贝构造初始化匿名对象。


隐式转换法


 Person p2 = 10;// 有参构造函数
  Person p3 = p2;// 拷贝构造函数

这个方法不推荐使用,调用的很不明显,建议使用前面两个方法调用构造函数。


正确调用拷贝构造函数


class Person
{
public:
  Person()
  {
    cout << "Person的无参构造函数调用" << endl;
  }
  Person(int a)
  {
    m_age = a;
    cout << "Person的有参构造函数调用" << endl;
  }
  Person(const Person& p)
  {
    m_age = p.m_age;
    cout << "Person的拷贝构造函数调用" << endl;
  }
  ~Person()
  {
    cout << "Person 的析构函数调用" << endl;
  }
  int m_age;
};


正常调用


void test01()
{
  Person p1(20);
  Person p2(p1);
  cout << "p2的年龄为:" << p2.m_age << endl;
}


主函数中直接调用test01,这时候会显示 p2的年龄为20,并且打印:拷贝构造函数的调用。所以说,使用一个已经创建完毕的对象来初始化一个新对象的时候会调用拷贝构造函数


值传递的方式给函数参数传值


 
         

大家可以猜一下,在主函数调用,会运行出什么结果,答案是:无参构造函数调用和拷贝构造函数调用,最后是两个析构函数调用;浅析一下过程,调用test02时创建了对象P,所以自动调用无参构造函数,当运行到doWork(p)时,调用拷贝构造函数,随后拷贝构造函数被清理,调用析构函数,程序结束前,p被清理,再次调用析构函数,程序结束。



值传递方式返回局部对象


void doWork(Person p)
{  }
void test02()
{
  Person p;
  doWork(p);
}

这里doWork2返回值时Person类型,也就是说return p1后会拷贝构造其属性给test03调用的p,但是p1p2并不是同一个对象,我们可以输出他们的地址来验证。



这里的调用顺序是:Person P1 的无参构造,随后输出p1地址,然后返回值的时候先调用拷贝构造函数,把值赋给p,随后清理p1调用析构;然后回到test03中,输出p的地址,程序结束前调用析构,程序结束。


image.png


构造函数的调用规则


编译器提供:


1、创建一个类,c++编译器会给每个类都至少添加三个函数
默认构造(空实现)
析构函数(空实现)
值拷贝构造(值拷贝)


2、如果我们写了有参构造,编译器不再提供默认构造,但是提供值拷贝构造
     如果我们写了拷贝构造函数,编译器不再提供其他普通构造函数


Person doWork2()
{
  Person p1;
  cout << (int)&p1<<"  1  " << endl;
  return p1;//返回就拷贝构造函数,随后释放掉,调用析构
}
void test03()
{
  Person p = doWork2();//重新创建局部对象,并不是上面返回的对象p1
  cout << (int)&p<<"  2  " << endl;
}


也就是说,就算我们不写无参和拷贝构造,调用test03也会得到值拷贝后的p2年龄,这是编译器默认提供的三个函数。但是如果写了有参构造,Person p1这行代码就会报错,提示找不到默认构造函数;同样的如果自己写了拷贝构造,Person p1也会显示同样的错误。


image.png


相关文章
|
8天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
33 5
|
14天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
46 4
|
2月前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
75 30
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
20 1
|
1月前
|
C++
C++构造函数初始化类对象
C++构造函数初始化类对象
21 0
|
1月前
|
C++
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
21 0
|
3月前
|
编译器 C++
C++的基类和派生类构造函数
基类的成员函数可以被继承,可以通过派生类的对象访问,但这仅仅指的是普通的成员函数,类的构造函数不能被继承。构造函数不能被继承是有道理的,因为即使继承了,它的名字和派生类的名字也不一样,不能成为派生类的构造函数,当然更不能成为普通的成员函数。 在设计派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数完成,但是大部分基类都有 private 属性的成员变量,它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。 这种矛盾在C++继承中是普遍存在的,解决这个问题的思路是:在派生类的构造函数中调用基类的构造函数。 下面的例子展示了如何在派生类的构造函数中调用基类的构造函数:
|
4月前
|
C++ 运维
开发与运维函数问题之析构函数在C++类中起什么作用如何解决
开发与运维函数问题之析构函数在C++类中起什么作用如何解决
44 11
|
4月前
|
编译器 C++
【C++】详解构造函数
【C++】详解构造函数
|
5月前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。