lesson-2C++类与对象(中)(一)

简介: lesson-2C++类与对象(中)(一)

类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。

class null   //null是类名
{
};

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员

函数。

构造函数

概念

我们可以先通过一个对象的初始化函数引入。

#include <iostream>
using namespace std;
class Date
{
public:
  void Init(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << _year << "-";
    cout << _month << "-";
    cout << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date a;
  a.Init(2023, 10, 20);
  a.Print();
  return 0;
}

每一次我们创建出一个对象都要手动为其初始化,如果忘了的话,轻点是随机值,重点程序就崩了,所以我们的构造函数就解决了这个问题。

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证

每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

看代码。

#include <iostream>
using namespace std;
class Date
{
public:
    //构造函数支持重载
  Date()
  {
    _year = 1;
    _month = 1;
    _day = 1;
  }
  Date(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << _year << "-";
    cout << _month << "-";
    cout << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date a;
  a.Print();
  return 0;
}

我们可以看到我们a对象里的年月日确实有值了,被我们所写的的默认构造函数所调用,并全部赋值为1.

接下来我们试试有参数的构造函数。

int main()
{
  Date a(2023,10,21);
  a.Print();
  return 0;
}

我们发现编译器调用的是我们有参数的构造函数。

特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任

并不是开空间创建对象,而是初始化对象

特征如下:

1. 函数名与类名相同。

2. 无返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。

在上述代码中,也体现出了构造函数的这四个特性。

但是有几点我们要注意

1:没有参数的构造函数,系统默认的构造函数,全缺省的构造函数,都叫做默认构造函数,而这三个构造函数不可同时写出,任意二者不可同时存在,我们看代码解释。

class Date
{
public:
  Date()
  {
    _year = 1;
    _month = 1;
    _day = 1;
  }
  Date(int year = 10, int month = 10, int day = 10)
  {
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date a;
  return 0;
}

因为编译器无法区分到要调用哪个构造函数,所以就报错了。

2:当我们定义了构造函数后,编译器不会再生成默认构造函数,但这样也会出现一个小问题需要我们去掌控。

class Date
{
public:
  Date(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date a;
  return 0;
}

因为我们自定义了一个构造函数,所以系统不会再生成默认构造函数,而我们实例化的对象要调用无参的构造函数,但是我们又没有无参的构造函数,所以编译器就只能报错,我们后面会讲到有解决方法。

3.如果说我们没有写构造函数,内置类型的成员变量使用系统的默认构造函数,不会做处理,自定义类型的成员变量会去调用他自己的默认构造函数。

解释:内置类型就是系统自带的,比如int,double之类的,像类和结构体等就是自定义类型。

class Stack
{
public:
  Stack(int capacity = 4)
  {
    _capacity = capacity;
    int top = 0;
    int* a = (int*)malloc(sizeof(int) * capacity);
  }
  ~Stack()
  {
    free(_a);
    _top = 0;
    _capacity = 0;
  }
private :
  int* _a;
  int _top;
  int _capacity;
};
class Queue
{
  Stack _a;
  Stack _b;
  int size;
};
int main()
{
  Stack a;
  Queue b;
  return 0;
}

细心的朋友们可能会发现size被初始化为0了,你不是说内置类型的变量不初始化吗?是的,但是不同的编译器处理结果不同,我这个是Visual Studio 2022 ,在2013下是不会给初始化的,我们想要我们写出的代码具有跨平台性,就不要寄希望于编译器会给优化,所以我们就当做他不会给优化处理,写出优质代码。

4:C++11新特性,允许声明成员变量时给默认值。

class Date
{
public:
  void Print()
  {
    cout << _year << _month << _day;
  }
private:
  int _year = 1;
  int _month = 1;
  int _day = 1;
};
int main()
{
  Date a;
  a.Print();
  return 0;
}

析构函数

概念

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由

编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

像我们上面有代码~Stack就是析构函数。

特性

析构函数是特殊的成员函数,其特征如下:

1. 析构函数名是在类名前加上字符~

2. 无参数无返回值类型。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

   注意:析构函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数

我们来测试一下。

class Stack
{
public:
  Stack(int capacity = 4)
  {
    _capacity = capacity;
    int top = 0;
    int* a = (int*)malloc(sizeof(int) * capacity);
  }
  ~Stack()
  {
    free(_a);
    _top = 0;
    _capacity = 0;
    cout << "haha" << endl;
  }
private :
  int* _a;
  int _top;
  int _capacity;
};
class Queue
{
  Stack _a;
  Stack _b;
  int size;
};
int main()
{
  Queue b;
  return 0;
}

结果显而易见,我们的确是调用了析构函数。

拷贝构造函数

概念

拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存

在的类类型对象创建新对象时由编译器自动调用

特性

我们先来写一段代码证明拷贝构造函数的必要性

首先是日期类的测试

class Date
{
public:
  Date(int year = 2023, int month = 10, int day = 22)
  {
    _year = year;
    _month = month;
    _day = day;
    cout << "Date的构造" << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
void func1(Date a)
{
  cout << "func1(Date a)" << endl;
}
int main()
{
  Date d1(2023, 10, 22);
  func1(d1);  
  return 0;
}

注意日期类函数调用的析构函数是系统默认的,而且也没什么需要释放的。

所以在我们将d1这个对象传给func1这个函数的时候时值传递,当fun1结束时,a这个对象要销毁,会去调用析构函数。

接下来我们来看栈这个类

class Stack
{
public:
  Stack(int capacity)
  {
    _capacity = capacity;
    int top = 0;
    _a = (int*)malloc(sizeof(int) * _capacity);
    if (_a == nullptr)
    {
      perror("malloc");
    }
    cout << "Stack的构造" << endl;
  }
  ~Stack()
  {
    free(_a);
    _a = nullptr;
    _top = 0;
    _capacity = 0;
    cout << "Stack的析构" << endl;
  }
private:
  int* _a;
  int _top;
  int _capacity;
};
void func2(Stack st)
{
  //...
}
int main()
{
  Stack st1(4);
  func2(st1);
  return 0;
}

这个栈类再这么调用就会出问题,当st销毁时,去调用我们所写的析构函数,在func2函数结束时会释放一次_a,当主函数中的对象st1销毁时,会对已经释放的那块空间再释放一次,这样程序就崩溃了,因为此时的_a就是野指针了,别忘了我们是传值,空间释放后_a就成了野指针,释放野指针指向的空间是不合法的,所以就崩了。

那也许有人会说,我传引用不就好了吗,但是假设我们有一个需求,不改变对象本身,就是要拷贝一份去实现,那么拷贝构造就凸显出作用来了。(如果还有人说栈这个类我用系统默认的析构函数不好吗?那您可真是昏了头了)

lesson-2C++类与对象(中)(二)+https://developer.aliyun.com/article/1393892

目录
相关文章
|
6月前
|
存储 Serverless C++
【C++】——类与对象(一)
【C++】——类与对象(一)
|
编译器 C语言 C++
C++(类与对象)详解 - 1(上)
C++(类与对象)详解 - 1
63 0
|
Java 编译器
【JAVASE】类与对象 下
【JAVASE】类与对象
|
1月前
|
编译器 C++
C++之类与对象(3)(下)
C++之类与对象(3)(下)
35 0
|
1月前
|
存储 C++
C++之类与对象(1)(上)
C++之类与对象(1)(上)
22 0
|
1月前
|
存储 编译器 C++
C++之类与对象(1)(下)
C++之类与对象(1)(下)
29 0
|
6月前
|
存储 编译器 C++
【C++】类和对象(中)(1)
【C++】类和对象(中)(1)
|
6月前
|
存储 编译器 C语言
C++类与对象(上)
C++类与对象(上)
43 1
|
6月前
|
存储 编译器 C语言
C++:类与对象(2)
C++:类与对象(2)
|
6月前
|
存储 编译器 C语言
C++初阶--类与对象(1)
C++初阶--类与对象(1)