01C++复习.类&对象/构造函数/拷贝构造函数/操作符重载/析构函数
一、C++类 & 对象
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。
1、定义C++ 类
定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。例如,我们使用关键字 class 定义 Box 数据类型,如下所示:
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。您也可以指定类的成员为 private 或 protected。
2、定义C++对象
类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Box 的两个对象:
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
对象 Box1 和 Box2 都有它们各自的数据成员。
3、访问数据成员
类的对象的公共数据成员可以使用直接成员访问运算符 (.) 来访问。为了更好地理解这些概念,让我们尝试一下下面的实例:
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
double volume = 0.0; // 用于存储体积
// box 1 详述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box 2 详述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// box 1 的体积
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的体积:" << volume <<endl;
// box 2 的体积
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的体积:" << volume <<endl;
return 0;
}
需要注意的是,私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。
4、类的成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类的成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
让我们看看之前定义的类 Box,现在我们要使用成员函数来访问类的成员,而不是直接访问这些类的成员:
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void);// 返回体积
};
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。所以可以按照如下方式定义 Volume() 函数:
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
//这里的成员函数getVolume()是一个内联函数
double getVolume(void)
{
return length * breadth * height;
}
};
也可以在类的外部使用范围解析运算符 :: 定义该函数,如下所示:
{
return length * breadth * height;
}
在C++中建立一个类,这个类中肯定会包括构造函数、析构函数、拷贝构造函数和重载赋值操作。
二、类的构造函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
构造函数包括默认构造函数和带参构造函数。
1、默认构造函数
默认构造函数没有返回值,也没有任何参数,下面是一个默认构造函数的实例:
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数
private:
double length;
};
// 构造函数定义
Line::Line(void)
{
cout << "Object is being created" << endl;
}
2、带参构造函数
带参构造函数可以理解为有参数的默认构造函数,这样在创建对象时就会给对象赋初始值,如下面的例子所示:
{
public:
void setLength( double len );
double getLength( void );
Line(double len); // 这是带参构造函数
private:
double length;
};
// 带参构造函数定义
Line::Line( double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
3、使用初始化列表来初始化字段
使用初始化列表来初始化字段:
{
cout << "Object is being created, length = " << len << endl;
}
上面的语法等同于如下语法:
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:
{
....
}
初始化列表初始化和构造函数初始化的区别:
(1)初始化列表初始化,是对类的成员进行显式的初始化,而构造函数初始化是对累的成员进行赋值;
(2)对于内置类型的成员变量,使用初始化列表进行初始化和使用构造函数初始化,在性能和结果上都是一样的。
(3)对与非内置类型的成员变量,因为类类型的数据成员的数据成员对象,在进入函数体之前就已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用一个赋值操作符才能完成(如果并未提供,则使用编译器提供的默认成员赋值行为)。为了避免两次构造,推荐使用类构造函数初始化列表。
但有很多场合必须使用带有初始化列表的构造函数。例如,成员类型是没有默认构造函数的类,若没有提供显示初始化时,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试调用默认构造函数将会失败。再例如const成员或者引用类型的成员,因为const对象或引用类型只能初始化,不能对它们进行赋值。
C++基本内置类型包括算术类型和空类型。算数类型包括整型(整数、字符、布尔)和浮点型。空类型指void类型
三、类的拷贝构造函数
1、拷贝构造函数定义
拷贝构造函数是一种特殊的构造函数,其作用也是为类的成员初始化以及为对象的构造分配存储空间。函数的名称必须和类名称一致,无返回类型,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变。
拷贝构造函数原型如下:
对于一个类X, 如果一个构造函数的第一个参数是下列之一:
const & X;
volatile & X;
const volatile & X;
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数,如下:
X::X(& X, int=1);
X::X(& X, int a=1, int b=2);
2、拷贝构造函数实现
{
public:
Date(int year = 1995,int month = 12,int day = 8) // 带参构造函数
:_year(year) , _month(month) , _day(day) //初始化列表初始化
{
cout << "date()构造函数"<< this << endl;
}
Date(const Date& d)// 拷贝构造函数
:_year(d._year) , _month(d._month) , _day(d._day)
{
cout << "date(&d)" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
四、类的赋值运算符重载
1、重载赋值运算符的定义
重载赋值操作符是一个特别的赋值运算符,通常是用来把已存在的对象赋值给其它相同类型的对象。
重载赋值操作符的原型如下:
2、重载赋值运算符的实现
{
cout << "operator= 赋值运算符重载 " << this << endl;
if (this != &d) // 判断是否自己对自己赋值
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
注意:重载赋值运算符的返回值是一个引用。
3、拷贝构造函数和重载赋值运算符的调用
当类的对象需要拷贝时,复制构造函数将会被调用。以下情况都会调用复制构造函数:
一个对象以值传递的方式传入函数体;
一个对象以值传递的方式从函数返回;
一个对象需要通过另外一个对象进行初始化。
如果对象在声明的同时将另一个已存在的对象赋给它,就会调用拷贝构造函数;如果对象已经存在了,然后再将另一个已存在的对象赋给它,调用的就是重载赋值运算符。
CTest obj;
CTest obj1(obj); // 调用复制构造函数
obj1 = obj; // 调用重载赋值操作符
4、深拷贝(deepcopy)与浅拷贝(shallowcopy)
如果在类中没有显式地声明,那么编译器会自动生成默认的复制构造函数和重载赋值操作符。默认的复制构造函数和赋值运算符进行的都是“shallow copy”,只是简单地复制字段,把值一一赋给要拷贝的值。因此如果对象中含有动态分配的内存,就需要我们自己重写复制构造函数和重载赋值操作符来实现“deep copy”,确保数据的完整性和安全性。
例如:类内成员变量需要动态开辟堆内存,如果实行浅拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,资源重新分配,使对象拥有不同的资源,但资源的内容是一样的,这个过程就是深拷贝;反之,没有重新分配资源,两个对象就有用共同的资源,同时对资源可以访问,就是浅拷贝。
浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
五、类的析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
下面的实例有助于更好地理解析构函数的概念:
{
public:
void setLength( double len );
double getLength( void );
Line(); // 构造函数声明
~Line(); // 析构函数声明
private:
double length;
};
//构造函数定义
Line::Line(void)
{
cout << "Object is being created" << endl;
}
//析构函数定义
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
三法则假如类型有明显地定义下列其中一个成员函数,那么程序员必须连其他二个成员函数也一同编写至类型内,亦即下列三个成员函数缺一不可:
(1)析构函数(Destructor)
(2)拷贝构造函数(copy constructor)
(3)重载赋值运算符(copy assignment operator)
转自https://blog.csdn.net/sinat_33924041/article/details/83621017
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具