类和对象类的6个默认成员函数

Posted 银背欧尼酱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类和对象类的6个默认成员函数相关的知识,希望对你有一定的参考价值。

这里写目录标题


前言

本篇文章将带大家进一步了解C++的基础知识,本节将介绍一些C++的六个默认成员函数。


1. 构造函数

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

注:构造函数的作用是初始化对象。
单参构造函数的作用是隐式类型转化

特征:

1.函数名和类名相同

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

3.对象实例化时编译器自动调用对应的构造函数

4.构造函数可以重载

class Date

public :
 // 1.无参构造函数
     Date ()
     
 
 // 2.带参构造函数
     Date (int year, int month , int day )
     
         _year = year ;
         _month = month ;
         _day = day ;
     
private :
     int _year ;
     int _month ;
     int _day ;
;
void TestDate()

     Date d1; // 调用无参构造函数
     Date d2 (2015, 1, 1); // 调用带参的构造函数

     // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
     // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
     Date d3(); 

1.1无参构造函数

如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数

class Date

public:
 /*
 // 如果用户显式定义了构造函数,编译器将不再生成
     Date (int year, int month, int day)
     
         _year = year;
         _month = month;
         _day = day;
 	
 */
private:
     int _year;
     int _month;
     int _day;
;
void Test()

     // 没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数
     Date d; 
 

1.2默认构造函数

默认构造函数只能有一个。无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

// 默认构造函数
class Date
 
public:
     Date()
     
         _year = 1900 ;
         _month = 1 ;
         _day = 1;
     
 
     Date (int year = 1900, int month = 1, int day = 1)
     
         _year = year;
         _month = month;
         _day = day;
     
private :
     int _year ;
     int _month ;
     int _day ;
;
// 以下测试函数不能通过编译,因为默认构造函数只能有一个
void Test()

     Date d1; 
 

1.3成员变量的命名风格

class Date

public:
     Date(int year)
     
         _year = year;
     
private:
     int _year;
;
// 或者这样。
class Date

public:
     Date(int year)
     
         m_year = year;
     
private:
     int m_year;
;

1.4构造函数的赋值

在创建对象时,编译器通过调用构造函数,给对象各个成员变量一个合适的初始值

class Date

public:
 Date(int year, int month, int day)
 
 _year = year;
 _month = month;
 _day = day;
 
 
private:
 int _year;
 int _month;
 int _day;
;

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

1.5初始化列表

概念:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。

代码示例:

class Date

public:
     Date(int year, int month, int day)
         : _year(year)
         , _month(month)
         , _day(day)
     
 
private:
     int _year;
     int _month;
     int _day;
;

注:

1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2.类中包含以下成员,必须放在初始化列表位置进行初始化:

  • (1)引用成员变量
  • (2)const成员变量
  • (3)自定义类型成员(该类没有默认构造函数)
class A 
public:
     A(int a)
         :_a(a)
     
private:
     int _a;
;
class B 
public:
     B(int a, int ref)
         :_aobj(a)
         ,_ref(ref)
         ,_n(10)
     
private:
     A _aobj; // 没有默认构造函数
     int& _ref; // 引用
     const int _n; // const 
;

3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

class Time

public:
     Time(int hour = 0)
         :_hour(hour)
     
         cout << "Time()" << endl;
     
private:
     int _hour;
;
class Date

public:
     Date(int day)
     
private:
     int _day;
     Time _t;
;
int main()

     Date d(1);

构造函数体内部是赋值,并不是对成员变量来初始化。初始化列表的位置是对“成员变量”来初始化的。

class Date

public:
	// 初始化列表的位置是对“成员变量”来初始化的
	Date(int year = 1900, int month = 1, int day = 1) 
		: _year(year)
		, _month(month)
		, _day(day)
		// , _day(10)   // 成员变量在初始化列表的位置只能出现一次
		, _a(10)
		, ra(_year)
	
		// 构造函数体内部是赋值,并不是对成员变量来初始化
		/*_year = year;
		_month = month;
		_day = day;
		_day = 10;*/
	

	void PrintDate()const
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;

	const int _a;
	int& ra;
;

int main()

	int a = 10;
	int& ra = a;
	Date d(2021, 6, 10);
	return 0;

普通的构造方法:

class Time

public:
	// 默认成员函数
	/*Time()
	
	_hour = 0;
	_minute = 0;
	_second = 0;
	*/

	// 普通的构造方法
	Time(int hour, int minute, int second)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	
private:
	int _hour;
	int _minute;
	int _second;
;

class Date

public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
		, _t(21,7,30)
	

	void PrintDate()const
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
private:
	int _year;
	int _month;
	int _day;
	Time _t;
;

int main()

	Date d(2021, 6, 10);
	return 0;

成员变量初始化的真正次序与成员变量在类中的声明先后次序一致,与其再初始化列表出现的先后次序无关

class Date

public:
// 成员变量初始化的真正次序与成员变量在类中的声明先后次序一致
// 与其再初始化列表出现的先后次序无关
	Date(int year = 1900, int month = 1, int day = 2)
		: _day(day)
		, _month(_day// 建议:尽量不要使用成员来初始化成员
		, _year(year)
	
		cout << "Date(int,int,int):" << this << endl;
	

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

int main()

	Date d1;
	return 0;

2.析构函数

析构函数:与构造函数功能相反。析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而

对象在销毁时会自动调用析构函数,析构函数的作用是完成类的一些资源清理工作

特性:

  1. 析构函数名是在类名前加上字符 ~。

  2. 无参数无返回值。

  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

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

typedef int DataType;
class SeqList
 
public :
     SeqList (int capacity = 10)
     
         _pData = (DataType*)malloc(capacity * sizeof(DataType));
         assert(_pData);

         _size = 0;
         _capacity = capacity;
 	
 
     ~SeqList()
     
         if (_pData)
         
             free(_pData ); // 释放堆上的空间
             _pData = NULL; // 将指针置为空
             _capacity = 0;
             _size = 0;
         
     
 
private :
     int* _pData ;
     size_t _size;
     size_t _capacity;
;

编译器可以自动生成和调用析构函数。

class String

public:
	String(const char* str = "")
	
		cout << "String(const char*)" << endl;
		if (nullptr == str)
			str = "";

		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	

	~String()
	
		cout << "~String()" << endl;
		if (_str)
		
			free(_str);
			_str = nullptr;
		
	
private:
	char* _str;
;


class Person

	// ....
	/*
	// 编译器会给Person类生成如下的析构函数
	public:
	    ~Person()
		
		    ~String(&_gender);
			~String(&_name);
		
	*/
public:
	~Person()
	

private:
	String _name;
	String _gender;
	int age;
;

void TestPerson()

	Person p;


int main()

	TestPerson();
	return 0;

3. 拷贝构造函数

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

特征:

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

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

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;
     
private:
     int _year;
     int _month;
     int _day;
;
int main()

     Date d1;
     Date d2(d1);
     return 0; 
 

若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷

贝,这种拷贝我们叫做浅拷贝,或者值拷贝

// 这里会发现下面的程序会崩溃掉
class String

public:
     String(const char* str = "jack")
     
         _str = (char*)malloc(strlen(str) + 1);
         strcpy(_str, str);
     
     ~String()
     
         cout << "~String()" << endl;
         free(_str);
     
private:
     char* _str;
;
int main()

     String s1("hello");
     String s2(s1);//s2和s1本质是指针,指向同一块地址空间,当释放掉s1空间时,等于把s2指向的空间也释放了,所以程序崩溃

4. 运算符重载

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

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

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

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型或者枚举类型的操作数
  • 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员的重载函数时,其形参看起来比操作数数目少1个,成员函数的操作符有一个默认的形参this,限定为第一个形参
  • .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
// 全局的operator==
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; 
 
void Test ()

     Date d1(2018, 9, 26);
     Date d2(2018, 9, 27);
     cout<<(d1 == d2)<<endl;
 
 
class Date
 
public:
     Date(int year = 1900, int month = 1, int day = 1)
     
         _year = year;
         _month = month;
         _day = day;
     
 
 // bool operator==(Date* this, const Date& d2)
 // 这里需要注意的是,左操作数是this指向的调用函数的对象
     bool operator==(const Date& d2)
     
        return _year == d2._year;
         && _month == d2._month
         && _day == d2._day;
     
private:
     int _year;
     int _month;
     int _day;
;
void Test ()

     Date d1(2018, 9, 26);
     Date d2(2018, 9, 27);
     cout<<(d1 == d2)<<endl; 
 

4.1赋值运算符

1.参数类型

2.返回值

3.检测是否自己给自己赋值

4.返回*this

5.一个类如果没有显式定义赋值运算符重载,编译器也会生成一

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;
;
int main()

     Date d1;
     Date d2(2018,10, 1);

     // 这里d1调用的编译器生成operator=完成拷贝,d2和d1的值也是一样的。
     d1 = d2;
     return 0; 
 

当涉及到动态内存管理时:

// 这里会发现下面的程序会崩溃掉
class String

public:
     String(const char* str = "")
     
         _str = (char*)malloc(strlen(str) + 1);
         strcpy(_str, str);
     
     ~String()
     
         cout << "~String()" << endl;
         free(_str);
     
     private:
     char* _str;
;
int main()

     String s1("hello");
     String s2("world");//此时s1指向的空间已经被释放掉了,所以程序崩溃

     s1 = s2; 
 

一些运算符重载实现:

	Date& operator=(const Date& d)
	
		if (this != &d)
		
			_year = d._year;
			_month = d._month;
			_day = d._day;
		

		return *this;
	

	// 前置++
	Date& operator++()
	
		_day += 1;
		return *this;
	

	// 后置++
	Date operator++(int)
	
		Date temp(*this);
		_day += 1;
		return temp;
	

	// 前置--
	Date& operator--()
	
		_day -= 1;
		return *this;
	

	// 后置++
	Date operator--(int)
	
		Date temp(*this);
		_day -= 1;
		return temp;
	

5. const成员

5.1 const修饰类的成员函数

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

普通类型的对象:可读可写,调用普通成员函数,调用const修饰的成员函数。

const修饰的对象:可读,不能调用普通成员函数,只能调用const修饰的成员函数。

class Date

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

	// 1 const:修饰的是函数的返回值
	// 2 const: 修饰的是函数的参数
	// 3 const: 修饰成员函数本身---const成员函数
	// 即:在const成员函数中,可以读取"成员变量"中的内容,但是不能修改
	// 例外:被mutable关键字修饰的成员可以在const成员函数中被修改的
	// 1                  2           3
	const int TestFunc1(const int a)const
	
	// 原本this指针的类型:Date* const
	// const(3)修饰成员函数,本质实际在修饰this指针
	// 此时this指针的类型:const Date* const
		this->_day += 1;
		// this->_month += 1;
		//this->_year += 1;
		cout << _day << endl;
		return 0;
	

	// 普通成员函数
	// this指针的类型:Date* const
	void TestFunc2()
	
		this->_day += 1;
	

	Date* operator&()
	
		//_day += 1;
		cout << this << endl;
		return this;
	

	const Date* operator&()const
	
		cout << this << endl;
		return this;
	

	// 该方法只是将对象中的成员变量打印出来---不需要修改成员变量的内容
	// 为了提高代码的安全性--可以将其设置为const成员函数
	void PrintDate()const
	
		cout << _year << "-" << _month << "-" << _day << endl;
	
private:
	int _year;
	int _month;
	mutable int _day;
;

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

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

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

总结

以上就是今天内容,希望大家有所收获。

以上是关于类和对象类的6个默认成员函数的主要内容,如果未能解决你的问题,请参考以下文章

生成一个派生类对象时,调用基类和派生类构造函数按啥次序

C++类和对象—— 类的6个默认成员函数及日期类的实现

C++初阶---类和对象(类的默认成员函数和其他)

C++类和对象(this指针6个默认成员函数const成员)

类和对象(二)——6个默认成员函数

类和对象万字总结