C嘎嘎之类与对象(中)

 类的默认成员函数

默认成员函数就是当用户没有直接在类里面实现,而编译器会自动生成的成员函数叫默认成员函数。以下图片中的6个成员函数中最重要的是前四个。


构造函数

构造函数是特殊的成员函数,虽然他叫构造函数,但实际上并不是开空间创建对象,而是对对象进行初始化。

构造函数的特点:

  1. 函数名与类名相同。
  2. 无返回值,前面不需要写任何返回值类型 包括void。
  3. 对象实例化时系统会自动调用。
  4. 构造函数可以重载;以上四点由下代码实现可见:
    #include<iostream>
    using namespace std;
    
    class Person
    {
    public:
    	//构造函数对成员变量进行初始化
    	Person(int age, int cardcode, int bornyear)
    	{
    		_Age = age;
    		_CardCode = cardcode;
    		_BornYear = bornyear;
    		cout << "Person有参构造函数的调用" << endl;
    	}
    	//函数重载
    	Person()
    	{
    		cout << "Person无参构造函数的调用" << endl;
    	}
    private:
    	int _Age;
    	int _CardCode;
    	int _BornYear;
    };
    
    int main()
    {
    	Person p1(19, 123, 2005);
    	cout << "下面是函数重载的实现:" << endl;
    	Person p2;
    	return 0;
    }

    👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

    运行结果为:

  5. 如果类中没有显式定义构造也就是我们人为直接实现构造函数,那么编译器就会自动实现一个无参构造函数,当用户实现显式定义构造函数的时候编译器就不会实现。

  6. 默认构造函数分为:无参构造函数、全缺省构造函数、还有编译器自动实现的构造函数。而且这三个函数有且只有一个存在不能同时存在;无参构造函数和全缺省构造函数虽然构成函数重载,但调用时会存在歧义;如下代码实现:

    #include<iostream>
    using namespace std;
    
    class Person
    {
    public:
    	Person(int age = 18)
    	{
    		cout << "全缺省函数的调用" << endl;
    	}
    
    	Person()
    	{
    		cout << "无参构造函数的调用" << endl;
    	}
    private:
    
    };
    
    int main()
    {
    	Person p1;
        return 0;
    }

    👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

    错误结果为:

  7. 编译器默认生成的构造函数,对于内置类型成员(也就是int / char / double / short / 指针)的出初始化没有要求,看编译器;但自定义类型(也就是我们能够自己定义的类型例如class/ struct之类的)就要调用这个成员变量自己的默认构造函数来初始化。如果这个成员变量没有默认的构造函数就会报错。

    #include<iostream>
    using namespace std;
    
    class Stack
    {
    public:
    	Stack(int n = 4)
    	{
    		_a = (int*)malloc(sizeof(int) * n);
    		if (_a == nullptr)
    		{
    			perror("malloc fail!");
    			return;
    		}
    		_capacity = n;
    		_top = 0;
    	}
    
    private:
    	int* _a;
    	size_t _capacity;
    	size_t _top;
    };
    
    
    class MyQueue
    {
    public:
    //编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化
    private:
    	Stack pushst;
    	Stack popst;
    };
    int main()
    {
    	MyQueue mq;
    	return 0;
    }

    对这串代码进行调试我们会发现当我们在MyQueue实例化对象mq的时候就会进入到Stack pushst这个对象里去调用他们原本的构造函数。


拷贝构造函数

如果构造函数的第一个参数是自身类类型的引用,则此构造函数也叫拷贝构造函数,拷贝构造函数也是一个特殊的构造函数。

拷贝构造函数的特点:

  1. 拷贝构造是构造函数的一个重载。
  2. 拷贝构造的第一个参数必须是类类型的引用,使用传值方式程序会崩溃(因为会进入死循环调用)。拷贝构造可以有多个参数,但第一个参数必须是类类型对象的引用,而且后面的参数必须有缺省值。
  3. C++规定自定义类型的拷贝必须调用拷贝构造函数来实现,所以这里的自定义传值传参和船只返回都会调用拷贝构造函数。(以上三个代码实现):
    #include<iostream>
    using namespace std;
    
    class Person
    {
    public:
    	Person(int age = 1, int cardcode = 1, int bornyear = 1)
    	{
    		_age = age;
    		_cardcode = cardcode;
    		_bornyear = bornyear;
    	}
    
    	Person(const Person& p)
    	{
    		_age = p._age;
    		_cardcode = p._cardcode;
    		_bornyear = p._bornyear;
    	}
    
    	void Print()
    	{
    		cout << "age is:" << _age << endl;
    		cout << "cardcode is:" << _cardcode << endl;
    		cout << "bornyear is:" << _bornyear << endl;
    
    	}
    private:
    	int _age;
    	int _cardcode;
    	int _bornyear;
    };
    
    int main()
    {
    	//实例化一个对象p1
    	Person p1(19, 123321, 2005);
    	p1.Print();
    	cout << "-----------------------" << endl;
    	//第一种拷贝构造函数的使用方法:
    	Person p2 = p1;
    	p2.Print();
    	cout << "-----------------------" << endl;
    	//第二种拷贝构造函数的使用方法:
    	Person p3(p1);
    	p3.Print();
    	return 0;
    }

    👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

    运行结果为:

  4. 若用户没有显式实现拷贝构造函数,编译器则会自己生成一个拷贝构造函数。编译器自己生成的拷贝构造函数会对内置类型的成员变量进行浅拷贝(一个字节一个字节的拷贝),对于自定义类型则会调用他们自身的拷贝构造函数。如下代码实现:
    #include<iostream>
    using namespace std;
    
    class Person
    {
    public:
    	//构造函数初始化
    	Person(int age = 1, int bornyear = 1)
    	{
    		_age = age;
    		_bornyear = bornyear;
    	}
    
    	void Print()
    	{
    		cout << "age is:" << _age << endl;
    		cout << "bornyear is:" << _bornyear << endl;
    	}
    private:
    	int _age;
    	int _bornyear;
    };
    
    int main()
    {
    	//以上是没有显式实现拷贝构造函数
    	Person p1(19, 2005);
    	Person p2 = p1;
    	p2.Print();
    	return 0;
    }

    👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

    运行结果为:

  5. 拷贝构造函数也分为深拷贝和浅拷贝,浅拷贝就是对于内置成员变量进行一个一个字节的拷贝;而深拷贝则需要另外开辟一块与要拷贝对象的成员变量空间大小一样大的空间,因为如果我们不进行深拷贝的话就会出现一块空间被析构函数释放两次;如下代码实现:

    #include<iostream>
    using namespace std;
    
    class Stack
    {
    public:
    	//构造函数进行初始化
    	Stack(int n = 4)
    	{
    		_arr = (int*)malloc(sizeof(int) * n);
    		if (nullptr == _arr)
    		{
    			perror("Stack malloc fail!");
    			return;
    		}
    		_capacity = n;
    		_top = 0;
    	}
    
    	Stack(const Stack& st)
    	{
    		_arr = st._arr;
    		_capacity = st._capacity;
    		_top = st._top;
    	}
    
    	//析构函数
    	~Stack()
    	{
    		free(_arr);
    		_arr = nullptr;
    		_capacity = 0;
    		_top = 0;
    	}
    
    	void push(int x)
    	{
    		if (_top == _capacity)
    		{
    			int newcapacity = _capacity * 2;
    			int* temp = (int*)malloc(sizeof(int) * newcapacity);
    			if (nullptr == temp)
    			{
    				perror("push malloc fail");
    				return;
    			}
    			_arr = temp;
    			_capacity = newcapacity;
    		}
    		_arr[_top++] = x;
    	}
    private:
    	int* _arr;
    	size_t _capacity;
    	size_t _top;
    };
    
    int main()
    {
    	Stack st1;
    	st1.push(1);
    	st1.push(2);
    
    	Stack st2 = st1;
    	return 0;
    }

    👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

    这是错误的代码运行结果为:

为什么会程序崩溃呢?原因就是上面所说的这个拷贝构造只是进行浅拷贝,直接就是_arr = st._arr,正确方法应该是再动态申请一块与st._arr一样大的空间,然后再把它所有的值复制过去,以下是正确代码:

#include<iostream>
using namespace std;

class Stack
{
public:
	//构造函数进行初始化
	Stack(int n = 4)
	{
		_arr = (int*)malloc(sizeof(int) * n);
		if (nullptr == _arr)
		{
			perror("Stack malloc fail!");
			return;
		}
		_capacity = n;
		_top = 0;
	}

	Stack(const Stack& st)
	{
		_arr = (int*)malloc(sizeof(int) * st._capacity);
		if (nullptr == _arr)
		{
			perror("Stack& malloc fail!");
			return;
		}
		memcpy(_arr, st._arr, sizeof(int) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}

	//析构函数
	~Stack()
	{
		free(_arr);
		_arr = nullptr;
		_capacity = 0;
		_top = 0;
	}

	void push(int x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			int* temp = (int*)malloc(sizeof(int) * newcapacity);
			if (nullptr == temp)
			{
				perror("push malloc fail");
				return;
			}
			_arr = temp;
			_capacity = newcapacity;
		}
		_arr[_top++] = x;
	}
private:
	int* _arr;
	size_t _capacity;
	size_t _top;
};

int main()
{
	Stack st1;
	st1.push(1);
	st1.push(2);

	Stack st2 = st1;
	return 0;
}

析的构函数

C++规定在对象销毁的时候会自动调用析构函数,完成对象中资源清理的释放的工作;例如在对象中动态申请了空间,使用完后并不需要像前面数据结构的栈一样实现Destory功能(也就是free掉动态申请的),而是直接在析构函数里面实现,如果说创建的对象中没有资源需要释放,那就没有必要写析构函数。

析构函数的特点:

  1. 析构函数名是在类名前加“ ~ “。
  2. 无参无返回值。(跟上面的构造函数类似,不需要写void)。
  3. 一个类只有一个析构函数。
  4. 对象生命周期结束时会自动调用析构函数。
  5. 跟构造函数类似,我们如果不显式实现析构函数,编译器会自动生成一个析构函数且对内置定类型(int/ char/ double/ 指针)成员不进行处理,自定义类型则会自动调用他们自身的析构函数。
  6. 如果类中没有申请资源时,也就是没有动态申请空间时我们可以写析构函数。
  7. 一个局部域的多个对象,C++规定后定义先析构。
    #include<iostream>
    using namespace std;
    
    class Stack
    {
    public:
    	//全缺省构造函数初始化
    	Stack(int n = 4)
    	{
    		_arr = (int*)malloc(sizeof(int) * n);
    		if (nullptr == _arr)
    		{
    			perror("Stack malloc fail!");
    			return;
    		}
    
    		_capacity = n;
    		_top = 0;
    	}
    
    	//析构函数对动态申请的_arr进行销毁
    	~Stack()
    	{
    		cout << "_Stack的调用" << endl;
    		free(_arr);
    		_arr = nullptr;
    		_capacity = 0;
    		_top = 0;
    	}
    
    private:
    	int* _arr;
    	size_t _capacity;
    	size_t _top;
    };
    
    class Queue
    {
    public:
    
    private:
    	Stack pushst;
    	Stack popst;
    };
    int main()
    {
    	Stack st;
    	Queue q1;
    	return 0;
    }

    对于第五点和第七点我们需要对代码进行调试才能看得清,调试顺序首先是Stack 类实例化出对象st,然后调用st的构造函数进行初始化后,再通过Queue类实例化出对象q1然后调用q1的默认构造函数(因为我们没有写所以是编译器默认生成,我们看不见),再调用pushst和popst自身的构造函数对他们自身进行初始化,结束后先调用对象q1里的pushst和popst他们的析构函数对他们的内存进行清理,再调用对象st里的析构函数然后结束整个程序。


    赋值运算符重载

  • 当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错,举个例子,比如两个年月日的相减得到的是之间差多少天。

  • 运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。

  • 重载运算符函数的参数个数和该运算符作用的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。

  • 如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少⼀个。

  • 不能对C++语法中不存在的运算符进行重载;以下代码通过实现运算符的重载来判断两个对象的年月日是否相等来体现以上的几点:
    #include<iostream>
    using namespace	std;
    
    class Date
    {
    public:
    	//构造函数进行初始化
    	Date(int year = 1, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print()
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    
    	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;
    }
    int main()
    {
    	Date d1(2004, 1, 10);
    	Date d2(2004, 7, 3);
    	bool q1 = operator==(d1, d2);
    	if (q1)
    	{
    		cout << "相等" << endl;
    	}
    	else
    		cout << "不等" << endl;
    	return 0;
    }

    👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

    运行结果为: 还有一种实现方法是通过间接使用this隐含指针来简化,接以下代码实现:

    #include<iostream>
    using namespace	std;
    
    class Date
    {
    public:
    	//构造函数进行初始化
    	Date(int year = 1, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print()
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    	bool operator==(const Date& d)
    	{
    		return _year == d._year
    			&& _month == d._month
    			&& _day == d._day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	Date d1(2004, 1, 10);
    	Date d2(2004, 7, 3);
    	bool q1 = d1.operator==(d2);
    	if (q1)
    	{
    		cout << "相等" << endl;
    	}
    	else
    		cout << "不等" << endl;
    	return 0;
    }

    这段代码的改进在于把赋值运算符重载放到类里面实现,这样就不需要把成员变量拿到公共域里面去,然后又因为其实运算符重载的函数原型是operator==(Date* const this, Date& d);这样我们看上面的代码,d1.operator==(d2)其实实际上是d1.operator==(&d1,d2);把d1的地址传给了this指针,这也是为什么只用传一个d2过去就能实现。


    END!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值