类 —— 友元、常/静态成员函数
类
类的大小
和结构体大小求法一致。但需注意,普通空类也会占用 1 字节大小,因为普通空类可以实例化对象。

而 抽象空类占 4 字节(32 位机中),因为抽象空类中含有虚指针(含有虚函数的非抽象空类同理)。

友元
某些情况下,需要频繁读写类的数据成员,特别是在对某些成员函数多次调用时,由于参数传递、类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。引入友元。
友元函数
全局函数作为类的友元函数
用到哪一个类中的私有成员,就把全局函数的“声明”前加上 friend,放到哪一个类中就可以了。
全局函数的“声明”可以放置到类中任何位置,不受权限修饰符的影响。
为避免不必要的麻烦,全局函数写在类的声明下面。一个全局友元函数理论上来说可以访问多个类。
#include <iostream>
#include <math.h>
using namespace std;class N;class M
{float x;
public:M() { }M(float x):x(x) { }friend double func(M m, N n); // 声明全局函数 func 是 M 的友元函数// 全局函数的“声明”可以放置到类中任何位置,不受权限修饰符的影响
};class N
{float y;
public:N() { }N(float y):y(y) { }friend double func(M m, N n); // 声明全局函数 func 是 N 的友元函数// 全局函数的“声明”可以放置到类中任何位置,不受权限修饰符的影响
};double func(M m, N n)
{return sqrt(m.x*m.x + n.y*n.y);
}int main()
{M m(3);N n(4);cout << "√3²+4² = " << func(m, n) << endl;return 0;
}

一个类中成员函数作为另一个类的友元函数
如果 M 类中的函数,是 N 类的友元函数(由 N 声明),则 M 中的函数可以访问 N 中的私有成员。
1、需要把类做前置声明,但是类的前置声明仅说明可以使用类定义变量/形参,并不能声明类中的成员。
2、如果一个类中的成员函数,作为另一个类的友元,该类的成员函数必须在类内声明,类外定义。
#include <iostream>
using namespace std;class N;class M
{float x;
public:M() { }M(float x):x(x) { }void show(N n); // 想要此函数作为 N类 的友元函数,须类内声明,类外定义
};class N
{float y;
public:N() { }N(float y):y(y) { }friend void M::show(N n); // 把 M 中的 show 函数,声明为了 N 中的友元
};void M::show(N n) // 因为此函数是其他类N的友元,所以要在类外定义
{cout << "It's a function belongs to class M. " << endl;cout << "But it can use variables of class N: " << n.y << endl;// 访问另一个类 N 中的私有成员
}int main()
{M m;N n(4);m.show(n); // M 中的函数可以访问 N 中的私有成员return 0;
}

友元类
如果 M类 中,声明了 friend class N,则称 M类 把 N类 作为友元类。N类 中的所有成员都能访问 M类 中的私有成员。
如果 N类 中,声明了 friend class M,则称 N类 把 M类 作为友元类。M类 中的所有成员都能访问 N类 中的私有成员。
如果声明友元类,就不涉及到类中成员的问题,写代码时不需要考虑类的先后顺序。但后写的类要提前声明。
#include <iostream>
#include <math.h>
using namespace std;class N;class M
{float x;
public:M() { }M(float x):x(x) { }void show(N n);void show();
};void M::show()
{cout << "This is a function belongs to class M: " << this->x << endl;
}class N
{float y;
public:N() { }N(float y):y(y) { }friend class M;
};void M::show(N n)
{cout << "This is a function belongs to class M. " << endl;cout << "But it can use variables of class N: " << n.y << endl;
}int main()
{M m(3);N n(4);m.show(n);cout << endl;m.show();return 0;
}

💡 练习
定义两个类 Dog 和 Cat 类,分别具有私有的成员属性:颜色、性别、年龄。
写出两个类的无参构造和有参构造,定义一个全局函数:计算猫和狗的年龄之和,并输出。
定义 Dog 类为 Cat 类的友元,在 Dog 类中定义一个 c_show 函数,输出猫和狗的颜色。
#include <iostream>
using namespace std;class Dog;class Cat // 必须定义在 Dog 前
{string color;string gender;int age;
public:Cat(){cout << "A constructor of cat without arguments." << endl;}Cat(string color, string gender, int age):color(color), gender(gender), age(age){cout << "A constructor of cat with arguments." << endl;}friend int sum(Dog &d, Cat &c);friend class Dog;
};class Dog
{string color;string gender;int age;
public:Dog(){cout << "A constructor of dog without arguments." << endl;}Dog(string color, string gender, int age):color(color), gender(gender), age(age){cout << "A constructor of dog with arguments." << endl;}friend int sum(Dog &d, Cat &c);void c_show(Cat &c);
};void Dog::c_show(Cat &c)
{cout << "The cat is " << c.color<< "." << endl; // Cat 必须定义在前面的原因cout << "The dog is " << this->color << "." << endl;
}int sum(Dog &d, Cat &c)
{return d.age + c.age;
}int main()
{Dog d("husky", "female", 5);Cat c("ragdoll", "male", 6);cout << "Age: " << sum(d, c) << endl;d.c_show(c);return 0;
}

友元的注意事项
1、类的前置声明,只能表明存在该类,并不能说明类中有哪些成员。
2、如果一个类的成员函数作为另一个类的友元,需要在该类内部声明,在该类外部定义。
3、友元是单向的,A 声明了 B 为友元类,B 中的成员可以访问 A 中的私有成员。但这并不意味着 A 中的成员,可以访问 B 中的私有成员。
4、友元没有传递性。A中:friend class B,且 B中:friend class C ≠ A 把 C 当做友元类
5、提高了程序的运行效率,但是破坏了类的封装性和隐藏性,使得非成员函数也能够访问类中的私有成员。导致程序的维护性变差,因此使用友元要慎重。
A中:friend class B ⇔ A 声明了 B 为友元类 ⇔ A 把 B 当做友元类 ⇔ B 是 A 的友元类 ⇔ B 可访问 A
常成员变量 & 常局部变量
常成员变量
该成员变量的值无法被修改。
常成员变量的初始化方式有两种:
● 直接赋值
声明后赋值:
● 构造初始化列表
上述两种方式同时使用时,前者失效,以后者为准。
#include <iostream>
using namespace std;class Demo
{
private:// ● 定义时直接赋值const int a = 1;const int b = 2;const int c = 3;public:// ● 构造初始化列表的方式进行初始化Demo(int a, int b, int c):a(a), b(b), c(c) {}// Demo(int a, int b, int c)
// {
// this->a = a;
// this->b = b;
// this->c = c;
// }void show(){cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;}void test() // 错误{
// a++;
// b++;
// c++;}
};int main()
{Demo d(10, 20, 30); d.show();return 0;
}
常局部变量
该局部变量不可被修改,常用于引用参数。
#include <iostream>
using namespace std;class Demo
{
public:void test(){int a = 1;const int b = 2;a++;
// b++; // 错误cout << "a = " << a << endl;cout << "b = " << b << endl;}};int main()
{Demo d;d.test();return 0;
}
常成员函数 & 常对象
对比普通常函数:const int add(int n1, int n2); ——> const 修饰函数的返回值
常成员函数
格式
返回值类型 成员函数名() const;
性质
1、常成员函数内,不能修改成员变量;
2、可以保护成员变量不被随意修改;
3、如果常成员函数的声明和定义分开,两个位置都要写上 const 修饰;
4、不能调用非 const 的成员函数;
建议只要成员函数不修改成员变量就使用 const 修饰,例如 get 函数。
#include <iostream>
using namespace std;class Demo
{
private:int a;
public:Demo(int a){this->a = a;}void show(){cout << "哈哈哈哈哈" << endl;}// const 修饰的成员函数int get_a() const{return a;}void test() const{cout << get_a() << endl;
// show(); // 错误
// a++; // 错误cout << "a = " << a << endl;}
};int main()
{Demo d(1);cout << d.get_a() << endl;d.test();return 0;
}

常成员函数的 this 指针
const 类名 *const this;
this 的指向和值均不能更改。

mutable(慎用)
取消常属性,在类中给成员变量加上 mutable 关键字,就允许在常成员函数内修改成员变量。
#include <iostream>
using namespace std;class M
{mutable float x; // mutable 取消成员变量的常属性,可以在常成员函数中修改该成员变量
public:M() { }M(float x):x(x) { }void set_value(float val)const;void show();
};void M::set_value(float val)const
{this->x = val;
}void M::show()
{cout << this->x << endl;
}int main()
{M m(12);cout << "Before: ";m.show();m.set_value(5);cout << "After: ";m.show();return 0;
}

常对象
实例化对象时,前面加上 const 关键字修饰。
常对象的性质:
1、常对象只能调用常成员函数(默认调用的特殊成员函数除外);
2、非常对象既可以调用非常成员函数,也可以调用常成员函数;
3、常对象可以调用成员变量,但不能修改;

#include <iostream>
using namespace std;class M
{mutable float x;
public:M() { }M(float x):x(x) { }void set_value(float val)const;void show();void show(M m);
};void M::set_value(float val)const
{this->x = val;
}void M::show()
{cout << this->x << endl;
}void M::show(M m)
{cout << m.x << endl;
}int main()
{const M m1(12);m1.set_value(13);M m2(5);m2.show(m1);m2.show();m2.set_value(361);m2.show();return 0;
}

#include <iostream>
using namespace std;class Demo
{
private:int a;
public:int b = 10;Demo(int a){this->a = a;}void show(){cout << "哈哈哈哈哈" << endl;}// const修饰的成员函数int get_a() const{return a;}void test() const{cout << get_a() << endl;
// show(); // 错误
// a++; // 错误cout << "a = " << a << endl;}
};int main()
{// 两种初始化方式,两种等效const Demo d(1);
// Demo const d(1);// 常对象可以调用 const 修饰的成员函数。cout << d.get_a() << endl;// 常对象不可以调用 非const 修饰的成员函数。
// d.show();// 错误
// d.b = 11;// 可以调用成员变量,但是不能做修改cout << d.b << endl;return 0;
}

💡 练习
封装圆类 Circle,私有属性半径 R,圆周率 PI,在程序运行过程中 PI 的值不变,写出圆的构造函数,公有的求面积函数。
#include <iostream>
#include <iomanip>
using namespace std;class Circle
{float radius;
// mutable double PI; // 方法一const double PI = 3.14159265359; // 方法二,仅此一句
public:Circle() { }Circle(float radius):radius(radius) { }double area(Circle &cir){return cir.radius * cir.radius * PI;}// void set_pi()const // 方法一// {// PI = 3.14159265359;// }
};int main()
{Circle cir(2);
// cir.set_pi(); // 方法一cout << setprecision(8) << "S = " << cir.area(cir) << endl;return 0;
}

静态局部变量、静态成员变量 & 静态成员函数
静态局部变量在第一次调用时创建,直到程序结束后销毁,同一个类的所有对象共用一份静态局部变量。
静态成员函数 和 静态成员变量,既不依赖类的对象而存在,也不占用类的空间。
静态局部变量(类的函数中:static 数据类型 变量名;)
#include <iostream>
using namespace std;class Test
{
public:void func(){int a = 1; // a可能会在同一个地址反复创建销毁。static int b = 1; // 静态局部变量,同一个类的所有对象共用一份cout << "a = "<< ++a << "\t" << &a << endl;cout << "b = "<< ++b << "\t" << &b << endl;}};int main()
{Test t1;t1.func();Test t2;t2.func();t1.func();t2.func();return 0;
}

静态成员变量(类中:static 数据类型 成员名;)
1、静态成员变量 独立于类体存在;
2、每一个类对象公用同一个静态成员;
3、即使没有实例化类对象,也可以使用类中的静态成员;
4、静态成员一般是 public 权限;
5、静态成员需要在全局处声明,声明的同时可以进行初始化操作,不初始化默认为 0;
6、静态成员不占用类的大小;
● 在程序开始运行时就开辟内存,直到程序运行结束销毁。
● 虽然静态成员变量可以使用对象调用,但是更建议直接使用类名进行调用。
静态成员函数
1、不依赖于任何类对象(可以在没有类对象的情况下调用);
2、静态成员函数 没有 this 指针(因为可以不通过类对象被调用);
3、静态成员函数,不能直接使用非静态成员变量; ——> 间接使用方法见链接 (👈 链接至另一博主,放心跳转)
4、调用方式:① 直接通过类名加域限定符调用,② 通过类对象调用;
5、非静态成员函数和静态成员函数可以构成重载;
6、不能在静态成员函数中,调用同类中其他非静态成员函数;
7、若要 类内声明类外定义,声明 需要写 static 关键字,类外的实现不用写;
#include <iostream>
using namespace std;class Rectangle
{
public:static float height;float width;static void show(){//cout << "width = " << width << endl; // 3、cout << "height = " << height << endl;}void show(float width) // 5、{}
};float Rectangle::height; // 需要在全局处声明,可以初始化,也可以不初始化(默认为0)int main()
{Rectangle::show(); // 1、4 ①cout << Rectangle::height << endl;Rectangle rec1;rec1.height = 80;Rectangle rec2;rec2.show(); // 4 ②cout << rec2.height << endl;cout << "rec1.height\t" << &(rec1.height) << endl;cout << "rec2.height\t" << &(rec2.height) << endl;cout << sizeof(Rectangle) << endl;return 0;
}

#include <iostream>
using namespace std;class Test
{
public:void func0(){cout << "void func0()" << endl;func1();}// 静态成员函数static void func1(){cout << "static void func1()"<< endl;func2();}static void func2(){cout << "static void func2()" << endl;
// func0(); // 6.错误,静态成员函数,不能调用非静态成员函数}
};int main()
{// 类名直接调用Test::func1();Test t1;t1.func0();t1.func2();return 0;
}

如果要在静态成员函数内调用非静态成员的属性方法,可以通过参数将对象传进来。
#include <iostream>
using namespace std;class Test
{
public:void func0(){cout << "void func0()" << endl;
// func1();}// 静态成员函数static void func1(Test &t){t.func0();cout << "static void func1(Test &t)"<< endl;func2();}static void func2(){cout << "static void func2()" << endl;
// func0(); // 错误,静态成员函数,不能调用非静态成员函数}
};int main()
{// 类名直接调用
// Test::func1();Test t1;t1.func0();t1.func1(t1);t1.func2();return 0;
}

相关文章:
类 —— 友元、常/静态成员函数
类 类的大小 和结构体大小求法一致。但需注意,普通空类也会占用 1 字节大小,因为普通空类可以实例化对象。 而 抽象空类占 4 字节(32 位机中),因为抽象空类中含有虚指针(含有虚函数的非抽象空类同理&am…...
单页面应用
单页面应用 1.什么是SPA 多页面应用:每个页面都是独立的html文件,页面切换是整体刷新,需要重新加载html、css、JS等文件,容易实现搜索引擎,数据通过url、cookie、localStore传递。 单页面应用:多个页面是…...
六、ZooKeeper Java API操作
目录 1、引入maven坐标 2、节点的操作 这里操作Zookeeper的JavaAPI使用的是一套zookeeper客户端框架 Curator ,解决了很多Zookeeper客户端非常底层的细节开发工作 。 Curator包含了几个包:...
美甲美睫店预约会员管理小程序作用如何
美甲美睫是美业中较为重要的类目,主要以小摊、门店/连锁形式,随着线上化程度加深,传统线下美业店面临着困境,想要进一步增长及解决痛点,就需要线上数字化运营得到更多生意。 那么通过【雨科】平台搭建美甲美睫店小程序…...
Vue3为什么会推出组合式API
前言 大学前端入门学的vue2,工作了又用的React,现在想学习一下Vue3,开篇就介绍了组合式API,这和我认知里的vue2的选项式API区别还是蛮大的。本篇文章简单介绍一下组合式API。 什么是组合式API Vue 3引入了一种新的 API…...
windows11 phpstudy_pro php8.2 安装redis扩展
环境:windows11 phpstudy_pro php8.2.9 一、命令查看是否安装redis扩展 在对应网站中通过打开,,选择对应的PHP版本,用命令 php -m 查看自己的php 有没有redis扩展 上面如果有,说明已经安装了,如果没有安装࿱…...
中英双语大模型ChatGLM论文阅读笔记
论文传送门: [1] GLM: General Language Model Pretraining with Autoregressive Blank Infilling [2] Glm-130b: An open bilingual pre-trained model Github链接: THUDM/ChatGLM-6B 目录 笔记AbstractIntroductionThe design choices of GLM-130BThe …...
力扣题:字符串的反转-11.24
力扣题-11.24 [力扣刷题攻略] Re:从零开始的力扣刷题生活 力扣题1:151. 翻转字符串里的单词 解题思想:保存字符串中的单词即可 class Solution(object):def reverseWords(self, s):""":type s: str:rtype: str"&quo…...
NIO--07--Java lO模型详解
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 何为 IO?先从计算机结构的角度来解读一下I/o.再从应用程序的角度来解读一下I/O 阻塞/非阻塞/同步/异步IO阻塞IO非阻塞IO异步IO举例 Java中3种常见的IO模型BIO (Blo…...
OpenSSH 漏洞修复升级最新版本
Centos7系统ssh默认版本一般是OpenSSH7.4左右,低版本是有漏洞的而且是高危漏洞,在软件交付和安全扫描上是过不了关的,一般情况需要升级OpenSSH的最新版本 今天详细说下升级最新版本的处理过程(认真看会发现操作很简单,…...
【数据结构和算法】无限集中的最小数字
其他系列文章导航 Java基础合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 三、代码 四、总结 前言 这是力扣的2336题,难度为中等,解题方案有很多种,本文讲解我认为…...
SimpleDataFormat 非线程安全
目录 前言 正文 1.出现异常 2.解决方法1 3.解决方法2 总结 前言 SimpleDateFormat 类是 Java 中处理日期和时间格式化和解析的类,但它并不是线程安全的。这意味着多个线程不能安全地共享一个 SimpleDateFormat 实例进行日期和时间的解析和格式化。当多个…...
SpringBoot : ch12 多模块配置YAML文件
前言 当您使用SpringBoot框架进行项目开发时,通常需要配置一些参数和属性。在实际开发中,可能需要将这些配置参数分成多个不同的YAML文件,并将它们组织到不同的模块中。这样可以方便管理和维护配置文件,并且可以避免配置文件的冲…...
TensorRT之LeNet5部署(onnx方式)
文章目录 前言LeNet-5部署1.ONNX文件导出2.TensorRT构建阶段(TensorRT模型文件)🧁创建Builder🍧创建Network🍭使用onnxparser构建网络🍬优化网络🍡序列化模型🍩释放资源 3.TensorRT运行时阶段(推理)&#x…...
Xilinx FPGA平台DDR3设计详解(二):DDR SDRAM组成与工作过程
本文主要介绍一下DDR SDRAM的基本组成以及工作过程,方便大家更好的理解和掌握DDR的控制与读写。 一、DDR SDRAM的基本组成 1、SDRAM的基本单元 SDRAM的基本单元是一个CMOS晶体管和一个电容组成的电路。 晶体管最上面的一端,称作栅极,通过…...
ios(swiftui) 属性包装器详解
目录 1. State 2. Binding 3. ObservedObject 和Published 4. StateObject 5. EnvironmentObject和Environment 6. AppStorage 在 SwiftUI 中,属性包装器用于增强和管理视图的状态,以及处理视图与数据模型之间的绑定和交互。下面是一些常见…...
【智能家居】面向对象编程OOP和设计模式(工厂模式)
面向对象编程 类和对象 面向对象编程和面向过程编程区别 设计模式 软件设计模式按类型分 工厂模式 面向对象编程 面向对象编程(Object-Oriented Programming,OOP)是一种程序设计范式,其中程序被组织成对象的集合,每…...
Docker安装Memcached+Python调用
简介:Memcached是一个通用的分布式内存缓存系统。它通常用于通过在RAM中缓存数据和对象来加速动态数据库驱动的网站,以减少必须读取外部数据源(如数据库或API)的次数。Memcached的API提供了一个分布在多台机器上的非常大的哈希表。…...
网页开发 HTML
目录 HTML概述 HTML结构 HTML标签语法 基本标签 标题标签 换行标签 段落标签 文本格式化标签 特殊符号 div和span标签 超链接标签 锚点 img标签 列表标签 表格标签 表单标签 HTML概述 HTML,即超文本标记语言(HyperText Markup Language …...
SHAP(五):使用 XGBoost 进行人口普查收入分类
SHAP(五):使用 XGBoost 进行人口普查收入分类 本笔记本演示了如何使用 XGBoost 预测个人年收入超过 5 万美元的概率。 它使用标准 UCI 成人收入数据集。 要下载此笔记本的副本,请访问 github。 XGBoost 等梯度增强机方法对于具有…...
书成紫微动,律定凤凰驯:一破一立,铁哥的两部作品如何构成完整的文化闭环
书成紫微动,律定凤凰驯。 —— 唐《开元占经》卷一〇三 引言:千年谶语里的文明算法 无破则旧局不死,无立则新局不生。 一句千古古句,藏着文明迭代最严谨的底层逻辑: 先破后立,破立相生,方能形成…...
手把手教你为AK7739音频芯片移植TDM接口(基于Linux ALSA框架)
手把手教你为AK7739音频芯片移植TDM接口(基于Linux ALSA框架) 在嵌入式音频系统开发中,TDM(Time Division Multiplexing)接口因其高带宽和多通道支持能力,成为专业音频设备的首选方案。AK7739作为一款高性能…...
H3C HCL模拟器实战:IS-IS单区域基础配置与排错指南
1. 实验目标与网络环境准备如果你正在学习网络路由协议,特别是运营商级网络常用的IS-IS,那么通过模拟器进行实操是绕不开的一步。这次我用H3C的HCL模拟器,带大家走一遍IS-IS单区域的基本配置。这个实验的目标很明确:不是让你死记硬…...
开源AI代码助手本地化部署:从Cursor10x看私有化编程助手实践
1. 项目概述:当AI代码助手遇上开源社区如果你是一名开发者,最近可能频繁听到一个词:Cursor。这款集成了强大AI能力的代码编辑器,正以惊人的速度改变着许多人的编程习惯。它不再仅仅是一个文本编辑器,更像是一个能理解你…...
Android14实战:在Android Studio中配置Pixel6 Pro模拟器与SDK API 34
1. 为什么你需要一个Android14模拟器? 作为一名Android开发者,我深知在真机上测试应用的重要性。但现实情况是,我们不可能拥有所有型号的设备。还记得去年我在开发一个适配多屏幕的应用时,手头只有两台测试机,结果上线…...
BepInEx插件框架:为什么它是Unity游戏Mod开发的终极解决方案?
BepInEx插件框架:为什么它是Unity游戏Mod开发的终极解决方案? 【免费下载链接】BepInEx Unity / XNA game patcher and plugin framework 项目地址: https://gitcode.com/GitHub_Trending/be/BepInEx 你是否曾经想过为喜欢的Unity游戏添加新功能&…...
SQL如何提取分组中的第一条记录_使用ROW_NUMBER定位数据
ROW_NUMBER() 是最稳的分组取首行解法,需在子查询或CTE中按PARTITION BY分组、ORDER BY排序,外层筛选rn1;GROUP BY配MIN(id)易导致数据错乱,且无ORDER BY时顺序不保证;须建联合索引覆盖分组与排序字段,并注…...
开源工作流引擎ByteChef:从组件化架构到自动化编排实战
1. 项目概述:一个面向开发者的自动化工作流引擎如果你是一名开发者,或者经常需要处理跨系统、跨应用的数据同步、定时任务、API调用编排,那么你大概率对“自动化”有着强烈的需求。我们可能都经历过这样的场景:每天手动从A系统导出…...
基于hexascribe/chatbot-builder框架的对话机器人开发实战指南
1. 项目概述:一个面向开发者的对话机器人构建框架最近在GitHub上看到一个挺有意思的项目,叫hexascribe/chatbot-builder。乍一看名字,你可能会觉得这又是一个“又一个聊天机器人框架”,市面上类似的工具确实不少,从早期…...
MoviePilot批量重命名:5步解决NAS媒体库命名混乱问题
MoviePilot批量重命名:5步解决NAS媒体库命名混乱问题 【免费下载链接】MoviePilot NAS媒体库自动化管理工具 项目地址: https://gitcode.com/gh_mirrors/mo/MoviePilot 你是否曾为NAS中杂乱无章的媒体文件名而烦恼?"Avengers.Endgame.2019.1…...
