3. C++构造和析构

简介: 3. C++构造和析构

C++构造和析构

构造函数

  • 名字和类名相同
  • 没有返回值
  • 构造函数是用来构造对象,构造对象时候必定调用构造函数
  • 不写构造函数,存在一个默认的构造函数,默认的构造函数是无参,所以可以构造无参对象
  • 默认的构造函数可以删掉,通过delete删除默认的构造函数
  • 显示使用默认的构造函数,  通过default做显示调用
  • 通常情况构造函数是public属性
  • 自己写了构造函数,默认的构造函数就不存在了
  • 构造函数决定对象的长相(构造函数无参,对象无参,构造有一个,对象必须也要一个参数)
  • 构造函数通常做的事情,就是给数据成员初始化
  • 构造函数也是函数,所以也可以重载,也可以缺省
  • 通过重载和缺省,实现构造不同长相对象
#include <iostream>
#include <string>
using namespace std;
class MM
{
public:
  //MM() = default;   //显式使用默认的构造函数
  //MM() = delete;      //删掉默认的构造函数
  MM(string name, int age)
  {
    m_name = name;
    m_age = age;
    cout << "两个参数的构造函数" << endl;
  }
  MM() = default;   //默认无参构造函数,据说速度更快
  void printMM() 
  {
    cout << m_name << "\t" << m_age << endl;
  }
protected:
  string m_name;
  int m_age;
};
//MM::MM() 
//{
//  cout << "调用无参构造函数" << endl;
//}
struct Boy 
{
  string name;
  int age;
  int num;
    //一旦C++结构体中写了构造函数,必须当做类去操作,不能用C语言的那种方式使用
  Boy() {}
  Boy(string bname, int bage) 
  {
    name = bname;
    age = bage;
  }
};
void testStruct() 
{
  //Boy boy = { "string",18,1001};   //错误
  //这个地方也是创建对象过程,所以数据也需要和构造函数
  Boy boy = { "string",18};  //这里数据必须和构造函数的一致
  Boy boy2;
  Boy array[3];
}
int main() 
{
  //MM mm;        //因为构造函数有两个参数,对象也必须带有两参数
  MM mm("对象", 18);    //这步创建对象的过程就是调用构造函数构造函数的过程
  mm.printMM();
  MM empty;       //调用无参构造函数
  //new一个对象
  MM* p = new MM;         //调用无参的构造函数
  MM* p2 = new MM("对象", 29);    //调用有参的构造函数
  return 0;
}

析构函数

  • ~类名 当做析构函数名字
  • 没有参数
  • 释放数据成员new的内存
  • 在对象死亡前自动调用
  • 通常如果数据成员没有做new操作,就可以不写析构函数
  • 不写析构函数,存在一个默认的析构函数
#include <iostream>
#include <cstring>
using namespace std;
class MM 
{
public:
  MM(const char* str="ILoveyou")        //缺省 相当于存在两个构造函数,一个是无参的,一个是有参
  {
    int length = strlen(str) + 1;
    name = new char[length];
    strcpy_s(name, length, str);
  }
  ~MM();
protected:
  char* name;
};
MM::~MM() 
{
  if (name != nullptr) 
  {
    delete[] name;
    name = nullptr;
  }
  cout << "析构函数....\n" << endl;
}
int main() 
{
  {
    MM mm;
    MM* p = new MM;
    cout << "1......" << endl;
    delete p;     //立刻调用析构函数
    p = nullptr;
    cout << "2......." << endl;
  }
  {
    MM beauty("Continue");
  }
  cout << "对象死亡" << endl;
  return 0;
}

拷贝构造函数

  • 拷贝构造函数也是构造函数
  • 拷贝构造函数参数是固定的:对对象的引用
  • 拷贝构造函数不写会存在一个默认的拷贝构造函数
  • 拷贝构造函数作用: 通过一个对象产生另一个对象
  • 关键点:一定是有一个新的对象产生
#include <iostream>
#include <string>
using namespace std;
class MM 
{
public:
  MM() {}
  MM(string name, int age) 
  {
    m_name = name;
    m_age = age;
  }
  MM(MM& object);
  void printMM() 
  {
    cout << m_name << "\t" << m_age << endl;
  }
protected:
  string m_name;
  int m_age;
};
//拷贝构造函数
//通过传入对象属性确定创建对象的属性
MM::MM(MM& object)
{
  m_name = object.m_name;
  m_age = object.m_age;
  cout << "调用自己写的拷贝构造函数" << endl;
}
//函数传参也可以隐式调用
void print(MM object)   //MM object=girl
{
  object.printMM();
}
//C++中传参能用引用就用,效率搞
void printData(MM& object) //引用就是别名,没有产生新的对象
{
  object.printMM();
}
int main() 
{
  MM mm("C神", 18);
  //产生一个对象
  MM beauty = mm;     //隐式调用拷贝构造函数
  MM girl(beauty);    //显式调用拷贝构造函数
  print(girl);      //调用拷贝构造函数
  printData(girl);
  MM boy;
  //对象先有了,才赋值,不调用拷贝
  boy = girl;       //不调用拷贝构造函数-->默认重载=运算符(后续会讲)
  return 0;
}

深浅拷贝问题

浅拷贝

  • 没有在拷贝构造函数中给数据成员做new操作
  • 默认拷贝构造函数都是浅拷贝

深拷贝

  • 在拷贝构造函数中做了new操作

浅拷贝导致内存释放问题

浅拷贝会导致同一段内存重复释放问题

#include <iostream>
#include <cstring>
using namespace std;
class MM 
{
public:
  MM() {}
  MM(const char* str) 
  {
    int length = strlen(str) + 1;
    name = new char[length];
    strcpy_s(name, length, str);
  }
  ~MM() 
  {
    if (name != nullptr) 
    {
      delete[] name;
      name = nullptr;
    }
  }
private:
  char* name;
};
int main() 
{
  {
    MM girl("girl");
    MM mm = girl;     //调用拷贝构造函数
  }
  return 0;
}

深拷贝解决方案

#include <iostream>
#include <cstring>
using namespace std;
class MM 
{
public:
  MM() {}
  MM(const char* str) 
  {
    int length = strlen(str) + 1;
    name = new char[length];
    strcpy_s(name, length, str);
  }
  MM(MM& object) 
  {
    //name = object.name;
    int length = strlen(object.name) + 1;
    name = new char[length];
    strcpy_s(name, length, object.name);
  }
  ~MM() 
  {
    if (name != nullptr) 
    {
      delete[] name;
      name = nullptr;
    }
  }
private:
  char* name;
};
int main() 
{
  {
    MM girl("girl");
    MM mm = girl;     //调用拷贝构造函数
  }
  return 0;
}

综上: 一旦类中有指针,做了内存申请,并且要对对象做拷贝操作,就必须使用深拷贝

匿名对象

匿名对象就是无名对象,匿名对象只能充当右值,所以匿名对象拷贝必须存在移动拷贝,或者准备一个ecosnt限定普通拷贝构造函数。

#include <iostream>
#include <string>
using namespace std;
class MM 
{
public:
  MM() {};
  MM(string name, int age) 
  {
    m_name = name;
    m_age = age;
  }
  void printMM() 
  {
    cout << m_name << "\t" << m_age << endl;
  }
  MM(MM& object) 
  {
    m_name = object.m_name;
    m_age = object.m_age;
  }
  //移动构造--->新标准的
  MM(MM&& object)         
  {
    m_name = object.m_name;
    m_age = object.m_age;
    cout << "调用移动构造" << endl;
  }
  ~MM() 
  {
    cout << "析构打印name:" << m_name << endl;
  }
private:
  string m_name;
  int m_age;
};
MM returnMM(string name, int age) 
{
  return MM(name, age);   //构造一个匿名对象当做函数返回值
}
int main() 
{
  {
    MM girl("girl", 19);
    //匿名对象是一个右值
    MM mm=MM("mm", 29);  //匿名对象  --->匿名对象转正 ,mm接管所有权
    cout << "匿名对象已死亡" << endl;
  }
  MM beauty = returnMM("返回值", 18);
  beauty.printMM();
  return 0;
}

构造和析构顺序问题

  • 一般情况构造顺序和析构是相反的
  • 静态的和全局的是最后释放的
  • delete 立刻调用析构函数
#include <iostream>
#include <string>
using namespace std;
class Test 
{
public:
  Test(string data="A")
  {
    m_data = data;
    cout << m_data;
  }
  ~Test() 
  {
    cout << m_data;
  }
protected:
  string m_data;
};
int main() 
{
  {
    Test t1;          //A
    static Test t2("B");    //B
    Test array[3];        //AAA   //数组就是多个无参
    Test* p = new Test("C");  //C
    delete p;         //C
    p = nullptr;
  }
  //ABAAACCAAAAB
  return 0;
}

小试牛刀

//用构造函数的方式实现单链表
//构造函数创建节点
//构造函数做表头法插入
//加个遍历链表的操作即可
struct Node
{
  int data;
  struct Node* next;
  Node(int data);
  Node(int data,Node* next);
};
class List
{
public:
  List(); 
  void insertData(int data);
  void printList();
protected:
  Node* headNode;
  int curSize;
};
目录
相关文章
|
2月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
63 1
|
7月前
|
设计模式 编译器 C++
C++中的构造方法和析构方法详解
C++中的构造方法和析构方法详解
49 0
|
4月前
|
JavaScript Java C语言
面向对象编程(C++篇3)——析构
面向对象编程(C++篇3)——析构
36 2
|
4月前
|
JavaScript 前端开发 Java
面向对象编程(C++篇2)——构造
面向对象编程(C++篇2)——构造
35 0
|
7月前
|
编译器 C语言 C++
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
42 1
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中)
|
6月前
|
C++ 容器
C++之deque容器(构造、赋值、大小、插入与删除、存取、排序)
C++之deque容器(构造、赋值、大小、插入与删除、存取、排序)
|
6月前
|
C++ 容器
C++字符串string容器(构造、赋值、拼接、查找、替换、比较、存取、插入、删除、子串)
C++字符串string容器(构造、赋值、拼接、查找、替换、比较、存取、插入、删除、子串)
|
7月前
|
存储 安全 C语言
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(上)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
38 2
|
7月前
|
编译器 C语言 C++
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(下)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
45 1
|
6月前
|
算法 C++ 容器
C++之vector容器操作(构造、赋值、扩容、插入、删除、交换、预留空间、遍历)
C++之vector容器操作(构造、赋值、扩容、插入、删除、交换、预留空间、遍历)
291 0