【C++】类和对象--类的6个默认成员函数

news2025/6/10 10:33:27

目录

  • 1.类的6个默认成员函数
  • 2.构造函数
    • 2.1概念
    • 2.2特性
  • 3.析构函数
    • 3.1概念
    • 3.2特性
  • 4.拷贝构造函数
    • 4.1概念
    • 4.2特征
  • 5.赋值运算符重载
    • 5.1运算符重载
    • 5.2赋值运算符重载
    • 5.3前置++和后置++重载
    • 5.4流插入和流提取运算符重载
  • 6.const成员
  • 7.取地址重载和const取地址操作符重载

1.类的6个默认成员函数

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

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

但空类中真的是什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。

class Date{};
  1. 构造函数: 完成初始化工作
  2. 析构函数: 完成清理工作
  3. 拷贝构造函数: 使用同类对象初始化创建对象
  4. 赋值重载: 把一个对象赋值给另一个对象
  5. 取地址操作符重载: 对普通对象取地址
  6. const取地址操作符重载: 对const修饰的对象取地址

在这里插入图片描述

2.构造函数

2.1概念

对于下面的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 today1;
	today1.Init(2023,1,16);
	today1.Print();

	Date today2;
	today2.Init(2023, 1, 17);
	today2.Print();

	return 0;
}

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

我们就需要一个函数:保证对象被创造出来就被初始化了。

C++的构造函数提供了这个功能:

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

2.2特性

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

其特征如下:

  1. 函数名与类名相同。

  2. 无返回值(也不用写void)

  3. 对象实例化时编译器自动调用对应的构造函数。
    在这里插入图片描述

  4. 构造函数可以重载。(一个类可以有多个构造函数)

    class Date
    {
    public:
    	Date()
    	{
    		cout << "自定义默认构造函数" << endl;
    	}
    	//Date(int year = 1, int month= 2, int day = 3)
    	//{
    	//	cout << "自定义全缺省默认构造函数" << endl;
    	//}
    	//Date(int year, int month, int day = 1)
    	//{
    	//	cout << "自定义半缺省构造函数" << endl;
    	//}
    	Date(int year, int month, int day)
    	{
    		cout << "自定义构造函数" << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	Date today(2023, 2, 6);
    
    	return 0;
    }
    
  5. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数一个类中只能有一个。

    原因: 这两个构造函数虽然满足重载,但编译器无法调用,存在歧义。如上面代码的第一个无参构造函数和第二个注释的全缺省的构造函数,所以默认构造函数一个类只能有一个。(非默认构造函数也只能有一个,如第三个半缺省构造函数和第四个构造函数,需要传参,同时存在会有歧义)

    注意: 更具构造函数需不需要传参数,我们将其分为两种

    1. 默认构造函数: 无参构造函数、全缺省构造函数,我们没写编译器默认生成的构造函数(下一条)(这些不需要传参数的构造函数,都认为是默认构造函数)
    2. 传参构造函数: 不缺省构造函数、全缺省构造函数
    • 创建对象时,调用默认构造函数不要在对象后加括号(加括号后编译器会将其看作函数的声明,而不是创建的对象)。调用传参的构造函数在对象后加括号加参数。
  6. 如果类没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生成。(构造函数可以重载,有很多种,只要我们写一种,编译器就不会默认生成构造函数)

    注意: 如下,创建对象时不带括号,调用的是默认构造函数,带括号后跟参数,调用传参构造函数。

    如下图类中已经定义了构造函数,编译器不会在自动生成默认构造函数。
    在这里插入图片描述
    在增加默认构造函数后,正常运行
    在这里插入图片描述

关于编译器生成的默认构造函数,很多人会疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?

如下面的代码,today对象调用了编译器生成的默认构造函数,但是today对象的三个成员变量_day/_month/_year,依然是随机数,也就是说在这里编译器生成的默认构造函数并没有什么用?

在这里插入图片描述
先介绍一点,C++将类型分为以下两种:

  1. 内置类型: 语言提供的数据类型,如:int、char…
  2. 自定义类型: 我们使用struct、class、union等自己定义的类型

如果一个类中存在自定义类型的成员变量,需要使用该成员变量对应类的默认构造函数来初始化,否则无法通过。这也就是默认构造函数存在的意义。

  1. 自定义类型的成员变量对应类存在默认构造函数

    class A
    {
    public:
    	A()
    	{
    		cout << "A" << endl;
    	}
    private:
    	int a;
    	int b;
    };
    
    class Date
    {
    public:
    	Date()
    	{
    		cout << "默认构造函数" << endl;
    	}
    	Date(int year, int month, int day)
    	{
    		cout << "传参构造函数" << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    	A a1;
    };
    
    int main()
    {
    	Date today;
    
    	return 0;
    }
    

    在这里插入图片描述

  2. 自定义类型的成员变量对应类不存在默认构造函数

    class A
    {
    public:
    	A(int c)
    	{
    		cout << "A" << endl;
    	}
    private:
    	int a;
    	int b;
    };
    
    class Date
    {
    public:
    	Date()
    	{
    		cout << "默认构造函数" << endl;
    	}
    	Date(int year, int month, int day)
    	{
    		cout << "传参构造函数" << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    	A a1;
    };
    
    int main()
    {
    	Date today;
    
    	return 0;
    }
    

    在这里插入图片描述

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

class A
{
public:
	void Print()
	{
		cout << _a << " " << _b << endl;
	}
private:
	int _a = 10;
	int _b = 20;
};

class Date
{
public:
	void Print()
	{
		a1.Print();
	}

private:
	int _year = 2000;
	int _month = 1;
	int _day = 1;
	A a1;
};
int main()
{
	Date today;
	today.Print();

	return 0;
}

在这里插入图片描述

  • 此为缺省值,当构造函数没有初始化成员变量时,成员变量的值即为该缺省值,若初始化,以构造函数为主(如下面代码,初始化了一个变量,该变量就以构造函数初始化为主,其他成员变量为缺省值)
class A
{
public:
	A()
	{
		_a = 40;
	}
	void Print()
	{
		cout << _a << " " << _b << endl;
	}
private:
	int _a = 10;
	int _b = 20;
};

class Date
{
public:
	void Print()
	{
		a1.Print();
	}

private:
	int _year = 2000;
	int _month = 1;
	int _day = 1;
	A a1;
};
int main()
{
	Date today;
	today.Print();

	return 0;
}

在这里插入图片描述

3.析构函数

3.1概念

我们知道了对象创建时需要构造函数来初始化,那对象销毁时又需要什么呢?

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

我们创建一个对象,它是在对象生命周期结束后,对应函数的栈帧销毁时一并销毁,而析构函数是在销毁前函数自动调用,对该对象的资源做清理清空对象的空间或将申请的空间还给编译器。

对于清理工作,我们必须要做,否则可能会造成内存泄漏,而我们又常常忘记这一操作,于是C++增加了这么一个函数。

3.2特性

  1. 析构函数名是在类名前加上字符**~**(取反符号)

  2. 无参数也无返回值

  3. 一个类只能有一个析构函数。若未显示定义,系统会自动生成默认的析构函数。注意:析构函数不能重载

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

    我们编写如下代码,向内存申请空间,利用析构函数释放对应的空间。

    class Stack
    {
    public:
    	Stack()
    	{
    		ArrStack = (int*)malloc(sizeof(int) * 4);
            if(!ArrStack)//下图中未写
            {
                preeor("malloc fail!");
                exit(-1);
            }
    		_size = 4;
    		_top = 0;
    	}
    	~Stack()
    	{
    		if (ArrStack)
    		{
    			free(ArrStack);
    			ArrStack = nullptr;
    			_size = 0;
    			_top = 0;
    		}
    	}
    private:
    	int* ArrStack;
    	int _size;
    	int _top;
    };
    
    int main()
    {
    	Stack st;
    
    	return 0;
    }
    

    在这里插入图片描述

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

如下面的代码,当我们对同一个类创建两个变量时,构造函数的执行顺序为:s1、s2,而函数是一种栈的形式,创建变量就是压栈,s1先入栈,s2后入栈,销毁时,s2先出栈,s1后出栈,析构函数的调用顺序为:s2、s1

class Stack
{
public:
	Stack(int num)
	{
		ArrStack = (int*)malloc(sizeof(int) * num);
        if(!ArrStack)//下图中未写
        {
            preeor("malloc fail!");
            exit(-1);
        }
		_size = 4;
		_top = 0;
	}
	~Stack()
	{
		if (ArrStack)
		{
			free(ArrStack);
			ArrStack = nullptr;
			_size = 0;
			_top = 0;
		}
	}
private:
	int* ArrStack;
	int _size;
	int _top;
};

int main()
{
	Stack s1(10);
	Stack s1(40);

	return 0;
}

观察下图this->_size的变化

在这里插入图片描述

当一个类中有自定义类型的成员变量,那再销毁这个类创建的对象时,会调用该类中自定义类型的成员变量的析构函数

  1. 写析构函数

    class A
    {
    public:
    	~A()
    	{
    		cout << "A" << endl;
    	}
    private:
    	int a;
    	int b;
    };
    
    class Date
    {
    public:
    	~Date()
    	{
    		cout << "Date" << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    	A a1;
    };
    int main()
    {
    	Date today;
    
    	return 0;
    }
    

    在这里插入图片描述

  2. 不写析构函数

    class A
    {
    public:
    	~A()
    	{
    		cout << "A" << endl;
    	}
    private:
    	int a;
    	int b;
    };
    
    class Date
    {
    public:
    	
    private:
    	int _year;
    	int _month;
    	int _day;
    	A a1;
    };
    int main()
    {
    	Date today;
    
    	return 0;
    }
    

在这里插入图片描述

注意:

  • 默认生成构造函数和默认生成析构函数,对内置类型不处理,处理自定义类型。(有些编译器会,但那时编译器的个人行为,和C++的语法无关)

4.拷贝构造函数

4.1概念

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

该函数功能为将一个对象的数据赋值给另一个对象,发生拷贝时编译器就会调用该函数,如下:

class Date
{
public:
	Date(int year,int month,int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)//拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "拷贝构造函数" << endl;
	}

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

void test(Date d)//调用拷贝构造函数
{}

int main()
{
	Date today1(2023,2,7);
	Date today2(today1);//调用拷贝构造函数

	test(today1);

	return 0;
}

在这里插入图片描述

4.2特征

  1. 拷贝构造函数是构造函数的一个重载形式。

  2. 拷贝构造函数的参数只有一个必须是类类型对象的引用 ,使用传参方式编译器会直接报错 ,因为会引发无穷递归调用。

    如果不使用引用,代码如下:

    class Date
    {
    public:
    	Date(const Date d)//拷贝构造函数
    	{
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    	}
    
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    

    这样的拷贝构造函数,我们在调用它时会发生拷贝,而需要拷贝我们就要调用拷贝构造函数,这就会形参死循环,因为要用你我调用你,而想要调用你就要用你,编译器不会允许这样的事情发生。

    在这里插入图片描述

    如上图,将对象d1的数据拷贝到d2,需要调用拷贝构造函数,而调用的过程形参发生拷贝又要调用拷贝构造函数,就这样一直下去,很明显这是不行的。

    所以在这里我们要使用引用,如下:

    	Date(const Date& d)//拷贝构造函数
    	{
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    	}
    

    在第一次调用的时候,使用d给对象起别名,就不用再调用其他拷贝构造函数。

    对于这个函数建议使用const修饰,防止我们在写这个函数时不小心写错,使对象的成员变量发生改变。

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

    即上面的Date类对象,若发生浅拷贝只是将一个对象所占空间内所有成员变量的值拷贝到另一个对象的成员变量,这么做看起来似乎很合理其实不然,对于内置类型,这么做当然没有问题,但如栈这样的数据结构,是万万不能的。如下面栈的代码

    class Stack
    {
    public:
        Stack(size_t capacity = 10)
        {
            _array = (int*)malloc(int* sizeof(int));
            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:
        int *_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;
    }
    

    在这里插入图片描述

    这样程序必定会发生错误。

    如果想要让程序正确运行我们,需要我们自己编写拷贝构造函数,也就是深拷贝,让他们每个对象的的成员变量在面对这种情况时都有自己独立的空间,而不是共用一块空间。

    这也是拷贝构造函数存在的意义,编译器只能做浅拷贝的工作,若果一个对象的拷贝需要使用深拷贝,就需要程序员手动来完成这个任务,这也是C语言存在的缺陷,C++的很好的弥补了这一点。

    修改后的栈代码如下:

    class Stack
    {
    public:
        Stack(size_t capacity = 10)
        {
            _array = (int*)malloc(capacity * sizeof(int));
            if (nullptr == _array)
            {
                perror("malloc申请空间失败");
                return;
            }
            _size = 0;
            _capacity = capacity;
        }
        Stack(const Stack& st)
        {
            _array = (int*)malloc(sizeof(int) * st._capacity);
            if (_array == nullptr)
            {
                perror("malloc申请空间失败");
                return;
            }
            for (int i = 0; i < st._size; i++)
            {
                _array[i] = st._array[i];
            }
            _size = st._size;
        }
        void Push(const int& data)
        {
            // CheckCapacity();
            _array[_size] = data;
            _size++;
        }
        ~Stack()
        {
            if (_array)
            {
                free(_array);
                _array = nullptr;
                _capacity = 0;
                _size = 0;
            }
        }
    private:
        int* _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;
    }
    

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

  4. 拷贝构造函数调用频率最多的三种场景场景如下

    • 使用以存在的对象创建新对象
    • 函数参数类型为类类型对象
    • 函数返回值类型为类类型对象

    在这里插入图片描述

通过这些我们也可以看出,拷贝在编写代码中是一个平常的事情,但其消耗的资源却不少,所以在实际使用中,如果可以使用引用,尽量使用引用,减少计算机消耗,创出更优得程序。

5.赋值运算符重载

5.1运算符重载

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

在C++中类的封装性是做的很好的,如果想在类和类之间进行比较,拷贝等操作需要在类内调用函数,而对应普通的内置类型,只需要使用简单的运算符即可完成,C++规定可以将部分运算符重载来完成这个功能,增强了代码的可读性。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整形+,不能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1.因为成员函数的第一个参数为隐藏的this
  • .* :: sizeof ?: .注意以上5个运算符不能重载。在这个经常在笔试选择题中出现。

如下代码,若运算符重载函数作用域为全局,那类的成员变量必须为公有的,这样封装性就无法保证

class Date
{
public:

	Date(int year = 2000, 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;
}

bool test()
{
	Date today1(2023, 2, 7);
	Date today2;
	return today1 == today2;
}

这里我们可以使用友元解决,也可以将运算符重载函数放入类中,我们一般将其放入类中。

class Date
{
public:

	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator==(const Date& d1)
	{
		return _year == d1._year && _month == d1._month
			&& _day == d1._day;
	}

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

bool test()
{
	Date today1(2023, 2, 7);
	Date today2;
    //today1.operator==(today2)
	return today1 == today2;
}

在调用成员函数时,编译器会自动将调用的对象作为this指针传递,我们只要写入一个参数即可。

注意:

  1. 在使用时需要注意运算符优先级,如下面使用运算符重载需使用括号

    	cout << (today1 == today2) << endl;
    
  2. 运算符重载中,如果有多个参数,第一参数为左操作数,第二个参数为右操作数,以此类推。如上面的代码,第一个参数为today1,为左操作数,由该对象调用运算符重载函数,第二参数today2即为参数。

5.2赋值运算符重载

赋值运算符如果不自己实现,编译器会默认生成,只有赋值取地址是这样的,其它的自定义类型需要使用,就要我们自己写。(取地址在下面)

赋值运算符重载格式:

  • 参数类型: const Typedef&,传递引用可以提高传参效率
  • 返回值类型: Typedef&,返回引用可以提高返回得效率,有返回值目的是为了支持连续赋值。
  • 检测是否自己给自己赋值
  • **返回*this:**要符合连续赋值得含义
class Date
{
public:

	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator=(const Date& d)
	{
		if (this != &d)//检测是否自己给自己赋值
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;//返回*this
	}

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

赋值运算符只能重载成类得成员函数不能重载成全局函数

class Date
{
public:

	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = 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;//返回*this
}
  1. 其中为了访问类中得成员变量,将其公有化,失去了封装性。
  2. 这样得函数注定编译失败,其中赋值运算符没有实现,则编译器会在类中自己实现一个默认的赋值运算符,而在调用得时候,我们自己实现了一个,编译器又实现了一个这就产生冲突。

所以,赋值运算符重载只能是类的成员函数。

上面已经讲了,如果我们没有自己写,编译器会自己实现一个默认的赋值运算符重载,在运行是是以值得方式逐字节拷贝。 上面得拷贝构造函数中,编译器自己默认创建的拷贝构造函数也是相同的,只能进行浅拷贝,只能拷贝值无法为其分配内存,但赋值运算符重载还是有一点不同的,它初始化需要分配空间的时候,会先为创建的对象分配空间,之后在使用赋值运算符,将分配好的空间舍弃,存入其他对象的空间地址。

如下代码:

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

在这里插入图片描述

我们要注意,如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

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

对于前置++,我们按照正常的运算符重载模式写即可,但记得返回类型需要使用类类型&,将修改后的对象返回。

class Date
{
public:

	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator++()
	{
		_year += 1;
		return *this;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date today(2023, 2, 7);
	Date d;
	d = ++today; //d:2024.2,7  today:2024,2,7

	return 0;
}

至于后置++,为了可以让两个函数实现重载,规定增加一个int类型的参数,作为区分。

注意:前置++是先++后使用,所以可以直接返回其修改后的对象,对于后置++是先使用后++,所以返回的应该是未修改的对象,我们可以在修改原对象前对其进行拷贝,然后修改原对象,返回时直接返回之前拷贝的对象,这样原对象即改变了,使用的也是未改变的对象,符合后置++

class Date
{
public:

	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date operator++()
	{
		Date& temp(*this);
		_year += 1;
		return temp;
	}

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

int main()
{
	Date today(2023, 2, 7);
	Date d;
	d = today++; //d:2023,2,7  today:2024,2,7

	return 0;
}

5.4流插入和流提取运算符重载

在C++中,我们输出和输入一个数据通常是通过cout、cin,它们两其实就是一个类对象,重载了<<、>>两个运算符,所以输入、输出其实就是调用两个运算符重载函数。

在这里插入图片描述
如上图,它们的类型分别为ostream、istream,存放在iostream这个头文件中,而C++库内定义的东西都存放在std这个命名空间内,所以我们每次开头需要写这两行代码。

对于内置类型,如下:

int a = 10;
double b = 10.0;
cout << a;
cout << b;

通过函数重载调用不同的运算符函数,将其打印。

下面我们一起来看一下这两个运算符是如何重载的。

流提取

在类中定义:

class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void operator<<(ostream& out)
	{
        //下面就是输出内置类型的值,流提取调用头文件<iostream>内的
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	}

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

int main()
{
	Date today;
    //第一个参数为左操作数,第二个参数为右操作数,由创建的对象调用类内的重载函数
    //today.operator<<(cout)
	today << cout;

	return 0;
}

在这里插入图片描述

我们看到,函数的使用形式是today << cout;,类对象抢占第一个参数,一定在左边,cout在右边,这么写肯定不符合我们平常的习惯,如果要将cout放在第一个位置,我们需要将函数在全局定义。

class Date
{
public:
	friend ostream& operator<<(ostream& out, const Date& d);
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

//不对对象的成员变量做修改,最好使用const修饰,防止写错,发生错误
ostream& operator<<(ostream& out,const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;
}

int main()
{
	Date today;
	cout << today;

	return 0;
}

在这里插入图片描述

如上面的代码,我们函数变为全局后,很好的解决了位置的问题,但我们又无法访问类中的成员变量,这里有三种方法,我们使用第一种

  1. 使该函数变为类的友元函数,在类中public作用域下,使用friend修饰函数的声明,即可在该函数内使用对应类的对象调用成员变量。
  2. 增加接口,在类中创建输出函数,调用对应函数即可得到对应的成员变量值,对象在类外无法访问成员变量,但可以访问对外开发的函数。(java喜欢这么做)
  3. 删除private作用域,这样成员变量就可以访问。(不建议这么做,破坏封装性)

为了防止出现下面的情况,以此要输出多个对象的值,我们需要使重载的函数返回cout,使函数可以正常运行。

cout << d1 << d2 << d3 << endl;
//cout << d1  //调用重载函数,调用后返回cout继续执行
//cout << d2  //同时,运行后返回cout
//..
//cout << endl; //与重载的类型不匹配,调用头文件内的函数

流插入

class Date
{
public:
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

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

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

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

//需要改变对象的成员变量,不能使用const修饰
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

int main()
{
	Date today;

	cin >> today;
	cout << today;

	return 0;
}

在这里插入图片描述

如上面的代码与流提取相似。

6.const成员

如下面的代码,是否可以正常运行呢?

class Date
{
public:
	Date(int year=2000,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	const Date d;
	d.Print();

	return 0;
}

它不能正常运行,因为对象d使用const修饰了,它的值是无法改的(该对象的成员变量无法修改)。在调用成员函数时,编译器默认传过去的值为Date* const this,this指针表示对象本身,意味着在此函数内成员变量可以改变,这产生了冲突。更简单的说,这就是将原本只能读的对象变成可读可写,无视其权限。

想要解决这个问题,只要使用const修饰*this使其无法改变即可,而this又是编译器默认的,它是被隐藏着的不好修改,C++给出了如下方法,在成员函数的括号后直接加const即表示修饰*this,如下

	void Print() const
	{
		cout << "Print" << endl;
	}

如果我们使用为被修饰的const对象调用被const修饰的成员函数,这时可以的,原本对象就可以通过调用成员函数修改和读取,现在只是传过去只能使成员函数读取这没有问题。

class Date
{
public:
	Date(int year=2000,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const
	{
		cout << "Print" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d;
	d.Print();

	return 0;
}

同理:在类中成员函数是可以相互调用的,但被const修饰的成员函数无法调用没有被修饰的,因为被修饰的成员函数所函数*this指针是无法改变的,而没有被修饰的是可以改变的,const失去了作用,这种写法是错误的。而没有被修饰的成员函数是可以调用被修饰的,这属于即可读又可写的情况向只可读的情况发展,没有改变语法。

注意:

  1. 类内部不改变成员变量的成员函数,最好加上const,防止数据被修改

  2. 一般会在下面的场景用到const成员

    class Date
    {
    public:
    	Date(int year=2000,int month = 1,int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print() const
    	{
    		cout << _year << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    void test(const Date& d)
    {
    	d.Print();
    }
    
    int main()
    {
    	Date td;
    	test(td);
    
    	return 0;
    }
    

    我们在创建对象之初一般不为其修饰cosnt,但我们会经常将对象作为实参传递给其他函数,如果形参被const修饰,那在这个函数内它只能被读,无法修改意味着调用的成员函数也必须被const修饰。

  3. const这种写法只针对成员函数

  4. 若定义和声明分离,需要修饰const时,定义和声明都要修饰

  5. 成员函数被const修饰和不被修饰构成const重载

    	void Print() const
    	{
    		cout << _year << endl;
    	}
    	void Print()
    	{
    		cout << _year << endl;
    	}
    

    一个形参为Date* const this,一个为const Date* const this,形参不同满足重载

  6. 若是成员函数被const修饰注意它的返回值类型,若返回的是成员变量,也需要修饰const,否则权限发生变化,编译会出错

7.取地址重载和const取地址操作符重载

取地址重载和const取地址操作符重载是最后两个编译器默认生成的成员函数,我们一般不会去写它,而是直接去使用编译器默认生成的。

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

int main()
{
	Date d1;
	const Date d2;
	cout << &d1 << endl;
	cout << &d2 << endl;

	return 0;
}

在这里插入图片描述

我们如果想要写出来也可以,如下:

class Date
{
public:
	Date(int year=2000,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date* operator&()//取地址重载
	{
		return this;
	}
	const Date* operator&() const //const取地址操作符重载
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	const Date d2;
	cout << &d1 << endl;
	cout << &d2 << endl;

	return 0;
}

在这里插入图片描述

两个使用的场景不同,取地址重载用在取一般的对象的地址,const取地址操作符重载用在取被const修饰的对象的地址。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/334053.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“搜索大战”正式打响,微软发布ChatGPT版搜索引擎和浏览器

微软公司宣布推出由ChatGPT支持的最新版本Bing&#xff08;必应&#xff09;搜索引擎和Edge浏览器&#xff0c;今天上线&#xff0c;免费使用&#xff01; 自去年开始&#xff0c;Stable Diffusion、ChatGPT 等 AI 工具的横空出世&#xff0c;貌似在告诉人们“AI 正在准备重塑整…

常见的编程语言有哪些?

一、编程语言定义 编程语言的定义是指主要用于人和计算机之间通信的语言&#xff0c;它既能够让程序员能够准确的定义计算机所需数据&#xff0c;也能让计算机精准的识别人的意图。 二、编程语言排行榜以上排名信息来源于Tiobe公司公布的编程语言排行榜&#xff0c;近两年由于大…

【学习笔记】Nginx学习

Nginx是高性能的HTTP和反向代理的web服务器&#xff0c;占用内存小&#xff0c;处理高并发连接强&#xff0c;处理静态文件好耗费内存少但是不支持Java语言&#xff0c;Java程序只能通过与Tomcat配合完成正向代理客户端无法直接访问目标服务器&#xff0c;而是通过向代理服务器…

微信小程序 Springboot英语在线学习助手系统 uniapp

四六级助手系统用户端是基于微信小程序端&#xff0c;管理员端是基于web端&#xff0c;本系统是基于java编程语言&#xff0c;mysql数据库&#xff0c;idea开发工具&#xff0c; 系统分为用户和管理员两个角色&#xff0c;其中用户可以注册登陆小程序&#xff0c;查看英语四六级…

Java后端项目IDEA配置代码规范检查,使用checkStyle实现

最近的Java后端项目想实现代码的规范检查&#xff0c;调研了一圈&#xff0c;终于找到了简单的方式实现&#xff1a;以下是常见的几种方案&#xff1a; 1、在客户端做 git hook&#xff0c;主要是用 pre-commit 这个钩子。前端项目中常见的 husky 就是基于此实现的。但缺点也很…

Revit过滤器怎么用?过滤可见性操作方法

一、Revit中关于项目传递“可见性中设置的过滤器规则”的方法 设计院结构专业给机电专业提资&#xff0c;为了对于净高有一个直观快捷的表达&#xff0c;结构专业工程师就可以利用Revit可见性(快捷键&#xff1a;VV)中的过滤器来给本专业的梁、板相对标高用颜色进行区分&#x…

python爬虫工程师 | 都会遇到的反爬手段,详细展示低难度反爬

在爬虫实战过程中&#xff0c;常见的反爬手段如下所示。 IP 地址限制&#xff1a;网站可以检测爬虫的 IP 地址&#xff0c;并限制爬虫访问。User-Agent 限制&#xff1a;网站可以通过检测请求头中的 User-Agent 来识别爬虫。Referrer 限制&#xff1a;网站可以通过检测请求头中…

TCO-PNB ester,1438415-89-4 ,反式环辛烯对硝基苯酯,可用于标记蛋白质

TCO-PNB ester&#xff0c;TCO-PNB&#xff0c;反式环辛烯-对硝基苯酯 &#xff0c;反式环辛烯对硝基苯酯&#xff0c;TCO-PNB酯产品结构式&#xff1a;产品规格&#xff1a;1.CAS号&#xff1a;1438415-89-4 2.分子式&#xff1a;C15H17NO53.分子量&#xff1a;291.34.包装规格…

[python入门㊶] - python写入文件

目录 ❤ 写入空文件&#xff08;覆盖&#xff09; ❤ 写入多行 ❤ 在原有文件上添加内容 保存数据的最简单的方式之一是将其写入到文件中❤ 写入空文件&#xff08;覆盖&#xff09; # codingUTF-8 filename test_text.txt with open(filename, w) as file_object:file_o…

怎么做室内导航?室内导航图用什么做的?

目前大多数的图资系统提供的室内地图多为静态信息&#xff0c;并没有随时间空间改变而更新的能力&#xff0c;在现有技术中缺乏一种展示室内真实场景的室内地图生成方法。由于室内的环境空间信息会因为时空迁变而有所不同&#xff0c;因此&#xff0c;如何以更快速且低成本的方…

Python深度学习实战PyQt5布局管理项目示例详解

本文具体介绍基本的水平布局、垂直布局、栅格布局、表格布局和进阶的嵌套布局和容器布局&#xff0c;最后通过案例带小白创建一个有型的图形布局窗口布局管理就是管理图形窗口中各个部件的位置和排列。图形窗口中的大量部件也需要通过布局管理&#xff0c;对部件进行整理分组、…

Qt 5 架构和特点

Qt 5 模块构架&#xff1a; 模块&#xff1a;功能&#xff1a;Qt CoreQt 5 的核心类库&#xff0c;每个模块都建立在Core上Qt GUI图形用户界面开发的最基础的类库Qt Widgets提供c用户界面部件&#xff08;是对Qt GUI的拓展&#xff09;Qt SQL对数据库进行操作Qt Multimedia、…

windows 上编译 cpu 版本的 ncnn

windows 上编译 cpu 版本的 ncnn 从 发布页面 下载最新的完整的 ncnn 代码包&#xff0c;即 -full-source 后缀的文件。以及 protobuf 的代码包&#xff08;用于生成 caffe2ncnn 和 onnx2ncnn 工具&#xff09;。 我下载的 20221128 版本的 ncnn 和 3.11.2 版本的 protobuf。…

C语言经典编程题100例(1-20)

1、练习2-1 Programming in C is fun!本题要求编写程序&#xff0c;输出一个短句“Programming in C is fun!”。输入格式:本题目没有输入。输出格式:在一行中输出短句“Programming in C is fun!”。代码&#xff1a;#include<stdio.h> int main() {printf("Progra…

https之数字证书分析

写在前面 当我们要给网站配置https时&#xff0c;都需要申请 一个数字证书&#xff0c;然后将数字证书配置在网站上&#xff0c;如下可能配置: <Connector port"446" protocol"org.apache.coyote.http11.Http11Protocol" SSLEnabled"true"s…

TCP的三次握手 四次挥手 和相关问题

TCP 三次握手 四次挥手 tcp在传输层 tcp&#xff1a; tcp报文&#xff1a; 三次握手&#xff1a; tcp 其中也涉及到了状态的切换 利用了这种状态保证了建立连接和断开连接的逻辑 两次握手不可&#xff1f; 第一个解释: 这个问题的本质是, 信道不可靠, 但是通信双发需要就某个…

var const let

菜鸟学前端 本文&#xff1a;https://www.jianshu.com/p/b7116525273b 文章目录varlet和const写不动了参考说实话&#xff0c;在看到这个之前&#xff0c;我只知道 var&#xff0c;以前也只用过这玩意。 后面那俩都不知道是干啥用的。 感谢同桌的提示。 记&#xff01; var v…

LR2023磨皮滤镜插件Portraiture4最新版

Portraiture4是一款智能磨皮的滤镜插件&#xff0c;该插件能够给Photoshop和Lightroom添加智能磨皮美化功能&#xff0c;可以帮助用户快速对图片中的人物的皮肤、头发、眉毛等部位进行美化&#xff0c;省去了手动调整的麻烦&#xff0c;大大提高P图的效率。Portraiture这是一款…

【线下沙龙】如何用项目管理的思维经营自己的生活

为什么自己参加那么多的学习&#xff0c;也没有过好自己的人生&#xff1f;这个问题出在哪里&#xff1f; 你勾画过自己未来的生活吗&#xff1f; 你没有渴望过的东西会出现在你的生活里吗&#xff1f; 如何实现自己想要的生活&#xff1f; 有一个全球性的调查问卷&#xff1a;…

IDEA运行IAM3.0的管理后台项目(WAR包运行)

1、查看打包方式 远程仓库拉取最新代码 首先查看项目打包方式&#xff0c;为war说明是在容器中运行的。 2、修改数据库配置 将数据库配置修改为本机配置&#xff0c;一般有一个properties配置文件&#xff0c;避免启动之后发现数据库不对又得重启。 3、 系统环境配置 Ct…