类 —— 友元、常/静态成员函数
类
类的大小
和结构体大小求法一致。但需注意,普通空类也会占用 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 等梯度增强机方法对于具有…...
Tetrazine-NHBoc,cas:1380500-93-5,四嗪-氨基叔丁酯的结构特点
Tetrazine-NHBoc(四嗪-氨基叔丁酯)是一种结合了四嗪基团和N-叔丁氧羰基(NHBoc)保护基的有机化合物,以下是对其的详细介绍:一、基本信息中文名称:四嗪-氨基叔丁酯英文名称:Tetrazine-…...
如何评估 SEO 优化的成本效益_SEO优化应该重点关注哪些方面
如何评估 SEO 优化的成本效益 在当今互联网时代,搜索引擎优化(SEO)已经成为了提升网站流量和品牌知名度的关键手段。SEO 优化的成本效益评估并不是一件简单的事情。如何在有限的预算内实现最大的效益,是每一个企业和网站运营者都需…...
seo实用工具对网站长期发展有什么影响
SEO实用工具对网站长期发展的影响 在当今数字化时代,网站的长期发展离不开搜索引擎优化(SEO)。而SEO实用工具,则是推动网站长期发展的重要助手。它们不仅帮助提升网站的搜索排名,还能够提供数据分析、关键词研究和竞争…...
Linux服务器日志爆满?5个实用命令快速定位并清理大日志文件
Linux服务器日志爆满?5个实用命令快速定位并清理大日志文件 当服务器磁盘空间告急时,日志文件往往是罪魁祸首。作为系统管理员,我们需要快速定位问题并安全清理,避免服务中断。本文将分享5个核心命令的组合使用技巧,帮…...
从零开始玩转translategemma-27b-it:Ollama环境搭建与提示词详解
从零开始玩转translategemma-27b-it:Ollama环境搭建与提示词详解 1. 环境准备与快速部署 想要体验强大的图文翻译能力,首先需要搭建好运行环境。translategemma-27b-it是一个基于Ollama部署的翻译模型,支持文本和图片的翻译功能。 1.1 系统…...
芯片研发的残酷真相:流片成功只是开始
芯片成功"点亮"那一刻,项目算完成了吗?如果你认为算,那大概率还没经历过真正的芯片项目后期。事实是,点亮和demo跑通,只不过是拿到了入场券而已。真正的战斗,从客户拿到样片那一刻才开始。很多工…...
告别手动操作!Open-AutoGLM部署教程,让AI接管你的手机
告别手动操作!Open-AutoGLM部署教程,让AI接管你的手机 1. 引言:AI手机助手的革命性突破 想象一下这样的场景:早上醒来,你只需要对手机说"帮我点一杯星巴克燕麦拿铁,加双份浓缩,送到公司&…...
保姆级教程:手把手教你用GLM-4v-9b搭建图片问答机器人
保姆级教程:手把手教你用GLM-4v-9b搭建图片问答机器人 你是不是经常遇到这样的情况:看到一张复杂的图表,想快速了解里面的数据含义;或者收到一张产品图,想知道它的具体型号和功能;又或者辅导孩子作业时&am…...
救命!电路板维修高频故障排查口诀,背会秒上手,修板快准稳
修板半天没头绪?工控伺服板一修就慌?测遍元件还烧板?其实电路板故障排查不用死磕,一套好记的速记口诀,能帮你少走弯路、少赔成本,新手能快速上手,老手直接拉高效率,刷到这篇干货&…...
告别硬件烧钱!用Proteus仿真Arduino UNO做智能小车传感器方案选型
告别硬件烧钱!用Proteus仿真Arduino UNO做智能小车传感器方案选型 在创客和电子竞赛领域,智能小车一直是热门项目,但高昂的硬件成本常常让爱好者望而却步。一套完整的智能车系统可能包含多个传感器、电机驱动模块和控制器,实体采购…...
