类和对象(中)

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

在上一篇博客中我谈到了空类的大小是1,但是空类里面真的什么都没有吗,其实每个类里面都有6个默认成员函数,如果我们不写的话,系统会为我们默认生成。

这六个默认成员函数分别是:

构造函数:主要完成初始化工作

析构函数:主要完成资源的清理

拷贝构造:使用一个同类对象初始化另一个对象

赋值重载:将一个同类对象赋值给另一个对象

最后两个是普通对象和const对象取地址,这两个一般都直接用系统默认生成的,不用自己实现。

2、构造函数

概念

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

	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022,9,28);
	d1.Display();

	system("pause");
	return 0;
}

以上就是一个日期类的构造函数,这里使用了缺省参数,下面介绍构造函数的特性。

特性

1、构造函数主要任务并不是开空间给对象,而是初始化对象。

2、构造函数的函数名必须与类名相同

3、构造函数没有返回值

4、对象实例化时会自动调用构造函数

5、构造函数可以重载

class Date
{
public:
    //有参的构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

    //无参的构造函数
	Date()
	{
		_year = 2022;
		_month = 9;
		_day = 28;
	}

	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(100,2,2);
	d1.Display();

    //d2后面不要带(),否则就会被认定为函数调用,而我们本意是创建对象
	Date d2;
	d2.Display();

	system("pause");
	return 0;
}

上面的程序中有参的构造函数千万不能写成全缺省的构造函数,因为一个函数中不能存在多个默认构造函数

默认构造函数有三种

①我们不写,编译器默认生成的构造函数,默认生成的是无参的

②我们写的全缺省的默认构造函数

③我们写的无参的构造函数

6、编译器默认生成的构造函数初始化的值是随机值

class Date
{
public:
	//Date()
	//{
	//	_year = 2022;
	//	_month = 9;
	//	_day = 25;
	//}

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

int main()
{
    //只有调用有参的时候才需要加(),无参一定不要加()
	Date d1;

	return 0;
}

 7、对于内置类型(int、double、char、short等等)编译器才会去自动调用构造函数,而对于自定类型(我们自己用类、结构体等创建出的对象)会去调用他们自己的构造函数。

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
};
class B
{
public:
	B()
	{
		cout << "B()" << endl;
	}
private:
	int _n;
    A a;    //B类中包含一个A类对象
};

int main()
{
	B b;

	return 0;
}

 可以看到先调用的是自定义类型的构造函数,后调用内置类型,这里不会因为声明顺序而改变两个构造函数的调用顺序。

8、成员变量的命名风格

通过上面的示例可以看到我定义的成员变量前都加了 _ ,这是为了避免与局部变量同名的情况,而且这样一眼就能看出这是成员变量。

9、构造函数不一定非要在函数体内初始化,还可以通过初始化列表初始化,并且对于常量、自定义类型这种必须通过初始化列表初始化

class A
{
public:
	A(int a = 0)
	{
		_aa = a;
	}
//private:
	int _aa;
};

class B
{
public:
	B(int a, int b, int c)
		:_a(a)
		,_b(b)
		,_c(c)
	{}
//这里为了方便打印,所以不加私有属性
//加上私有属性,类外不能直接访问
//private:
	int _b;
	const int _c;
	A _a;
};

int main()
{
	B b(1, 2, 3);
	cout << b._a._aa << b._b << b._c << endl;

	return 0;
}

 

3、析构函数

概念

上面提到析构函数是完成对象资源的清理,析构函数不是完成对象的销毁,销毁工作是编译器完成的,而对象在销毁时会自动调用构造函数去完成资源的清理

特性

1、析构函数函数名就是类型前加上字符 ~

2、析构函数没有返回值

3、一个类中只能有一个析构函数,如果没有写,编译器会自动生成析构函数

4、当对象销毁时,编译器会自动调用析构函数完成资源清理

class Arr
{
public:
	Arr(int capacity)
	{
		_p = (int*)malloc(sizeof(int) * capacity);
		_size = 0;
		_capacity = capacity;
		cout << "Arr()" << endl;
	}
	
	~Arr()
	{
		if (_p != nullptr)
		{
			free(_p);
			_p = nullptr;
		}
		_size = _capacity = 0;
		cout << "~Arr()" << endl;
	}
private:
	int* _p;
	int _size;
	int _capacity;
};


int main()
{
	Arr a(10);

	return 0;
}

5、对于自定义类型,编译器也会像构造函数一样,会去调用他们自己的析构函数。

6、编译器默认生成的构造函数只会进行简单的清理工作,像在堆上开辟的空间,不会清理,所以可能造成内存泄漏,这种情况最好自己写析构函数。

4、拷贝构造函数

概念

拷贝构造函数也可以用来初始化对象,其作用是将一个已经实例化的本类对象去创建一个新的对象。

特征

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

2、拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

 下面是正确写法:

class A
{
public:
	A(int capacity)
	{
		_size = 0;
		_capacity = capacity;
	}

	A(const A& a)
	{
		_size = a._size;
        _capacity = a._capacity;
	}

private:
	int _size;
	int _capacity;
};


int main()
{
	A a(10);
    A b(a);
	
	return 0;
}

3、如果没有显示定义,那么到需要调用拷贝构造时,系统会去调用默认生成的

可以看到系统默认生成的也能完成拷贝。

4、 编译器默认生成的拷贝构造是值拷贝,俗称浅拷贝,当成员变量有指针时,浅拷贝就不能用了,这时就得写一个深拷贝。

 现在就写一个深拷贝来解决这种问题

class A
{
public:
	A(int capacity)
	{
		_p = (int*)malloc(sizeof(int) * capacity);
		_size = 0;
		_capacity = capacity;
	}

	A(const A& a)
	{
		//重新开辟一块和 a 一样大小的空间
		_p = (int*)malloc(sizeof(int) * a._capacity);
		_size = a._size;
		_capacity = a._capacity;
	}

	~A()
	{
		if (_p != nullptr)
		{
			free(_p);
			_p = nullptr;
		}
		_size = _capacity = 0;
	}

private:
	int* _p;
	int _size;
	int _capacity;
};


int main()
{
	A a(10);
	A b(a);
	
	return 0;
}

 5、赋值运算符重载

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

函数名字:operator后面跟上想要重载的运算符

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

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型或者枚举类型的操作数
  • 重载函数的形参第一个默认是this,如果该运算符是单目操作符,形参列表就不要添加了
  • .* :: sizeof?:.  注意以上5个运算符不能重载。

如果没有显示定义赋值运算符重载,编译器也同要会默认生成一个,完成的也是简单的值拷贝

下面写一个常用的赋值 = 运算符的重载(深拷贝)

class A
{
public:
	A(int capacity)
	{
		_p = (int*)malloc(sizeof(int) * capacity);
		_size = 0;
		_capacity = capacity;
	}

	A& operator=(const A& a)
	{
		//避免自己给自己赋值,提高效率
		if (this != &a)
		{
			//先释放自身_p申请的空间
			free(_p);
			//然后申请一个新空间
			_p = (int*)malloc(sizeof(int) * a._capacity);
			_size = a._size;
			_capacity = a._capacity;
		}
		return *this;
	}

	~A()
	{
		if (_p != nullptr)
		{
			free(_p);
			_p = nullptr;
		}
		_size = _capacity = 0;
	}

private:
	int* _p;
	int _size;
	int _capacity;
};


int main()
{
	A a(10);
	A b = a;
	
	return 0;
}

6、const成员

cosnt修饰的成员函数

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

class B
{
public:
	B(int b, int c)
		:_b(b)
		,_c(c)
	{}

	/*void Print()
	{
		_b = 20;
		_c = 30;
		cout << this->_b << this->_c << endl;
	}*/

    //此时参数列表隐含的this指针就变成了const B* this
	void Print()const
	{
		//_b = 20;//err
		//_c = 30;//err
		cout << this->_b << this->_c << endl;
	}

private:
	int _b;
	int _c;
};

int main()
{
	B b(2, 3);
	b.Print();

	system("pause");
	return 0;
}

 总结:

1、函数加const表示参数列表的参数都是常量

2、函数加const表示函数返回值是常量

3、const对象不能调用非const函数,这属于权限的放大

4、非const对象可以调用const函数,这属于权限的缩小

5、const对象可以调用const函数,非const对象可以调用非const函数

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

这两个默认成员函数一般不怎么使用,而且编译器默认生成的也够用

class Date
{
public:
	Date* operator&()
	{
		return this;
	}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值