当前位置:网站首页>类和对象(中上)
类和对象(中上)
2022-08-03 12:30:00 【Hey pear!】
1.类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
class Date {
};
2. 构造函数
大部分的类都需要自己写构造函数
只有像myqueue这样的类不需要写显示构造函数
每个类最好都要提供默认构造函数
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2022, 7, 5);
d1.Print();
Date d2;
d2.Init(2022, 7, 6);
d2.Print();
return 0;
}
//运行时奔溃了,是因为没有初始化
//C语言调用Init初始化可能会忘记,导致奔溃或出现随机值
//C++ 构造函数 ——保证初始化
2.1概念
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次
2.2特征
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开
空间创建对象,而是初始化对象。
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函数,该函数无参,返回一个日期类型的对象
// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
Date d3();
}
练习:栈的构造函数
typedef int DataType;
class Stack
{
public:
Stack(int capacity)//构造函数
{
cout << "Stack(int capacity = 4)" << endl;
_array = (DataType*)malloc(sizeof(DataType)*capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack st;
st.Push(0);
st.Push(1);
return 0;
}
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
class Date
{
public:
/* // 如果用户显式定义了构造函数,编译器将不再生成 Date(int year, int month, int day) { _year = year; _month = month; _day = day; } */
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生
成
// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
Date d1;
return 0;
}
6.构造函数并不开空间
C++将类型分为两种:
内置类型(基本原生类型):int double char 指针
自动类型:struct class
默认生成构造函数:
内置类型成员不做处理
自定义类型成员回去调用他(自定义类型成员变量)的默认构造函数
class Date
{
public:
// 不写 默认生成构造函数
private:
int _year = 1;
int _month = 1;
int _day = 1;
//Time* _t; // 指针都是内置类型
Time _t;
};
int main()
{
Date d1;//调用构造函数初始化,只能调用默认构造函数
return 0;
}
对于这样的类才有价值
class MyQueue
{
private:
Stack _st1;
Stack _st2;
};
int main()
{
MyQueue q;
return 0;
}
这是c++早期设计的一个缺陷,默认生成构造函数,本来应该和内置类型也一并处理
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值
总结:
一般的类都不会让编译器默认生成构造函数,都是自己写。写一个全缺省,非常好用
特殊情况下才会默认生成
class MyQueue {
public:
void push(int x) {
}
//...
private:
size_t _size = 0;
Stack _st1;
Stack _st2;
};
int main()
{
Date d1;
// MyQueue 用默认生成构造函数就挺好,不需要显示写
MyQueue q;
return 0;
}
7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数
默认构造函数有三类:特点——不传参数就可以调用
- 我们不写,编译器自动生成
- 我们自己写的,全缺省构造函数
- 我们自己写的,无参构造函数
using namespace std;
class Date
{
public:
// 1.无参构造函数
Date()
{}
// 2.带参构造函数(最好的方式——提供全缺省)
Date(int year = 1, 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(2022, 7, 23);
return 0;
}
3.析构函数
一些类需要显示写析构函数,比如:stack,queue
一些类不需要显示写析构函数。a.date这类,没有资源需要清理 b.myqueue也可以不写,默认生成的就可以
3.1 概念
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
析构函数:
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。
而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
生命周期,函数结束就会自动销毁了
3.2 特性
析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数(没有参数就不会构成重载了)。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重
载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
5. 默认生成的析构函数特点:
- 跟构造函数类似,内置函数不处理,
- 自定义类型成员回去调用他的析构函数
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 不需要写,默认生成就够用。但是默认生成的也没做什么事情
//~Date()//~按位取反
//{
// //~Date()没有什么需要清理
// cout << "~Date()" << endl;
//}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType)* capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 需要显示写,手动明释放资源,不释放会内存泄漏
//析构函数是特殊函数,写了之后会自动调用
~Stack()
{
cout << "~Stack()->" << _array << endl;
free(_array);
_capacity = _size = 0;
_array = nullptr;
}
private:
DataType* _array;
int _capacity;
int _size;
};
class MyQueue {
public:
void push(int x) {
}
//...
// 不需要写,默认生成就够用。但是默认生成对于Stack自定义成员,调用Stack析构函数
private:
size_t _size = 0;
Stack _st1;
Stack _st2;
};
void func()
{
Date d;
Stack st;
MyQueue q;
}
int main()
{
func();
return 0;
}
6.分为三类:
- 不需要写 date日期类
- 需要写 stack queue List SeqList binarytree
- 需要写但可以默认生成 myqueue(两个栈实现队列)
- 析构的顺序
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)->" <<_a<< endl;
}
~A()
{
cout << "~A()->" <<_a<<endl;
}
private:
int _a;
};
int main()
{
A aa1(1);
A aa2(2);
return 0;
}
先定义的先构造 后定义的先析构
栈帧和栈里面的对象都要符合:后进先出
A aa3(3);
int main()
{
static A aa0(0);
A aa1(1);
A aa2(2);
static A aa4(4);
return 0;
}
全局的最先初始化,局部和静态是第一次运行进入main函数的时候初始化所以是按顺序来的
1 2 是在栈帧里的
0 4 在局部静态区
3是在去全局
析构大方向是先栈帧然后局部静态最后全局 遵循后进先出
每个部分里面也是遵循后进先出
A aa3(3);
void f()
{
static int i = 0;
static A aa0(0);
A aa1(1);
A aa2(2);
static A aa4(4);
}
// 构造顺序:3 0 1 2 4 1 2
// 析构顺序:~2 ~1 ~2 ~1 ~4 ~0 ~3
int main()
{
f();
f();//两次调用
return 0;
}
第二次调用的时候静态就不会再构造了
而且第一次调用之后静态和全局也不会析构,直到第二次调完才会析构
4. 拷贝构造函数
一些类需要显示写拷贝额赋值,比如stack,queue
一些类不需要显示写拷贝和赋值。a.date这样的类,默认生成就会完成值拷贝/浅拷贝 b.比如myqueue这样的类,默认生成就会调用他的自定义类型成员stack的拷贝和赋值
4.1 概念
拷贝构造函数:
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型
对象创建新对象时由编译器自动调用。
4.2 特征
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发
无穷递归调用
传值传参和传引用传参
:
- 传值传参:d是d1的拷贝,形参是实参的拷贝 会在get1函数中开辟一个新的空间吧d1拿过去
- 传引用传参:d是d1的别名 公用一块空间,只不过取了不同的名字 是为自定义类型准备的
void get1(Date d)
{
}
void get2(Date& d)
{
}
void func()
{
get1(d1);
get2(d1);
}
传参就是一个拷贝构造
传值返回和传引用返回
:
A func3()
{
static A aa(3);
return aa;//返回aa的拷贝
}
A& func4()
{
static A aa(4);
return aa;//返回的是aa的别名
}
int main()
{
func3();//析构的也是拷贝的对象
cout << endl << endl;
func4();
return 0;
}
需要注意的是,如果aa出来func4的作用域就销毁了,那么引用返回是有问题的
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//1.引用传参
Date(const Date& d)
{
cout << "Date(Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
//写反了——用const避免这种错误发生
/*d._year = _year; d._month = _month; d._day = _day;*/
}
//2.指针——可以但是达不到想要的效果
Date(Date* d)
{
cout << "Date(Date& d)" << endl;
_year = d->_year;
_month = d->_month;
_day = d->_day;
}
private:
int _year;
int _month;
int _day;
};
void func()
{
Date d0;
Date d1(2022, 7, 23);
Date d2(d1);
Date d3 = d1;
Date d4(&d1);
Date d5 = &d1;
// 内置类型
int i = 0;
int j = i;
//拷贝场景:
// 1 3 2 6 7 打印一下路径值 下标路径
Stack path;
Stack copy(path);
while (!copy.Empty())
{
cout << copy.Top() << endl;
copy.Pop();
}
}
int main()
{
func();
return 0;
}
- 若未显式定义,编译器会生成默认的拷贝构造函数。
默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝memcpy
注意
:
在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的
自定义类型是调用其拷贝构造函数完成拷贝的
4**.浅拷贝**:
一个对象修改会影响另一个对象会析构两次,程序会崩溃
解决方式:自己实现深拷贝
注意
:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
- 拷贝构造函数典型调用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
边栏推荐
猜你喜欢
随机推荐
【Verilog】HDLBits题解——验证:阅读模拟
实数取整写入文件(C语言文件篇)
net start mysql 启动报错:发生系统错误5。拒绝访问。
php microtime 封装工具类,计算接口运行时间(打断点)
Apache APISIX 2.15 版本发布,为插件增加更多灵活性
新评论接口——京东评论接口
图像融合SDDGAN文章学习
浅谈程序员的职业操守
使用工作队列管理器(四)
如何免费获得一个市全年的气象数据?降雨量气温湿度太阳辐射等等数据
从器件物理级提升到电路级
【深度学习】高效轻量级语义分割综述
数据库基础知识一(MySQL)[通俗易懂]
The Yangtze river commercial Banks to the interview
15. PARTITIONS「建议收藏」
The common problems in the futures account summary
Win11怎么禁止软件后台运行?Win11系统禁止应用在后台运行的方法
【蓝桥杯选拔赛真题48】Scratch跳舞机游戏 少儿编程scratch蓝桥杯选拔赛真题讲解
链游NFT元宇宙游戏系统开发技术方案及源码
【云原生 · Kubernetes】部署Kubernetes集群