C++——类和对象
1:类的定义
1.1:类的创建
class date
{
public:
void Days(int year, int month, int day)
{
_year = year;
_month = _month;
_day = day;
}
void print()
{
std::cout << _year << "/" << _month << "/" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
注:
①:class的创建与C语言中的struct创建类似
②:在C++中,对struct做了升级,除了能像class一样封装函数外,只需要结构体名称就可以代表类型,如下:
struct ListNodeCPP
{
void Init(int x)
{
next = nullptr;
val = x;
}
ListNodeCPP* next;//C语言中必须以 struct ListNodeCPP * next 这种方式定义
int val;
};
1.2:访问限定符
class中存在访问限定:private、public、protected;默认不加访问限定符时,class 默认所有类中的变量都是 private,外界无法访问;而struct 默认所有结构体中的变量都是 public,外界可以直接访问。
1.3:类域
类定义了一个新的作用域——类域
对于新手(我)而言,代码一般是从上往下执行的按顺序执行的,为什么类中,最底下定义的成员能够在函数中被使用呢?
答:对于类而言,先检查类名;再检查类的成员;最后才是类的成员函数。
2:类的实例化
了解实例化前,需要知道一个概念:类的定义是不占用空间的,它就是一个房子的模型图,里面规定了各种各样的东西,只有当把他实例化了以后,实例化的对象才会占用一定空间,就像将房子的模型实际建造了出来。
同时一个类可以实例化出多个对象,就像一个房屋模型可以建造很多间房屋一样。
每个对象相互独立,没有任何关联。
class date
{
void House(int height, int area, int furniture)
{
_house_height = height;
_house_area = area;
_house_furniture_num = furniture;
}
void print()
{
std::cout << _house_height << std::endl;
std::cout << _house_area << std::endl;
std::cout << _house_furniture_num << std::endl;
}
int _house_height;
int _house_area;
int _house_furniture_num;
};
int main()
{
date h1;//房子1
date h2;//房子2
return 0;
}
2.1对象大小
实例化对象的大小包含类中所有成员变量,但不包含成员函数(成员函数会存储在一个单独的区域),如果一定要存储的话,那只能是成员函数的指针。
在知道了这一点后,对象的大小就和在学习C语言时所讲的sizeof()求结构体大小的对齐规则是一致的了,这里不再过多追叙。
注:我们知道对齐规则会导致系统空间的浪费,那么为什么一定需要浪费这一部分空间呢?不浪费不行吗?
答:系统每次会以4个字节访问,倘若前一个为 char,后一个为 int,如果不按对其规则来,数据读取完毕后还得进行拼接操作,极其繁琐,其空间如图所示:
因此需要对齐操作来使得系统在读取数据时更为方便快捷。
3:this指针
以下代码为例:
class House
{
public:
void house(int height, int area, int furniture)
{
_house_height = height;
_house_area = area;
_house_furniture_num = furniture;
}
void print()
{
std::cout <<"房子高度:"<< _house_height << std::endl;
std::cout <<"占地面积:"<< _house_area << std::endl;
std::cout <<"家具数量:"<< _house_furniture_num << std::endl;
}
private:
int _house_height;
int _house_area;
int _house_furniture_num;
};
int main()
{
House h1;
House h2;
std::cout << "房子h1相关信息" << std::endl;
h1.house(20, 100, 30);
h1.print();
std::cout << std::endl;
std::cout << "房子h2相关信息" << std::endl;
h2.house(15, 150, 50);
h2.print();
return 0;
}
为了使类名 h1 、h2 正确调用函数,类的成员函数会默认在第一个位置增加一个当前类类型的指针,可以理解为,在传参的过程中,把类的地址也作为实参传递给形参中,这样函数就能够根据对应的地址来进行后续操作。
注:
①:不能将this指针写进实参中,因为系统会默认传递。
②:在函数体内可以使用this指针
4:类的默认成员函数
定义:默认成员函数就是用户没有显式实现,编译器会⾃动⽣成的成员函数称为默认成员函数。
我们需要带着以下两个疑问去学习默认构造函数:
①:我们不写时,编译器默认生成的函数行为是什么?是否满足我们的需求。
②:当编译器默认生成的函数不满足我们的需求,我们需要自己实现时,那么如何自己实现呢?
4.1:构造函数
非默认(一般)构造函数的编写:
class stack //stack为类名
{
public:
//构造函数
stack(int* arr , int capacity, int top )
{
_arr = arr;
_capacity = capacity;
_top = top;
}
private:
int* _arr;
int _capacity;
int _top;
};
int main()
{
stack st1(nullptr,4,0);//非默认构造函数,在实例化对象时,需要将参数传入
return 0;
}
根据上述代码可知构造函数具有以下特点:
①:函数名类名相同
②:无返回值
③:对于非默认构造函数而言,在实例化对象时需要传参,参数个数与缺省的参数数量有关。
默认构造函数有三个:在类中没有写构造函数、无参构造函数、全缺省构造函数。
//默认构造函数——不传参
class stack
{
public:
stack()
{
_arr = nullptr;
_capacity = 0;
_top = 0;
}
private:
int* _arr;
int _capacity;
int _top;
};
int main()
{
stack st1;
return 0;
}
//默认构造函数——全缺省
class stack
{
public:
stack(int* arr = nullptr, int capacity = 0, int top = 0)
{
_arr = arr;
_capacity = capacity;
_top = top;
}
private:
int* _arr;
int _capacity;
int _top;
};
int main()
{
stack st1;
return 0;
}
根据上述代码,可以得出构造函数的另外一个特点:
④:三种默认构造函数在类中只能存其一,不能同时存在。
///
在讲述最后一点特点前,需要知道一点,特别是对于初学者(我)而言。
什么是内置类型?
答:整数类型(int,short,long)、字符类型char、浮点型float、指针类型等都是内置类型,编译器都认识他们,在编译时能够做出反应。
什么又是自定义类型?
答:结构体(struct)、类(class)、枚举、联合等,这类都是自定义类型,编译器在编译过程中是不认识他们的,无法做出反应。
有了上述的了解后,再来认识构造函数的最后一个特点:
⑤:当我们不写默认构造函数时,编译器生成的默认构造函数,对内置类型成员变量的初始化没有要求,即初始化为何值取决于编译器。如下图:
对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化,如果该成员变量没有默认构造函数,那么就会报错。
其次,假设现存在类A(A中有构造函数),类B(B中的成员有 类A的实例化对象d),当我们在主函数创建 类B的实例化对象时,B中的 成员类A的实例化对象 d 为自动调用A中的构造函数进行初始化。如下图:
类queue中定义个两个自定义类stack,在创建实例化对象q时,会对其成员变量调用他们自己的构造函数,结果如下:
4.2:析构函数
析构函数的编写:
class stack
{
public:
stack(int n = 4)
{
_arr = (int*)malloc(sizeof(int) * n);
assert(_arr);
_capacity = n;
_top = 0;
}
//析构函数
~stack()
{
free(_arr);
_arr = nullptr;
_capacity = 0;
_top = 0;
}
private:
int* _arr;
int _capacity;
int _top;
};
class queue
{
public:
private:
stack q1;
stack q2;
};
int main()
{
stack st;
queue q;
return 0;
}
析构函数具有以下特点:
①:析构函数名是在类名前加上 ~
②:无参数无返回值
③:一个类只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
④:对象生命周期结束时,系统会自动调用析构函数
⑤:跟构造函数类似,我们不写时,编译器自动生成的析构函数对内置类型成员不做处理,自定义类型会调用他的析构函数
⑥:还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数
⑦:如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如果默认生成的析构就可以⽤,也就不需要显示写析构,如MyQueue;但是有资源申请时,⼀定要自己写析构,否则会造成资源泄漏,如Stack。
⑧:⼀个局部域的多个对象,C++规定后定义的先析构。
4.3:拷贝构造
拷贝构造代码如下:
拷贝复制——浅拷贝//值拷贝
class date
{
public:
//构造函数
date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造 d是d1的别名 this 指针指向 d2
date(date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
//也可以写成这种形式,对于新人来说方便理解
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
void print()
{
std::cout << _year << "/" << _month << "/" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1(2024, 10, 13);
date d2(d1);
date d2 = d1; //这两种拷贝方式都可以
d2.print();
return 0;
}
根据上述代码,可以得出拷贝构造有如下特点:
①:拷贝构造时使用同类对象初始化创建对象,拷⻉构造函数的第⼀个参数必须是类类型对象的引⽤。
②:C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这⾥⾃定义类型传值传参和传值返回都会调⽤拷⻉构造完成。
③:若未显示定义拷贝构造,编译器会自动生成拷贝构造函数。⾃动生成的拷贝构造对内置类型成
class stack
{
public:
stack(int n = 4)
{
_arr = (int*)malloc(sizeof(int) * n);
assert(_arr);
_capacity = n;
_top = 0;
}
//深拷贝,需要开辟一块新的内存空间,将原来的值 copy到新的空间内。
stack(stack& st)
{
_arr = (int*)malloc(sizeof(int) * st._capacity);
assert(_arr);
memcpy(_arr, st._arr, sizeof(int) * st._top);
_capacity = st._capacity;
_top = st._top;
}
~stack()
{
free(_arr);
_arr = nullptr;
_capacity = 0;
_top = 0;
}
private:
int* _arr;
int _capacity;
int _top;
};
class queue
{
public:
private:
stack q1;
stack q2;
};
int main()
{
//stack st1;
//stack st2 = st1;
//queue q1;
//queue q2 = q1;
return 0;
}
问:为什么要用到深拷贝呢?
答:如果采用之值拷贝/浅拷贝,对成员进行一个字节一个字节的拷贝,假设成员中包含资源调用(例如通过动态内存函数开辟的空间),那么定义两个实例化的对象后,这两个对象会指向同一块空间,这显然是不可取的,因此需要进行深拷贝。
④:拷贝构造与默认构造类似,当对象A中包含拷贝构造,对象B中包含成员A,那么将B拷贝构造给C时,B中的成员A会调用他自己的拷贝构造。
注:小贴士——类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。
⑤:传值返回会产生一个临时对象调用拷贝构造,而传引用返回,返回的是返回对象的别名,没有调用拷贝构造,能够提升效率。但是,如果返回对象是一个当前函数局部域的局部对象,函数结束时就销毁了,那么引用时有问题的,这时的引用相当于一个野引用。
注:把局部函数的局部对象转换成全局变量就可以用传引用返回,例如用 static 去作用局部对象。
4.4:赋值运算符重载
赋值运算符用于完成两个已经存在的对象直接的拷贝赋值。
4.4.1:运算符重载
②:运算符重载是具有特殊名字的函数,他的名字是由operator和后⾯要定义的运算符共同构成。因此,运算符重载的代码如下:
date d1(2024, 9,19 );
date d2(1997, 5, 5);
//这两种都可以
d1.operator<(d2);
d1 < d2;
③:运算符重载作为成员函数时,参数比运算对象少一个,对于二元运算符,左侧运算对象传给第一个参数,右侧运算对象传给第二个参数,因此上述代码中,operator< 函数的实现方式如下:
bool date::operator<(date& d)//d1传递给隐含的 this 指针 而d作为d2的别名
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year && _month < d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day < d._day)
{
return true;
}
return false;
}
4.4.2:赋值运算符重载
赋值运算符重载的实现代码:
date d1(2024, 9,19 );
date d2(1997, 5, 5);
date d3(d1);//这个是拷贝构造,⽤于⼀个对象拷⻉初始化给另⼀个要创建的对象
d1 = d2;//这个是赋值运算符重载,⽤于完成两个已经存在的对象直接的拷⻉赋值
可以通过调试证明,在调试过程中,dated3(d1)会调用拷贝构造,而 d1=d2不会。
赋值运算符重载的特点:
①:赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。
②:有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了⽀持连续赋值场景。注:注意是否存在野引用。
5:部分代码
date.h:
#pragma once
#include <iostream>
#include <assert.h>
#include <stdbool.h>
class date
{
public:
//构造函数: 没有返回类型和返回值,与类名一致,参数与类成员尽量不一致
date(int year, int month, int day)
{
if (!checkdate(year, month, day))
{
std::cout << "日期输入有误,请重新输入" << std::endl;
}
_year = year;
_month = month;
_day = day;
}
//析构函数:同样没有返回类型和返回值,在类名前面加上 ~ ,
~date()
{
_year = 0;
_month = 0;
_day = 0;
}
//拷贝构造:同样没有返回类型和返回值,形参在同类的类名后加上& 表示拷贝构造
date(const date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void print();
bool operator<(date& d);
private:
int _year;
int _month;
int _day;
};
date.cpp:
#define _CRT_SECURE_NO_WARNINGS
#include "date.h"
bool date::operator<(date& d)
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year && _month < d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day < d._day)
{
return true;
}
return false;
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include "date.h"
int main()
{
date d1(2024, 9,19 );
date d2(1997, 5, 5);
date d3(d1);
d1 = d2;
return 0;
}
原文地址:https://blog.csdn.net/m0_51952310/article/details/142172635
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!