当前位置: 首页 > ds >正文

C++:类和对象(中)

        前面已经初步认识了C++的类和对象。简单来说,类相当于一个壳子,将成员函数和成员变量封装起来。如果你觉得C++中的类不过如此,只是把C语言中的结构体多加了一个成员函数,那你就大错特错了。本篇文章,我们将学习类中的默认成员函数。

目录

一、类的默认成员函数

 1.1、默认成员函数的概念

1.2、默认成员函数的分类

二、构造函数

2.1、构造函数的概念(干什么用的?)

2.2、构造函数的原型(语法形式)

2.3、构造函数的注意事项(怎么用)

2.4、默认构造函数

三、析构函数

3.1、析构函数的概念

3.2、析构函数的语法形式

3.3、注意事项

四、拷贝构造函数

4.1、拷贝构造函数的基本概念

4.2、基本语法

4.3、注意事项

五、赋值运算符重载

5.1、运算符重载

5.2、赋值运算符重载


一、类的默认成员函数

 1.1、默认成员函数的概念

        在类中,如果你没有显示定义某些成员函数(就是你没有写出来的某些函数),编译器会自动生成相关的函数,成为默认成员函数。

1.2、默认成员函数的分类

        共有六种:构造函数、析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值预算符。(最后两个函数为C++11引入的)

二、构造函数

2.1、构造函数的概念(干什么用的?)

        构造函数是特殊的成员函数,其作用是对象实例化时初始化对象。说人话,构造函数就是用来给你用类创建的对象初始化用的。

2.2、构造函数的原型(语法形式)

classname( 参数 )
{//…………
}

注意:构造函数的名字不能随便取,构造函数的名字必须是类的名字!!!

2.3、构造函数的注意事项(怎么用)

(1)构造函数的名字必须是类的名字,重要的事情要多说几遍!

(2)构造函数无返回值,连前面的void也不能写。因为不返回返回值也是一种返回值;

(3)构造函数可以重载;

(4)对象实例化时,系统会自动调用对应的构造函数

下面举个例子:

using namespace std;
class student
{
private:string _name;int _number;int _age;
public:student(){_name = "张三";_number = 2023001;_age = 18;}//构造函数重载student(string name, int number, int age){_name = name;_number = number;_age = age;}//打印学生信息void StudentPrint(){cout << "学生姓名:" << _name << endl;cout << "学生学号:" << _number << endl;cout << "学生年龄:" << _age << endl;}
};
int main()
{student s1;student s2("李四" ,2023005 ,19);s1.StudentPrint();s2.StudentPrint();return 0;
}

注意,在你用类创建对象(实例化)的时候,编译器会自动调用构造函数,所以,构造函数的传参直接在对象后面加个括号传。比如:student s2("李四" ,2023005 ,19);

(5)只有你自己不写构造函数的时候,编译器才会替你写,一旦你自己编写构造函数,编译器就不再生成构造函数了;

(6)对于编译器自己生成的构造函数,是有缺陷的:对内置类型成员变量的初始化没有要求。换句话说,是否初始化时不确定的,主要看编译器的种类。

2.4、默认构造函数

        把编译器自己生成的构造函数,我们自己写的无参构造函数,全缺省构造函数统一称为默认构造函数。主包这里概括一下,就是不用你传参构造函数也能运行的就是默认构造函数

这三个默认构造函数同时只能出现一个,不能同时存在。

三、析构函数

3.1、析构函数的概念

        析构函数也是一个特殊的成员函数,当对象声明周期结束时,自动执行必要的清理工作。主要是清理对象在生命周期内申请的内存等资源,防止泄露。

3.2、析构函数的语法形式

~classname()
{//…………
}

析构函数的名字是类的名字前加上一个~ ,必须是这样!!!

3.3、注意事项

(1)析构函数的名字必须是 ~ 加上类的名字;

(2)析构函数没有返回值,void也不能写;

(3)析构函数不能接收参数;

(4)析构函数在一个类中只能有一个,不能重载;

(5)对象生命周期结束时,系统会自动调用析构函数;

(6)对于系生成的默认构造函数,对内置类型不做处理;

(7)如果类中没有申请资源,那么析构函数可以不写,直接让编译器自己生成好了;

(8)一个局部域的多个对象,C++规定后定义的先析构。

下面通过栈来演示一下析构函数:

typedef int StackDataType;
class Stack
{
private:StackDataType* _arr;int _top;int _capacity;
public:Stack(){_arr = nullptr;_top = _capacity = 0;}//入栈void StackPush(StackDataType x){if (_top == _capacity){int newCapacity = _capacity == 0 ? 4 : 2 * _capacity;StackDataType* tmd = (StackDataType*)realloc(_arr, sizeof(StackDataType) * newCapacity);if (tmd == nullptr){perror("realloc fail!");exit(1);}_arr = tmd;_capacity = newCapacity;}_arr[_top++] = x;}//析构函数~Stack(){free(_arr);_arr = nullptr;_top = _capacity = 0;}
};

四、拷贝构造函数

4.1、拷贝构造函数的基本概念

        如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫拷贝构造函数。说人话,拷贝构造函数是用一个已存在的对象(拷贝源)来初始化另一个对象(拷贝对象)。

4.2、基本语法

classname( classname& other)
{//…………
}

拷贝构造函数的名字必须是类的名字,且第一个参数必须是类的引用!!!

4.3、注意事项

(1)拷贝构造函数是构造函数的重载;

(2)拷贝构造函数的第一个参数必须是当前类类型对象的引用,后面的参数必须有缺省值;

(3)C++规定自定义类型对象进行拷贝行为必须调用拷贝构造函数,所以自定义类型的传值传参和传值返回都会调用拷贝构造函数;

(4)若为定义显示拷贝构造函数,编译器会自己生成一个拷贝构造函数;

(5)如果一个类显式实现了析构并释放了资源,那么他就需要显式拷贝构造函数,否则就不需要;

(6)传值返回会产生一个临时对象的拷贝构造,传值引用返回,返回的是对象的别名,没有产生拷贝。

下面举个例子(以栈为例):

Stack(const Stack& st)
{_arr = (StackDataType*)malloc(sizeof(StackDataType) * st._capacity);if (_arr == nullptr){perror("malloc fail!");exit(1);}_top = st._top;_capacity = st._capacity;memcpy(_arr, st._arr, sizeof(StackDataType) * _top);
}

调用拷贝构造函数的用法也很简单:

Stack s1;
Stack s2 = s1;
Stack s3(s1);   //两种方式均可以

五、赋值运算符重载

5.1、运算符重载

        主包这里不想copy这个冗杂不好理解的定义了,主包要自己定义。运算符重载就是赋予C++运算符(如 +, -, =, ==, [] 等)操作自定义类型(类或结构体)的能力,让它们用起来和内置类型(如 int, double)一样直观。

什么意思?比如一开始我们创建的Student这个类,你拿两个学生相减 s1 - s2 算怎么回事?编译器无法理解,这时候你可以通过特殊手段赋予减号一定的含义,比如:两个学生的成绩之差。

class Student
{
private:string _name;int _number;int _age;float score;
public:float operator(Student& s1){return _score - s1._score;}
};

下面有几个注意事项:

(1)运算符重载是一个具有特殊名字的函数,它的名字由 operator 和后面的运算符组成;

(2)如果一个重载运算符是类的成员函数,则它的第一个运算对象默认传给 this 指针,所以在类里面的重载运算符定义时可以少写一个参数;

(3)运算符重载不改变它的优先级和结合性;

(4) .*   ::    sizeof   ?:    .    这五个运算符不能重载;

(5)重载运算符至少要给一个类型的参数;

(6)++ 有前置 ++a和 后置 a++,为了区分这两个的重构,C++规定,后置++需要增加一个int形参。

int operator++()
{cout << " ++a" << endl;return _age+1;
}
//后置++,需要跟一个int形参
int operator++(int)
{cout << " a++" << endl;return _age+1;
}

5.2、赋值运算符重载

        主包老毛病又犯了,不想看那些冗长且复杂的定义了,主包要自己定义。赋值运算符重载 (operator=) 让你能像内置类型(如 int)一样,用等号 (=) 将一个对象的值“拷贝”给另一个已存在的同类型对象。

        赋值运算重载符是一个默认成员函数,用于完成两个已经存在的对象的拷贝赋值。

student& operator=(const student& s1)
{if (this != &s1){_age = s1._age;_name = s1._name;_number = s1._number;_score = s1._score;}return *this;
}

注意,这里加一个 if 条件是为了处理有些人闲的让自己给自己赋值。返回*this 的引用是为了处理连续赋值的情况,s1 = s2 = s3 ,因为如果不返回,那么s2的值就无法得到,返回引用是为了避免再次创建临时空间调用拷贝构造函数,提高效率。

注意事项:

(1)赋值运算符重载规定必须重载为成员函数;

(2)有返回值,推荐写成当前类类型引用,提高效率;

(3)没有显示实现赋值运算符重载时,编译器会自己生成一个默认赋值运算符重载,对内置类型成员变量,对内置类型成员变量完成值拷贝(浅拷贝)。

        

http://www.xdnf.cn/news/20221.html

相关文章:

  • 在线测评系统---第n天
  • 执行select * from a where rownum<1;,数据库子进程崩溃,业务中断。
  • LabVIEW--二维数组、三维数组、四维数组
  • Pydantic模型验证测试:你的API数据真的安全吗?
  • Selenium 页面加载超时pageLoadTimeout与 iframe加载关系解析
  • 静态电流Iq 和 ICONT_MAX
  • GD32入门到实战32--产品配置参数存储方案 (NORFLASH)
  • rabbitmq 入门知识点
  • Go 自建库的使用教程与测试
  • 脑卒中目标检测含完整数据集
  • CSS 优先级详解:理解选择器权重和层叠规则
  • 鸿蒙NEXT动画开发指南:组件与页面典型动画场景解析
  • 【C++练习】06.输出100以内的所有素数
  • Java 攻克 PDF 表格数据提取:从棘手挑战到自动化实践
  • 深度学习——数据增强
  • devcpp 5.11的详细安装步骤
  • 上位机知识篇---conda run
  • 文华财经多空提示指标公式 变色K线多空明确指标 文华wh6赢顺多空买卖提示指标
  • 平衡车 --- 测量减速比 M法、T法测速
  • 当公司在你电脑上安装了IP-guard,你必须知道的事
  • 【面试题】你在项目中做过哪些相关性优化?
  • 【sft技巧】模型微调技巧
  • 零样本学习与少样本学习
  • 下载及交叉编译libconfig
  • 惊爆!耐达讯自动化RS485转Profinet,电机连接的“逆天神器”?
  • C++17无锁编程实战
  • 2025数学建模国赛A题思路首发!
  • 【赛题已出】2025高教社杯全国大学生数学建模竞赛ABCDE赛题已发布!
  • 阵列信号处理之均匀面阵波束合成方向图的绘制与特点解读
  • 从零开始学大模型之预训练语言模型