Welcome to NICK's BLOG!
At Beginning
iiiii This is my first blog !!!!!
我的肺痒痒
This paper introduces Abductive Reflection (ABL-Refl), a novel neuro-symbolic (NeSy) learning framework that efficiently detects and corrects inconsistencies between neural network outputs and symbolic reasoning. Inspired by human dual-process cognition (System 1 & System 2), ABL-Refl uses a reflection mechanism to flag errors in neural predictions and invokes abductive reasoning to fix them.
Why it matters?
Dataset
stores data samples and expected values
dataset = MyDataset(file)
Dataloader
groups data in batches, enables multiprocessing
dataloader = DataLoader(dataset, batch_size, shuffle=True)
热键 | 功能描述 |
---|---|
鼠标左键 | 选择或操作组件。 |
鼠标滚轮 | 启用手型工具,可拖拽界面(平移视图)。 |
鼠标右键 | 旋转视角,配合 WASD 键可自由移动相机(飞行模式)。 |
Q | 手形工具:平移整个 Scene 视图。 |
W | 移动工具:移动选中的游戏对象。 |
E | 旋转工具:按任意角度旋转选中的游戏对象。 |
R | 缩放工具:调整选中对象的大小(整体或单轴缩放)。 |
补充说明
W
、E
、R
)。1 | npm i pinia |
在main.js中:
1 | import { createPinia } from 'pinia' |
核心步骤:
1- 定义store
1 | import { defineStore } from 'pinia' |
2- 组件使用store
1 | <script setup> |
“信息安全的核心不是完美的技术,而是有效的管理与人性化的考虑。” —— Bruce Schneier
在项目开源的过程中,不可避免地会碰到敏感信息,例如:
AppID
、AppSecret
API_KEY
,如支付网关、地图服务、邮件发送等。.env
文件中明文存储的敏感数据。这些敏感信息如果在开源项目中暴露,可能会引发数据泄露、财务损失、甚至法律责任等严重后果,因此在项目中需要妥善处理和保护。
more >>WebSocket是一种在基于TCP连接上进行全双工通信的协议
全双工(Full Duplex):允许数据在两个方向上同时传输。
半双工(Half Duplex):允许数据在两个方向上传输,但是同一个时间段内只允许一个方向上传输。
请求数据
1 | GET ws://localhost/chat HTTP/1.1 |
响应数据
1 | HTTP/1.1 101 Switching Protocols |
Get、Head、Post和PostForm函数发出HTTP/HTTPS请求。
1 | resp, err := http.Get("http://example.com/") |
程序在使用完response后必须关闭回复的主体。
1 | resp, err := http.Get("http://example.com/") |
Download Anaconda Distribution | Anaconda
下载安装后,搜索Anaconda Navigator,打开后运行JupyterLab,在里面敲代码就行
The corresponding dictionary:
1 | letterToDna = { |
encode name
1 | #Replace with dictionary |
创建文件 hello.go
1 | package main |
命令行运行
1 | go run hello.go |
在 Go 语言里,命名为 main 的包具有特殊的含义。 Go 语言的编译程序会试图把这种名字的包编译为二进制可执行文件。所有用 Go 语言编译的可执行程序都必须有一个名叫 main 的包。一个可执行程序有且仅有一个 main 包。
当编译器发现某个包的名字为 main 时,它一定也会发现名为
main()
的函数,否则不会创建可执行文件。
main()
函数是程序的入口,所以,如果没有这个函数,程序就没有办法开始执行。
程序编译时,会使用声明 main 包的代码所在的目录的目录名作为二进制可执行文件的文件名。
more >>类型 | 位 | 范围 |
---|---|---|
char | 1 个字节 | -128 到 127 或者 0 到 255 |
unsigned char | 1 个字节 | 0 到 255 |
signed char | 1 个字节 | -128 到 127 |
int | 4 个字节 | -2147483648 到 2147483647 |
unsigned int | 4 个字节 | 0 到 4294967295 |
signed int | 4 个字节 | -2147483648 到 2147483647 |
short int | 2 个字节 | -32768 到 32767 |
unsigned short int | 2 个字节 | 0 到 65,535 |
signed short int | 2 个字节 | -32768 到 32767 |
long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
signed long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
unsigned long int | 8 个字节 | 0 到 18,446,744,073,709,551,615 |
float | 4 个字节 | 精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字) |
double | 8 个字节 | 双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字) |
long long | 8 个字节 | 双精度型占8 个字节(64位)内存空间,表示 -9,223,372,036,854,775,807 到 9,223,372,036,854,775,807 的范围 |
long double | 16 个字节 | 长双精度型 16 个字节(128位)内存空间,可提供18-19位有效数字。 |
sizeof()
运算符sizeof(<类型>)
或者sizeof(<变量>)
1 | int a; sizeof(a) |
C++的转换语法不像Java那么严格,例如,double可以自动(隐式)转换出float,浮点数自动转换得到整数,但自由的代价是风险增大
1 | int x = 1.7;//居然可以 |
C++支持的强制类型转化语法
Java风格,C风格 (类型名)(表达式):(double)a
,(int)(x+y)
,(int)6.2%4
C++风格 类型作为函数名,()作为参数:double(a)
,int(x+y)
,int(6.2)%4
复杂风格,引入注目,突出这儿有类型转换:static_cast \
1 | int ii = static_cast<int>(5.5)://等效于 int ii = 5.5;等效于 imt ii = int(5.5); |
在强制类型运算后原变量不变,但得到一个所需类型的中间变量。
C/C++系统函数是C/C++编译系统(开发包)已预定义的函数。
#include <iostream>
#include <iomanip>
#include <bits/stdc++.h>
函数的参数
在C++中,函数的参数计算顺序是未定义的。
这意味着在表达式 max(f(x), g(x))
中,f(x)
和 g(x)
的计算顺序是不确定的。
内联函数(inline) , 编译时,就将被调函数体的代码直接插到调用处(c/c++特色)
1 | inline int max (int x, int y) |
重载函数是相同的作用域,两个或更多具有相同函数名,不同参数列表的函数。编译器根据调用时的实参个数以及类型来确定调用淮。
重载函数必须具有不同参数列表,理解为:不同的参数个数,或不同的参数类型。
形参缺省值与重载有些类似,注意区分
```c++
int func(int xl, int x2 = 2, int x3 = 3) {
return xl + x2 + x3;
}// int
void main(void){
cont<< func(10) <<endl; // 10, 2,3
cout<< func(10,8} <<endl; //10,8,3
1
1 |
|
赋值时不可以中间缺省
1 | cout<< func(lO, , 8) <<endl; II 错误 |
不可以定义与声明同时赋值
如果一个函数有原型声明,则默认参数值应在函数原型声明中给出
1 | int func(int X 1 , int x2 = 3, int x3=4); //声明 |
当形参缺省多处声明+重载相遇
1
2
3
4
5
6
7
8 int f(int x,int y=3); int f(int);
int main(){
int f(int x,int y=4); //如果去掉,则因为二义性导致编译错误
cont<< f(3) << endl; //结果7
return O;
}
int f(int x,int y){ return x+y;} //重载
int f(int x){ return x;} //重载
以类型作为变量
1 | template<typename T>//作用域就是下面的一个方法 |
变量具有生命周期和作用域。函数具有作用域。
生命周期指变量的空间分配到释放的周期,三类生命周期,为方便管理,C++将不同生命周期的变量存储在不同性质的存储空间中。
存储类别
静态局部变量:作用范围局部(函数内部),生命周期:静态。
static int i =3
; 但i 其实不—定是3 , i只是最初是3 。作用域
局部作用域:在函数内部或在块(复合语句)中定义的变量都是局部变量,其作用域开始于声明处,结束于函数/块的结尾处。
全局作用域(物理文件)
在函数外定义的变量称为全局变量,对应文件作用域。其缺省的作用范围是: 从定义位置开始到该源程序文件结束。
全局变量与局部变量同名时,局部变量优先。可以利用域作用符::标记全局变量
1 | #include <iostream.h> |
函数要访问当前源文件中定义的全局变量:
通常在前面定义全局变量,后面定义函数,函数可以访问全局变量
如果在后面定义全局变量,需要在前面利用extern声明全局变量,声明之后可以使用
1 | #include < iostream > |
声明可以在函数内部或者外部,声明的位置决定了其作用范围
static修饰函数
static 也可以修饰函数,描述其为内部函数
内部函数:函数只局限于在本文件中调用,其它文件不能调用,用static 定义该函数。
外部函数:是函数的默认形式,即可以被外部调用。
在调用文件中用extern 声明,把该函数的作用域扩展到调用文件。
1 | static float fac1(float n) |
1 | extern float fac(int n); |
命名空间作用域
c++提供了名空间(namespace) 机制来解决名冲突问题。
在—个名空间中定义的全局标识符(函数/全局变呈),其作用域为该名空间。
当在—个名空间外部需要使用该名空间中定义的全局标识符时,可用该名空间的名字来修饰或受 限。
1 | namespace A |
1 、不使用using
1 | ...A::x... //A中的x |
2、使用using省略整个命名空间
1 | using namespace A; |
3、使用using省略命名空间的某个函数
1 | using A::f; |
文件包含
一个源文件也可以将另外一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中。
#include "文件名“
通常情况下, 很少在一个cpp中include另一个cpp , 而应当include其声明文件h , 即包含file的声明(引用),而不是其全部。(可以使用,但不拥有)
mathx.h
1 | const double Pl = 3.14; |
mathx.cpp
1 | #include "mathx.h" |
file1.cpp
1 | #include < iostream > |
.h文件中能包含:
不能包含:
因为一个头文件的内容实际上是会被引入到多个不同的 .cpp 文件中的,并且它们都会被编译。放声明当然没事,如果放了定义,那么也就相当于在多个文件中出现了对于一个符号(变量或函数)的定义,纵然这些定义都是相同的,但对于编译器来说,这样做不合法。
数组是同一类型的一组值(例如, 10个int或15个char) , 在内存中顺序存放。
整个数组共用—个名字,其中的每—项称为—个元素,对应一个下标,下标从0开始。
C++规定,数组元素个数必须是常量,不能通过赋值修改数组长度。(VC 会报错,但MinGW 允许)
C++ 中, 数组不是对象,不用new去创建
int a [10]; a [15] = 1 ;
可能带病工作,可能立即崩溃定义数组时,可以用{}给数组元素赋值而不指定长度
赋值元素的个数为数组的长度。int a[]= {0,1, 2, 3}; 则a的长度为4 。
定义时,同时指定长度与{ }赋值,应该保证指定长度>=赋值元素个数
未赋值的元素为0 、0.0或者null 。int b[10]= {1};
int c[2]= {1, 2, 3}; 挑衅编译器,规范的编译器捉示语法错;
不赋初值时, static 或全局数组均默认其为0或’\0 ‘ , 其他局部数组赋值随机
在C++中,数组名被认为是数组在内存中存放的首地址。Java中也是这样认为的
用数组名作函数参数。实参传递的是数组在内存中的地址
被调函数内外,共用同一段内存被调函数内可以修改数组的内容
用数组名作函数参数,实参/形参类型一致。
如果是一维数组,形参通常省略元素个数,例如int a[]
如果是二维数组,形参通常省略第一维,但不可省略第二维,且第二维必须与实参中的维数相等。
如果是高维数组,形参通常省略第一维,但不可省略低维,其他维度必须一致
约定:用可作为字符串的结束标志,它占内存空间,但不计入串长度。C程序依据’\0’ 判断字符串是否结束,而不是根据字符数组的长度。
1 | char a[]= {'C','H','I','N','A'}; |
a是字符数组,非字符串; b是字符数组也是字符串,数组长度6B, 字符串长度5B
c是字符数组也是字符串,数组长度为10, 串长度为5, 串占有6个字节
字符串处理
要求有足够空间可以存放运笢结果
1 | #include <string.h> |
包含头文件#include<vector>
标准操作:初始化、添加、遍历、插入、删除、查找、排序、释放(动态数据管理)
vector 的初始化:可以有五种方式
vector< int> a(10);
//10个整型元素的向最,初始长度,可变长vector< int> a(10, 1);
//10个整型元素的向鼠,且初值均为1vector< int> b(a);
//用b 向量来创建a 向量,整体复制性赋值vector< int> a(b.begin(),b.begin()+ 3);
//从容器b中获取第0个到第2个(共3个)元素int b[7] ={1,2,3,4,5,9,8}; vector< int> a(b,b+ 7);
//从数组b中获得初值添加,插入,删除: 对千连续空间的数据结构,插入,删除代价巨大
vector< int> vec1; for(int i =O;i < 1 O;i + +) vec1 .push_back(i);
//后端插入vec1.insert(vec1.end(),5,3);
//从vec1.back位置插入5个值为3 的元素vec1 .erase(vec1.begin(),vec1.begin()+3);
//删除之间的元素,其他元素前移遍历:
for(int i =O;i < =v.size()-1 ;i + +) cout < <v[i] < < " "
for (const int i:v)cout < < i < < "\t";
排序,查找:需要include<algorithm>
1 | int a[10] = { 9, 0, 1, 2, 3, 7, 4, 5, 100, 10 }; |
1 | bool cmp(const student &a, const student &b){ |
指针变量的定义(语法)
类型标识符*指针变量名
1 | int * p; // *用于定义指针p, int定义指针p指向整数 |
1 | void * p; //无类型指针、空类型指针、万能指针,只考虑指向,不考虑类型 |
空类型指针可以指向任何带类型的指针(地址)。
void * p= &x;
x可以是基本数据类型,或者对象/结构体类型;
p = &table; p = &book;
空指针在使用(获取内容)时必须转换成具体的类型,一般是原本的类型
1 | int a=3; |
数组在内存中占据—段连续的单元,通过首地址+偏移可以访问到每个元素。
C++规定: 数组名就是数组的起始地址,同时也是指针,也是0号元素的地址;
实际应用中,通常额外引入一个新的指针变量p, 通过++,—在数组上游走
例如:
1 | int a[10],*p; |
可以通过p++指向下一个整数,但不可以a++因为数组名a是常量指针,只能指向数组首地址
C++ 规定,可以通过指针引用数组元素, 即a [i]
可以理解为*(a+i)
。
1 | const int pi=3; |
如果&a 的前面有类型符, 则是对引用的声明/定义;
int &a
如果&a 的前面无类型符,则是取变量的地址运算。
int a, * p; p = &a;
作用
用作函数的(形式)参数,实现参数的双向传递。
1 | void swap(int &x, int &y) |
引用作为形参,实参是变呈而不是地址,这与指针变呈作形参不一样
但实际效果—样,习惯上,双向传递一个参数,使用引用,传递数组,使用指针(数组首地址)
引用可以部分代替指针的操作,例如双向传递参数,降低程序设计的难度
引用更符合数据结构代码风格
常量引用:用canst修饰的引用,防止通过引用修改数值
1 | void m3(const int& x){ ~ } // 传递常引用,通过x修改数值, 语法错误 |
在创建对象时,负责将对象初始化(成员变量赋初值)。
任何类都会有构造函数,无论你是否主动定义
没有主动定义的情况,编译器自动产生一个缺省构造函数className::classNameO { }
,对所有数据成员置0, 或者NULL
主动显式的定义了类的(任何)构造函数,编译器不再产生缺省构造函数。
但可以使用” = default “ 强制编译器生成缺省构造函数
1
2 Clock() = default;//指示编译器提供默认构造函数
Clock(int newH, int newM, int newS); //构造函数
参数初始化表
在函数头部对数据成员初始化。
1 | Box::Box(int h,int w,int len) : height(h), width(w), length(len){} |
其中, height; width; length是成员变量;
相当于
1 | Box::Box(int h,int w,int len) {height=h; width=w; length=len;} |
委托构造函数
构造函数调用其他构造函数
1 | Clock(): Clock(0, 0, 0) {}//委托构造函数(delegat i ng constructor) |
析构函数在对象的生命期结束时,被系统自动调用,作用是收回为对象分配的存储空间。
析构函数名必须是在类名前面加上字符”~” 。
1 | ClassNaine::~ClassName( ){ …… } |
析构函数不能带有任何参数,没有返回值,不指定函数类型。
如果没有显式定义析构函数,则编译器自动产生一个缺省的析构函数, 其函数体为空。
分配空间时,构造;回收空间时,析构
如果在构造函数中用new为对象动态分配了存储空间,则在类中应该定义一个析构函数,使用delete,收回由new申请的存储空间。
编译器自动生成的复制构造函数,用初始值对象的每个成员数据的值
1 | A::A(const A &a){ |
C++11: 用”= delete” 指示编译器不要生成默认的复制构造函数。
1 | class Point{//Point 类的定义 |
缺省的复制函数为浅拷贝,如果数据成员是指针,则复制函数拷贝指针,不拷贝指针指向的内存空间。推论:使用new 申谓空间, 通常需要自定义复制函数,实现深拷贝
1 | class Str{ |
复制构造函数被自动调用的三种情况
定义—个对象时,以本类另—个对象作为初始值,发生复制构造; A a2 = a1;//Aa2(a1)
如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
1 |
|
复制构造函数被自动调用的三种情况
定义—个对象时,以本类另—个对象作为初始值,发生复制构造;
1 |
|
如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化—个临时无名对象,传递给主调函数,此时发生复制构造。
Box box1(12,15,18);
return box1;//返回值是Box类的对象
}Box box2;//定义Box类的对象box2
box2=f();//返回Box类的临时对象,并将它赋值给box2
}1 |
|
返回引用,调用和被调函数对同一个对象进行操作,节省时间和内存。
静态成员被该类的所有对象所共有、共享。用关键字static声明
在内存中只占一份空间。如果改变它的值,则各对象中这个数据成员的值都同时改变。
只要在类中定义了静态数据成员,即使不创建对象,也为静态数据成员分配空间,可以通过类名访间到类的静态成员。
全生命周期,在程序启动时被分配空间,到程序结束时释放;不随对象的建立而分配空间,也不随对象的撤销而释放。
成员函数
1 | static int volume(); |
在类外调用静态成员函数, 可以用类名或者对象名。如
1 | Box::volume(); |
静态成员函数可以访问本类中的静态数据成员,不能访问本类中的非静态成员。
友元将—个模块(函数、类)声明为本类的朋友,从而可以访问本类中隐截内容。
友元函数
外部函数f () , 在类A中用friend修饰声明,则f()为类A的友元函数, f可以访问A的所有成员。
friend void f () ;
友元函数不一定是独立函数,也可以是另外一个类的函数
友元类
在类A中声明另—个类B声明为friend , 则类A所有数据类B可以访问: friend class B;
友元的关系是单向的而不是双向的,友元关系不能传递
作用:增加灵活性,使程序员可以在数据安全性和使用便利性之间做选择。指明哪些是朋友,对淮开放。
副作用: 破坏数据封装和隐截。建议尽呈不要使用友元类。
副作用2 : 程序设计更加复杂了,—些额外的问题,例如父亲的朋友是不是朋友
const修饰的变量称为常量。
const修饰的成员数据称为常数据成员
只能通过构造函数的参数初始化表对常数据成员进行初始化
```cpp
const int hour; //声明hour为常数据成员
Time::Time (int h):hour(h){ hour = h; //错误}
1 |
|
const声明的对象为常对象。
常对象必须进行初始化,不能被更新(修改)。
对象的数据成员的值不被改变
```c++
const Time tl(l2,34,46); //tl是常对象
1 |
|
重载运算符+=
+=重载函数,其返回值类型为void。因为+=-=*=等修正运算,不会产生新的对象
1 | void operator+=(const A &a){//重载运算符+= |
自增运算符
1 | A& operator++() { // 重载前置自增运算符 |
运算符重载为成员函数的参数只能有二种情况:没有参数或带有—个参数。
对于单目运算符(如++),正载时通常不能有参数;
对于双目运算符,只能带有—个参数。参数可以是对象,对象的引用,或其它类型的参数。
只能对C++ 中已定义了的运筒符进行重载。但有5个不允许重载
.
(点运算符)s.length
,但是->
(箭头运算符)是可以重载的Math::P
.*
(点星运算符,)s.*p
,即成员是指针类型?:
(条件运算符)sizeof
,不可以重载禁止重载(修改)基本类型数据的运算符。参数不能全部是C++的基本标准类型。
静态成员函数不能是运算符重载函数
运算符重载为类成员函数, 成员函数所属对象是一个操作数,另一个操作数是函数参数
1 | A a ,b, c; |
运算符重载为类的友元函数(或者普通函数), 参与运算的对象全部成为函数参数。
1 | c=a+b; //实际上是c=operator+(a, b); 两个参数friend A operator+ (A &a, A &b) |
如果参数有基本数据类型时,则运算符建议重载为友元函数。例如:
1 | Complex Complex::operator+(int &i){ |
则限制使用者必须写作c3 = c2+100;
而不能写成c3=100+c2; 因为100不是类对象,100.operator+(c2)编译错误
1 | #include<iostream> |
1 | A& A::operator =(A &b){//重载赋值运算符 |
注意
1 | A a2;//调用了默认构造函数 |
1 | class SubClass: public/protected/private BaseClass |
公有派生,基类中所有成员在派生类中保持访问权限。
保护派生时,基类中公有成员在派生类中均变为保护的;
私有派生时,基类中所有成员在派生类中均变为私有的;
1 | class A{ |
派生类的构造函数要么调用基类的构造函数,要么调用自己的另一个构造函数二选一。
派生类的构造函数调用不能出现递归
派生类如果有多个构造函数,必然有至少一个调用基类的构造函数
必须在初始化列表中调用
1 | class A{ |
构造的顺序
1.执行参数初始化列表
A)调用基类的构造函数,初始化基类成员
A(i+1)
B)按照成员变量定义顺序,依次初始化
int b;A a;
则b(i++),a(i)A a;int b;
则执行a(i),b(i++)
2.调用自身构造函数的实现部分
析构顺序相反
1 | class D: public A, protected B, private C { |
二义性冲突
当派生类中新增加的数据或函数与基类中成员同名时,缺省优先调用派生类中的成员。
多个基类数据或函数同名时,利用类作用域符号来指明数据或函数的来源。
同一个公共的基类在派生类中产生多个拷贝,多占用空间,容易混淆。
int x;
A(int a) { x=a;}};
class B: public A{int y;
B(int a=0,int b=0):A(a) { y=b;}};
class C: public A{int z;
C(int a,int c):A(a){ z=c; }};
class D: public B, public C{int dx;
D(int al,int b,int c,int d,int a2):B(al,b),C(a2,c)
{dx=d;}};
void main(void){D dl(l0,20,30,40,50);
cout<<dl.x<<endl;//模糊引用错误
cout<<dl.B::x<<endl; //10
cout<<dl.C::x<<endl; //50
}1 |
|
• 非虚函数:静态绑定(编译时确定),根据指针/引用的静态类型调用。
1
2
3void non_virtual() { ... } // 非虚函数
Base* obj = new Derived();
obj->non_virtual(); // 始终调用 Base::non_virtual()
• 必要性:基类析构函数必须为 virtual
,确保通过基类指针删除派生类对象时,调用完整的析构链。
1
2
3
4
5
6
7
8
9
10
11class Base {
public:
virtual ~Base() { cout << "Base析构"; }
};
class Derived : public Base {
public:
~Derived() { cout << "Derived析构"; }
};
Base* obj = new Derived();
delete obj; // 输出 Derived析构 → Base析构
• 未使用虚析构的后果
仅调用基类析构函数,导致派生类资源泄漏。
• 同名成员变量:派生类变量会遮蔽基类同名变量。
1
2
3
4
5
6
7
8
9class Base { protected: int a = 10; };
class Derived : public Base {
protected: int a = 20; // 遮蔽 Base::a
public:
void print() {
cout << a; // 20(访问派生类变量)
cout << Base::a; // 10(显式访问基类变量)
}
};
• 自动调用:派生类析构函数执行完毕后,编译器自动调用基类析构函数。
1
2
3
4~Derived() {
// 析构派生类成员
// 编译器自动插入 Base::~Base();
}
• 禁止显式调用:手动调用基类析构函数(如 Base::~Base()
)会导致重复析构。
• 允许修改权限:派生类虚函数的访问权限可以不同于基类。
1
2
3
4class Base { public: virtual void foo() {} };
class Derived : private Base {
private: void foo() override {} // 合法但不推荐
};
• 设计建议:保持派生类虚函数权限不低于基类(如 public
→ public
)。
使用 override
关键字(C++11+):明确标记重写,避免签名错误。
1 | void foo() override { ... } // 编译器检查签名匹配 |
虚析构函数规则:
• 基类必须有虚析构函数。
• 若类可能被继承,析构函数默认声明为 virtual
。
避免变量遮蔽:
• 基类和派生类避免同名变量。
• 若需访问基类变量,使用 Base::variable
。
多态接口设计:
• 虚函数用于定义接口(如 calculate()
)。
• 非虚函数用于工具方法(如 validate()
)。
场景 | 关键点 |
---|---|
虚函数与动态绑定 | 通过虚函数表实现运行时多态 |
虚析构函数 | 必须为虚,避免资源泄漏 |
变量遮蔽 | 派生类变量遮蔽基类变量,需显式指定作用域访问基类成员 |
虚函数覆盖权限 | 可以修改权限,但应保持接口一致性 |
override 关键字 |
强制编译器检查重写签名,避免隐藏(Shadowing)错误 |
1 | // 基类析构函数非虚 |
掌握 virtual
的机制,是写出安全、灵活 C++ 代码的关键!
1 | class Point{ |
1 | class A{ |
父类(基类)的析构方法一般为virtual, 保证派生类的析构方法得到执行
• 抽象类:包含至少一个 纯虚函数(Pure Virtual Function) 的类,无法被实例化。
• 纯虚函数:声明时在函数末尾添加 = 0
,表示该函数无默认实现,必须由派生类重写。
1
2
3
4class Shape {
public:
virtual double area() const = 0; // 纯虚函数
};
• 定义接口规范:强制派生类实现特定方法,统一多态行为。
• 实现多态性:通过基类指针或引用调用不同派生类的实现。
• 代码复用:抽象类可包含部分已实现的成员函数或数据成员,供派生类继承使用。
1 | #include <iostream> |
输出结果:1
2Woof!
Meow!
• 无法实例化:直接创建抽象类对象会引发编译错误。
1
Animal animal; // 错误:Animal 是抽象类
• 派生类必须实现所有纯虚函数:否则派生类仍为抽象类。
1
class Bird : public Animal {}; // 错误:未实现 makeSound(),Bird 仍是抽象类
• 纯虚函数可以有实现:可在类外为纯虚函数提供默认实现(需显式调用)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Shape {
public:
virtual void draw() const = 0;
};
void Shape::draw() const { // 纯虚函数的默认实现
cout << "Drawing a shape." << endl;
}
class Circle : public Shape {
public:
void draw() const override {
Shape::draw(); // 显式调用基类实现
cout << "Drawing a circle." << endl;
}
};
• 接口(C++模拟):抽象类的特例,所有函数均为纯虚函数,无数据成员。
1
2
3
4
5class IPrintable {
public:
virtual void print() const = 0;
virtual ~IPrintable() = default;
};
• 抽象类:可包含数据成员和已实现的方法,更灵活。
• 虚析构函数:抽象类应定义虚析构函数,确保派生类对象通过基类指针删除时正确释放资源。
• 避免过度抽象:合理设计抽象层级,防止继承结构过于复杂。
• 组合优先于继承:若无需多态行为,优先使用组合而非继承抽象类。
• 框架设计:定义通用接口(如 GUI 控件的渲染方法)。
• 插件系统:规定插件必须实现的函数。
• 算法策略:通过抽象基类定义算法骨架,派生类实现具体步骤(模板方法模式)。
定义:通过模板继承在编译期实现多态,无需虚函数表(vtable),实现零运行时开销。
核心思想:基类模板以派生类作为模板参数,通过 static_cast
将 this
转换为派生类指针,直接调用派生类方法。
1 | template <typename Derived> |
关键点:
• 零运行时开销:所有函数调用在编译期静态绑定。
• 应用场景:
• 静态多态(替代虚函数)。
• Mixins(为类动态添加功能)。
• 优化性能敏感代码(如数学库中的向量运算)。
std::variant
+ std::visit
定义:基于类型安全的联合体(std::variant
)和访问者模式(std::visit
),在编译期根据实际存储的类型分发操作。
1 | #include <variant> |
关键点:
• 类型安全:std::variant
替代传统联合体,避免类型错误。
• 访问者模式:通过重载的 operator()
为不同类型定义操作。
• 编译期分发:std::visit
在编译期生成类型分发代码,无运行时类型检查(RTTI)开销。
• 应用场景:
• 状态机(不同状态对应不同类型)。
• 解析器(处理多种类型的语法节点)。
• 替代继承体系的多态设计。
JPA全称Java Persistence API(2019年重新命名为 Jakarta Persistence API ),是Sun官方提出的一种ORM规范。
O:Object R: Relational M:mapping
作用
1.简化持久化操作的开发工作:让开发者从繁琐的 JDBC 和 SQL 代码中解脱出来,直接面向对象持久化操作。
2.Sun希望持久化技术能够统一,实现天下归一:如果你是基于JPA进行持久化你可以随意切换数据库。
该规范为我们提供了:
1)ORM映射元数据:JPA支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对
象持久化到数据库表中;
如:@Entity
、 @Table
、@Id
与
@Column
等注解。
2)JPA 的API:用来操作实体对象,执行CRUD操作,框架在后台替我们完成所有的事情,开发者从繁琐的JDBC和
SQL代码中解脱出来。
如:entityManager.merge(T t);
3)JPQL查询语言:通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。
如:from Student s where s.name = ?
So: JPA仅仅是一种规范,也就是说JPA仅仅定义了一些接口,而接口是需要实现才能工作的。
Hibernate与JPA:
所以底层需要某种实现,而Hibernate就是实现了JPA接口的ORM框架。
和mybatis区别:
mybatis:
小巧、方便?、高效、简单、直接、半自动
半自动的ORM框架,
小巧: mybatis就是jdbc封装
在国内更流行。
场景: 在业务比较复杂系统进行使用,
hibernate:
强大、方便、高效、(简单)复杂、绕弯子、全自动
全自动的ORM框架,
强大:根据ORM映射生成不同SQL
在国外更流。
场景: 在业务相对简单的系统进行使用,随着微服务的流行。
Log4j有三个主要的组件/对象:Loggers(记录器),Appenders (输出源)和Layouts(布局)。这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出。
每条日志语句都要设置一个等级(DEBUG、INFO、WARN、ERROR和FATAL)。
其中DEBUG < INFO < WARN < ERROR < FATAL。fatal等级最高
对应调试信息 一般信息 警告信息 错误信息 严重错误信息
1、Loggers 在设置日志输出位置的时候,会给那个位置设置一个级别,只有大于等于那个级别的日志才会打印输出到指定位置。
例如:某个Loggers(日志输出位置的等级记录器)级别设定为INFO,则INFO、WARN、ERROR和FATAL级别的日志信息都会输出到那个文件,而级别比INFO低的DEBUG则不会输出。
2、Appenders
禁用和使用日志请求只是Log4j的基本功能,Log4j日志系统还提供许多强大的功能,比如允许把日志输出到不同的地方,如控制台(Console)、文件(Files)等,可以根据天数或者文件大小产生新的文件,可以以流的形式发送到其它地方等等。
常使用的类如下:
org.apache.log4j.ConsoleAppender(控制台) org.apache.log4j.FileAppender(文件) org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件) org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件) org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
基本上可以满足我们的日常需求,当然如果你的需求比较特殊,可以自己实现Appender输出路径。只需要定义一个类,实现Appender接口就可以了。Appender接口中定义了一系列记录日志的方法,按照自己的规则实现这些方法即可
3、Layouts
用户可以根据自己的喜好格式化自己的日志输出,Layouts提供四种日志输出样式,如根据HTML样式、自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式。
常使用的类如下:
org.apache.log4j.HTMLLayout(以HTML表格形式布局) org.apache.log4j.PatternLayout(可以灵活地指定布局模式) org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串) org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等信息)
在pom.xml
1 | <dependency> |
创建配置文件
在src/main/resources目录下创建log4j.properties
1 | ### 日志的输出级别是dubug,输出位置名字叫stdout,D,E |
1 | public class HttpRequestUtil { |
同理还有:
1 | package com.log4j; |
控制台信息:
1 | [DEBUG] |
输出的文件:
1 | 2019-03-31 11:33:36 [ main:0 ] - [ DEBUG ] 调试信息. |
<details style="margin: 10px 0;">
<summary style="font-weight: bold; border: 2px solid rgb(240, 98, 146); border-radius: 5px; background-color: rgb(252, 228, 236); padding: 5px;">PROOF</summary>
<img style="display: block; margin: 0 auto;" src="image-20240406184732973.png"></img>
</details>
Java 序列化是一种将对象转换为字节流的过程,以便可以将对象保存到磁盘上,将其传输到网络上,或者将其存储在内存中,以后再进行反序列化,将字节流重新转换为对象。
序列化在 Java 中是通过 java.io.Serializable 接口来实现的,该接口没有任何方法,只是一个标记接口,用于标识类可以被序列化。
当你序列化对象时,你把它包装成一个特殊文件,可以保存、传输或存储。反序列化则是打开这个文件,读取序列化的数据,然后将其还原为对象,以便在程序中使用。
实现 Serializable
接口:
要使一个类可序列化,需要让该类实现 java.io.Serializable
接口,这告诉 Java 编译器这个类可以被序列化.
1 | public class User implements java.io.Serializable { //该接口没有任何方法,只是一个标记接口,用于标识类可以被序列化。 |
java.io.ObjectOutputStream
代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obi对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream
代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
通过Socket传输对象
客户端
1 | public class userClient { |
服务端
1 | public class ServerHandleThread implements Runnable{ //表示该类的实例可以被一个线程执行 |
1 | public class simpleServer { |
序列化对象
1 | import java.io.*; |
反序列化对象
1 | import java.io.*; |
在BlessingChess项目中,想要做一个图片实时上传和读取的需求,第一想到的是直接把文件放在static目录下,然后实时上传和读取。
读取资源(也就是web端访问static资源)其实就很简单,Spring Boot 默认就配置了 /static/** 映射,所以无需任何配置就能访问。
很快啊,工具类起手:
1 | package com.example.BlessingChess.utils; |
这还没有问题,但是当实际测试的时候,发现新上传的文件,无法访问到,会报错NoResourceFoundException
资源的实时访问问题,比如上传图片后,然后再访问,可能需要重启才能继续访问,
jar对resources目录进行保护措施,可能读取不到上传的资源
但是有些极少量的文件需要存储到resources目录下,这就需要先获取到reources下的相应目录,此时应该考虑将来运行jar包时不能出错
因此推荐一下两种方式获取static目录:
通过ResourceUtils工具获取static目录
1 | try { |
通过 ClassPathResource 获取
1 | // 具体到要访问的文件,然后拿到文件流对象 |
当然,更好的方法是避开static目录存储,考虑做成本地硬盘上的映射目录:
添加配置文件WebMVCConfig,然后在添加资源映射
1 | package com.example.BlessingChess.config; |
这里会把**通配符给映射到后面那个目录后面
比如:http://localhost:8080/image/icon/0/icon.jpg
会被映射成file:E:/StudentOnline/BlessingChess/image/icon/0/icon.jpg
这时候,你可以实时修改该目录里的内容,也可以实时访问。
还没完,当你用浏览器访问的时候,控制台又报错了:
corg.springframewOPk.web.servlet.nesounce.NOResourceFoundException: No static resource favicon.ico.
这不是代码的问题,而是用浏览器请求资源时,都会同时请求标签页图标,当你在postman请求资源时就不会发生这个问题了,或者直接在static目录下放一个favicon.ico文件也行
关于相对路径:
./**代表的是E:,src的同级路径,这个./也可以直接省略
我们来看一下出现异常之后,最终服务端给前端响应回来的数据长什么样。
1 | {"timestamp:"2022-12-09Te6:05:34.323+00:00","status":"500","error ":"Internal Server Error","path" "/depts""] |
响应回来的数据是一个JSON格式的数据。但这种JSON格式的数据显然并不是我们开发规范当中所提到的统一响应结果Result
由于返回的数据不符合开发规范,所以前端并不能解析出响应的JSON数据。
出现异常之后,当前案例项目的异常没有做任何的异常处理
当我们没有做任何的异常处理时,我们三层架构处理异常的方案:
解决方案
那么在三层构架项目中,出现了异常,该如何处理?
我们该怎么样定义全局异常处理器?
@RestControllerAdvice
,加上这个注解就代表我们定义了一个全局异常处理器。@ExceptionHandler
。通过@ExceptionHandler
注解当中的value属性来指定我们要捕获的是哪一类型的异常。1 | @RestControllerAdvice //表示当前类为全局异常处理器 |
@RestControllerAdvice
=@ControllerAdvice
+@ResponseBody
处理异常的方法返回值会转换为json后再响应给前端
此时,出现异常之后,异常已经被全局异常处理器捕获了。然后返回的错误信息,被前端程序正常解析,然后提示出了对应的错误提示信息。
LoginController
1 | @RestController |
EmpService
1 | public interface EmpService { |
EmpServiceImpl
1 | @Slf4j |
EmpMapper
1 | @Mapper |
以上的功能无论用户是否登录,都可以访问部门管理以及员工管理的相关数据。所以我们目前所开发的登录功能,它只是徒有其表。而我们要想解决这个问题,我们就需要完成一步非常重要的操作:登录校验。
more >>文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程。
文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。
在前端程序中要完成哪些代码:
1 | <form action="/upload" method="post" enctype="multipart/form-data"> |
上传文件的原始form表单,要求表单必须具备以下三点(上传文件页面三要素):
表单必须有file域,用于选择要上传的文件
1 <input type="file" name="image"/>
表单提交方式必须为POST
通常上传的文件会比较大,所以需要使用 POST 提交方式
表单的编码类型enctype必须要设置为:multipart/form-data
普通默认的编码格式是不适合传输大型的二进制数据的,所以在文件上传时,表单的编码格式必须设置为multipart/form-data
删除form表单中enctype属性值,会是什么情况?
此时表单的编码格式为默认值,提交后仅仅提交了该文件的名字
首先在服务端定义这么一个controller,用来进行文件上传,然后在controller当中定义一个方法来处理/upload
请求
在定义的方法中接收提交过来的数据 (方法中的形参名和请求参数的名字保持一致)
Spring中提供了一个API:MultipartFile,使用这个API就可以来接收到上传的文件
问题:如果表单项的名字和方法中形参名不一致,该怎么办?
- ~~~javascript public Result upload(String username, Integer age, MultipartFile file) //file形参名和请求参数名image不一致
1
2
3
4
5
6
7
解决:使用@RequestParam注解进行参数绑定
- ~~~java
public Result upload(String username,
Integer age,
@RequestParam("image") MultipartFile file)
UploadController代码:
1 | @Slf4j |
什么是MyBatis?
MyBatis是一款优秀的 持久层 框架,用于简化JDBC的开发。
MyBatis本是 Apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
官网:https://mybatis.org/mybatis-3/zh/index.html
在上面我们提到了两个词:一个是持久层,另一个是框架。
持久层:指的是就是数据访问层(dao),是用来操作数据库的。
框架:是一个半成品软件,是一套可重用的、通用的、软件基础代码模型。在框架的基础上进行软件开发更加高效、规范、通用、可拓展。
创建springboot工程,并导入 mybatis的起步依赖、mysql的驱动包。
项目工程创建完成后,自动在pom.xml文件中,导入Mybatis依赖和MySQL驱动依赖
1 | <!-- 仅供参考:只粘贴了pom.xml中部分内容 --> |
创建用户表user,并创建对应的实体类User。
1 | -- 用户表 |
关系型数据库(RDBMS)
概念:建立在关系模型基础上,由多张相互连接的二维表组成的数据库。
而所谓二维表,指的是由行和列组成的表。
二维表的优点:
使用表存储数据,格式统一,便于维护
使用SQL语言操作,标准统一,使用方便,可用于复杂查询
我们之前提到的MySQL、Oracle、DB2、SQLServer这些都是属于关系型数据库,里面都是基于二维表存储数据的。
基于二维表存储数据的数据库就成为关系型数据库,不是基于二维表存储数据的数据库,就是非关系型数据库(比如Redis,就属于非关系型数据库)。
2. 数据模型
MySQL是关系型数据库,是基于二维表进行数据存储的,具体的结构图下:
SQL:结构化查询语言。一门操作关系型数据库的编程语言,定义操作所有关系型数据库的统一标准。
1、SQL语句可以单行或多行书写,以分号结尾。
1 | mysql> create |
2、SQL语句可以使用空格/缩进来增强语句的可读性。
3、MySQL数据库的SQL语句不区分大小写。
4、注释:
## 1.前端开发介绍
那在讲解web前端开发之前,我们先需要对web前端开发有一个整体的认知。主要明确一下三个问题:
1). 网页有哪些部分组成 ?
文字、图片、音频、视频、超链接、表格等等。
2). 我们看到的网页,背后的本质是什么 ?
程序员写的前端代码 (备注:在前后端分离的开发模式中,)
3). 前端的代码是如何转换成用户眼中的网页的 ?
通过浏览器转化(解析和渲染)成用户看到的网页
浏览器中对代码进行解析和渲染的部分,称为 浏览器内核
而市面上的浏览器非常多,比如:IE、火狐Firefox、苹果safari、欧朋、谷歌Chrome、QQ浏览器、360浏览器等等。 而且我们电脑上安装的浏览器可能都不止一个,有很多。
但是呢,需要大家注意的是,不同的浏览器,内核不同,对于相同的前端代码解析的效果也会存在差异。 那这就会造成一个问题,同一段前端程序,不同浏览器展示出来的效果是不一样的,这个用户体验就很差了。而我们想达到的效果则是,即使用户使用的是不同的浏览器,解析同一段前端代码,最终展示出来的效果都是相同的。
要想达成这样一个目标,我们就需要定义一个统一的标准,然后让各大浏览器厂商都参照这个标准来实现即可。 而这套标准呢,其实早都已经定义好了,那就是我们接下来,要介绍的web标准。
Web标准也称为网页标准,由一系列的标准组成,大部分由W3C( World Wide Web Consortium,万维网联盟)负责制定。由三个组成部分:
超文本:超越了文本的限制,比普通文本更强大。除了文字信息,还可以定义图片、音频、视频等内容。
标记语言:由标签构成的语言
当然了,随着技术的发展,我们为了更加快速的开发,现在也出现了很多前端开发的高级技术。例如:vue、elementui、Axios等等。
Maven是Apache旗下的一个开源项目,是一款用于管理和构建java项目的工具。
它基于项目对象模型(Project Object Model , 简称: POM)的概念,通过一小段描述信息来管理项目的构建、报告和文档。
官网:https://maven.apache.org/
Apache 软件基金会,成立于1999年7月,是目前世界上最大的最受欢迎的开源软件基金会,也是一个专门为支持开源项目而生的非盈利性组织。
开源项目:https://www.apache.org/index.html#projects-list
依赖管理:
方便快捷的管理项目依赖的资源(jar包,平时使用需要手动下载并导入),避免版本冲突问题
当使用maven进行项目依赖(jar包)管理,则很方便的可以解决这个问题。 我们只需要在maven项目的pom.xml文件中,添加一段配置即可实现。
统一项目结构 :
在项目开发中,当你使用不同的开发工具 (如:Eclipse、Idea),创建项目工程时,目录结构是不同的
若我们创建的是一个maven工程,是可以帮我们自动生成统一、标准的项目目录结构:
more >>目录说明:
- src/main/java: java源代码目录
- src/main/resources: 配置文件信息
- src/test/java: 测试代码
- src/test/resources: 测试配置文件信息
1 | public class HelloWorld { //创建类 “HelloWorld”需要与文件名一致 |
使用cmd运行:(需要在该文件目录中)
1 | $ javac HelloWorld.java |
1 | int a, b, c; // 声明三个int型整数:a、 b、c |
命名规则:
只能包含 大小写字母、数字、下划线 和 $ ,且不能由数字开头。
保留字不等于关键字,保留字中的const
,goto
都不是关键字,是java保留以供后续版本使用的
整数类型
整型 | 占用字节空间大小 | 取值范围 | 默认值 |
---|---|---|---|
byte | 1字节 | \([-128 , 127]\) | 0 |
short | 2字节 | \([-32768 , 32767]\) | 0 |
int | 4字节 | $[-2^{31} , 2^{31} - 1 ] $ \(\approx 2\times{10}^9\) | 0 |
long | 8字节 | \([-2^{63} , 2^{63} - 1]\) \(\approx 9\times{10}^{18}\) | 0L |
1 | public class HelloWorld { //创建类 “HelloWorld”需要与文件名一致 |
使用cmd运行:(需要在该文件目录中)
1 | $ javac HelloWorld.java |
1 | int a, b, c; // 声明三个int型整数:a、 b、c |
命名规则:
只能包含 大小写字母、数字、下划线 和 $ ,且不能由数字开头。
保留字不等于关键字,保留字中的const
,goto
都不是关键字,是java保留以供后续版本使用的
整数类型
整型 | 占用字节空间大小 | 取值范围 | 默认值 |
---|---|---|---|
byte | 1字节 | \([-128 , 127]\) | 0 |
short | 2字节 | \([-32768 , 32767]\) | 0 |
int | 4字节 | $[-2^{31} , 2^{31} - 1 ] $ \(\approx 2\times{10}^9\) | 0 |
long | 8字节 | \([-2^{63} , 2^{63} - 1]\) \(\approx 9\times{10}^{18}\) | 0L |
tag:
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia-plus根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true