资讯详情

【C++基础】三、类和对象(中篇)

文章目录

    • 1.6个默认成员函数
    • 2.构造函数
      • 2.1 概念
      • 2.2 特性
    • 3.析构函数
      • 3.1 概念
      • 3.2 特性
    • 4.复制结构函数
      • 4.1 概念
      • 4.2 特性
    • 5.赋值操作符重载
      • 5.1 运算符重载
      • 5.2 赋值运算符重载
      • 5.3 特性
    • 6.日期类的完整实现
    • 7.const 成员
      • 7.1 const修饰成员函数
      • 7.2 请考虑以下问题:
    • 8.取地址及const取地址操作符重载
    • 9.思维导图总结


1.6个默认成员函数

如果一个类别中没有成员,它被称为空类。空类中什么都没有吗?不,任何类别都会自动生成以下动生成以下六个默认成员函数。

 class Date { 
         }; 

image-20220317080959489

2.构造函数

2.1 概念

#include<iostream> using namespace std; class Date { 
         public:  //void Display(Date* this)  void Display()  { 
           cout << _year << "-" << _month << "-" << _day << endl;   //cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;  }  //void SetDate(Date* this,int year,int month,int day)  void SetDate(int year, int month, int day)  { 
           _year = year;//this->_year = year;   _month = month;//this->_month = year;   _day = day;//this->_day = year;  } private:  int _year;//年  int _month;//月  int _day;//日 }; int main) { 
          Date d1, d2; d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19) d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19) d1.Display();//d1.Display(&d1) d2.Display();//d2.Display(&d2) return 0; } 

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

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务

其特征如下:

#include<iostream>
using namespace std;
class Date
{ 
        
public:
	//void Display(Date* this)
	void Display()
	{ 
        
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}
	//void SetDate(Date* this,int year,int month,int day)
	void SetDate(int year, int month, int day)
	{ 
        
		_year = year;//this->_year = year;
		_month = month;//this->_month = year;
		_day = day;//this->_day = year;
	}
	//构造函数 相当于SetDate,初始化,但是由程序自动调用,不需要我们去调用
	//1.无参数构造函数
	Date() { 
        
		_year = 0;
		_month = 1;
		_day = 1;
	}
	//2.带参数构成函数 无参和带参两种方式均可
	Date(int year, int month, int day) { 
        
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
};
int main()
{ 
        
	Date d1(2021, 8, 21);
	d1.Display();
	d1.SetDate(2021, 10, 1);
	d1.Display();

	Date d2;
	d2.Display();

	//Date d1;//调用无参构造函数 注意后面不能带(),否则变成函数声明了
	//Date d2(2015, 1, 1);//调用带参的构造函数

	//d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19)
	//d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19)
	//d1.Display();//d1.Display(&d1)
	//d2.Display();//d2.Display(&d2)
	return 0;
}

Date d2;//后面不能加(),d2(),这是语法规定的

这句话的意思就是”如果我们定义了构造函数,那么C++编译器就会使用我们定义的构造函数来初始化新建的对象;如果我们没有定义构造函数,那么C++编译器会自动生成一个没有参数的默认构造函数,然后用这个默认构造函数去初始化新建的对象。”

看起来编译器默认构造函数啥也没干,还是随机值,那这个默认构造函数有啥用呢?

#include<iostream>
using namespace std;
class Time { 
        
public:
	Time() { 
        
		_hour = 0;
		_minute = 0;
		_second = 0;
		cout << "Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{ 
        
public:
	//void Display(Date* this)
	void Display()
	{ 
        
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}
	//void SetDate(Date* this,int year,int month,int day)
	void SetDate(int year, int month, int day)
	{ 
        
		_year = year;//this->_year = year;
		_month = month;//this->_month = year;
		_day = day;//this->_day = year;
	}
	//我们没有显式定义构造函数,这里编译器生成无参默认构造函数

	构造函数 相当于SetDate,初始化,但是由程序自动调用,不需要我们去调用
	1.无参数构造函数
	//Date() { 
        
	// _year = 0;
	// _month = 1;
	// _day = 1;
	//}
	2.带参数构成函数 无参和带参两种方式均可
	//Date(int year, int month, int day) { 
        
	// _year = year;
	// _month = month;
	// _day = day;
	//}
private:
	int _year;//年
	int _month;//月
	int _day;//日
	Time _t;//自定义类型
};
int main()
{ 
        
	//Date d1(2021,8,21);
	Date d1;//调用了编辑器生成的默认构造函数
	d1.Display();
	d1.SetDate(2021, 10, 1);
	d1.Display();

	Date d2;
	d2.Display();

	//Date d1;//调用无参构造函数 注意后面不能带(),否则变成函数声明了
	//Date d2(2015, 1, 1);//调用带参的构造函数

	//d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19)
	//d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19)
	//d1.Display();//d1.Display(&d1)
	//d2.Display();//d2.Display(&d2)
	return 0;
}

添加一个自定义类型Time后,可以发现Date的编译器生成的默认构造函数实际上调用了Time的构造函数。也就是说:

默认生成的无参构造函数(语法坑:双标狗)

为了解决上面内置类型不初始化的问题,C++11打了一个补丁,允许给内置类型设置缺省值。

int _year = 0;
int _month = 1;
int _day = 1;
//注意这里是成员变量的声明,不是定义,没有开空间

**6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。**注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

#include<iostream>
using namespace std;
class Date
{ 
        
public:
	无参构造函数
	//Date() { 
	// _year = 0;
	// _month = 1;
	// _day = 1;
	//}
	带参构成函数
	//Date(int year, int month, int day) { 
	// _year = year;
	// _month = month;
	// _day = day;
	//}
	//上面两种方式的合二为一 全缺省构造函数
	Date(int year = 0, int month = 1, int day = 1) { 
        
		_year = year;
		_month = month;
		_day = day;
	}
	void Display()
	{ 
        
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
};
int main()
{ 
        
	Date d1(2021, 10, 1);
	Date d2;
	d1.Display();
	d2.Display();
	return 0;
}

注意:无参的构造函数和全缺省的构造函数,只能存在一个;如果两个同时存储,那么调用的时候会产生歧义,因为编译器不知道该调用哪个了。

d对象调用了编译器生成的默认构造函数,但是d对象的_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么作用??

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…,。自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序

就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数

(这个前面已经说的很清楚了,语法坑:双标狗)

建议成员变量名称前面加上下划线_

int _year;
int _month;
int _day;
//或者也可以这样
int m_year;
int m_month;
int m_day;
//m-member 成员

3.析构函数

3.1 概念

前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?

析构:对象生命周期到了以后,自动调用。完成对象里面的资源清理(资源:对象创建时向系统申请的空间,比如所 malloc 出来的空间)

3.2 特性

析构函数是特殊的成员函数。其特征如下:

#include<iostream>
using namespace std;
class Date
{ 
        
public:
	//构造函数
	Date(int year = 0, int month = 1, int day = 1) { 
        
		_year = year;
		_month = month;
		_day = day;
		cout << "自动调用构造函数" << endl;
	}
	//析构函数
	~Date() { 
        
		cout << "自动调用析构函数~" << endl;
	}
	void Display()
	{ 
        
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}

private:
	int _year;//年
	int _month;//月
	int _day;//日
};
int main()
{ 
        
	Date d1(2021, 10, 1);
	Date d2;
	d1.Display();
	d2.Display();
	return 0;
}

当我们动态开辟数组,比如malloc/colloc/realloc出来的空间,使用之后需要将这些堆上的空间释放掉,就可以利用析构函数将资源清理掉。

#include<iostream>
using namespace std;
class Stack { 
        
public:
	//构造函数,完成初始化
	Stack(int n = 10) { 
        
		_array = (int*)malloc(sizeof(int) * n);
		_size = 0;
		_capacity = 10;
	}
	//析构函数,完成资源清理
	~Stack() { 
        
		free(_array);
		_array = NULL;
		_size = 0;
		_capacity = 0;
	}
private:
	int* _array;
	int _size;
	int _capacity;
};
int main() { 
        
	Stack st;
	return 0;
}

调用构造函数完成初始化

调用析构函数完成资源清理

一个类要有什么样的成员变量和成员函数,需要根据类的实际使用场景来确定。

下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。

(类似于构造函数的语法坑:双标狗)

跟构造函数类似,编译器默认生成的析构函数做了偏心的处理:

#include<iostream>
using namespace std;
class Time { 
        
public:
	~Time() { 
        
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{ 
        
public:
	//构造函数
	Date(int year = 0, int month = 1, int day = 1) { 
        
		_year = year;
		_month = month;
		_day = day;
		cout << "自动调用构造函数" << endl;
	}
	析构函数
	//~Date() { 
        
	// cout << "自动调用析构函数~" << endl;
	//}
	void Display()
	{ 
        
		cout << _year << "-" << _month << "-" << _day << endl;
		//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
	}

private:
	int _year;//年
	int _month;//月
	int _day;//日
	Time _t;//自定义类型
};
int main()
{ 
        
	Date d1(2021, 10, 1);
	Date d2;
	d1.Display();
	d2.Display();
	return 0;
}

4.拷贝构造函数

4.1 概念

在现实生活中,可能存在长得一模一样的事物,我们称其为双胞胎(孪生/双生)。

那在创建对象时,可否创建一个与一个对象一模一样的新对象呢?

4.2 特性

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

#include<iostream>
using namespace std;
class Date 
{ 
        
public:
	Date(int year = 0, 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(2021, 10, 1);
	Date d2(2021, 10, 1);
	Date d3(d1);
	return 0;
}

如果不使用引用,在调用拷贝构造函数之前需要先传参,传参的时候要先进行拷贝构造,拷贝构造之前又要先传参….语义上

#include<iostream>
using namespace std;
class Date
{ 
        
public:
	Date(int year = 0, 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(2021, 10, 1);
	Date d2(2021, 10, 1);
	Date d3(d1);
	return 0;
}

传参的过程又是一个拷贝构造的调用?这句话怎么理解?

func1(int i) 
{ 
        
	//函数内部代码
}
int main() 
{ 
        
	int j = 0;
	func1(j);//调用func1的时候,要先将j传给i
}

实参传给形参是一个赋值的方式

换成class Date类的话

d1传给d的过程就是一个赋值的过程,但是这个自定义类的赋值不同于内置类型的直接赋值,而是再次通过拷贝构造函数给其赋值,然后就形成了语义上的无穷递归……

传值的方式,实参接收形参传过来的值时,需要创建一个,就会引发对象的拷贝构造。

而传引用的方式(引用是一个变量的别名)没有传参赋值的这个过程,也就不会形成无穷的递归。

拷贝构造也可以通过类似赋值的方式进行

Date d4 = d1;

Date(const Date &d)这里为什么要加上const的呢?

1)使用const可以保证对象d的内容不被修改

2)如果Date(const Date &d){ };花括号内部代码出错了,比如说写反了,编译器会查出来。

默认的拷贝构造函数,跟默认的构造和析构函数不太一样,不会去区分内置类型和自定义类型,都会区处理

1)内置类型,字节序的浅拷贝

2)自定义类型,会去调用它的拷贝构造函数完成拷贝

字节序的浅拷贝:就像用 memcpy 函数完成拷贝一样(一个字节接着一个字节的拷贝复制)

Stack St1;
Stack st2(st1);
//同一块空间会释放两次

编译器默认生成的拷贝构造函数并不能解决所有问题,所以需要我们定义深拷贝构造来解决这个问题。

#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;
 };
int main()
{ 
        
	Date d1;
	//这里d2调用默认拷贝构造完成拷贝,d2和d1的值是一样的
	Date d2(d1);
	return 0;
}

//这里会发现下面的程序会崩溃掉?需要我们以后学了深拷贝去解决
#include<iostream>
using namespace std;
class String
{ 
        
public:
	String(const char* str = "jack")
	{ 
        
		_str = (char*)malloc(str

标签: 006m重载连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

 锐单商城 - 一站式电子元器件采购平台  

 深圳锐单电子有限公司