C++基础三类和对象(中篇)
Posted 大家好我叫张同学
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++基础三类和对象(中篇)相关的知识,希望对你有一定的参考价值。
文章目录
1.类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。
class Date ;
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 特性
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1.函数名与类名相同
2.无返回值。
3.对象实例化时编译器自动调用对应的构造函数
4.构造函数可以重载。
#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(),这是语法规定的
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
这句话的意思就是”如果我们定义了构造函数,那么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的构造函数。也就是说:
默认生成的无参构造函数(语法坑:双标狗)
1、针对内置类型的成员变量没有做处理(比如int、char等类型)
2、针对用户自定义类型的成员变量,调用它的构造函数初始化(比如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;
注意:无参的构造函数和全缺省的构造函数,只能存在一个;如果两个同时存储,那么调用的时候会产生歧义,因为编译器不知道该调用哪个了。
7.关于编译器生成的默认成员函数,很多童鞋会有疑惑∶在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?
d对象调用了编译器生成的默认构造函数,但是d对象的_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么作用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…,。自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序
就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数
(这个前面已经说的很清楚了,语法坑:双标狗)
8.成员变量的命名风格
建议成员变量名称前面加上下划线_
int _year;
int _month;
int _day;
//或者也可以这样
int m_year;
int m_month;
int m_day;
//m-member 成员
3.析构函数
3.1 概念
前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
析构:对象生命周期到了以后,自动调用。完成对象里面的资源清理(资源:对象创建时向系统申请的空间,比如所 malloc 出来的空间)
3.2 特性
析构函数是特殊的成员函数。其特征如下:
1.析构函数名是在类名前加上字符~
2.无参数无返回值
3.一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
4.对象生命周期结束时,C++编译系统系统自动调用析构函数
#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;
调用构造函数完成初始化
调用析构函数完成资源清理
一个类要有什么样的成员变量和成员函数,需要根据类的实际使用场景来确定。
5.关于编译器自动生成的析构函数,是否会完成一些事情呢?
下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。
(类似于构造函数的语法坑:双标狗)
跟构造函数类似,编译器默认生成的析构函数做了偏心的处理:
1)内置类型不处理
2)自定义类型会去调用它的析构函数
#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 概念
在现实生活中,可能存在长得一模一样的事物,我们称其为双胞胎(孪生/双生)。
那在创建对象时,可否创建一个与一个对象一模一样的新对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
4.2 特性
拷贝构造函数也是特殊的成员函数,其特征如下:
1.拷贝构造函数是构造函数的一个重载形式。
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) ;花括号内部代码出错了,比如说写反了,编译器会查出来。
3.若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
默认的拷贝构造函数,跟默认的构造和析构函数不太一样,不会去区分内置类型和自定义类型,都会区处理
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;
;
C++基础三类和对象(中篇)