C++类和对象(二)类的默认成员函数:取地址及const取地址重载 | 初始化列表 | 友元 | 隐式类型转换

前言:

        本篇文章我们先对之前未完成的内容进行补充,之后还有很多重磅内容,我们都需要去了解,废话不多说,开始吧。

类的默认成员函数(补档):

        之前我们只介绍了4个,一共有6个,那么今天我们就来把剩余两个介绍一下。

取地址重载:

class Date
{
public:Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 取地址重载Date* operator&(){return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};int main()
{Date d1;const Date d2;cout << &d1 << endl;return 0;
}

        这个其实很鸡肋,没啥用,我们平时使用编译器生成的就行了。

const取地址操作符重载: 

// const取地址重载
const Date* operator&()const
{return this;
}

        这个和取地址操作符重载没有区别。

友元: 

        我们之前在日期类中直接使用了但是没有详细讲解。

        在C++中,友元(friend)是一个特殊的关键字,它允许某个函数或另一个类访问当前类的私有(private)和保护(protected)成员。友元关系可以是单向的,即被声明为友元的函数或类可以访问当前类的私有成员,但当前类不能访问友元的私有成员。

        只需要在类的内部添加上类外定义的函数的声明,并在声明前加上关键字friend即可,一般这种友元函数允许写在类内部的任意地方,一般来说会把它放在整个类的开头。 当一个函数成为一个类的友元,那么这个函数内部就可以随意使用类中的私有(private)或保护(protected)成员了

        当时我们为了去访问私有成员,所以我们将这两个全局函数设置为Date类友元。

友元函数:

        我们上面就已经设置了友元函数。

说明:

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

友元类: 

        友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

友元类的一些特性:

  • 友元关系是单向的,不具有交换性。比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递。如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  • 友元关系不能继承。在继承位置再给大家详细介绍。

        我们给出代码方便理解:

class Time
{// 声明Date类为Time类的友元类,则在Date类中就可以直接访问Time类中的私有成员变量friend class Date; 
public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}
private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour = hour;_t._minute = minute;_t._second = second;}
private:int _year;int _month;int _day;Time _t;
}

        也就是说,A是B的友元,A中就可以直接访问B。注意友元的关系是单向的。

初始化列表:

        这是本篇的王炸,大家做好迎击准备(一段神秘的呓语:su gu mu kae u tsu jun bi wo)!

        C++的初始化列表(Initializer List)是构造函数的一种特性,用于初始化类的数据成员。在构造函数体执行之前,初始化列表会先执行,确保数据成员在构造函数体开始执行之前就已经被正确地初始化

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

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

        这是什么含义呢?我们平时不用初始化列表会这样写:

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

        这两段内容使其目的和结果都是相同的,但是本质上是有区别的。

        我们为了方便讲解,使用一个题目来说明:用栈实现队列的代码来讲解。

class stack
{
public:stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);_size = 0;_capacity = capacity;}void push(int x){_a[_size++] = x;}private:int* _a;int _size;int _capacity;
};
class MyQueue
{
private:stack _pushst;stack _popst;int _size;
};

        这里有一个大家可能会忽视的问题,就是stack类中已经有了默认构造函数。

        方便复习,重要的事情说三遍!

        默认构造函数只能有一个。注意:无参构造函数、全缺省函数、编译器默认生成构造函数,都可以认为是默认构造函数。

         所以此时stack中已经有了默认构造函数,那么接下来我们如果再stack类中没有默认构造参数,必须提供一个值才能正常使用,这是该怎么办呢?

class stack
{
public:stack(int capacity){_a = (int*)malloc(sizeof(int) * capacity);_size = 0;_capacity = capacity;}void push(int x){_a[_size++] = x;}private:int* _a;int _size;int _capacity;
};
class MyQueue
{
private:stack _pushst;stack _popst;int _size;
};

        以上代码会报错,因为默认构造无法生成。 此时只能在MyQueue中显示的写构造了。此时stack没有提供默认构造,只能使用初始化列表进行初始化!

class stack
{
public:stack(int capacity){_a = (int*)malloc(sizeof(int) * capacity);_size = 0;_capacity = capacity;}void push(int x){_a[_size++] = x;}private:int* _a;int _size;int _capacity;
};
class MyQueue
{
public:// stack 不具备默认构造,只能MyQueue显示的写构造// 此时只能使用初始化列表MyQueue(int n = 20): _pushst(n), _popst(n), _size(0){}
private:stack _pushst;stack _popst;int _size;
};int main()
{MyQueue q(10);return 0;
}

        其本质可以理解为每个对象中成员定义的地方。

        这里我们可以发现,其实我们不在初始化列表中初始化也可以,但是有3个例外。

以下三种类的成员,必须放在初始化列表的位置进行初始化:

  • 引用成员变量(因为要先有具体变量,才能有引用)
  • const成员变量(因为只有一次赋值机会)
  • 自定义类型成员(且没有默认构造函数)

        我们以代码的形式进行说明:

class A
{
public:A(int a):_a(a){}
private:int _a;
};
class B
{
public:B(int a, int& ref):_aobj(a), _ref(ref), _n(10){}
private:A _aobj; // 没有默认构造函数int& _ref; // 引用const int _n; // const
};

        建议:能在初始化列表中初始化就在初始化列表中初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

        还没完,王炸怎么可能就这点,观察以下代码:

class stack
{
public:stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);_size = 0;_capacity = capacity;}void push(int x){_a[_size++] = x;}private:int* _a;int _size;int _capacity;
};
class MyQueue
{
public:// stack 不具备默认构造,只能MyQueue显示的写构造// 此时只能使用初始化列表MyQueue(){_size = 0;}
private:stack _pushst;stack _popst;int _size;
};int main()
{MyQueue q;return 0;
}

        我们调试一下:

        所以你无论写不写,对于自定义类型,都会在初始化列表中调用它的默认构造(注意,此时stack类中有默认构造,没有默认构造会报错)。
        我们之前将可以给成员缺省值,这个缺省值其实就是给初始化列表用的。

class stack
{
public:stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);_size = 0;_capacity = capacity;}void push(int x){_a[_size++] = x;}private:int* _a;int _size;int _capacity;
};
class MyQueue
{
public:// stack 不具备默认构造,只能MyQueue显示的写构造// 此时只能使用初始化列表MyQueue(){}
private:stack _pushst;stack _popst;//此时给定了缺省值int _size = 0;
};

         如果此时在初始化列表中给定了_size的值,那么缺失值讲不起作用,比如:

MyQueue(): _size(5)
{}

        此时我们要注意,一定是先走初始化列表,之后再走函数体,所以效率会提高。所以实际中我们尽量使用初始化列表初始化。

        我们来看一道面试题:

class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print(){cout << _a1 << " " << _a2 << endl;}private:int _a2;int _a1;
};int main()
{A aa(1);aa.Print();return 0;
}

        这是为啥?因为成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关!

        这句话很重要。因为先声明的是_a2,所以在初始化列表中会先执行_a2(_a1),之后执行_a1(a),所以_a2为随机值。

初始化列表的特点:

  • 初始化列表,不管写没写,每个成员变量都会走一遍,而且在初始化列表中只能出现一次(初始化只能初始化一次)
  • 对于自定义类型,会调用默认构造(没有默认构造则报错)。
  • 先走初始化列表,再走函数体。
  • 拷贝构造也有初始化列表。
  • 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

隐式类型转换:

        之前我们讲过,不同类型的内置类型变量在相互赋值时会有隐式类型转换。

double a = 10.5;
int b = a;

        就如上面这个简单的赋值,在a赋值给b之前,会产生一个临时变量,最终赋给b值的就是这个临时变量。 

        当将不同类型的变量取引用时,需要加const的原因,是因为临时变量具有常性。

double a = 10.5;
// int& b = a;// 报错
// int& c = 10;// 报错
const int& b = a;// 正常运行
const int& c = 10;// 正常运行

        上述代码中b取的就是a产生的临时变量的引用,临时变量存储在内存的静态区,具有常性,就跟第四行代码的数字10性质是一样的,当你加上const时,这种引用权限就被放开了,因为const确保了你不会对静态区的变量做出改动。对于C++的自定义类型,与内置类型遵循的规则是一样的。 

单参数构造: 

        C++支持一种类型转换式的构造:

class A
{
public:A(int a):_a(a){}private:int _a;
};int main()
{A aa1(1);A aa2 = aa1;//拷贝构造//隐式类型转换A aa3 = 3;return 0;
}

        这里是内置类型转换为自定义类型。这里是单参数构造函数可以这样。至于用处嘛,看以下代码:

class A
{
public:A(int a):_a(a){}private:int _a;
};class Stack
{
public:void Push(const A& aa){//...}//...
};int main()
{Stack st;A a1(1);st.Push(a1);//这样写很冗余st.Push(2);//可以直接这样写return 0;
}

        这样写就很爽。

多参数构造: 

        至于多参数构造,需要换一种写法。 

class A
{
public:A(int a1, int a2):_a1(a1),_a2(a2){}void Print(){cout << _a1 << " " << _a2 << endl;}private:int _a1;int _a2;
};int main()
{A a1 = { 3, 2 };A a2{ 4, 5 };//这两者等价,但是不建议下面这样写a2.Print();return 0;
}

 

explicit关键字: 

        这个知识点稍稍提一下,如果不想允许构造时出现类的隐式类型转换,可以在拷贝构造前加个explicit关键字,就可以成功限制类的隐式类型转换了。

class A
{
public://此时就限制了隐式类型转换explicit A(int a):_a(a){}private:int _a;
};int main()
{A a1 = 3;return 0;
}

        关于它的更多内容,我们后续再讲。 

总结:

        我们要多去使用才能更好的掌握,加油吧各位!

         

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

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

相关文章

数据库管理-第185期 23ai:一套关系型数据干掉多套JSON存储(20240508)

数据库管理185期 2024-05-08 数据库管理-第185期 23ai:一套关系型数据干掉多套JSON存储&#xff08;20240508&#xff09;1 上期示例说明2 两个参数2.1 NEST/UNNEST2.2 CHECK/NOCHECK 3 一数多用3.1 以用户维度输出订单信息3.2 以产品维度3.3 以产品种类维度 4 美化输出总结 数…

Leetcode127.单词接龙

https://leetcode.cn/problems/word-ladder/description/?envTypestudy-plan-v2&envIdtop-interview-150 文章目录 题目描述解题思路代码-BFS解题思路二——双向BFS代码 题目描述 字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 …

【通信】为什么用复形式表示信号

引入&#xff1a; 一个实例反映复信号和实信号对应关系&#xff08;幅度与相位&#xff09; 复信号的意义 在实际工程中&#xff0c;没有数学意义上的复数信号。再信号与系统中引入复数是为了&#xff1a; ①简化公式&#xff0c;特别是三角函数 ②复数的实部和虚部实际上代…

ASP.NET校园新闻发布系统的设计与实现

摘 要 校园新闻发布系统是在学校区域内为学校教育提供资源共享、信息交流和协同工作的计算机网络信息系统。随着网络技术的发展和Internet应用的普及&#xff0c;互联网已成为人们获取信息的重要来源。由于现在各大学校的教师和学生对信息的需求越来越高&#xff0c;校园信息…

怎么解决端口被占用

目录 一、引言 二、解决方法 一、引言 最近用vscode写网页&#xff0c;老是遇见端口被占用&#xff0c;报错如下&#xff1a; listen tcp :8080: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted. 二、解决方法 1.换…

oracle 数据库找到UDUMP的文件名称

oracle 数据库找到UDUMP的文件名称 select p.value||\||i.instance_name||_ora_||spid||.trc as "trace_file_name" from v$parameter p ,v$process pro, v$session s, (select sid from v$mystat where rownum1) m, v$instance i where lower(p.name)user_dump_…

设计模式(2)创造型设计模式

创建型模式 创建型模式1.工厂模式1.1 抽象工厂模式&#xff08;Abstract factory&#xff09;1.2 工厂方法模式&#xff08;Factory Method&#xff09;1.3 简单工厂模式&#xff08;Simple Factory&#xff09; 2. 建造者模式&#xff08;Builder&#xff09;3. 原型模式&…

【数据库原理及应用】期末复习汇总高校期末真题试卷03

试卷 一、选择题 1 数据库中存储的基本对象是_____。 A 数字 B 记录 C 元组 D 数据 2 下列不属于数据库管理系统主要功能的是_____。 A 数据定义 B 数据组织、存储和管理 C 数据模型转化 D 数据操纵 3 下列不属于数据模型要素的是______。 A 数据结构 B 数据字典 C 数据操作 D…

了解tensorflow.js

1、浏览器中进行机器学习的优势 浏览器中进行机器学习&#xff0c;相对比与服务器端来讲&#xff0c;将拥有以下四大优势&#xff1a; 不需要安装软件或驱动&#xff08;打开浏览器即可使用&#xff09;&#xff1b;可以通过浏览器进行更加方便的人机交互&#xff1b;可以通过…

今天又发现一个有意思的问题:SQL Server安装过程中下载报错,证明GPT是可以解决问题的

我们在安装数据库的时候&#xff0c;都会有报错问题&#xff0c;无论是Oracle、SQL Server、还是MySQL&#xff0c;都会遇到各种各样的报错&#xff0c;这归根到底还是因为电脑环境的不同&#xff0c;和用户安装的时候&#xff0c;操作习惯的不一样导致的问题。今天的问题是&am…

C++语言·string类

1. 为什么有string类 C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数(strcpy,strcat)&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;不太符合OOP(Object Oriented Programming面向对…

加密“发射台”:未来通信的新模式

随着区块链技术的飞速发展&#xff0c;加密“发射台”作为一种新兴的安全通信工具&#xff0c;正逐渐受到关注。本文将从专业角度深入探讨加密“发射台”的概念、原理、应用场景及其未来发展趋势&#xff0c;以期为读者提供有深度和逻辑性的思考。 一、加密“发射台”的概念与…