C++类与对象-六大成员函数

默认成员函数就是用户没有显式实现,编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个空类编译器会默认⽣成8个默认成员函数。本文只介绍其中6个,C++11增加两个函数见后续博客。

目录

一、构造函数

1.1 概念

1.2 特性

1.3 使用举例

1.4 初始化列表

1.4.1 概念

1.4.2 特性

1.4.3 使用

二、析构函数

2.1 概念

2.2 特性

2.3 使用

三、拷贝构造

3.1 概念

3.2 特性

3.3 使用

四、赋值运算符重载

4.1 特性

4.2 使用

4.3 与拷贝构造的区别

五、取地址操作符重载

5.1 cosnt

5.2 重载

六、操作符重载补充

6.1 前置++与后置++

6.2 流操作符重载


一、构造函数

1.1 概念

构造函数是一个特殊的成员函数,名字与类名相同,不写返回值。创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。

1.2 特性

  1. 函数名与类名相同。⽆返回值。 (返回值啥都不需要给,也不需要写void)
  2. 构造函数可以重载。分为无参构造函数,带参构造函数,全缺省构造函数
    class Date
    {
    public:// 1. ⽆参构造函数Date(){_year = 1;_month = 1;_day = 1;}// 2. 带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}//3. 全缺省构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
    private:int _year;int _month;int _day;
    };
  3. 如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦用户显式定义编译器将不再⽣成。
  4. 默认构造函数:⽆参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数。这三个函数有且只有⼀个存在,不能同时存在。不传实参就可以调⽤的构造就叫默认构造。
  5. 用户不写,编译器默认⽣成的构造,对内置类型成员变量的初始化是不确定的,看编译器。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错

1.3 使用举例

#include<iostream>
using namespace std;
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1; // 调⽤默认构造函数Date d2(2005, 6, 8); // 调⽤带参的构造函数d1.Print();d2.Print();return 0;
}

1.4 初始化列表

1.4.1 概念

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值表达式

class Date
{
public:
Date(int year, int month, int day): _year(year), _month(month), _day(1)
{}
private:int _year;int _month;int _day;
};

 如何理解初始化列表?

借助C语言的特性:在private中可以看作是变量的声明,如果用之前在函数体中的构造,我们可以发现缺少了变量的定义。事实上如果我们不写初始化列表,编译器会自动生成初始化列表。

由此可知,初始化列表其实是每个成员变量定义初始化的地⽅

为什么会有初始化列表?

有些变量比如:const变量,还有引用----要求在定义时初始化,仅仅依靠之前在函数体内初始化的方式是无法对这些变量进行初始化的,所以初始化列表至关重要。

1.4.2 特性

  1. 引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始化,否则会编译报错。
  2. C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的成员使⽤的。
  3. 在初始化列表的成员-显式写;不在初始化列表的成员:a.声明的地方有缺省值用缺省值 b.没有缺省值:内置类型看编译器处理,自定义类型调用默认构造。如果没有默认构造会报错。
  4. 初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。
    /*试分析下列程序*/
    #include<iostream>
    using namespace std;
    class test
    {
    public:test(int a):_t1(a), _t2(_t1){}void Print() {cout << _t1 << " " << _t2 << endl;}
    private:int _t2 = 2;int _t1 = 2;
    };
    int main()
    {test t(1);t.Print();
    }

    由于初始化列表是按照声明顺序初始化的,先初始化_t2,此时_t1没有初始化为随机值,所以结果如下

#include<iostream>
using namespace std;
class Test
{
public:Test(int a):_t(a){}
private:int _t;
};
class Date
{
public:Date(int& x, int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day)/*, _t(12), _ref(x), _n(1)*/{}void Print() const{cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;Test _t; // 没有默认构造int& _ref; // 引⽤const int _n; // const
};
int main()
{int i = 0;Date d1(i);d1.Print();return 0;
}

1.4.3 使用

由于每个构造函数都有初始化列表,建议以后都采用初始化列表的方式进行构造函数的初始化,使用中建议建议声明顺序和初始化列表顺序保持⼀致。

二、析构函数

2.1 概念

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

2.2 特性

  1. 析构函数名是在类名前加上字符~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 析构函数不能重载。编译器自动调用
  5. 不写编译器会自动生成默认析构函数

2.3 使用

  1. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
    Date类;
  2. 有资源申请时,必须要写,否则会造成资源泄漏,比如Stack类。
#include<iostream>
using namespace std;class Stack
{
public:Stack(size_t capacity = 3){_arr = (int*)malloc(sizeof(int) * capacity);if (_arr == NULL){perror("malloc");exit(1);}_capacity = _capacity;_size = 0;}~Stack(){if (_arr){free(_arr);_arr = NULL;_capacity = _size = 0;}}private:int* _arr;int _capacity;int _size;
};

三、拷贝构造

3.1 概念

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存
在的类类型对象创建新对象时由编译器自动调用。

为什么必须是引用?

由C语言相关知识我们可以知道:传值调用实际上是传了当前变量的拷贝。所以如果拷贝构造函数的参数为传值调用,会引发无限的调用递归。所以要用传引用调用

3.2 特性

  1. 拷⻉构造函数是构造函数的⼀个重载。
  2. C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥⾃定义类型传值传参和传值返回都会调⽤拷⻉构造完成。
  3. 若未显式定义拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造。

浅拷贝:默认的浅拷贝仅复制指针的地址,两个对象共享同一块动态内存。如果一个对象释放了内存,另一个对象会出现悬空指针的问题。适用于对象不包含动态内存或对动态内存的共享是可接受的情况。
深拷贝:通过手动实现深拷贝构造函数和赋值运算符,确保每个对象都有自己独立的动态内存,避免悬空指针和共享内存的问题。适用于对象包含动态内存且需要独立内存空间的情况。

3.3 使用

有以下三种情况

  1. 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的拷⻉构造就可以完成需要的拷⻉,所以不需要我们显⽰实现拷⻉构造。
  2. 像Stack这样的类,虽然也都是内置类型,但是指向了资源,编译器⾃动⽣成的拷⻉构造完成的浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉
  3. 像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器自动生成的拷贝构造会调⽤Stack的拷⻉构造,也不需要我们显⽰实现MyQueue的拷⻉构造。
#include<iostream>
using namespace std;class Stack
{
public:Stack(size_t capacity = 3){_arr = (int*)malloc(sizeof(int) * capacity);if (_arr == NULL){perror("malloc");exit(1);}_capacity = _capacity;_size = 0;}~Stack(){if (_arr){free(_arr);_arr = NULL;_capacity = _size = 0;}}Stack(const Stack& s){_arr = (int*)malloc(sizeof(int) * s._capacity);if (_arr == NULL){perror("malloc");exit(1);}_capacity = s._capacity;_size = s._size;memcpy(_arr, s._arr, sizeof(int) * s._size);}private:int* _arr;int _capacity;int _size;
};

注意事项:

传值返回会产⽣⼀个临时对象调⽤拷⻉构造。

传值引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。

但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。

四、赋值运算符重载

4.1 特性

  1. 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引⽤,否则会传值传参会有拷⻉。
  2. 有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了支持连续赋值场景。
  3. 没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认构造函数类似,对内置类型成员变量会完成浅拷⻉,对⾃定义类型成员变量会调⽤他的拷⻉构造。

4.2 使用

与拷贝构造函数使用相同,分为三类

  1. 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的赋值运算符重载就可以完成需要的拷⻉,所以不需要我们显⽰实现赋值运算符重载。
  2. 像Stack这样的类,虽然也都是内置类型,但是指向了资源,编译器⾃动⽣成的赋值运算符重载完成的浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉。
  3. 像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器⾃动⽣成的赋值运算符重载会调⽤Stack的赋值运算符重载,也不需要我们显⽰实现MyQueue的赋值运算符重载。
#include<iostream>
using namespace std;class Stack
{
public:Stack(size_t capacity = 3){_arr = (int*)malloc(sizeof(int) * capacity);if (_arr == NULL){perror("malloc");exit(1);}_capacity = _capacity;_size = 0;}~Stack(){if (_arr){free(_arr);_arr = NULL;_capacity = _size = 0;}}Stack(const Stack& s){_arr = (int*)malloc(sizeof(int) * s._capacity);if (_arr == NULL){perror("malloc");exit(1);}_capacity = s._capacity;_size = s._size;memcpy(_arr, s._arr, sizeof(int) * s._size);}Stack& operator=(const Stack& s){//检查是否自己给自己赋值if (this != &s){_arr = (int*)malloc(sizeof(int) * s._capacity);if (_arr == NULL){perror("malloc");exit(1);}_capacity = s._capacity;_size = s._size;memcpy(_arr, s._arr, sizeof(int) * s._size);}}private:int* _arr;int _capacity;int _size;
};
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}// d1 = d2 表达式的返回对象应该为d1 ,也就是*thisreturn *this;}
private:int _year;int _month;int _day;
};

4.3 与拷贝构造的区别

  1. 赋值重载完成两个已经存在的对象直接的拷⻉赋值
  2. 拷⻉构适用于⼀个对象拷⻉初始化给另⼀个要创建的对象(还没创建)
int main()
{Date d1(2024, 7, 5);Date d2(d1);//拷贝构造Date d3(2024, 7, 6);d1 = d3;//赋值运算符重载Date d4 = d1;//拷贝构造return 0;
}

五、取地址操作符重载

5.1 cosnt

  • 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后⾯。
  • const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。
    //以Date类为例
    //this指针由 Date* const this 变为 const Date* const this
    class Date
    {
    public:void Print() const{cout << _year << "-" << _month << "-" << _day << endl;}
    private:int _year;int _month;int _day;
    };

5.2 重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动⽣成的就足够使用了,不需要去显⽰实现。

六、操作符重载补充

具体应用实现详见:

6.1 前置++与后置++

重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。所以C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分。

6.2 流操作符重载

重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了 对象<<cout,不符合使⽤习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/877323.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索LLM编程新纪元:AI赋能的编程之旅

探索LLM编程新纪元&#xff1a;AI赋能的编程之旅 引言 在人工智能&#xff08;AI&#xff09;的浪潮中&#xff0c;大型语言模型&#xff08;LLM&#xff09;作为自然语言处理&#xff08;NLP&#xff09;领域的璀璨明珠&#xff0c;正逐步渗透到编程的每一个角落。从代码自动…

【C#】文件流和文本处理

1. 文件流的基本概念 文件流是C#中处理文件读写的抽象&#xff0c;它提供了对文件内容进行顺序访问的能力。在文件流中&#xff0c;数据按照字节或块的方式传输&#xff0c;而不受文件中数据的格式影响。文件流通常与System.IO命名空间中的类一起使用&#xff0c;包括FileStrea…

如何通过前端表格控件实现自动化报表?

背景 最近伙伴客户的项目经理遇见一个问题&#xff0c;他们在给甲方做自动化报表工具&#xff0c;项目已经基本做好了&#xff0c;但拿给最终甲方&#xff0c;业务人员不太买账&#xff0c;项目经理为此也是天天抓狂&#xff0c;没有想到合适的应对方案。 现阶段主要面临的问…

geotools 读取shape文件

对于GIS开发者而言&#xff0c;矢量数据是我们经常要用到的&#xff0c;而shape数据是矢量数据中最常用的格式&#xff0c;因此解析shape数据也是作为GIS软件开发人员必备的基础技能&#xff0c;而GeoTools无疑是Java最好用来处理GIS数据的三方库&#xff0c;下面例子是简单的g…

算法训练(leetcode)第三十四天 | 56. 携带矿石资源(第八期模拟笔试)、198. 打家劫舍、213. 打家劫舍 II、337. 打家劫舍 III

刷题记录 *56. 携带矿石资源&#xff08;第八期模拟笔试&#xff09;198. 打家劫舍213. 打家劫舍 II*337. 打家劫舍 III解法一 &#xff08;记忆化递推&#xff09;*解法二 &#xff08;动态规划&#xff09; *56. 携带矿石资源&#xff08;第八期模拟笔试&#xff09; leetco…

防火墙USG2000USG5000配置nat server 时 no-reverse的含义

防火墙USG2000&USG5000配置nat server 时 no-reverse的含义 https://forum.huawei.com/enterprise/zh/thread/580888066099396608?page2 多次执行带参数no-reverse的nat server命令&#xff0c;可以为该内部服务器配置多个公网地址&#xff1b;未配置参数no-reverse则表…

实现代码灵活性:用Roslyn动态编译和执行存储在数据库中的C#代码

在许多现代应用程序中&#xff0c;动态编译和执行代码是提升灵活性和功能的一种强大技术。本文将介绍如何使用Roslyn编译器平台动态编译和执行存储在数据库中的C#代码&#xff0c;并结合实际公司案例来说明这些技术的应用场景。 1. 引言 在很多应用场景中&#xff0c;我们可能…

牛客周赛53---DEF

D.小红组比赛 题目描述 \,\,\,\,\,\,\,\,\,\,小红希望出一场题目&#xff0c;但是他的实力又不够&#xff0c;所以他想到可以从以前的比赛中各抽一题&#xff0c;来组成一场比赛。不过一场比赛的难度应该是有限制的&#xff0c;所以所以这一场比赛会给一个目标难度分数 target…

docker笔记7-dockerfile

docker笔记7-dockerfile 一、dockerfile介绍二、dockerfile指令三、构建自己的镜像 一、dockerfile介绍 Dockerfile是用来构建Docker镜像的构建文件&#xff0c;是由一系列命令和参数构成的脚本。 以下是常用的 Dockerfile 关键字的完整列表和说明&#xff1a; 二、docker…

Vue3自研开源Tree组件 - Vitepress文档功能演示

基于自己学习的一点Vue3和ts的知识&#xff0c;写的一个企业级的开源Tree组件&#xff0c;给大伙儿演示下组件功能和API的使用&#xff0c;如果您喜欢&#xff0c;不妨点赞和关注下&#xff0c;后续会继续分享源码教程&#xff0c;感谢支持&#xff01; 演示视频地址&#xff…

【计算机毕业设计】838装修公司CRM系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

基于微信小程序的高校大学生信息服务平台设计与实现

基于微信小程序的高校大学生信息服务平台设计与实现 Design and Implementation of a College Student Information Service Platform based on WeChat Mini Program 完整下载链接:基于微信小程序的高校大学生信息服务平台设计与实现 文章目录 基于微信小程序的高校大学生信息…

【Python】基础学习技能提升代码样例7:代码测试

1. doctest doctest用于测试 # 文件外命令测试 # file name: foo.py """ My square function. Usage: >>> a my_square(4) >>> b my_square(3) >>> a b 25 """ def my_square(num):return num * num > pytho…

为什么要做边界值测试?

边界值测试的理解 边界值测试&#xff08;Boundary Value Testing&#xff09;是一种常用的软件测试方法&#xff0c;它侧重于测试输入值的边缘或临界条件。这些边缘条件通常包括最小值、最大值以及接近这些最小值和最大值的值。边界值测试的基本思想是&#xff0c;许多软件错…

React 学习——Context机制层级组件通信

核心思路&#xff1a;&#xff08;适用于所有层级&#xff0c;不仅仅是爷孙 父子&#xff09; createContext方法创建一个上下文对象在顶层组件 通过Provider组件提供数据在底层组件&#xff0c;通过useContext钩子函数使用数据 import { createContext, useContext } from …

R语言 爬取数据+简单清洗

小小练习。见代码注释 # 加载必要的包 library(rvest) library(dplyr) library(tidyr)# 指定网页URL url <- "https://research.un.org/en/unmembers/scmembers"# 读取网页内容 webpage <- read_html(url)# 提取所有表格节点 table_nodes <- html_nodes(web…

vite查漏补缺

一、静态资源处理 //png txt后缀的文件不会检测是否存在&#xff0c;js json vue文件会检测是否存在 import test from "/assets/imgs/test1.png" console.log(test); // /src/assets/imgs/test1.png返回图片的绝对路径// 显式加载资源为一个 URL import test fr…

如何设置SQL Server的端口:详细步骤指南

如何设置SQL Server的端口&#xff1a;详细步骤指南 在SQL Server中&#xff0c;配置端口是确保数据库服务能够正确通信的重要步骤。无论是为了提高安全性还是满足特定的网络配置需求&#xff0c;正确设置SQL Server的端口都是必要的。本文将详细介绍如何设置SQL Server的端口…

深入理解Java中的volatile关键字

深入理解Java中的volatile关键字 1. 引言 在Java多线程编程中,volatile关键字扮演着重要角色。它能够保证变量的可见性和有序性,是实现线程安全的重要工具之一。本文将深入探讨volatile的实现原理,以及与之密切相关的指令重排概念。 2. volatile的作用 volatile关键字主要有…