专业编程基础技术教程

网站首页 > 基础教程 正文

C++之构造与析构 c++构造类

ccvgpt 2024-10-12 13:51:34 基础教程 7 ℃

C++构造函数

C++构造函数| 构造函数基础

C++中的构造函数是用来初始化对象的特殊成员函数,它们的名称与类的名称相同。构造函数在创建类的新对象时自动调用,用于对对象进行初始化工作,比如给属性赋初值。一个类可以有多个构造函数,以便在创建对象时使用不同的参数列表。所以构造函数的长相决定了对象的长相,无参构造函数,对象无参,一个参数构造函数,对象一个参数,以此类推。

C++之构造与析构 c++构造类

构造函数的特点:

  • 函数名与类名相同。
  • 返回值没有返回类型,包括void。
  • 如果没有显式地定义构造函数,则编译器会生成一个默认的构造函数。如果自己写了,默认的就不存在
  • 构造函数允许被重载
  • 构造函数允许缺省
  • 构造函数不需要自己调用,在构造对象的时候调用构造函数

如下测试代码

#include <iostream>
#include <string>
class GirlFriend {

public:
  GirlFriend(std::string name, int age) 
  {
    m_name = name;
    m_age = age;
    std::cout << "两个参数:构造函数" << std::endl;
  }
  void print() 
  {
    std::cout << m_name<<"\t"<< m_age << std::endl;
  }
protected:
  std::string m_name;
private:
  int m_age;
};
int main()
{
  //错误没有一个参数的构造函数
  //GirlFriend object;
  //1.构造对象
  GirlFriend  object("baby", 18);
  object.print();
  //2.new一个对象
  GirlFriend* p = new GirlFriend("小芳", 29);
  p->print();
  
  //3.隐式转换创建,必须具备两个参数构造函数
  GirlFriend girl = { "小美",28 };
  girl.print();
  return 0;
}

结构体中包含构造函数,也是一样的规则,不能再使用C语言的方式去创建结构体变量。

C++构造函数| explicit 禁止隐式转换

在C++中,关键字explicit通常用于构造函数声明。使用explicit关键字可以防止隐式转换,以便只有显式调用才能将单个参数构造函数作为隐式转换执行。如下测试代码

#include <iostream>
#include <string>
class GirlFriend {

public:
  explicit GirlFriend(std::string name, int age) 
  {
    m_name = name;
    m_age = age;
  }
  void print() 
  {
    std::cout << m_name<<"\t"<< m_age << std::endl;
  }
protected:
  std::string m_name;
private:
  int m_age;
};
int main()
{
  //错误:禁止隐式转换
  //GirlFriend girl = { "小美",28 };
  GirlFriend girl("小美", 28);
  girl.print();
  return 0;
}

C++构造函数| delete与default

在C++类中, deletedefault都是关键字。

  • delete 关键字在类中用于删除特殊成员函数、释放动态分配的内存。
  • default 关键字用于使用类中默认的函数

如下测试代码

#include <iostream>
#include <string>
class GirlFriend {

public:
  //删掉默认构造函数
  GirlFriend() = delete;
protected:

};

class BoyFriend {

public:
  //使用默认的构造函数,故可以创建无参对象
  BoyFriend() = default;
  BoyFriend(int age) {}
protected:

};
int main()
{
  //错误,默认构造函数被删除,不存在构造函数,无法创建对象
  //GirlFriend girl;
  //调用默认函数构造无参对象
  BoyFriend boy;
  BoyFriend moying(18);
  return 0;
}

C++构造函数| 构造函数委托

在C++11中,我们可以使用构造函数委托来简化类的构造函数。构造函数委托可以让一个构造函数调用另一个构造函数来完成部分或全部的初始化工作。调用方式必须用初始化参数列表方式。

#include <iostream>
#include <string>
class GirlFriend
{

public:
  GirlFriend(std::string name,int age)
  {
    m_name = name;
    m_age = age;
  }
  //构造函数委托,初始化参数列表
  GirlFriend() :GirlFriend("默认", 0)
  {

  }
  void  print() 
  {
    std::cout << m_name << "\t" << m_age << std::endl;
  }
protected:
  std::string m_name;
  int m_age;
};

int main()
{
  GirlFriend object;
  object.print();
  return 0;
}

C++构造函数| 匿名对象

C++中的匿名对象是指没有被命名的临时对象。匿名对象是一个右值,引用传参需要用常引用或者右值引用,它们通常在函数调用的时候被创建,用于传递参数或作为函数返回值。 匿名对象的生命周期仅限于当前表达式或语句结束之前,在此之后便会被销毁。

以下是一些产生匿名对象的常见方式:

  • 匿名对象充当函数参数
  • 匿名对象赋值给对象
  • 匿名对象充当函数返回值

如下测试代码

#include <iostream>
#include <string>
class GirlFriend
{

public:
  GirlFriend(std::string name,int age)
  {
    m_name = name;
    m_age = age;
  }
  void  print() 
  {
    std::cout << m_name << "\t" << m_age << std::endl;
  }
protected:
  std::string m_name;
  int m_age;
};
//用常引用需要调用常成员函数
void printObject(GirlFriend&& object) 
{
  object.print();
}
GirlFriend returnObject()
{
  return GirlFriend("匿名对象", 18);
}

int main()
{
  GirlFriend object=GirlFriend("匿名对象",18);
  object.print();
  printObject(GirlFriend("匿名对象", 18));
  returnObject().print();
  return 0;
}

C++析构函数

C++中析构函数是一种特殊的成员函数,用于在对象销毁时清理对象所占用的资源。析构函数的名称与类名相同,前面加上一个波浪号(~),没有参数。

析构函数在以下情况下会被自动调用:

  • 对象在离开其作用域时,比如一个局部对象在函数结束时;
  • 通过使用 delete 运算符进行动态分配的内存释放对象;
  • 对象在一个包含它的程序块结束时。

析构函数可以在销毁对象时执行必要的内存清理操作,如释放动态分配的内存、关闭文件等资源。如果一个类拥有指向其他对象的指针、动态分配了内存或打开了文件,那么这个类应该定义一个析构函数来处理这些操作,以避免内存泄漏和资源浪费的问题。

如下测试代码

#include <iostream>
#include <string>
class GirlFriend
{

public:
  ~GirlFriend() 
  {
    std::cout << "析构函数" << std::endl;
  }

protected:

};

int main()
{
  {
    //普通对象代码块结束调用析构
    GirlFriend girl;
    //new出来的对象需要delete才会调用析构
    GirlFriend* p = new GirlFriend;
  }
  std::cout << "--------------" << std::endl;
  {
    //普通对象代码块结束调用析构
    GirlFriend girl;
    //new出来的对象需要delete才会调用析构
    GirlFriend* p = new GirlFriend;
    delete p;
  }
  return 0;
}

运行结果如下

C++拷贝构造函数

C++的拷贝构造函数(Copy Constructor)是一种特殊的构造函数,用于创建一个新对象并且以传入的同类对象为初始值。拷贝构造函数在许多场合下自动被调用,例如当函数参数是一个对象时、当函数返回值是一个对象时、当一个对象被另一个对象赋值时,等等。拷贝构造函数唯一的参数就是对对象的引用,作用就是通过一个对象产生另一个对象。

C++拷贝构造函数| 左值引用

左值引用的拷贝构造函数,只能实现左值对象的拷贝,不能传入常对象,或者右值对象。如下测试代码:

#include<iostream>
#include<string>
class MM 
{
public:
  MM() {}
  MM(std::string name, int age) 
  {
    m_name = name;
    m_age = age;
  }
  //拷贝构造函数+构造函数委托
  MM(MM& object):MM(object.m_name,object.m_age)
  {

  }
  void print()
  {
    std::cout << m_name << "\t" << m_age << std::endl;
  }

protected:
  std::string m_name;
  int m_age;
};
//不调用
void printInfo(MM& object) 
{
  object.print();
}
//调用拷贝构造函数
void printData(MM object) 
{
  object.print();
}
int main()
{
  MM girl("girl", 19);
  //调用拷贝构造函数
  MM mm = girl;  
  mm.print();
  //调用拷贝构造函数
  MM baby(mm);
  baby.print();
  //错误
  //MM error1 = std::move(baby);
  //错误
  //MM error2(MM("error", 18)) ;
  MM test;
  //不调用拷贝构造函数
  test = mm;
  printInfo(test);
  printData(test);
  return 0;
}

C++拷贝构造函数| 常引用

常引用的拷贝构造函数要注意的是在拷贝构造函数中不能修改数据成员。基本上满足大部分的需求。如下测试代码:

#include<iostream>
#include<string>
class MM 
{
public:
  MM() {}
  MM(std::string name, int age) 
  {
    m_name = name;
    m_age = age;
  }
  //拷贝构造函数+构造函数委托
  MM( const MM& object):MM(object.m_name,object.m_age)
  {
    //这个函数中不能修改object数据
  }
  void print()
  {
    std::cout << m_name << "\t" << m_age << std::endl;
  }

protected:
  std::string m_name;
  int m_age;
};
//不调用
void printInfo(MM& object) 
{
  object.print();
}
//调用拷贝构造函数
void printData(MM object) 
{
  object.print();
}
int main()
{
  MM girl("girl", 19);
  //调用拷贝构造函数
  MM mm = girl;  
  mm.print();
  //调用拷贝构造函数
  MM baby(mm);
  baby.print();
  //正确
  MM ok1 = std::move(baby);
  MM ok2(MM("error", 18));
  MM test;
  //不调用拷贝构造函数
  test = mm;
  printInfo(test);
  printData(test);
  return 0;
}

C++拷贝构造函数| 右值引用

C++11引入了移动语义(Move Semantics)的概念,这种语义的目的是在对象拷贝的过程中避免不必要的复制操作,从而提高程序的性能。移动拷贝(Move Constructor)是一种特殊的构造函数,用于将另一个同类对象的资源所有权移动到一个新对象中,即移动拷贝不是复制原有对象的数据,而是基于原对象构造一个新对象,并将原对象的资源指针移到新对象,使新对象掌握该资源的控制权。相比于常引用来说,在拷贝构造函数中可以修改数据。如下测试代码:

#include<iostream>
#include<string>
class MM 
{
public:
  MM() {}
  MM(std::string name, int age) 
  {
    m_name = name;
    m_age = age;
  }
  //拷贝构造函数+构造函数委托
  MM(MM& object):MM(object.m_name,object.m_age)
  {
  }
  MM(MM&& object) :MM(object.m_name, object.m_age)
  {
    object.m_name = "name";
  }
  void print()
  {
    std::cout << m_name << "\t" << m_age << std::endl;
  }

protected:
  std::string m_name;
  int m_age;
};
//不调用
void printInfo(MM& object) 
{
  object.print();
}
//调用拷贝构造函数
void printData(MM object) 
{
  object.print();
}
int main()
{
  MM girl("girl", 19);
  //调用拷贝构造函数
  MM mm = girl;  
  mm.print();
  //调用拷贝构造函数
  MM baby(mm);
  baby.print();
  //正确
  MM ok1 = std::move(baby);
  MM ok2(MM("error", 18));

  printInfo(baby);
  printData(baby);
  return 0;
}

C++拷贝构造函数| 深浅拷贝问题

C++类中包含指针的时候并且做内存申请,才会存在深浅拷贝问题。

  • 浅拷贝:浅拷贝是一种将源对象的指针直接复制到新对象中的操作。这意味着源对象和新对象共享指向相同位置的内存,当该对象的生命周期结束时,会同时释放该内存空间。
  • 深拷贝:深拷贝是一种在创建新对象时,将指向源对象的指针重新分配到新的内存空间中的操作。这意味着源对象和新对象各自拥有自己的内存空间,他们的生命周期互相独立。


通常不写拷贝构造函数,会存在一个默认的拷贝构造函数,默认拷贝构造和直接赋值的拷贝构造都是浅拷贝,浅拷贝会引发内存重复被释放问题。如下测试代码:

#include <iostream>
#include <cstring>
using namespace std;
class MM 
{
public:
  MM(const char* str)
  {
    int length = strlen(str) + 1;
    pname = new char[length];
    strcpy_s(pname, length, str);
  }
  MM(MM& object)
  {
    //默认拷贝构造和直接赋值都是浅拷贝
    pname = object.pname;
  }
  void print() 
  {
    cout << pname << endl;
  }
  ~MM() 
  {
    delete[] pname;
  }
protected:
  char* pname;
};
int main() 
{
  {
    MM mm("浅拷贝");
    MM girl(mm);
    girl.print();
    mm.print();
  }
  return 0;
}

运行结果如下

深拷贝实现代码

#include <iostream>
#include <cstring>
using namespace std;
class MM 
{
public:
  MM(const char* str)
  {
    int length = strlen(str) + 1;
    pname = new char[length];
    strcpy_s(pname, length, str);
  }
  //委托构造
  MM(MM& object):MM(object.pname)
  {
    //或者自己实现代码
    //int length = strlen(object.pname) + 1;
    //pname = new char[length];
    //strcpy_s(pname, length, object.pname);
  }
  void print() 
  {
    cout << pname << endl;
  }
  ~MM() 
  {
    delete[] pname;
  }
protected:
  char* pname;
};
int main() 
{
  {
    MM mm("深拷贝");
    MM girl(mm);
    girl.print();
    mm.print();
  }
  return 0;
}

做了内存的在申请,这样就不会有问题。

C++构造和析构顺序

C++构造和析构顺序主要有以下三点:

  • 普通对象构造顺序和析构顺序是相反的
  • new出来的对象,delete时候立刻马上调用析构
  • static对象最后被释放

如下测试代码:

#include <iostream>
#include <string>
using namespace std;
class MM
{
public:
  MM(string _name="默认")
  {
    name = _name;
    cout << name;
  }
  ~MM() 
  {
    cout << name;
  }
protected:
  string name;
};
int main() 
{
  //普通对象,构造顺序和析构顺序相反
  cout << "普通对象,构造顺序和析构顺序相反" << endl;
  {
    MM a("a");
    MM b("b");
    MM c("c");
  }
  cout <<endl <<"new出来的对象,立刻马上调用析构" << endl;
  {
    MM a("a");
    MM b("b");
    auto c = new MM("c");
    delete c;
    MM d("d");
    //abccddba
  }
  cout <<endl << "static对象最后被释放" << endl;
  {
    MM a("a");
    MM b("b");
    static MM s(" static ");
    MM c("c");
  }
  cout << endl << "C++构造数组对象,一般会准备无参构造函数" << endl;
  {
    //MM array[3]{ {"a"},{"b"},{"c"}};    //带参的方式构造,比较不推荐 
    //构造数组其实是三个无参对象
    MM a("a");
    MM arr[3];
    MM b("b");
  }
  return 0;
}

如果能成功分析程序运行结果,基本就没什么问题。程序运行结果如下

相关

如果阁下正好在学习C/C++,看文章比较无聊,不妨关注下关注下小编的视频教程,通俗易懂,深入浅出,一个视频只讲一个知识点。视频不深奥,不需要钻研,在公交、在地铁、在厕所都可以观看,随时随地涨姿势。

最近发表
标签列表