Notas resumidas sobre classes e objetos

  • 1. Seis funções de membro padrão da classe
  • 2.Construtor
  • 3. Destruidor
  • 4. Construtor de cópia
  • 5. Sobrecarga do operador de atribuição
  • 6. função membro const
  • 7. Sobrecarga de operadores de tomada de endereço e de tomada de endereço const

    1. Seis funções de membro padrão de uma classe
    Se não houver membros em uma classe, ela será chamada de classe vazia, abreviadamente.
    Não há realmente nada em uma classe vazia?Não, quando nada é escrito em nenhuma classe, o compilador gerará automaticamente 6 funções de membro padrão. Função de membro padrão; se o usuário não a exibir explicitamente, a função de membro gerada pelo compilador é chamada de função de membro padrão.


    2. Construtor
    2.1. Conceito
    Para a seguinte classe Date:

    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(2022, 9, 30);
    	d1.Print();
    
    	Date d2;
    	d2.Init(2022, 10, 1);
    	d2.Print();
    	return 0;
    }
    

     Para a classe Date, você pode definir a data do objeto por meio do método público Init. No entanto, seria um pouco problemático chamar esse método para definir as informações sempre que o objeto for criado. As informações podem ser definidas quando o objeto é criado?
    O construtor é uma função de membro especial com o mesmo nome da classe. Ele é chamado automaticamente pelo compilador ao criar um objeto do tipo classe para garantir que cada membro de dados tenha um valor inicial apropriado e seja usado apenas durante todo o ciclo de vida do objeto. Chamado uma vez.

    2.2.Características

    O construtor é uma função membro especial. Deve-se notar que embora o nome do construtor seja chamado de construção, a principal tarefa do construtor não é abrir espaço para criar um objeto, mas inicializá-lo.
    As características são as seguintes:
      1. O nome da função e o nome da classe são iguais.
      2. Nenhum valor de retorno.
      3. O compilador chama automaticamente o construtor correspondente quando o objeto é instanciado.
      4. Construtores podem ficar sobrecarregados

  • class Date
    {
    public:
    	//1.无参的构造函数
    	Date()
    	{}
    	//2.带参数的构造函数
    	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;
    };
    
    void TestDate()
    {
    	Date d1;//调用无参的构造函数,
    	//如果调用无参的构造函数,后面不要跟括号,否则就成了函数声明
    	Date d2(2020, 9, 30);//调用带参的构造函数-
    }
    

    5. Se o construtor não estiver explicitamente definido na classe, o compilador C++ gerará um construtor sem parâmetros. Uma vez que o usuário o defina explicitamente, o compilador não o gerará mais.
    6. Em relação ao construtor padrão gerado pelo compilador por padrão, se não implementarmos o construtor padrão, o compilador irá gerá-lo automaticamente, mas o construtor padrão gerado pelo compilador não processa tipos integrados e chama seu construtor padrão para tipos personalizados.

  • 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;
    }
    

    Nota: No C++ 11, foi feito um patch para o defeito de que os membros do tipo integrado não são inicializados, ou seja, variáveis ​​​​de membro do tipo interno podem receber valores padrão quando declaradas em uma classe.

  • 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 = 2020;
    	int _month = 9;
    	int _day = 30;
    	//自定义类型
    	Time _t;
    };
    
    int main()
    {
    	Date d;
    	return 0;
    }
    

    7. O construtor sem parâmetros e o construtor totalmente padrão são chamados de construtores padrão e só pode haver um construtor padrão.
    Nota: Construtores sem argumentos, construtores totalmente padrão e construtores gerados pelo compilador podem ser considerados construtores padrão.

  • 3. Destruidor
    3.1.Conceito
    O destruidor é o oposto do construtor. O destruidor não completa a destruição do objeto. A destruição dos objetos locais é completada pelo compilador. Quando o objeto é destruído, o destruidor é automaticamente chamado para concluir a limpeza dos recursos do objeto.

    3.2 Características
    O destruidor é uma função membro especial com as seguintes características:
      1. O nome do destruidor é precedido pelo caractere ~ antes do nome da classe.
      2. Sem parâmetros, sem tipo de valor de retorno.
      3. Uma classe só pode ter um destruidor. Se nenhuma definição for exibida, o sistema gerará automaticamente um construtor padrão. Nota: Os destruidores não podem ser sobrecarregados.
      4. Quando o ciclo de vida do objeto terminar, o sistema de compilação C++ chamará automaticamente o destruidor.
     

    typedef int DataType;
    class Stack
    {
    public:
    	Stack(int capacity = 4)
    	{
    		_a = (int*)malloc(sizeof(int) * capacity);
    		if (_a == nullptr)
    		{
    			perror("malloc is fail\n");
    			exit(-1);
    		}
    		_top = 0;
    		_capacity = 4;
    	}
    	void Push(DataType data)
    	{
    		_a[_top] = data;
    		++_top;
    	}
    	~Stack()
    	{
    		if (_a)
    		{
    			free(_a);
    			_a = nullptr;
    		}
    		_top = 0;
    		_capacity = 0;
    	}
    private:
    	int* _a;
    	int _top;
    	int _capacity;
    };
    
    int main()
    {
    	Stack st;
    	st.Push(1);
    	st.Push(2);
    	st.Push(3);
    	st.Push(4);
    
    	return 0;
    }
    

    5. Em relação ao destruidor gerado automaticamente pelo compilador, ele realizará alguma coisa? Como você pode ver no programa abaixo, o destruidor padrão gerado pelo compilador chama seu destruidor para um membro de tipo personalizado.

  • class Time
    {
    public:
    	~Time()
    	{
    		cout << "Time()" << endl;
    	}
    private:
    	int _hour;
    	int _minute;
    	int _seconds;
    };
    class Date
    {
    private:
        //对于自定义类型,系统会调用它的析构函数,对于内置类型,系统会自动回收空间
    	int _year = 2022;
    	int _month = 9;
    	int _day = 30;
    	Time _t;
    };
    
    int main()
    {
    	Date d1;
    	return 0;
    }
    

    6. Se não houver recurso solicitado na classe, o destruidor não precisa ser escrito e o destruidor padrão do compilador pode ser usado diretamente. Por exemplo, classe Date; quando há um aplicativo de recurso, ele deve ser escrito, caso contrário causará vazamento de recursos, como a classe Stack.

    4. Construtor de cópia
    4.1.
    Construtor de cópia conceitual: Existe apenas um único parâmetro formal. Este parâmetro formal é uma referência a um objeto desta classe (geralmente modificado com const). Ele é usado automaticamente pelo compilador ao criar um novo objeto com um objeto de tipo de classe existente.transferência.

    4.2 Características
    1. O construtor de cópia é uma forma sobrecarregada do construtor.
    2. Existe apenas um parâmetro para o construtor de cópia e deve ser uma referência a um objeto do tipo classe. Se você usar o método de passagem por valor, o compilador reportará diretamente um erro porque causará chamadas recursivas infinitas.
     

    class Date
    {
    public:
    	Date(int year = 2022, int month = 10, 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;
    };
    

     3. Se a definição não for exibida, o compilador gerará um construtor de cópia padrão. O objeto construtor de cópia padrão é copiado na ordem de bytes de acordo com o armazenamento da memória. Este tipo de cópia é chamado de cópia superficial ou cópia de valor.
    Nota: No construtor de cópia padrão gerado pelo compilador, o tipo integrado é copiado diretamente no formato de byte, enquanto o tipo personalizado é copiado chamando seu construtor de cópia.
    4. O construtor de cópia padrão gerado pelo compilador já concluiu a cópia do valor endian. Ainda preciso implementá-lo explicitamente sozinho? Claro, uma aula como a aula de data não é necessária. E as aulas seguintes?
     

    typedef int DataType;
    class Stack
    {
    public:
    	Stack(size_t _capacity = 4)
    	{
    		_a = (int*)malloc(sizeof(int) * _capacity);
    		if (_a == nullptr)
    		{
    			perror("malloc is fail\n");
    			exit(-1);
    		}
    		_top = 0;
    		_capacity = 4;
    	}
    	void Push(const DataType& date)
    	{
    		_a[_top] = date;
    		_top++;
    	}
    	~Stack()
    	{
    		if (_a != nullptr)
    		{
    			free(_a);
    			_a = nullptr;
    		}
    		_top = 0;
    		_capacity = 0;
    	}
    private:
    	int* _a;
    	int _top;
    	int _capacity;
    };
    
    int main()
    {
    	Stack st1;
    	st1.Push(1);
    	st1.Push(2);
    	st1.Push(3);
    	st1.Push(4);
    	st1.Push(5);
    
    	Stack st2(st1);
    	return 0;
    }
    

     

    Nota: Se não houver nenhum aplicativo de recurso envolvido na classe, o construtor de cópia pode ser escrito ou não; uma vez envolvido o aplicativo de recurso, o construtor de cópia deve ser escrito, caso contrário será uma cópia superficial.
    5. Cenários típicos de chamada do construtor de cópia

  • Crie um novo objeto usando um objeto existente
  • O tipo de parâmetro de função é um objeto de tipo de classe
  • O tipo de valor de retorno da função é um objeto de tipo de classe  
    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;
    }
    

    insira a descrição da imagem aqui

    Para melhorar a eficiência do programa, geralmente tente usar tipos de referência ao transferir parâmetros para objetos. Ao retornar, use referências tanto quanto possível de acordo com o cenário real.

    5. Sobrecarga de operador de atribuição
    5.1. Sobrecarga de operador
    C++ introduz sobrecarga de operador para melhorar a legibilidade do código. A sobrecarga de operador é uma função com um nome de função especial e também tem seu tipo de valor de retorno, nome de função e lista de parâmetros. Seu retorno tipo de valor é o mesmo que A lista de parâmetros é semelhante à de uma função comum

    O nome da função é: A palavra-chave operador precisa ser seguida por um símbolo de operador sobrecarregado.
    Protótipo de função: operador de tipo de valor de retorno (lista de parâmetros)
    Nota:

    Novos operadores não podem ser criados concatenando outros símbolos, como operador@.
    Operadores sobrecarregados devem ter um parâmetro de tipo de classe
    . Para operadores de tipos integrados, seu significado não pode ser alterado.
    Quando sobrecarregado como uma função de membro de classe, seus parâmetros formais parecem ser 1 a menos que o número de operandos, porque o primeiro parâmetro da função de membro está oculto this . * ::
    sizeof ?: . Observe que os cinco operadores acima não podem ser sobrecarregados . Isso geralmente aparece em exames ou entrevistas escritas.
     

    class Date
    {
    public:
    	Date(int year = 2022, int month = 10, int day = 9)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    //这里如果我们把运算符重载成全局的话,就需要成员变量是公有的。这样的话,封装性如何保证?
    //其实这个问题可以用友元解决,或者干脆重载成成员函数。
    bool operator==(const Date& d1, const Date& d2)
    {
    	return d1._year == d2._year && d1._month == d2._month
    		&& d1._day == d2._day;
    }
    

     

    5.2. Sobrecarga do operador de atribuição

    1. Formato de sobrecarga do operador de atribuição

  • Tipo de parâmetro: const Date& A passagem por referência pode melhorar a eficiência da passagem de parâmetros.
  • Tipo de valor de retorno: Data& Retornar uma referência pode melhorar a eficiência do retorno. O objetivo de ter um valor de retorno é dar suporte à atribuição contínua.
  • Verifique se você atribui um valor a si mesmo.
  • Retorne *this para compor o significado da atribuição contínua.
  • class Date
    {
    public:
    	Date(int year = 2020, int month = 10, int day = 9)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    	Date(const Date& d)
    	{
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    	}
    
    	Date& operator=(const Date& d)
    	{
    		if (this != &d)
    		{
    			_year = d._year;
    			_month = d._month;
    			_day = d._day;
    		}
    		return *this;
    	}
    
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    

    2. O operador de atribuição só pode sobrecarregar funções membros da classe e não pode ser sobrecarregado em funções globais.

  • class Date
    {
    public:
    	Date(int year = 2020, int month = 10, int day = 9)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    	Date(const Date& d)
    	{
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    //赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要两个参数
    Date& operator=(const Date& d1,const Date& d2)
    {
    	if (&d1 != &d2)
    	{
    		d1._year = d2._year;
    		d1._month = d2._month;
    		d1._day = d2._day;
    	}
    	return d1;
    }
    // error c2801: "operator = "必须是非静态成员
    

    Motivo: se o operador de atribuição não puder ser implementado explicitamente, o compilador gerará um padrão. Neste momento, se o usuário implementar uma sobrecarga de operador global fora da classe, ele entrará em conflito com o operador de atribuição padrão gerado pelo compilador na classe Portanto, a sobrecarga do operador de atribuição só pode ser uma função membro da classe.

  • 3. Quando o usuário não o implementa explicitamente, o compilador irá gerar uma sobrecarga do operador de atribuição padrão e copiá-lo byte por valor na forma de valor. Nota: As variáveis ​​de membro do tipo interno são diretas, enquanto as variáveis ​​de membro do tipo personalizado precisam chamar a sobrecarga do operador de atribuição da classe correspondente para concluir a sobrecarga.
     
    class Time
    {
    public:
    	Time()
    	{
    		_hour = 1;
    		_minute = 1;
    		_second = 1;
    	}
    
    	Time& operator=(const Time& d)
    	{
    		_hour = d._hour;
    		_minute = d._minute;
    		_second = d._second;
    
    		return *this;
    	}
    private:
    	int _hour;
    	int _minute;
    	int _second;
    };
    
    class Date
    {
    private:
    	//内置类型
    	int _year;
    	int _month;
    	int _day;
    	//自定义类型
    	Time _t;
    };
    
    int main()
    {
    	Date d1;
    	Date d2;
    	d1 = d2;
    	return 0;
    }
    

    Agora que a função sobrecarregada do operador de atribuição padrão gerada pelo compilador pode concluir a cópia do valor da ordem de bytes, você ainda precisa implementá-la sozinho? Claro, uma aula como a aula de data não é necessária. E as aulas seguintes? Tente verificar isso?

  • #include <iostream>
    
    typedef int DataType;
    
    class Stack
    {
    public:
    	Stack(size_t capacity = 10)
    	{
    		_a = (DataType*)malloc(sizeof(DataType)*capacity);
    		if (_a == nullptr)
    		{
    			perror("malloc is fail\n");
    			exit(-1);
    		}
    		_top = 0;
    		_capacity = 0;
    	}
    	void Push(const DataType& data)
    	{
    		_a[_top] = data;
    		_top++;
    	}
    
    	~Stack()
    	{
    		if (_a)
    		{
    			free(_a);
    			_a = nullptr;
    		}
    		_top = 0;
    		_capacity = 0;
    	}
    private:
    	DataType* _a;
    	size_t _top;
    	size_t _capacity;
    };
    
    int main()
    {
    	Stack st1;
    	st1.Push(1);
    	st1.Push(2);
    	st1.Push(3);
    	st1.Push(4);
    	st1.Push(5);
    
    	Stack st2;
    
    	st2 = st1;
    	return 0;
    }
    

    Nota: Se a classe não envolve gerenciamento de recursos, não importa se o operador de atribuição está implementado. Uma vez envolvido o gerenciamento de recursos, ele deve ser implementado.

  • 4. Sobrecarga de operadores de data

    #pragma once
    #include <iostream>
    class Date
    {
    public:
    	// 获取某年某月的天数
    	int GetMonthDay(int year, int month);
    
    	// 全缺省的构造函数
    	Date(int year = 1900, int month = 1, int day = 1);
    
    	// 拷贝构造函数
        // d2(d1)
    	Date(const Date& d);
    
    	// 赋值运算符重载
        // d2 = d3 -> d2.operator=(&d2, d3)
    	Date& operator=(const Date& d);
    
    	// 析构函数
    	~Date();
    
    	// 日期+=天数
    	Date& operator+=(int day);
    
    	// 日期+天数
    	Date operator+(int day);
    
    	// 日期-天数
    	Date operator-(int day);
    
    	// 日期-=天数
    	Date& operator-=(int day);
    
    	// 前置++
    	Date& operator++();
    
    	// 后置++
    	Date operator++(int);
    
    	// 后置--
    	Date operator--(int);
    
    	// 前置--
    	Date& operator--();
    
    	// >运算符重载
    	bool operator>(const Date& d);
    
    	// ==运算符重载
    	bool operator==(const Date& d);
    
    	// >=运算符重载
    	bool operator >= (const Date& d);
    
    	// <运算符重载
    	bool operator < (const Date& d);
    
    	// <=运算符重载
    	bool operator <= (const Date& d);
    
    	// !=运算符重载
    	bool operator != (const Date& d);
    
    	// 日期-日期 返回天数
    	int operator-(const Date& d);
    
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    

     Data.cpp

  • #include "Date.h"
    
    int Date::GetMonthDay(int year, int month)
    {
    	static int a[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    	if ((year == 2) && ((year % 4 == 0 && year % 100 != 0)
    		|| (year % 400 == 0)))
    	{
    		return a[month] + 1;
    	}
    	return a[month];
    }
    
    Date::Date(int year = 1900, int month = 1, int day = 1)
    {
    	_year = year;
    	_month = month;
    	_day = day;
    }
    
    Date::Date(const Date& d)
    {
    	_year = d._year;
    	_month = d._month;
    	_day = d._day;
    }
    
    Date& Date::operator=(const Date& d)
    {
    	_year = d._year;
    	_month = d._month;
    	_day = d._day;
    }
    
    Date::~Date()
    {
    	_year = 0;
    	_month = 0;
    	_day = 0;
    }
    
    Date& Date::operator+=(int day)
    {
    	if (day < 0)
    	{
    		return *this -= -day;
    	}
    	_day += day;
    	while (_day > GetMonthDay(_year, _month))
    	{
    		_day -= GetMonthDay(_year, _month);
    		++_month;
    		if (_month == 13)
    		{
    			++_year;
    			_month = 1;
    		}
    	}
    	return *this;
    }
    
    Date Date::operator+(int day)
    {
    	Date tmp(*this);
    	tmp += day;
    	return tmp;
    }
    
    Date Date::operator-(int day)
    {
    	Date tmp(*this);
    	tmp -= day;
    	return tmp;
    }
    
    Date& Date::operator-=(int day)
    {
    	if (day < 0)
    	{
    		return *this += -day;
    	}
    	_day -= day;
    	while (_day <= 0)
    	{
    		--_month;
    		if (_month == 0)
    		{
    			--_year;
    			_month = 12;
    		}
    		_day += GetMonthDay(_year, _month);
    	}
    }
    
    Date& Date::operator++()
    {
    	*this += 1;
    	return *this;
    }
    
    Date Date::operator++(int)
    {
    	Date tmp(*this);
    	*this += 1;
    	//return *this - 1;
    	return tmp;
    }
    
    Date Date::operator--(int)
    {
    	Date tmp(*this);
    	*this -= 1;
    	return tmp;
    }
    
    Date& Date::operator--()
    {
    	*this -= 1;
    	return *this;
    }
    
    bool Date::operator>(const Date& d)
    {
    	if (_year > d._year)
    		return true;
    	if (_year == d._year && _month > d._month)
    		return true;
    	if (_year == d._year && _month == d._month && _day > d._day)
    		return true;
    	return false;
    }
    
    bool Date::operator==(const Date& d)
    {
    	return _year == d._year 
    	&& _month == d._month 
    	&& _day == d._day;
    }
    
    bool Date::operator >= (const Date& d)
    {
    	return *this > d || *this == d;
    }
    
    bool Date::operator < (const Date& d)
    {
    	return !(*this >= d);
    }
    
    bool Date::operator <= (const Date& d)
    {
    	return !(*this > d);
    }
    
    bool Date::operator != (const Date& d)
    {
    	return !(*this == d);
    }
    
    int Date::operator-(const Date& d)
    {
    	Date max = *this;
    	Date min = d;
    	int flag = 1;
    	if (*this < d)
    	{
    		max = d;
    		min = *this;
    		flag = -1;
    	}
    	int day = 0;
    	while (min != max)
    	{
    		++day;
    		min += 1;
    	}
    	return day*flag;
    }
    
    

    6. função membro const

    A "função membro" modificada por const é chamada de função de membro const. A função de membro de classe modificada por const na verdade modifica o ponteiro this implícito da função de membro, indicando que nenhum membro da classe pode ser modificado na função de membro.

  • class Date
    {
    public:
    	Date(int year,int month,int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    	void Print() const
    	{
    		cout << "Print() const" << endl;
    		cout << "year " << _year << endl;
    		cout << "month " << _month << endl;
    		cout << "day " << _day << endl;
    	}
    
    	void Print()
    	{
    		cout << "Print()" << endl;
    		cout << "year " <<  _year << endl;
    		cout << "month " <<  _month << endl;
    		cout << "day " <<  _day << endl;
    	}
    
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	Date d1(2022,10,11);
    	d1.Print();
    	const Date d2(2022, 10, 12);
    	d2.Print();
    	return 0;
    }
    
    

    7. Sobrecarga de operadores de tomada de endereço e de tomada de endereço const

    Essas duas funções-membro padrão geralmente não precisam ser definidas pelo compilador. O compilador irá gerá-las por si mesmo. Geralmente, as geradas pelo compilador por padrão são suficientes para nosso uso.

  • class Date
    {
    public:
    	Date* operator&()
    	{
    		return this;
    	}
    
    	const Date* operator&() const
    	{
    		return this;
    	}
    
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    

    Continua…

Acho que você gosta

Origin blog.csdn.net/jax2730/article/details/127272200
Recomendado
Clasificación