自从学了C++之后,小雅兰就有对象了!!!(类与对象)(上)——“C++”

简介: 自从学了C++之后,小雅兰就有对象了!!!(类与对象)(上)——“C++”

各位CSDN的uu们好呀,好久没有更新小雅兰的C++专栏啦,话不多说,让我们进入类和对象的世界吧!!!


类的6个默认成员函数

构造函数

析构函数

拷贝构造函数


类的6个默认成员函数

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

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

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

class Date {};


构造函数

概念

对于以下Date类:

#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 << "-" << _month << "-" << _day << endl;
  }
 
private:
  int _year;
  int _month;
  int _day;
};
 
int main()
{
  Date d1;
  d1.Init(2023, 8, 9);
  d1.Print();
  Date d2;
  d2.Init(2023, 8, 10);
  d2.Print();
  return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。  

也就是说,构造函数类比于Init函数!!!

其特征如下:

  • 函数名与类名相同。
  • 无返回值(不需要写void)。
  • 对象实例化时编译器自动调用对应的构造函数。
  • 构造函数可以重载(本质就是写多个构造函数,提供多种初始化方式)。
class Date
{
public:
  //无参构造函数
  Date()
  {
    cout << "Date()" << endl;
    _year = 1;
    _month = 1;
    _day = 1;
  }
  //带参构造函数
  Date(int year, int month, int day)
  {
    cout << "Date(int year, int month, int day)" << endl;
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  // 调用无参构造函数
  Date d1;
  d1.Print();
  // 调用带参的构造函数
  Date d2(2023, 8, 9);
  d2.Print();
  // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
  // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
  // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
  Date d3();
  d3.Print();
 
  return 0;
}

//无参构造函数
   Date()
   {
       cout << "Date()" << endl;
       _year = 1;
       _month = 1;
       _day = 1;
   }
   //带参构造函数
   Date(int year, int month, int day)
   {
       cout << "Date(int year, int month, int day)" << endl;
       _year = year;
       _month = month;
       _day = day;
   }

上述两个函数其实可以合并一下,写成全缺省参数的形式。

//带参构造函数
Date(int year = 1, int month = 1, int day = 1)
{
  cout << "Date(int year, int month, int day)" << endl;
  _year = year;
  _month = month;
  _day = day;
}

这样的写法也更灵活了,可以传一个参数,也可以传两个参数,也可以传三个参数,也可以不传参数。

再看下面这个实例:

class Stack
{
public:
  Stack(size_t n = 4)
  {
    if (n == 0)
    {
      a = nullptr;
      top = capacity = 0;
    }
    else
    {
      a = (int*)malloc(sizeof(int) * n);
      if(a == nullptr)
      {
        perror("realloc fail");
        exit(-1);
      }
 
      top = 0;
      capacity = n;
    }
  }
  void Push(int x)
  {
    if (top == capacity)
    {
      size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
      int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
      if (tmp == nullptr)
      {
        perror("realloc fail");
        exit(-1);
      }
      if (tmp == a)
      {
        cout << capacity << "原地扩容" << endl;
      }
      else
      {
        cout << capacity << "异地扩容" << endl;
      }
 
      a = tmp;
      capacity = newcapacity;
    }
 
    a[top++] = x;
  }
 
  int Top()
  {
    return a[top - 1];
  }
 
  void Pop()
  {
    assert(top > 0);
    --top;
  }
 
  void Destroy()
  {
    free(a);
    a = nullptr;
    top = capacity = 0;
  }
 
  bool Empty()
  {
    return top == 0;
  }
private:
  // 成员变量
  int* a;
  int top;
  int capacity;
};
 
int main()
{
  Stack st1;
  st1.Push(1);
  st1.Push(2);
  st1.Push(3);
  st1.Push(4);
  
  while (!st1.Empty())
  {
    cout << st1.Top() << " ";
    st1.Pop();
  }
  cout << endl;
 
  st1.Destroy();
 
  //Stack st2(1000);
  Stack st2;
  for (size_t i = 0; i < 1000; i++)
  {
    st2.Push(i);
  }
 
  while (!st2.Empty())
  {
    cout << st2.Top() << " ";
    st2.Pop();
  }
  cout << endl;
 
  st2.Destroy();
  return 0;
}

构造函数,是默认成员函数,不写,编译器会自动生成。

编译生成的默认构造的特点:

  • 我们不写才会生成,我们写了就不会生成了。
  • 内置类型的成员不会处理(C++11,声明支持给缺省值)。
  • 自定义类型的成员才会处理,会去调用这个成员的默认构造函数。

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。

class Date
{
public:
  如果用户显式定义了构造函数,编译器将不再生成
  //Date(int year, int month, int day)
  //{
  //  _year = year;
  //  _month = month;
  //  _day = day;
  //}
  
  void Print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
 
private:
  int _year;
  int _month;
  int _day;
};
 
int main()
{
  // 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
  // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
  // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
  Date d1;
  d1.Print();
  return 0;
}

所以,默认生成的构造函数一般没什么价值,但是在有一些场景下非常有价值,之前小雅兰写过一个题目,就是两个栈实现一个队列,既可以写构造函数,也可以不写。

// 两个栈实现一个队列
class MyQueue
{
private:
  Stack _pushst;
  Stack _popst;
};

总结:一般情况都需要我们自己写构造函数,决定初始化方式

成员变量全是自定义类型,可以考虑不写构造函数

关于编译器生成的默认成员函数,很多人会有疑惑:不实现构造函数的情况下,编译器会 生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默 认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认构造函数。

int*和Date*(指针)都是内置类型!!!

class Time
{
public:
  Time()
  {
    cout << "Time()" << endl;
    _hour = 0;
    _minute = 0;
    _second = 0;
  }
 
private:
  int _hour;
  int _minute;
  int _second;
};
 
class Date
{
private:
  // 基本类型(内置类型)
  int _year;
  int _month;
  int _day;
  // 自定义类型
  Time _t;
};
 
int main()
{
  Date d;
  return 0;
}

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在 类中声明时可以给默认值。

class Time
{
public:
  Time()
  {
    cout << "Time()" << endl;
    _hour = 0;
    _minute = 0;
    _second = 0;
  }
private:
  int _hour;
  int _minute;
  int _second;
};
 
class Date
{
private:
  // 基本类型(内置类型)
  int _year = 1970;
  int _month = 1;
  int _day = 1;
  // 自定义类型
  Time _t;
};
 
int main()
{
  Date d;
  return 0;
}

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为 是默认构造函数。

不传参就可以调用的构造就是默认构造!!!

class Date
{
public:
  Date()
  {
    _year = 1900;
    _month = 1;
    _day = 1;
  }
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
 
private:
  int _year;
  int _month;
  int _day;
};
 
// 以下测试函数能通过编译吗?
void Test()
{
  Date d1;
}
int main()
{
  Test();
  return 0;
}

上述代码是无法通过编译的。

如果是把无参构造函数屏蔽掉或者是把全缺省构造函数屏蔽掉,就可以通过编译了!!!!


析构函数

概念

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

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

也就是说,析构函数类比于Destroy函数!!!

特性

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

  • 析构函数名是在类名前加上字符 ~。
  • 无参数无返回值类型。
  • 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载。
  • 对象生命周期结束时,C++编译系统系统自动调用析构函数。

默认的析构函数跟默认构造函数类似:内置类型成员不会处理,自定义类型成员会调用这个成员的析构函数。

class Date
{
public:
  Date(int year = 1, int month = 1, int day = 1)
  {
    cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
 
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
 
  ~Date()
  {
    cout << "~Date()" << endl;
  }
 
private:
  int _year = 1;   // 声明给的缺省值
  int _month = 1;
  int _day = 1;
};
 
class Stack
{
public:
  Stack(size_t n = 4)
  {
    cout << "Stack(size_t n = 4)" << endl;
 
    if (n == 0)
    {
      a = nullptr;
      top = capacity = 0;
    }
    else
    {
      a = (int*)malloc(sizeof(int) * n);
      if (a == nullptr)
      {
        perror("realloc fail");
        exit(-1);
      }
 
      top = 0;
      capacity = n;
    }
  }
 
  ~Stack()
  {
    cout << "~Stack()" << endl;
    free(a);
    a = nullptr;
    top = capacity = 0;
  }
 
  void Push(int x)
  {
    if (top == capacity)
    {
      size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
      int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
      if (tmp == nullptr)
      {
        perror("realloc fail");
        exit(-1);
      }
      if (tmp == a)
      {
        cout << capacity << "原地扩容" << endl;
      }
      else
      {
        cout << capacity << "异地扩容" << endl;
      }
 
      a = tmp;
      capacity = newcapacity;
    }
 
    a[top++] = x;
  }
 
  int Top()
  {
    return a[top - 1];
  }
 
  void Pop()
  {
    assert(top > 0);
    --top;
  }
 
  void Destroy()
  {
    free(a);
    a = nullptr;
    top = capacity = 0;
  }
 
  bool Empty()
  {
    return top == 0;
  }
private:
  // 成员变量
  int* a;
  int top;
  int capacity;
};
 
// 两个栈实现一个队列
class MyQueue
{
private:
  Stack _pushst;
  Stack _popst;
};
int main()
{
  Date d1;
  Date d2;
 
  Stack st1;
  Stack st2;
  return 0;
}

其实日期类不需要写析构函数!!!

像栈这样的数据结构,就需要写析构函数!!!

关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器 生成的默认析构函数,对自定类型成员调用它的析构函数。

如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如

Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

class Time
{
public:
  ~Time()
  {
    cout << "~Time()" << endl;
  }
private:
  int _hour;
  int _minute;
  int _second;
};
 
class Date
{
private:
  // 基本类型(内置类型)
  int _year = 1970;
  int _month = 1;
  int _day = 1;
 
  // 自定义类型
  Time _t;
};
 
int main()
{
  Date d;
  return 0;
}
 
// 程序运行结束后输出:~Time()
// 在main函数中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在
// d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数
// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函
// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数


拷贝构造函数

概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。

那在创建对象时,可否创建一个与已存在对象一模一样的新对象呢?  

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。

特征

拷贝构造函数也是特殊的成员函数,其特征如下:

  • 拷贝构造函数是构造函数的一个重载形式。
  • 拷贝构造函数的参数只有一个且必须是类类型对象(同类型的对象)的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用

class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  // Date(const Date& d)// 正确写法
  Date(const Date d)// 错误写法:编译报错,会引发无穷递归
  {
    _year = d._year;
    _month = d._month;
    _day = d._day;
  }
private:
  int _year;
  int _month;
  int _day;
};
 
int main()
{
  Date d1;
  Date d2(d1);
  return 0;
}

class Date
{
public:
  Date(int year = 1, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
 
  Date(Date& d)
  {
    cout << "Date(Date& d)" << endl;
 
    _year = d._year;
    _month = d._month;
    _day = d._day;
  }
 
  void Print()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
  
private:
  // 内置类型
  int _year;
  int _month;
  int _day;
};
 
typedef int DataType;
class Stack
{
public:
  Stack(size_t capacity = 3)
  {
    _array = (DataType*)malloc(sizeof(DataType) * capacity);
    if (NULL == _array)
    {
      perror("malloc申请空间失败!!!");
      return;
    }
 
    _capacity = capacity;
    _size = 0;
  }
 
  Stack(Stack& s)
  {
    cout << "Stack(Stack& s)" << endl;
    // 深拷贝
    _array = (DataType*)malloc(sizeof(DataType) * s._capacity);
    if (NULL == _array)
    {
      perror("malloc申请空间失败!!!");
      return;
    }
 
    memcpy(_array, s._array, sizeof(DataType) * s._size);
    _size = s._size;
    _capacity = s._capacity;
  }
 
  void Push(DataType data)
  {
    _array[_size] = data;
    _size++;
  }
 
  ~Stack()
  {
    cout << "~Stack()" << endl;
    free(_array);
    _array = nullptr;
    _size = _capacity = 0;
  }
private:
  // 内置类型
  DataType* _array;
  int _capacity;
  int _size;
};
 
void func1(Date d)
{
  d.Print();
}
// 期望呢,s要插入一些数据,s的改变,不影响s1
void func2(Stack s)
{
  s.Push(1);
  s.Push(2);
}
 
int main()
{
  Date d1(2023, 7, 21);
  func1(d1);
 
  Stack s1;
  func2(s1);
 
  Stack s2(s1);
 
  // 以下两个写法是等价的,都是拷贝构造
  Date d2(d1);
  Date d3 = d1;
 
 
  return 0;
}

若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

class Time
{
public:
  Time()
  {
    _hour = 1;
    _minute = 1;
    _second = 1;
  }
  Time(const Time& t)
  {
    _hour = t._hour;
    _minute = t._minute;
    _second = t._second;
    cout << "Time::Time(const Time&)" << endl;
  }
private:
  int _hour;
  int _minute;
  int _second;
};
 
class Date
{
private:
  // 基本类型(内置类型)
  int _year = 1970;
  int _month = 1;
  int _day = 1;
 
  // 自定义类型
  Time _t;
};
 
int main()
{
  Date d1;
  // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
  // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
  Date d2(d1);
  return 0;
}

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定 义类型是调用其拷贝构造函数完成拷贝的。

我们不写,编译默认生成的拷贝构造,跟之前的构造函数特性不一样

  • 内置类型, 值拷贝
  • 自定义的类型,调用它的拷贝

总结:Date不需要我们实现拷贝构造,默认生成就可以用

       Stack需要我们自己实现深拷贝的拷贝构造,默认生成会出问题

MyQueue对于默认生成的几个函数非常受用,人生赢家

class MyQueue
{
private:
   Stack _pushst;
   Stack _popst;
};

MyQueue mq1;
MyQueue mq2 = mq1;

编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后学的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
  Stack(size_t capacity = 10)
  {
    _array = (DataType*)malloc(capacity * sizeof(DataType));
    if (nullptr == _array)
    {
      perror("malloc申请空间失败");
      return;
    }
    _size = 0;
    _capacity = capacity;
  }
  void Push(const DataType& data)
  {
    // CheckCapacity();
    _array[_size] = data;
    _size++;
  }
  ~Stack()
  {
    if (_array)
    {
      free(_array);
      _array = nullptr;
      _capacity = 0;
      _size = 0;
    }
  }
 
private:
  DataType* _array;
  size_t _size;
  size_t _capacity;
};
 
int main()
{
  Stack s1;
  s1.Push(1);
  s1.Push(2);
  s1.Push(3);
  s1.Push(4);
  Stack s2(s1);
  return 0;
}

 

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。  

拷贝构造函数典型调用场景:

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象  
class Date
{
public:
  Date(int year, int minute, int day)
  {
    cout << "Date(int,int,int):" << this << endl;
  }
  Date(const Date& d)
  {
    cout << "Date(const Date& d):" << this << endl;
  }
  ~Date()
  {
    cout << "~Date():" << this << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
Date Test(Date d)
{
  Date temp(d);
  return temp;
}
int main()
{
  Date d1(2022, 1, 13);
  Test(d1);
  return 0;
}

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。  


所有源代码如下:

#include
#include
using namespace std;
class Date
{
public:
   void Init(int year, int month, int day)
   {
       _year = year;
       _month = month;
       _day = day;
   }
   void Print()
   {
       cout << _year << "-" << _month << "-" << _day << endl;
   }

private:
   int _year;
   int _month;
   int _day;
};

int main()
{
   Date d1;
   d1.Init(2023, 8, 9);
   d1.Print();
   Date d2;
   d2.Init(2023, 8, 10);
   d2.Print();
   return 0;
}

 

class Date
{
public:
   无参构造函数
   //Date()
   //{
   //    cout << "Date()" << endl;
   //    _year = 1;
   //    _month = 1;
   //    _day = 1;
   //}
   带参构造函数
   //Date(int year, int month, int day)
   //{
   //    cout << "Date(int year, int month, int day)" << endl;
   //    _year = year;
   //    _month = month;
   //    _day = day;
   //}
   //带参构造函数
   Date(int year = 1, int month = 1, int day = 1)
   {
       cout << "Date(int year, int month, int day)" << endl;
       _year = year;
       _month = month;
       _day = day;
   }
   void Print()
   {
       cout << _year << "-" << _month << "-" << _day << endl;
   }
private:
   int _year;
   int _month;
   int _day;
};
int main()
{
   // 调用无参构造函数
   Date d1;
   d1.Print();
   // 调用带参的构造函数
   Date d2(2023, 8, 9);
   d2.Print();
   // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
   // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
   // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
   //Date d3();
   //d3.Print();
   Date d3(2023);
   d3.Print();
   Date d4(2023, 8);
   d4.Print();
   return 0;
}

class Stack
{
public:
   Stack(size_t n = 4)
   {
       if (n == 0)
       {
           a = nullptr;
           top = capacity = 0;
       }
       else
       {
           a = (int*)malloc(sizeof(int) * n);
           if(a == nullptr)
           {
               perror("realloc fail");
               exit(-1);
           }

           top = 0;
           capacity = n;
       }
   }
   void Push(int x)
   {
       if (top == capacity)
       {
           size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
           int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
           if (tmp == nullptr)
           {
               perror("realloc fail");
               exit(-1);
           }
           if (tmp == a)
           {
               cout << capacity << "原地扩容" << endl;
           }
           else
           {
               cout << capacity << "异地扩容" << endl;
           }

           a = tmp;
           capacity = newcapacity;
       }

       a[top++] = x;
   }

   int Top()
   {
       return a[top - 1];
   }

   void Pop()
   {
       assert(top > 0);
       --top;
   }

   void Destroy()
   {
       free(a);
       a = nullptr;
       top = capacity = 0;
   }

   bool Empty()
   {
       return top == 0;
   }
private:
   // 成员变量
   int* a;
   int top;
   int capacity;
};

int main()
{
   Stack st1;
   st1.Push(1);
   st1.Push(2);
   st1.Push(3);
   st1.Push(4);
   
   while (!st1.Empty())
   {
       cout << st1.Top() << " ";
       st1.Pop();
   }
   cout << endl;

   st1.Destroy();

   Stack st2(1000);
   //Stack st2;
   for (size_t i = 0; i < 1000; i++)
   {
       st2.Push(i);
   }

   while (!st2.Empty())
   {
       cout << st2.Top() << " ";
       st2.Pop();
   }
   cout << endl;

   st2.Destroy();
   return 0;
}

class Date
{
public:
   如果用户显式定义了构造函数,编译器将不再生成
   //Date(int year, int month, int day)
   //{
   //    _year = year;
   //    _month = month;
   //    _day = day;
   //}
   
   void Print()
   {
       cout << _year << "-" << _month << "-" << _day << endl;
   }

private:
   int _year;
   int _month;
   int _day;
};

int main()
{
   // 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
   // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
   // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
   Date d1;
   d1.Print();
   return 0;
}

// 构造函数,也是默认成员函数,我们不写,编译器会自动生成
// 编译生成的默认构造的特点:
// 1、我们不写才会生成,我们写了任意一个构造函数就不会生成了
// 2、内置类型的成员不会处理(C++11,声明支持给缺省值)
// 3、自定义类型的成员才会处理,回去调用这个成员的默认构造函数

// 总结:一般情况都需要我们自己写构造函数,决定初始化方式
// 成员变量全是自定义类型,可以考虑不写构造函数

class Time
{
public:
   Time()
   {
       cout << "Time()" << endl;
       _hour = 0;
       _minute = 0;
       _second = 0;
   }

private:
   int _hour;
   int _minute;
   int _second;
};

class Date
{
private:
   // 基本类型(内置类型)
   int _year;
   int _month;
   int _day;
   // 自定义类型
   Time _t;
};

int main()
{
   Date d;
   return 0;
}

 

class Time
{
public:
   Time()
   {
       cout << "Time()" << endl;
       _hour = 0;
       _minute = 0;
       _second = 0;
   }
private:
   int _hour;
   int _minute;
   int _second;
};

class Date
{
private:
   // 基本类型(内置类型)
   int _year = 1970;
   int _month = 1;
   int _day = 1;
   // 自定义类型
   Time _t;
};

int main()
{
   Date d;
   return 0;
}

class Date
{
public:
   Date()
   {
       _year = 1900;
       _month = 1;
       _day = 1;
   }
   Date(int year = 1900, int month = 1, int day = 1)
   {
       _year = year;
       _month = month;
       _day = day;
   }

private:
   int _year;
   int _month;
   int _day;
};

// 以下测试函数能通过编译吗?
void Test()
{
   Date d1;
}
int main()
{
   Test();
   return 0;
}

 

class Date
{
public:
   Date(int year = 1, int month = 1, int day = 1)
   {
       cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;

       _year = year;
       _month = month;
       _day = day;
   }
   void Print()
   {
       cout << _year << "/" << _month << "/" << _day << endl;
   }

   ~Date()
   {
       cout << "~Date()" << endl;
   }

private:
   int _year = 1;   // 声明给的缺省值
   int _month = 1;
   int _day = 1;
};

class Stack
{
public:
   Stack(size_t n = 4)
   {
       cout << "Stack(size_t n = 4)" << endl;

       if (n == 0)
       {
           a = nullptr;
           top = capacity = 0;
       }
       else
       {
           a = (int*)malloc(sizeof(int) * n);
           if (a == nullptr)
           {
               perror("realloc fail");
               exit(-1);
           }

           top = 0;
           capacity = n;
       }
   }

   ~Stack()
   {
       cout << "~Stack()" << endl;
       free(a);
       a = nullptr;
       top = capacity = 0;
   }

   void Push(int x)
   {
       if (top == capacity)
       {
           size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
           int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
           if (tmp == nullptr)
           {
               perror("realloc fail");
               exit(-1);
           }
           if (tmp == a)
           {
               cout << capacity << "原地扩容" << endl;
           }
           else
           {
               cout << capacity << "异地扩容" << endl;
           }

           a = tmp;
           capacity = newcapacity;
       }

       a[top++] = x;
   }

   int Top()
   {
       return a[top - 1];
   }

   void Pop()
   {
       assert(top > 0);
       --top;
   }

   void Destroy()
   {
       free(a);
       a = nullptr;
       top = capacity = 0;
   }

   bool Empty()
   {
       return top == 0;
   }
private:
   // 成员变量
   int* a;
   int top;
   int capacity;
};

// 两个栈实现一个队列
class MyQueue
{
private:
   Stack _pushst;
   Stack _popst;
};
int main()
{
   Date d1;
   Date d2;

   Stack st1;
   Stack st2;
   return 0;
}

 

class Time
{
public:
   ~Time()
   {
       cout << "~Time()" << endl;
   }
private:
   int _hour;
   int _minute;
   int _second;
};

class Date
{
private:
   // 基本类型(内置类型)
   int _year = 1970;
   int _month = 1;
   int _day = 1;

   // 自定义类型
   Time _t;
};

int main()
{
   Date d;
   return 0;
}

// 程序运行结束后输出:~Time()
// 在main函数中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在
// d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数
// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函
// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

class Date
{
public:
   Date(int year = 1, int month = 1, int day = 1)
   {
       _year = year;
       _month = month;
       _day = day;
   }

   Date(Date& d)
   {
       cout << "Date(Date& d)" << endl;

       _year = d._year;
       _month = d._month;
       _day = d._day;
   }

   void Print()
   {
       cout << _year << "/" << _month << "/" << _day << endl;
   }
   
private:
   // 内置类型
   int _year;
   int _month;
   int _day;
};

typedef int DataType;
class Stack
{
public:
   Stack(size_t capacity = 3)
   {
       _array = (DataType*)malloc(sizeof(DataType) * capacity);
       if (NULL == _array)
       {
           perror("malloc申请空间失败!!!");
           return;
       }

       _capacity = capacity;
       _size = 0;
   }

   Stack(Stack& s)
   {
       cout << "Stack(Stack& s)" << endl;
       // 深拷贝
       _array = (DataType*)malloc(sizeof(DataType) * s._capacity);
       if (NULL == _array)
       {
           perror("malloc申请空间失败!!!");
           return;
       }

       memcpy(_array, s._array, sizeof(DataType) * s._size);
       _size = s._size;
       _capacity = s._capacity;
   }

   void Push(DataType data)
   {
       _array[_size] = data;
       _size++;
   }

   ~Stack()
   {
       cout << "~Stack()" << endl;
       free(_array);
       _array = nullptr;
       _size = _capacity = 0;
   }
private:
   // 内置类型
   DataType* _array;
   int _capacity;
   int _size;
};

void func1(Date d)
{
   d.Print();
}
// 期望呢,s要插入一些数据,s的改变,不影响s1
void func2(Stack s)
{
   s.Push(1);
   s.Push(2);
}

int main()
{
   Date d1(2023, 7, 21);
   func1(d1);

   Stack s1;
   func2(s1);

   Stack s2(s1);

   // 以下两个写法是等价的,都是拷贝构造
   Date d2(d1);
   Date d3 = d1;

   return 0;
}

class Date
{
public:
   Date(int year = 1900, int month = 1, int day = 1)
   {
       _year = year;
       _month = month;
       _day = day;
   }
   // Date(const Date& d)// 正确写法
   Date(const Date& d)// 错误写法:编译报错,会引发无穷递归
   {
       _year = d._year;
       _month = d._month;
       _day = d._day;
   }
private:
   int _year;
   int _month;
   int _day;
};

int main()
{
   Date d1;
   Date d2(d1);
   return 0;
}

 

class Time
{
public:
   Time()
   {
       _hour = 1;
       _minute = 1;
       _second = 1;
   }
   Time(const Time& t)
   {
       _hour = t._hour;
       _minute = t._minute;
       _second = t._second;
       cout << "Time::Time(const Time&)" << endl;
   }
private:
   int _hour;
   int _minute;
   int _second;
};

class Date
{
private:
   // 基本类型(内置类型)
   int _year = 1970;
   int _month = 1;
   int _day = 1;

   // 自定义类型
   Time _t;
};

int main()
{
   Date d1;
   // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
   // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
   Date d2(d1);
   return 0;
}

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后学的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
   Stack(size_t capacity = 10)
   {
       _array = (DataType*)malloc(capacity * sizeof(DataType));
       if (nullptr == _array)
       {
           perror("malloc申请空间失败");
           return;
       }
       _size = 0;
       _capacity = capacity;
   }
   void Push(const DataType& data)
   {
       // CheckCapacity();
       _array[_size] = data;
       _size++;
   }
   ~Stack()
   {
       if (_array)
       {
           free(_array);
           _array = nullptr;
           _capacity = 0;
           _size = 0;
       }
   }

private:
   DataType* _array;
   size_t _size;
   size_t _capacity;
};

int main()
{
   Stack s1;
   s1.Push(1);
   s1.Push(2);
   s1.Push(3);
   s1.Push(4);
   Stack s2(s1);
   return 0;
}

class Date
{
public:
   Date(int year, int minute, int day)
   {
       cout << "Date(int,int,int):" << this << endl;
   }
   Date(const Date& d)
   {
       cout << "Date(const Date& d):" << this << endl;
   }
   ~Date()
   {
       cout << "~Date():" << this << endl;
   }
private:
   int _year;
   int _month;
   int _day;
};
Date Test(Date d)
{
   Date temp(d);
   return temp;
}
int main()
{
   Date d1(2022, 1, 13);
   Test(d1);
   return 0;
}

 


好啦,小雅兰今天的学习内容就到这里啦,还要继续加油噢!!!!


相关文章
|
23天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
36 2
|
29天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
82 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
79 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
86 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
26 1
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
31 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
30 4
|
2月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
2月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
2月前
|
存储 编译器 C语言
【C++类和对象(上)】—— 我与C++的不解之缘(三)
【C++类和对象(上)】—— 我与C++的不解之缘(三)