C++——类和对象[上]
目录
1.初识面向对象
2.类的引入
3.类的定义
4.成员变量的命名规则
5.类的实例化
6.类对象模型
7.this指针
1.初识面向对象
C语言是一门面向过程的语言,它关注的是完成任务所需要的过程;C++是一门面向对象的语言,将一个任务分为多个对象,每个对象具有不同的行为,将这些对象组合起来、互相交互从而达到完成任务的目的。
例如洗衣服这件事,我们的惯性思维是:将衣服放进洗衣机、撒上一些洗衣粉、放水、启动洗衣机、洗衣机工作完成将衣服拿出来晒......这就是一种典型的面向过程的思想,所以说C语言更加符合人的思维逻辑。而面向对象的思维逻辑则不同,它是一种宏观的、通用的思维逻辑,还拿洗衣服这件事来说,面向对象会将洗衣服这个任务拆分出两个对象,即人和洗衣机,人的行为有把衣服放进洗衣机、撒上洗衣粉、启动洗衣机、晒衣服等等,洗衣机的行为有洗衣服、脱水等等,那么这些对象之间通过互相交互就能完成洗衣服的任务。那么面向过程的思维导图可能是下面这样的:
而面向对象的思维导图可能是这样的:
2.类的引入
凭借C语言的经验我们可以知道结构体是某一类事务的属性集合。就比如用结构体描述一个学生,我们可以发现学生当中的很多共性:学生都有姓名、学号、年纪、班级......我们把这些属性集合起来构成一个结构体,再用这个结构体去定义一个变量,这个变量就可以说是一个具体的"人"。那么C语言的局限性非常明显,结构体不能定义行为(函数),虽然可以定义函数指针,但是这种做法好像没必要。所以C++便出手了,在原有的"struct"关键字上进行了扩展,其中之一便是struct结构体中不仅可以定义变量,还可以定义函数,并且C++的"struct"关键字保留了C语言的所有用法。所以我们再对学生的例子进行扩展,我们把学生的共有属性集合到一起之后,我们还可以发掘他们之间的共有行为:学生都会上课、吃饭、睡觉......那么用C++描述学生就非常合适了:
struct Student
{char* name;char* id;int age;void eat(){} // 吃饭行为void sleep(){}// 睡觉行为void study(){}// 学习行为
};
在C++中就没有结构体的概念了,我们上面代码的"Student"称为"Student类",这个"Student"是一个自定义类型,与内置类型不同的是,内置类型是C++帮我们定义好的类型,我们开箱即用;而自定义类型是我们自己规定的一个类型,如何管理这个类型是程序员自己要做的事。举个例子,内置类型int可以支持"+"、"-"、"*"、"/"等运算,如果我们想要"Student"类也支持这些运算,是我们程序员自己要去规定的(在后面的章节会介绍有关这部分的内容)。
那么类型有了,我们该如何定义"变量"?注意此时我将"变量"这两个字用双引号括起来,目的就是为了告诉读者,在C++中,几乎不会出现"变量"这种说法,而是统称对象。我们重新问一遍,有了"Student"类,如何定义"Student"类型的对象?
int main()
{// 哪种定义方式是正确的?struct Student s1;// 写法1Student s2;// 写法2return 0;
}
事实上,写法1在C++的角度看来是"错误"的写法,C++更倾向于写法2,也就是说,在C++中使用struct关键字实现的类,在实例化对象时,不需要再增加struct关键字!注意,这里又有一个新名词——实例化,实际上实例化和定义没有区别,这只是C++的习惯。因为C++要兼容C语言,所以保留了看似"累赘"的写法。
3.类的定义
定义类的时候,我们将在类中定义的变量称为成员变量,定义的函数称为成员函数或者成员方法,这些成员函数都被编译器视为内联函数(即使我们不加inline关键字)。同时,类的定义构建了一个全新的作用域——类域。
既然类构成了类域,在类中使用成员时,是否也需要遵循编译器的"向上搜索"呢?
struct Student
{char* name;// 函数当中这样使用成员,是否正确?void Init(){name = "";id = 0;age = 0;}char* id;int age;
};
这种使用方法是没有问题的,也就是说类域与常规的作用域不一样。编译器将类域当成一个整体,当某个成员函数需要使用某个成员变量时,会在整个类域当中搜索。
如果我们在类中声明函数,而在类外定义函数,就需要使用类域来配合完成工作:
#include <stdlib.h>
struct Stack
{void Init();int* a;int top;int capacity;
};struct Queue
{void Init();int* a;int front;int tail;int capacity;
};void Init()
{a = (int*)malloc(4);top = 0;capacity = 4;
}void Init()
{a = (int*)malloc(4);front = 0;tail = 0;capacity = 4;
}
这种写法是错误的。一是函数重定义,二是在类外定义的函数是普通函数,这些函数的作用域使用了当前函数作用域和全局域都没有定义的对象。我们的本意是定义Stack类和Queue类当中声明的Init函数,所以我们需要使用类域,指明到底是哪个类的成员函数。当我们指明了成员函数后,编译器就知道了该函数不是全局函数,而是某一个类当中的成员函数,所以自然而然能够使用类中其他的成员:
void Stack::Init()
{a = (int*)malloc(4);top = 0;capacity = 4;
}void Queue::Init()
{a = (int*)malloc(4);front = 0;tail = 0;capacity = 4;
}
那么要在类外使用成员的时候,需要通过对象去使用,不能直接使用类域指定成员使用,这样的语法是错误的:
struct Stack
{void Init(){a = (int*)malloc(4);top = capacity = 0;}int* a;int top;int capacity;
};int main()
{// 正确的用法Stack st;st.Init();cout << st.top << endl;// 错误的用法Stack::Init();cout << Stack::top << endl;return 0;
}
其实在C++当中,并不喜欢用struct定义类,而是用一个新的关键字——class来定义类。我们可以用class替换struct,但我们很可能会因为不熟悉而"触发"bug:
using namespace std;
#include <stdlib.h>class Stack
{void Init(){a = (int*)malloc(4);top = capacity = 0;}int* a;int top;int capacity;
};int main()
{Stack st;st.Init();cout << st.top << endl;return 0;
}
如果大家勤快的话,将这段代码粘贴到你的编译器当中,会发现你的编译器报错了。
这就不得不提到面向对象的三大特性:封装、继承、多态。三大特性并不是只面向对象只有这三个特性,而是这三个特性在面向对象中占据主要地位。那么C++为了考虑封装性,引入了访问限定符:public(公有)、protected(保护)、private(私有),而现在,我们主要使用两个访问限定符,即public和private。
访问限定符说明:
1.public修饰的成员可以在类外直接访问(通过对象)
2.private修饰的成员在类外不能直接访问
3.访问权限的作用域从当前访问限定符的位置直到类域结束或者直到下一个访问限定符
4.class定义的类的访问权限默认为private,struct默认为public
我们修改上面的代码,我们的本意是让Stack类的成员变量不能在类外访问,在类外能访问的只能是成员函数。这种屏蔽底层实现而只暴露接口的做法是封装的常用手段。
class Stack
{
public:void Init(){a = (int*)malloc(4);top = capacity = 0;}
private:int* a;int top;int capacity;
};
我们能够推导出,class和struct的区别就在于默认的访问权限不同。
4.成员变量的命名规则
上面的成员变量的命名都是"不规范"的,我们观察一下代码:
class Date// 日期类
{
public:void Init(int year = 0, int month = 0, int day = 0){year = year;month = month;day = day;}
private:int year;int month;int day;
};
有的读者可能就会钻牛角尖了,修改一下Init函数的参数不就可以了吗?道理是这么个道理,实际上真正的工程项目可能有几十几百个类,如果把成员函数的参数都修改成"a"、"b"、"c"......那还得了?所以在设计类的时候就需要考虑这个问题,C++的习惯(实际上不是C++规定的,而是公司、企业里面规定的)是在定义成员变量时,在其变量名之前或者之后加一个或多个"_",从而区分成员变量和非成员变量:
class Date// 日期类
{
public:void Init(int year = 0, int month = 0, int day = 0){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
5.类的实例化
使用类类型创建对象的过程,就称为类的实例化。
对于类,我们应当有一下两点认识:
1.类是对对象进行描述的,是一个抽象的类型。定义类的时候并不会给它分配实际的内存空间来存储它
2.一个类可以实例化出多个对象,实例化出的对象才实际占用物理空间,存储类的成员(暂且这么理解)
以一个具体的例子来方便大家理解:我们可以把类看成建筑物的设计图纸,而对象便是通过这张图纸实例化出的建筑物。
我们在上面说过不能直接通过类域去访问类当中的成员,其中一个原因便是因为不能去操作"设计图纸":
#include <iostream>
using namespace std;
class Date// 日期类
{
public:void Init(int year = 0, int month = 0, int day = 0){_year = year;_month = month;_day = day;}void print(){cout << _year << ":" << _month << ":" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date::_year = 2023;// 错误的用法Date::print();// 也是错误的return 0;
}
对于成员变量来说,它们在实例化对象之前都没有实际的存储空间,没有实际空间意味着它们只是声明。而我们要将整数2023赋值给一个没有空间的成员变量本身就是一种错误的做法。拿具体的例子来说,售楼中心有会有一个沙盘模型,它可以详细的看到每一栋楼、每一间房的具体设计,我们就可以把这个沙盘模型看作一个类,某天我们购买了一个冰箱,能直接放进沙盘模型里面去吗?当然,也有读者会产生一个疑问,那么成员函数print已经是定义好的函数,为什么不能指定类域直接访问?确实,print函数已经被定义好了,但是它缺失了一个调用条件,这个调用条件便是后面要介绍的this指针。
6.类对象模型
先计算下面这段程序中A类对象的大小(代码是在x86环境下跑的,计算过程与计算结构体大小相同):
#include <iostream>
using namespace std;
class A
{
public:void print(){cout << _a << endl;}
private:int _a;
};int main()
{A a;cout << sizeof(a) << endl;return 0;
}
很多读者觉得输出的结果应该是8,因为类当中有一个函数,可以当成函数指针来看待。实际上输出的是4,也就是说并没有计算函数的大小,换句话说,成员函数好像并不在对象中存储。我们可以猜测三种类对象的存储方式:
1.对象中存储类中声明的每个成员:
这种做法的缺陷是很明显的,会浪费很多空间。我们确实需要保证对象与对象之间的成员变量是独立的,但是成员函数并不需要各自私有一份,因为每个对象的行为都是一样的,每个对象调用的成员函数都是同一个函数。
2.成员函数放在内存的某个区域,对象模型只存储一个指向该区域的指针:
很显然,这种方案即使再合理也不被C++采用(已经证明过了)。
3.只保存成员变量,成员函数存放在代码段当中:
很明显,C++采用第三种对象存储模型。我们可以这么推理:这些成员函数都属于特定的类,那么编译器在维护这些函数时一定有方法可以分辨,对象在调用成员函数时一定只能调用属于该类域的函数,编译器就能够通过对象调用的函数去找到属于该类域的函数。
我们再研究一个不寻常的问题,请读者猜一猜该段程序当中A、B、C类的大小各是多少?
#include <iostream>
using namespace std;
class A
{};class B
{
public:void print(){}
};class C
{
public:void print(){}
private:int _c;
};int main()
{cout << sizeof(A) << endl;cout << sizeof(B) << endl;cout << sizeof(C) << endl;return 0;
}
那么对于类A和类B,我们都可以把它看作空类(对象模型不存储成员函数),其大小为1;类C的大小毋庸置疑为4。那么为什么空类的大小为1?这是一个占位大小,目的是告诉编译器存在这个类,这个类还能实例化出对象,如果空类的大小为0,那么对象的地址是取不到的:
int main()
{A a;B b;cout << &a << endl;cout << &b << endl;return 0;
}
空类的大小设置为1,表明该类是一个有效类、合法类,可以实例化出对象,并且该对象在内存当中是持有内存空间的,否则无法对其进行取地址操作。
7.this指针
以日期类为例:
#include <iostream>
using namespace std;
class Date// 日期类
{
public:void Init(int year = 0, int month = 0, int day = 0){_year = year;_month = month;_day = day;}void print(){cout << _year << ":" << _month << ":" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1, d2;d1.Init(2023, 4, 22);d2.Init(2024, 12, 11);d1.print();d2.print();return 0;
}
为什么d1和d2对象调用一个函数却能够打印出不同的结果?原因在于C++为类当中的每个普通成员函数增加了一个隐藏的this指针,注意我说的普通成员函数,后面还会介绍静态成员函数。这个this指针会被当做普通成员函数的第一个参数,this指针是指向调用该普通成员函数的对象,在普通成员函数的内部,需要使用成员变量的场景,编译器都会隐式地发生解引用。this指针的原型为[类名* const this],其中this以关键字的形式存在,不可被修改。
那么上面的Date类当中的print成员函数实际上的形式这样的:
void print(Date* const this)// 实际的代码当中不能这么写
{// 这种写法是可以的cout << this->_year << ":" << this->_month << ":" << this->_day << endl;
}
虽然说上面这段才是print函数全貌,但我们自己在写的时候不能显式定义this指针,但我们可以显式地使用this指针。
那么this指针从何而来?实际上在外部使用对象调用这些普通成员函数的时候编译器就会自动地、隐式地将调用普通成员函数的对象的地址(指针)作为实参传递过去:
int main()
{Date d1, d2;d1.Init(2023, 4, 22);// 隐式传递d1对象的地址d2.Init(2024, 12, 11);// 隐式传递d2对象的地址d1.print();// 隐式传递d1对象的地址d2.print();// 隐式传递d2对象的地址return 0;
}
那么现在又有一个关键问题,this指针能否为空指针(一道面试题)?答案是可以,我们看下面的两段代码:
#include <iostream>
using namespace std;class A
{
public:void print(){cout << "print()" << endl;}
private:int _a;
};int main()
{A* pa = nullptr;pa->print();return 0;
}
这段代码是编译错误,还是运行时崩溃或者是正常运行?答案是正常运行,其原因在于pa指针即使是一个空的对象指针,即使使用了"->",但是它并不会发生解引用,而是指明类域,之后编译器就能找到正确的函数调用,因为对象的成员函数并不存储在对象当中,而是存储在了所有同类对象可见的代码段。调用print函数时,编译器会隐式的传递一个对象指针,现在已经有了一个现成的对象指针,就是pa,pa传递给print后,在其内部并没有发生任何有关空指针的解引用问题,所以该程序不会发生错误,反而是正常运行。
#include <iostream>
using namespace std;class A
{
public:void print(){cout << _a << endl;}
private:int _a;
};int main()
{A* pa = nullptr;pa->print();return 0;
}
这段程序非常执行结果非常明显,运行时崩溃,原因是发生了空指针的解引用。
最后,再解答一个遗留的问题,为什么不能在类外通过使用类域指定调用普通成员函数?
#include <iostream>
using namespace std;class A
{
public:void print(){cout << _a << endl;}
private:int _a;
};int main()
{A::print();return 0;
}
事实上答案已经非常简单了。print作为A类的普通成员函数,表面上看起来是一个无参的函数,但实际上它有一个隐藏的this指针,所以print是一个单参数的函数。而在外部使用类域指定调用print时,指定的是无参的print,而A类当中并没有无参的prnit函数,所以不能调用。在不改变A类内部结构的情况下,我们无法修改代码使得通过编译,因为C++不允许我们显式传递this指针。
那么this指针变量存储在哪里?很多读者会认为this指针作为成员函数的参数应该存储在代码段中,实际上this指针作为成员函数的形参,只有在函数被调用时才会创建函数栈,创建了函数栈才能存放this指针,所以this指针存储在栈中。
相关文章:

C++——类和对象[上]
目录 1.初识面向对象 2.类的引入 3.类的定义 4.成员变量的命名规则 5.类的实例化 6.类对象模型 7.this指针 1.初识面向对象 C语言是一门面向过程的语言,它关注的是完成任务所需要的过程;C是一门面向对象的语言,将一个任务分为多个对…...

MySQL日志
目录 一 关于mysql的设计和运行逻辑 二 MySQL的三类日志 三 对于日志的利用 插入查询 1 备份 2 删除重复数据 一 关于mysql的设计和运行逻辑 mysql在启动的时候非常占空间,需要申请很大的空间,但是有时候内存并没有那么多,所以OS会把my…...

TinyURL 的加密与解密、猜数字游戏、 Fizz Buzz、相对名次----2023/4/28
TinyURL 的加密与解密----2023/4/28 TinyURL 是一种 URL 简化服务, 比如:当你输入一个 URL https://leetcode.com/problems/design-tinyurl 时,它将返回一个简化的URL http://tinyurl.com/4e9iAk 。请你设计一个类来加密与解密 TinyURL 。 加…...

Spring boot结合SkyWalking-Trace工具类实现日志打印请求链路traceid
背景: 随着业务的复杂化、解耦化,运维人员和开发人员需要对请求链路跟踪来快速发现和定位问题,基于应用已经集成了SkyWalking的前提下,如何通过获取SkyWalking生成的统一traceId并加入打印日志中,方便开发人员能够根据…...

精通ES=ElasticSearch
Elasticsearch 是一个分布式、高扩展、高实时的搜索与 数据分析引擎。它能很方便的使大量数据具有搜索、分析和探索的能力。充分利用Elasticsearch的水平 伸缩性,能使数据在 生产环境变得更有价值。Elasticsearch 的实现原理主要分为以下几个步骤,首先用…...

RabbitMQ-扇形交换机(Fanout )
扇形交换机:Fanout Exchange扇形交换机是最基本的交换机类型,它所能做的事情非常简单———广播消息。扇形交换机会把能接收到的消息全部发送给绑定在自己身上的队列。因为广播不需要“思考”,所以扇形交换机处理消息的速度也是所有的交换机类…...

Python 学习曲线 从 Python 新手到 Pro
Python 学习曲线 从 Python新手到 Pro 使用代码片段介绍: Python 是世界上最通用和使用最广泛的编程语言之一,以其简单性、可读性和多功能性而闻名。 在本文中,我们将探讨一系列示例场景,其中代码由具有三个不同专业知识水平的程序…...

薪资18K需要什么水平?来看看98年测试工程师的面试全过程…
我的情况 大概介绍一下个人情况,男,本科,三年多测试工作经验,懂python,会写脚本,会selenium,会性能,然而到今天都没有收到一份offer!从年后就开始准备简历,年…...

基于趋动云的 Stable Diffusion Webui 环境搭建
Stable Diffusion Webui 环境搭建,首先新建一个项目: 然后,选择镜像。注意点公开的,已近做好的这个镜像,superx创建,集成了miniconda3的镜像。 然后选择添加数据源,一样,还是点公开&…...

备忘录设计模式解读
目录 问题引进 游戏角色状态恢复问题 传统方案解决游戏角色恢复 传统的方式的问题分析 备忘录模式基本介绍 基本介绍 备忘录模式的原理类图 对原理类图的说明 游戏角色恢复状态实例 应用实例要求 思路分析和图解(类图) 代码实战 备忘录模式的注意事项和细节 问题引…...

股票期货模拟交易有用吗?股票期货模拟交易心得
股票期货市场为了满足新用户的需求,有专门的股票期货模拟交易平台,大家可以在这个平台上进行股票期货的模拟交易,这样可以通过不断总结,丰富我们的知识。下面整理的股票期货模拟交易实验心得,从股票期货模拟交易与实盘…...

2023年五月份图形化三级打卡试题
活动时间 从2023年5月1日至5月21日,每天一道编程题。 本次打卡的规则如下: 小朋友每天利用10~15分钟做一道编程题,遇到问题就来群内讨论,我来给大家答疑。 小朋友做完题目后,截图到朋友圈打卡并把打卡的截图发到活动群…...

【华为OD机试真题】字母组合(javapython)100%通过率 详细代码注释
字母组合 知识点回溯 时间限制:1s 空间限制:256MB 限定语言:不限 题目描述: 每个数字对应多个字母,对应关系如下: 0: a,b,c 1: d,e,f 2: g,hi 3: j,k,l 4: m,n,o 5: p,q,r 6: s,t 7:u,v 8: w,x 9: y,z 输入一串数字后,通过数字和字母的对应关系可以得到多个字母字符串 (要…...

精彩!openEuler 社区年度顶级会议发生了啥?
2023年4月20-21日,万涓汇流,奔涌向前,openEuler Developer Day2023(以下简称“ODD2023”)在上海以线上线下的方式圆满举办。 本次大会由开放原子开源基金会指导,中国软件行业协会、openEuler社区、边缘计算产业联盟共同主办&#…...

Confidential Containers发布0.5.0版本,龙蜥将基于八大特性构建开箱即用的机密容器解决方案
文/段勇帅 01 前言 机密容器(Confidential Containers,简称CoCo)是 Cloud Native Computing Foundation(CNCF)Sandbox 项目。目前机密容器项目的核心参与者包括阿里云、AMD、ARM、IBM、Intel、Microsoft、Red Hat、R…...

独立储能的现货电能量与调频辅助服务市场出清协调机制(Matlab代码实现)
💥 💥 💞 💞 欢迎来到本博客 ❤️ ❤️ 💥 💥 🏆 博主优势: 🌞 🌞 🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 …...

使用 Luckysheet 可实现 Web 的 Excel
一、写在前面 工作中会遇到excel的导入和导出,换个角度看,假如有个 web 版本的excel ,且能上传现有的,修改编辑后再下载也是个不错的方案。 Luckysheet 是实现 web版Excel的一个优秀的框架。 Luckysheet ,一款纯前端类…...

时间序列预测(一)基于Prophet的销售额预测
时间序列预测(一)基于Prophet的销售额预测 小O:小H,有没有什么方法能快速的预测下未来的销售额啊 小H:Facebook曾经开源了一款时间序列预测算法fbprophet,简单又快速~ 传统的时间序列算法很多&a…...

【电科复试第一名】23上交819考研经验分享
笔者来自通信考研小马哥23上交819全程班学员 819,上岸经验贴,知无不言 初试第十一,复试第一,总分第七(与第六同分) 考研经历:本科就读与湖南某末985,大学时间没好好学习,天天打王者,玩steam上…...

每日学术速递4.24
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Collaborative Diffusion for Multi-Modal Face Generation and Editing(CVPR 2023) 标题:多模态人脸生成和编辑的协同扩散 作者:Ziqi Huang, Kelvin C.K. …...

怎么把mkv文件转成mp4格式,3招立马处理
怎么把mkv文件转成mp4格式的方法你知道吗?我想很多朋友会遇到这样的情况,下载视频后发现无法打开。原来我们下载的视频格式是mkv,也许这个格式大家不是很熟悉的。那么今天就来认识一下,mkv是Matroska的一种媒体文件,mk…...

SEO机制算是让我玩明白了
获取当前时间时间戳,返回遵循ISO 8601扩展格式的日期 new Date(Date.now()).toISOString() 使用moment库转换回来 this.moment(new Date(Date.now()).toISOString()).format("YYYY-MM-DD") js去掉富文本中html标签和图片 filterHtmlTag(val) {if(!val){…...

JDBC连接数据库详细教程指南
目录 一、JDBC介绍 二、JDBC环境的搭建 三、JDBC的开发步骤 1、加载JDBC驱动程序 2、建立数据库连接 3、创建Statement对象 4、执行SQL语句 5、处理结果集 6、关闭连接 7、示例程序 8、注意 一、JDBC介绍...

换个花样玩C++(2)柔性数组怎么玩
如果你涉足网络传输方向的开发,我想你对这段类似的代码应该不会很陌生,先看代码: int check_msg(svr_proto_t* pkg, uint32_t bodylen, fdsession_t* fdsess) {struct report_msg {uint32_t gameid;uint32_t userid;uint32_t recvid;uint32_t onlineid;uint32_t …...

【前端】一个好看的前端页面
序言 突发奇想,看到这个特效还不错,就加工了一下,如果也能帮到你,很开心 先上效果图 部分代码讲解 前端生成uuid function getUUID(len, radix) {var chars 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.s…...

flink on k8s提交任务
目录 相关文档前置准备构建镜像提交任务 相关文档 https://nightlies.apache.org/flink/flink-docs-release-1.13/docs/deployment/resource-providers/native_kubernetes/ 前置准备 flink的lib目录下放入两个依赖 bcpkix-jdk15on-1.68.jar bcprov-jdk15on-1.69.jar 创建用户…...

如何判定自己适合自学编程还是报班?
首先在这里,不做偏向性推荐,主要还是看个人条件。 宝剑锋从磨砺出,学习本身是一件艰苦的事情。在决定之前,建议先按照下图问自己三个问题自我检测。 如果你还不能确定,自学和报班的优劣势分析,或许能帮你们…...

本地缓存解决方案Caffeine | Spring Cloud 38
一、Caffeine简介 Caffeine是一款高性能、最优缓存库。Caffeine是受Google guava启发的本地缓存(青出于蓝而胜于蓝),在Cafeine的改进设计中借鉴了 Guava 缓存和 ConcurrentLinkedHashMap,Guava缓存可以参考上篇:本地缓…...

Docker常用命令笔记
docker常用命令 1 基础命令 sudo docker version #查看docker的版本信息 sudo docker info #查看docker系统信息,包括镜像和容器的数量 2 镜像命令 1.sudo docker images #查看本地主机的所有主机镜像 #解释 **REPOSITORY **#镜像的仓库源TAG **** …...

Nachos系统的上下文切换
Fork调用创建进程 在实验1中通过gdb调试初步熟悉了Nahcos上下文切换的基本流程,但这个过程还不够清晰,通过源码阅读进一步了解这个过程。 在实验1中通过执行Threadtest,Fork创建子进程,并传入SimpleThread执行currentThread->…...