文章目录
目录
前言
面向对象编程(Object-Oriented Programming,简称OOP)是一种强大的编程范式,它将数据和操作数据的方法组织成类和对象,使得程序更易于理解、扩展和维护。在C++中,类和对象是OOP的核心概念,为程序员提供了一种结构化的方法来组织和设计代码。本节我们来学习C++的类和对象
一.类的定义
class className
{
// 类体:由成员函数和成员变量组成
};
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分 号不能省略。 比如下面的日期类定义
class Data
{
public:
Data(int year = 1970, int month = 1, int day = 1);
Data(const Data& d);
~Data();
void Print();
int GetMonthDay();
private:
int _year;
int _month;
int _day;
};
一个类基本包含以下
-
成员变量:类包含成员变量(也称为数据成员),用于存储对象的状态或属性。
-
成员函数:类包含成员函数,用于执行特定的操作或实现对象的行为。这些函数也被称为方法。
-
访问控制:类定义了访问成员变量和成员函数的权限。C++提供了三种访问权限:public(公有)、private(私有)、protected(受保护)。
-
构造函数和析构函数:构造函数用于初始化对象的状态,而析构函数用于清理对象在程序执行结束时分配的资源。
二.类的封装
1.访问限定符
public(公有):
- 所有的类成员在类的外部都是可见的和可访问的。
- 公有成员可以在类外部通过对象访问,也可以在派生类中继承并访问。
private(私有):
- 类的外部无法直接访问私有成员,只有在类的内部才能访问。
- 私有成员对外部代码隐藏,提高了封装性。
protected(受保护):
- 类的外部无法直接访问受保护的成员,但派生类可以继承和访问这些成员。
- 受保护成员在继承时扮演了一定的角色,允许子类访问基类的受保护成员。
注意:在c++中由于兼用c语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来 定义类,但默认成员函数和变量是public,而class定义的类的成员变量和函数则默认为private
2.封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互。 封装本质上是一种管理,让用户更方便使用类。
一般我们通过使用访问限定符实现封装,比如上方代码中的
私有化成员变量,这样可以避免外部直接修改类的内部状态,确保数据的安全性。除此还有私有化成员函数类,私有成员函数只能在类的内部被调用,外部无法直接调用。这样可以将类的实现细节封装在内部,不暴露给外部。
三.类的简单特性
1.作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
我们一般将类的声明和定义分开,即在外实现成员函数定义,比如上述代码的成员函数的实现
Data::Data(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
if (_year < 0 || _month<0 || _day < 0 || _month>12 || _day>GetMonthDay())
{
assert(false);
}
}
Data::Data(const Data& d)
{
......
}
.
.
.
在外实现函数需使用域作用操作符指名
2.实例化
用类类型创建对象的过程,称为类的实例化
1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没 有分配实际的内存空间来存储它;
2. 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
1.类的大小计算
一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐注意空类的大小,和计算结构体的大小类似,成员函数不占空间,它们单独存放在其他地方,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。比如上面日期类的大小
空类的大小是一个字节
3.this指针
C++编译器给每个“非静态的成员函数“增加了一个隐藏 的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
比如这个日期类的构造方法
Data::Data(int year, int month, int day)
{
_year=year;
_month=month;
_day=day;
if (_year < 0 || _month<0 || _day < 0 || _month>12 || _day>GetMonthDay())
{
assert(false);
}
}
其实是
Data::Data(int year, int month, int day)
{
this-> _year=year;
this->_month=month;
this->_day=day;
if (this->_year < 0 || this->_month<0 || this->_day < 0 || this->_month>12 || this->_day>GetMonthDay())
{
assert(false);
}
}
在用对象调用成员函数时都会隐式的传一个this指针
四.构造函数和析构函数
1.构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
比如这个Data类的构造函数
全参构造
Data::Data(int year, int month, int day)
{
_year=year;
_month=month;
_day=day;
if (_year < 0 || _month<0 || _day < 0 || _month>12 || _day>GetMonthDay())
{
assert(false);
}
}
全缺省构造
Data::Data(int year=1970, int month=1, int day=1)
{
_year=year;
_month=month;
_day=day;
if (_year < 0 || _month<0 || _day < 0 || _month>12 || _day>GetMonthDay())
{
assert(false);
}
}
当我
如果我不写构造函数就会使用默认构造
C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,编译器生成默认的构造函数会对自定类型成员调用的它的默认构造函数。对于内置类型不做处理
2.析构函数
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
5. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
比如这个Data类的析构函数
Data::~Data()
{
_year = 1970;
_month = 1;
_day = 1;
}
对这个类写析构函数实际上没有任何意义,但在一些使用申请内存需要释放内存时比如这个
void TreeNode::TreeDestroy()
{
if (this == nullptr)
return;
_left->TreeDestroy();
_right->TreeDestroy();
delete(this);
}
AVLTree::~AVLTree()
{
_root->TreeDestroy();
_root = nullptr;
}
AVLTree类的析构函数调用了Treenode类的销毁来销毁所有节点,然后将头节点置为空