当前位置: 首页 > article >正文

C++初阶教程——类与对象(中篇)

一、默认成员函数

        在C++中,存在六个默认成员函数,如果这些函数没有被显式定义,那么编译器会自动生成这些函数。它们分别是默认构造函数、复制构造函数、复制赋值运算符、移动构造函数、移动赋值运算符和析构函数。

二、构造函数

2.1构造函数的概念

        对于下面的类:

#include<iostream>
using namespace std;

class Date
{
private:
    int _day;
    int _month;
    int _year;
public:
    void Date_Init(int day, int month, int year)
    {
        this->_day = day;
        this->_month = month;
        this->_year = year;
    }
    void print()
    {
        cout << this->_year << "/" << this->_month << "/" << this->_day;
    }
};

int main()
{
    Date d1;
    d1.Date_Init(30, 10, 2024);
    d1.print();
    return 0;
}

        对于Date类,可以定义上面这种初始化函数。但是如果每次创建对象的时候都像这样去调用该方法设置信息,有些繁琐。那么能否在对象创建时,就将信息设置进去呢?

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

2.2构造函数的特性

        构造函数是特殊的成员函数,需要注意的是,构造函数虽然叫构造,但是构造函数的主要任务不是开辟空间创建对象,二是初始化对象。其特性为:

  1. 函数名与类名相同。
  2. 没有返回值。
  3. 对象实例化时,由编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
    #include<iostream>
    using namespace std;
    
    class Date
    {
    private:
        int _day;
        int _month;
        int _year;
    public:
         Date(int day, int month, int year)//带参构造函数
        {
            this->_day = day;
            this->_month = month;
            this->_year = year;
        }
         Date(){}//无参构造函数
        void print()
        {
            cout << this->_year << "/" << this->_month << "/" << this->_day;
        }
    };
    
    int main()
    {
        Date d1(30, 10, 2024);
        Date d2;//调用无参构造函数时,不要加(),否则变成函数声明了。
        d1.print();
        return 0;
    }
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成无参的默认构造函数,一旦显示定义任何一个构造函数,编译器就不再生成
  6. C++把类型分为内置类型和自定义类型。编译器生成的默认构造函数对内置类型的成员变量不做处理,对自定义类型的成员变量调用自定义类型的默认构造函数
    ​
    #include<iostream>
    using namespace std;
    
    class Time
    {
    private:
        int _hour;
        int _minute;
    public:
        Time()
        {
            cout << "Time构造函数已调用" << endl;
            _hour = 1;
            _minute = 1;
        }
    };
    class Date
    {
    private:
        int _day;
        int _month;
        int _year;
        Time _t;//自定义类型
    public:
         Date(int day, int month, int year)
        {
            this->_day = day;
            this->_month = month;
            this->_year = year;
        }
         Date(){}
        void print()
        {
            cout << this->_year << "/" << this->_month << "/" << this->_day;
        }
    };
    
    int main()
    {
        Date d2;
        return 0;
    }
    
    ​

  7. 无参的构造函数和全缺省的参数都成为默认构造函数,并且默认构造函数只能存在一个

三、析构函数

3.1析构函数的概念

        析构函数完成的不是对对象本身的销毁,对象的销毁由编译器完成。析构函数在对象销毁时被自动调用,完成对对象中资源清理。

3.2析构函数的特性

  1. 析构函数名是在类名前加~。
  2. 无参数且无返回类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。析构函数不能重载
  4. 对象生命周期结束时,C++编译器自动调用析构函数。
  5. 编译器自动生成的析构函数,对于内置类型的成员,销毁时不需要清理资源,最后系统直接将其内存回收即可,对于自定义类型的成员,会调用这个成员的析构函数。
    #include<iostream>
    using namespace std;
    
    class Time
    {
    private:
        int _hour;
        int _minute;
    public:
        ~Time()
        {
            cout << "Time析构函数已调用" << endl;
            _hour = 1;
            _minute = 1;
        }
    };
    class Date
    {
    private:
        int _day;
        int _month;
        int _year;
        Time _t;
    public:
         Date(int day, int month, int year)
        {
            this->_day = day;
            this->_month = month;
            this->_year = year;
        }
         Date(){}
        void print()
        {
            cout << this->_year << "/" << this->_month << "/" << this->_day;
        }
    };
    
    int main()
    {
        Date d2;
        return 0;
    }

  6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类,有资源申请时,一定要写,否则会造成资源泄漏。

四、拷贝构造函数

4.1拷贝构造函数的概念

        拷贝构造函数是一个特殊的构造函数,它用于创建一个对象作为另一个同类型对象的副本。当你需要通过值传递对象、返回对象、或者以对象为参数调用函数时,编译器可能会自动调用拷贝构造函数。

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

4.2拷贝构造函数的特性

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

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值的方式会引发无穷递归调用。
    #include<iostream)
    using namespace std;
    
    class Date
    {
    private:
        int _day;
        int _month;
        int _year;
    public:
        Date(int year = 1900, int month = 1, int day = 1)
        {
            _year = year;
            _month = month;
            _day =  day;
        }
        Date(Date d)
        {
            _year = d.year;
            _month = d.month;
            _year = d.year;
        }
    };
    int main()
    {    
        Date d1;
        Date d2(d1);
        return 0;
    }

            这么写是错误的,d2拷贝d1,需要用到拷贝构造函数,在上面的代码中,拷贝构造函数的参数d不是引用类型,而是按值传递。按值传递意味着在调用拷贝构造函数时,会创建d的副本,这又会调用拷贝构造函数,从而导致无限递归,最终导致程序栈溢出并崩溃。

    #include<iostream)
    using namespace std;
    
    class Date
    {
    private:
        int _day;
        int _month;
        int _year;
    public:
        Date(int year = 1900, int month = 1, int day = 1)
        {
            _year = year;
            _month = month;
            _day =  day;
        }
        Date(const Date& d)
        {
            _year = d.year;
            _month = d.month;
            _year = d.year;
        }
    };
    int main()
    {    
        Date d1;
        Date d2(d1);
        return 0;
    }

    拷贝构造函数中,只能使用引用做传递参数。

  3. 若为显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
    #include<iostream>
    using namespace std;
    
    class Time
    {
    private:
        int _hour;
        int _minute;
    public:
        Time()
        {
            _hour = 1;
            _minute = 1;
        }
        Time(const Time& t)
        {
            _hour = t._hour;
            _minute = t._minute;
            cout << "Time::Time(const Time&)" << endl;
        }
    };
    class Date
    {
    private:
        int _day;
        int _month;
        int _year;
        Time _t;
    public:
        Date(int day, int month, int year)
        {
            this->_day = day;
            this->_month = month;
            this->_year = year;
        }
        Date() {}
        void print()
        {
            cout << this->_year << "/" << this->_month << "/" << this->_day;
        }
    };
    
    int main()
    {
        Date d1;
        Date d2(d1);
        return 0;
    }

           上面的代码中,Date类没有显式定义拷贝构造函数,编译器为Date类生成一个默认的拷贝构造函数。在生成的拷贝构造函数中,内置类型是按照字节方式是直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

  4. 如果类中没有进行资源申请,拷贝构造函数可以不显式定义,但是一旦涉及到资源申请时,则拷贝构造函数是一定要显式定义的,否则就是浅拷贝。下面的代码中,a1对象在构造函数中,默认是申请了10个元素的空间。a2对象使用a1拷贝构造,而Array类没有显示定义拷贝构造函数,而编译器会给Array类自动生成一份默认的拷贝构造函数,默认拷贝构造函数是按值拷贝的,将a1中的内容原封不动的拷贝到a2中,因此a1和a2指向了同一块内存空间。当程序退出时,a2和a1要销毁,等于同一块地址空间被释放了两次。
    #include<iostream>
    using namespace std;
    
    class Array
    {
    private:
        int* _array;
        size_t _size;
        size_t _capacity;
    public:
        Array(size_t capacity = 10)
        {
            _array = (int*)malloc(capacity * sizeof(int));
            if (_array == nullptr)
            {
                perror("malloc申请空间失败");
                    return;
            }
            _size = 0;
            _capacity = capacity;
        }
        void push(const int& data)
        {
            _array[_size] = data;
            _size++;
        }
        ~Array()
        {
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
    };
    
    int main()
    {
        Array a1;
        a1.push(1);
        a1.push(2);
        a1.push(3);
        a1.push(4);
        a1.push(5);
        a1.push(6);
        Array a2(a1);//浅拷贝
        return 0;
    }

  5. 拷贝构造函数调用的典型场景:使用已存在的对象创建新对象,函数参数类型返回为类类型对象,函数返回值类型为类类型对象。
#include<iostream>
using namespace std;

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

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

五、赋值运算符重载

5.1运算符重载

        C++为了增强函数的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名以及参数列表。

ReturnType operatorOperators(paralist)
#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		 _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;
}

         如果这么写的话,运算符重载成全局函数需要成员变量是公有的,如何保证封装性呢?可以写成友元函数或者重载成成员函数。

#include<iostream>
using namespace std;

class Date
{
public:
	bool operator==(const Date& d1)
	{
		return _year == d1._year && _month == d1._month && _day == d1._day;
	}
	Date(int year = 1900, int month = 1, int day = 1)
	{
		 _year = year;
		 _month = month;
		 _day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

        运算符重载需要注意:

  1. 不能通过来凝结其他符号来创建新的操作符。
  2. 重载操作符必须有一个类类型参数。
  3. 用于内置类型的运算符,其含义不能改变。
  4. 作为类成员函数重载时,其形参看起来比操作数少1,因为成员函数的第一个参数为隐藏的this。
  5. ".*","::","sizeof","?:","." 这五个运算符不能重载。

5.2赋值运算符重载

  1. 赋值运算符重载个数:
    1. 参数类型:const T&,传递引用可以提高传参效率。
    2. 返回值类型:T&返回引用可以提高返回的效率,有返回值的目的是为了支持连续赋值。
    3. 检测是否有自己给自己赋值。
    4. 返回*this:要符合连续赋值的含义。
      class Date
      {
      public:
      	Date(int year = 1900, int month = 1, int day = 1)
      	{
      		_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. 赋值运算符只能重载成类的成员函数不能重载成全局函数。
    class Date
    {
    public:
    	Date(int year = 1900, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	int _year;
    	int _month;
    	int _day;
    };
    // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
    Date& operator=(Date& left, const Date& right)
    {
    	if (&left != &right)
    	{
    		left._year = right._year;
    		left._month = right._month;
    		left._day = right._day;
    	}
    	return left;
    }
    // 编译失败:
    // error C2801: “operator =”必须是非静态成员
    

           赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

    1. 用户没有显示实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
      内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
      重载完成赋值。
      class Time
      {
      public:
      	Time()
      	{
      		_hour = 1;
      		_minute = 1;
      		_second = 1;
      	}
      	Time& operator=(const Time& t)
      	{
      		if (this != &t)
      		{
      			_hour = t._hour;
      			_minute = t._minute;
      			_second = t._second;
      		}
      		return *this;
      	}
      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;
      	Date d2;
      	d1 = d2;
      	return 0;
      }

      注意:如果类中未涉及到资源管理,那么赋值运算符是否实现都可以;一旦涉及到资源管理则必须要显式实现。如果没有显示的实现赋值运算符重载,编译器会以浅拷贝的方式实现一份默认的赋值运算符重载。这就会引起一个老生常谈的问题,拷贝过程中会导致两个对象共享同一片地址空间,这会引起两个问题:

      1. 其中一个对象原来的空间丢失了,导致内存泄漏。
      2. 共享同一块内存空间,最后销毁时会把同一块内存空间释放两次而引起程序崩溃。

      5.3前置++和后置++的重载

      class Date
      {
      public:
      	Date(int year = 1900, int month = 1, int day = 1)
      	{
      		_year = year;
      		_month = month;
      		_day = day;
      	}
      	// 前置++:返回+1之后的结果
      	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
      	Date& operator++()
      	{
      		_day += 1;
      		return *this;
      	}
      	// 后置++:
      	// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
      	// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器会自动传递
          // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
      	// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
      	Date operator++(int)
      	{
      		Date temp(*this);
      		_day += 1;
      		return temp;
      	}
      private:
      	int _year;
      	int _month;
      	int _day;
      };
      int main()
      {
      	Date d;
      	Date d1(2022, 1, 13);
      	d = d1++;
      	d = ++d1; 
      	return 0;
      }

六、const成员 

        将const修饰的成员函数称之为const成员函数,cosnt修饰类成员函数实际修饰该成员函数隐含的this指针,表明该成员函数中不能对类的任何成员进行修改。

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
void Test()
{
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
}

七、取地址及const取地址操作符重载 

        这两个运算符一般不需要重载,使用编译器生成的默认取地址重载即可,只有特殊情况才需要重载。

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


http://www.kler.cn/a/374350.html

相关文章:

  • 掌握分布式系统的38个核心概念
  • Cloud Compare学习笔记
  • 【python】导包快速总结
  • Android启动流程_SystemServer阶段
  • vue添加省市区
  • Java集合常见面试题总结(5)
  • 2024年NSSCTF秋季招新赛-WEB
  • 算法笔记:Day-06(矩阵的顺时针遍历,特定顺序遍历,二维数组的变换)
  • 身份证人像照片验真 API 对接说明
  • Unity发布微信小程序-实战问题汇总
  • 数智驱动,纷享销客助力万东医疗实现精细化管理
  • ZYNQ AXI_Timer 中断
  • 从0学习React(9)
  • 100种算法【Python版】第32篇——Lucas-Lehmer测试
  • 【gRPC】什么是RPC——介绍一下RPC
  • HBM MM CDM HMM ESD TVS 浪涌
  • 【代码随想录Day54】图论Part06
  • 鸿蒙OS:中国智造的基石
  • w012基于springboot的社区团购系统设计
  • hadoop_yarn-site.xml
  • Chrome和夸克谁更护眼
  • OpenCV视觉分析之目标跟踪(2)卡尔曼滤波器KalmanFilter的使用
  • 【React】createContext 和 useContext
  • IDEA:ctrl+/ 快捷键生成的注释,设置“//”开始位置
  • 【机器学习】二分类神经网络
  • 摄像机实时接入分析平台LiteAIServer视频智能分析软件视频诊断中的抖动检测功能