【C++初阶:类和对象(下篇)】初始化列表 | static成员 | 友元

目录

  • 一、构造函数
    • 构造函数体赋值🐾
    • 初始化列表🐾
    • 💦 explicit关键字
  • 二、static成员🐾
    • 概念
    • **💦 关于静态的特性**
  • 三、友元
    • 💦 **友元函数**
    • 💦 **友元类**
  • **四、内部类**

一、构造函数

构造函数体赋值🐾

❓ 引出初始化列表 ❔

class A
{
public:A(int a = 0){_a = a;	}
private:int _a;
};
class B
{
private:int _b = 1;A _aa;
};
int main()
{B b;return 0;
}

📝 说明

对于 B,我们不写构造函数,编译器会默认生成 —— 内置类型不处理,自定义类型会去调用它的默认构造函数处理 (无参的、全缺省的、编译器默认生成的),注意无参的和全缺省的只能存在一个,如果写了编译器就不会生成,如果不写编译器会默认生成。这里 C++ 有一个不好的处理 —— 内置类型不处理,自定义类型处理。针对这种问题,在 C++11 又打了一个补丁 —— 在内置类型后可以加上一个缺省值,你不初始化它时,它会使用缺省值初始化。这是 C++ 早期设计的缺陷。

class A
{
public:A(int a = 0){_a = a;	cout << "A(int a = 0)" << endl;}A& operator=(const A& aa)//不写也行,因为这里只有内置类型,默认生成的就可以完成{cout << "A& operator=(const A& aa)" << endl;if(this != &aa){_a = aa._a;}return *this;}
private:int _a;
};
class B
{
public:B(int a, int b){//_aa._a = a;//err:无法访问private成员/*A aa(a);_aa = aa;*/ _aa = A(a);//简化版,同上_b = b;}
private:int _b = 1;A _aa;
};
int main()
{B b(10, 20);return 0;
}

📝 说明

对上,_b只能初始化成1,_a只能初始化成0 ❓

这里可以显示的初始化,利用匿名对象来初始化 _a。

但是这种方法代价较大 (见下图)。
在这里插入图片描述

初始化列表🐾

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

class A
{
public:A(int a = 0){_a = a;	cout << "A(int a = 0)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if(this != &aa){_a = aa._a;}return *this;}
private:int _a;
};
class B
{
public:B(int a, int b):_aa(a){_b = b;}
private:int _b = 1;A _aa;
};
int main()
{B b(10, 20);return 0;
}

在这里插入图片描述

  • 可以看到相比于在函数体内初始化,使用初始化列表初始化可以提高效率 —— 注意对于内置类型你使用函数体或初始化列表来初始化没有区别;但是对于自定义类型,使用初始化列表是更具有价值的。这里还要注意的是函数体内初始化和初始化列表是可以混着用的。

什么成员是必须使用初始化列表初始化的 ❓

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 
};

⚠ 注意

1️⃣ 每个成员变量在初始化列表 (同定义) 中只能出现一次 (初始化只能初始化一次)。

2️⃣ 类中包含以下成员,必须放在初始化列表位置进行初始化:

1、引用成员变量 (引用成员必须在定义的时候初始化)

2、const 成员变量 (const 类型的成员必须在定义的时候初始化)

3、自定义类型成员 (该类没有默认构造函数)

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中出现的先后次序无关

#include<iostream>
using namespace std;
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();	
}

📝 说明

上面的程序输出 ❓

A. 1  1

B. 程序崩溃

C. 编译不通过

D. 1  随机值

如上程序的输出结果是D 选项,因为 C++ 规定成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其初始化列表中出现的先后次序无关。实际中,建议声明顺序和初始化列表顺序保持一致,避免出现这样的问题。

💦 explicit关键字

class A
{
public:A(int a)//构造函数:_a(a){cout << "A(int a)" << endl;	}A(const A& aa)//拷贝构造函数{cout << "A(const A& aa)" << endl;	}
private:int _a;
};
int main()
{A aa1(1);A aa2 = 1;return 0;
}

在这里插入图片描述

📝 说明

A aa2 = 1; 意思和 A aa1(1)相同; 这是 C++98 支持的语法,它本质上是一个隐式类型转换 ,将 int 转换为 A,为什么 int 能转换成 A 呢 ? 因为它支持一个用 int 参数去初始化 A 的构造函数。它俩虽然结果是一样的,都是直接调用构造函数,但是对于编译器而言过程不一样(如下)。因此才会调用两次构造函数而不调用拷贝构造函数
在这里插入图片描述

❓ 如果不想允许这样的隐式类型转换的发生 ❔

这里可以使用关键字 explicit


class A
{
public:explicit A(int a):_a(a){cout << "A(int a)" << endl;	}
private:
int _a;
};int main()
{A aa2 = 1;return 0;
}
//error C2440: “初始化”: 无法从“int”转换为“A”

❓ 多参数隐式类型转换 ❔

class A
{
public:A(int a1, int a2):_a(a1){cout << "A(int a1, int a2)" << endl;	}A(const A& aa){cout << "A(const A& aa)" << endl;	}
private:int _a;
};
int main()
{A aa1(1, 2);//A aa2 = 1, 2;//errorA aa2 = {1, 2};return 0;
}

📝说明

  • A aa2 = 1, 2; 是错误写法, C++98 不支持多参数的隐式类型转换,但是 C++11 是支持的, A aa2 = {1, 2};同样编译器依然会优化。当我们使用 explicit 关键字限制时,它会 error C2440:“初始化”: 无法从“int”转换为“A”

  • 如果你将单参数构造函数改为explicit A(int a),那么A aa2 = 1;这行代码就会导致编译错误,因为编译器被禁止进行从int到A的隐式类型转换。你必须显式地调用构造函数,像A aa2(1);这样。

  • 总的来说,explicit关键字可以帮助你控制类型转换,防止因为不希望的隐式类型转换而导致的错误。

二、static成员🐾

概念

❓ 写一个程序,计算程序构造了多少个对象 (构造+拷贝构造) ❔

int countC = 0;//记录调用构造函数的次数
int countCC = 0;//记录调用拷贝构造函数的次数
class A
{
public:A(){++countC;}A(const A& a){++countCC;}
};
A f(A a)
{A ret(a);return ret;
}
int main()
{A a1 = f(A());A a2;A a3;a3 = f(a2);cout << countC << endl;cout << countCC << endl;return 0;
}

在这里插入图片描述

📝说明

这样虽然能计算出结果,但是有一个问题,countC 和 countCC 是可以随便改的,这样就很不好。

优化 ❓

class A
{
public:A(){++_count;}A(const A& a){++_count;}//静态成员函数特点是没有this指针static int GetCount(){return _count;	}
private:int _a;static int _count;//注意静态成员不能给缺省值,使用静态可以保证不同成员函数//++_count是同一个_count,而不是不同的临时变量//_count不是属于某个对象的,而是属于整个类的
};
//定义初始化
int A::_count = 0;
A f(A a)
{A ret(a);return ret;
}
int main()
{A a1 = f(A());A a2;A a3;a3 = f(a2);cout << sizeof(A) << endl;//这里就体现了static成员属于整个类,也属于每个定义出来的对象共享,但限制于公有/*cout << A::_count << endl;	cout << a1._count << endl;cout << a2._count << endl;*//*A ret;cout << ret.GetCount() - 1 << endl;*//*cout << A().GetCount() - 1 << endl;*/cout << A::GetCount() << endl;return 0;
}

在这里插入图片描述

📝说明

int _a; 存在定义出的对象中,属于对象。

static int _count; 存在静态区,属于整个类,也属于每个定义出来的对象共享。跟全局变量比较,它受类域和访问限定符限制,更好的体现封装,别人不能轻易修改。

static成员 ❓

对于非 static 成员它们的定义是在初始化列表中,但在 C++ 中,static 静态成员变量是不能在类的内部定义初始化的,这里的内部只是声明。注意这里虽然是私有成员,但是对于 static 成员它支持在外部进行定义,且不需要加上 static,sizeof 在计算的时候并不会计算 static 成员的大小。

  • _count是私有,怎么访问 ❓

定义一个公有函数 GetCount 函数,返回 _count:调用函数
1、最后实例化对象后调用 GetCount 函数并减 1
2、直接匿名对象并减 1
3、将 GetCount 函数定义成静态成员函数并使用类域调用

💦 关于静态的特性

1️⃣ 静态成员变量为所有类对象所共享,不属于某个具体的实例。

2️⃣ 静态成员变量必须在类外定义,定义时不添加 static 关键字。

3️⃣ 类静态成员即可用类名::静态成员或者对象.静态成员来访问。

4️⃣ 静态成员函数没有隐藏的 this 指针,不能访问任何非静态成员。

5️⃣ 静态成员和类的普通成员一样,也有 public、protected、private3种访问级别,也可以具有返回值。


【面试题1】
static 的作用 C 语言 | C++ ❓

C 语言:

1、 static 修饰局部变量,改变了局部变量的生命周期 (本质上是改变了变量的存储类型),局部变量由栈区转向静态区,生命周期同全局变量一样

2、 static 修饰全局变量,使得这个全局变量只能在自己所在的文件内部使用,而普通的全局变量却是整个工程都可以使用

❓ 为什么全局变量能在其它文件内部使用 ❔

因为全局变量具有外部链接属性;但是被 static 修饰后,就变成了内部链接属性,其它源文件不能链接到这个静态全局变量了

3、 static 修饰函数,使得函数只能在自己所在的文件内部使用,本质上 static 是将函数的外部链接属性变成了内部链接属性 (同 static 修饰全局变量)

C++:

1、修饰成员变量和成员函数,成员变量属于整个类,所有对象共享,成员函数没有 this 指针。


三、友元

友元分为:友元函数和友元类

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

💦 友元函数

❓ 重载<< ❔

class Date
{friend ostream& operator<<(ostream& out, const Date& d);//友元,位置可任意,一般是开头friend istream& operator>>(istream& in, Date& d);//友元
public:Date(int year = 0, int month = 0, int day = 1): _year(year), _month(month), _day(day){}/*void operator<<(ostream& out){out << _year << "/" << _month << "/" << _day << endl;}*/
private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& out, const Date& d)//支持连续输出
{out << d._year << "/" << d._month << "/" << d._day << endl;return out;
}
istream& operator>>(istream& in, Date& d)//支持连续输入
{in >> d._year >> d._month >> d._day;return in;
}
int main()
{Date d1, d2;cin >> d1 >> d2;//cout << d1 ? d1 << cout//cout << d1;/*d1.operator<<(cout);d1 << cout;*/cout << d1 << d2;return 0;
}

📝说明

cin | cout 怎么接收 ❓
在这里插入图片描述

在 C++ 里 cout 是一个 ostream 的对象;cin 是一个 istream 的对象。

cout << d1 | d1 << cout(d1.operator<<(cout)) ❓

**为啥不能 cout << d1 呢 ?**❓

运算符有几个操作数,重载函数就有几个参数。如果有两个操作数,左操作数是第一个参数,右操作数是第二个参数

在 Date 类成员函数 operator<< 里对象是第一个参数,因为隐含的 this 指针已经默认占据了,那么 cout 就只能作第二个操作数了。可以倒也可以,但是用起来不符合流运算符原来的特性。

**怎么改成 cout << d1 呢 ?**❓

也就是把 cout 作为第一个参数,那么这里就不能用成员函数了,之前我们用成员函数是因为成员变量是私有的。

如何取舍:使用成员函数 | 使用全局函数。这里成员函数的可读性差影响较大,所以将之舍弃,使用全局函数。

支持 cout << d1 后怎么解决私有 ?

解决方案1:提供公有的成员函数 GetYear、GetMonth、GetDay

解决方案2:友元函数

这里我们就引出了友元,C++ 默认是不能在类外访问私有的,但是它提供了友元以帮助我们解决这种场景的问题。友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加 friend 关键字。

💨小结

1、友元函数可访问类的私有和保护成员,但不是类的成员函数。

2、友元函数不能用 const 修饰,因为 const 修饰的是 this 指针指向的对象。

3、友元函数可以在类定义的任何地方声明,不受类访问限定符限制。

4、一个函数可以是多个类的友元函数。

5、友元函数的调用与普通函数的调用和原理相同。

🍳拓展

这里主要拓展代码错误的解决能力,先来看一段代码。

class A
{friend void f(const A& aa, const B& bb);
public:A(int a = 0): _a(0){}
private:int _a;
};
class B
{friend void f(const A& aa, const B& bb);
private:int _b = 0;A _aa;
};
void f(const A& aa, const B& bb)
{cout << aa._a << endl;	cout << bb._b << endl;
}
int main()
{A aa;B bb;f(aa, bb);return 0;
}

📝分析

相信到了这里绝大部分的人都能凭借着自己的经验去解决大部分的 bug了。但是对于上面代码出现的错误又百思不得其解。
在这里插入图片描述

注意面对这种情况的时候,有时候编译器报的错误是不准确的,这里有两条建议能帮助提升查找 bug 的能力。

1、有很多错误的时候,一定是看最上面的错误,因为下面的错误有可能是上面的错误间接导致的。

2、排除法,这里有一百行代码(程序崩了),你注释掉了部分代码程序正常了,那么不用怀疑,错误就是注释处代码引发的。

经过分析,我们发现错误处是:在 B 类的友元里能找到 A、B;但是在 A 类的友元里就找不到 B 了。所以解决方法就是加前置声明 —— class B;


💦 友元类

class Date; //前置声明
class Time
{friend class Date;//声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:Time(int hour, int minute, int second): _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;
};

📝分析

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

1、友元关系是单向的,不具有交换性。比如上述 Time 类和 Date 类,在 Time 类中声明 Date 类为其友元类,那么可以在 Date 类中直接访问 Time 类的私有成员变量,但想在 Time 类中访问 Date 类中私有的成员变量则不行。

2、友元关系不能传递,如果 C 是 B 的友元, B 是 A 的友元,则不能说明 C 是 A 的友元。

四、内部类

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。

注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部不是内部的友元。

特性:

1、内部类可以定义在外部类的 public、protected、private 都是可以的。

2、注意内部类可以直接访问外部类中的 static、枚举成员、不需要外部类的对象/类名。

3、sizeof (外部类) = 外部类,和内部类没有任何关系。

class A
{
private:static int k;int h;
public:class B//B类天生就是A的友元{public:void foo(const A& a){cout << k << endl;//okcout << a.h << endl;//ok	}private:int _b;};
};
int A::k = 0;
int main()
{cout << sizeof(A) << endl;//4A::B b;//要用B去定义,必须得指定域b.foo(A());return 0;
}

📝说明

sizeof 在计算 A 类型对象大小的时候,不考虑 B 类。因为 B 作为 A 的内部类,跟普通类没有什么区别,只是定义在 A 的内部,它受到 A 的类域的限制和访问限定符的限制。

一般情况是一个类专门为另一个类服务是才会用到内部类。


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

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

相关文章

VM和Linux安装

VM和Linux安装 一、下载VM 1.官网地址&#xff1a;https://www.vmware.com/cn.html 2.其他地址&#xff1a;http://ww7.nocmd.com/windows/740.html 许可证这个&#xff0c;大家可以自己上网搜索&#xff0c;很容易就搜索到就可以使用了 上面内容就是安装VM的步骤 安…

30. 异常

异常 1. 概述2. Throwable 方法2.1 概述2.2 代码示例 3. 异常分类4. 异常处理方式4.1 JVM默认处理异常4.2 自己处理&#xff08;捕获异常&#xff09;try...catch4.2.1 概述4.2.2 灵魂四问 4.3 抛出处理(throw和throws) 5. 自定义异常5.1 概述5.2 代码示例 6. 异常注意事项 文章…

代码随想录算法训练营第三十一天 |基础知识,455.分发饼干,376.摆动序列,53.最大子序和(已补充)

基础知识&#xff1a; 题目分类大纲如下&#xff1a; #算法公开课 《代码随想录》算法视频公开课(opens new window)&#xff1a;贪心算法理论基础&#xff01;(opens new window),相信结合视频再看本篇题解&#xff0c;更有助于大家对本题的理解。 #什么是贪心 贪心的本质…

python分离字符串 2022年12月青少年电子学会等级考试 中小学生python编程等级考试二级真题答案解析

目录 python分离字符串 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python分离字符串 2022年12月 python编程等级考试级编程题 一、题目要…

【数据结构】链表OJ面试题5《链表的深度拷贝》(题库+解析)

1.前言 前五题在这http://t.csdnimg.cn/UeggB 后三题在这http://t.csdnimg.cn/gbohQ 给定一个链表&#xff0c;判断链表中是否有环。http://t.csdnimg.cn/Rcdyc 给定一个链表&#xff0c;返回链表开始入环的第一个结点。 如果链表无环&#xff0c;则返回 NULLhttp://t.cs…

1 月 NFT 市场动态:Polygon 增长,Mooar 崛起,TinFun 掀起文化浪潮

作者&#xff1a;stellafootprint.network 数据源&#xff1a;NFT Research - Footprint Analytics 2024 年 1 月&#xff0c;加密货币与 NFT 市场迎来了重要的转折点&#xff0c;其中美国首批现货比特币 ETF 的亮相尤为引人注目&#xff0c;这一金融一体化的里程碑事件吸引了…

论文阅读-One for All : 动态多租户边缘云平台的统一工作负载预测

论文名称&#xff1a;One for All: Unified Workload Prediction for Dynamic Multi-tenant Edge Cloud Platforms 摘要 多租户边缘云平台中的工作负载预测对于高效的应用部署和资源供给至关重要。然而&#xff0c;在多租户边缘云平台中&#xff0c;异构的应用模式、可变的基…

【C/C++】2024春晚刘谦春晚魔术步骤模拟+暴力破解

在这个特别的除夕夜&#xff0c;我们不仅享受了与家人的温馨团聚&#xff0c;还被电视机前的春节联欢晚会深深吸引。特别是&#xff0c;魔术师刘谦的精彩表演&#xff0c;为我们带来了一场视觉和心灵的盛宴。在我的博客“【C/C】2024春晚刘谦春晚魔术步骤模拟暴力破解”中&…

c#cad 创建-直线(五)

运行环境 vs2022 c# cad2016 调试成功 一、代码说明 这段代码是用于在AutoCAD中创建一条直线。首先获取当前活动文档和数据库的引用&#xff0c;然后创建一个编辑器对象用于提示用户输入。接下来&#xff0c;在一个事务中获取模型空间的块表记录&#xff0c;并定义直线的长度…

Hive的Join连接、谓词下推

前言 Hive-3.1.2版本支持6种join语法。分别是&#xff1a;inner join&#xff08;内连接&#xff09;、left join&#xff08;左连接&#xff09;、right join&#xff08;右连接&#xff09;、full outer join&#xff08;全外连接&#xff09;、left semi join&#xff08;左…

docker磁盘不足!已解决~

目录 &#x1f35f;1.查看docker镜像目录 &#x1f9c2;2.停止docker服务 &#x1f953;3.创建新的目录 &#x1f32d;4.迁移目录 &#x1f37f;5.编辑迁移的目录 &#x1f95e;6.重新加载docker &#x1f354;7.检擦docker新目录 &#x1f373;8.删掉旧目录 1.查看doc…

Vulnhub靶场 DC-8

目录 一、环境搭建 二、信息收集 1、主机发现 2、指纹识别 三、漏洞复现 1、SQL注入 sqlmap工具 2、dirsearch目录探测 3、反弹shell 4、提权 exim4 5、获取flag 四、总结 一、环境搭建 Vulnhub靶机下载&#xff1a; 官网地址&#xff1a;https://download.vulnhub.com/dc/DC-…

鸿蒙开发系列教程(十八)--页面内动画(1)

页面内的动画 显示动画 语法&#xff1a;animateTo(value: AnimateParam, event: () > void): void 第一个参数指定动画参数 第二个参数为动画的闭包函数。 如&#xff1a;animateTo({ duration: 1000, curve: Curve.EaseInOut }, () > {动画代码}&#xff09; dura…

安装Centos系统

1.镜像安装 镜像安装:Centos7安装 2.安装过程(直接以图的形式呈现) 选择你已经下载好的镜像 回车即可,等待安装 等待安装即可

2月7号寒假作业

第七章 运算符重载 一、填空题 1、在下列程序的空格处填上适当的字句&#xff0c;使输出为&#xff1a;0&#xff0c;2&#xff0c;10。 #include <iostream> #include <math.h> class Magic {double x; public: Magic(double d0.00):x(fabs(d)) {} Mag…

华为机考入门python3--(13)牛客13-句子逆序

分类&#xff1a;列表 知识点&#xff1a; 列表逆序&#xff08;和字符串逆序是一样的&#xff09; my_list[::-1] 题目来自【牛客】 def reverse_sentence(sentence): # 将输入的句子分割words sentence.split() # 将单词逆序排列 words words[::-1] # 将单词用空…

VueCLI核心知识1:ref属性、props配置、mixin混入

1 ref 属性 ref属性类似于js原生获取DOM元素 <template><div><h1 v-text"msg" ref"title"></h1><button click"showDom">点我输出上方的Dom元素</button><School ref"sch"></School>…

漫漫数学之旅018

文章目录 经典格言数学习题古今评注名人小传 - 库尔特哥德尔 经典格言 一个毫无自由的社会——一个人凡事都要遵循严格统一的规则——将在行为上既不一致也不完全&#xff0c;甚至不能解决某些也许很重要的问题。——库尔特哥德尔&#xff08;Kurt Gdel&#xff09; 库尔特哥德…

Linux_信号

一个进程退出有两种情况&#xff1a;1.正常执行完毕。2.程序执行中异常退出。第一种情况可以通过进程退出码来获取进程执行结果&#xff0c;第二种情况需要通过信号来判断进程异常退出原因。那么进程在什么样的条件下会产生信号&#xff0c;进程又是怎样处理产生的信号呢&#…

算法沉淀——字符串(leetcode真题剖析)

算法沉淀——字符串 01.最长公共前缀02.最长回文子串03.二进制求和04.字符串相乘 01.最长公共前缀 题目链接&#xff1a;https://leetcode.cn/problems/longest-common-prefix/ 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串…