Welcome to NICK’s BLOG!
At Beginning
iiiii This is my first blog !!!!!
man ,what can i say ?
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 <type-id>(expression)
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)
的计算顺序是不确定的。
函数声明:在—个文件(或者函数) a中声明函数b, 写出b 的函数头。
C++中, 所有函数的定义都是独立的,不可嵌套定义。(Java可以定义匿名内类, C/C++不可以)
内联函数(inline) , 编译时,就将被调函数体的代码直接插到调用处(c/c++特色)
1 | inline int max (int x, int y) |
重载函数是相同的作用域,两个或更多具有相同函数名,不同参数列表的函数。编译器根据调用时的实参个数以及类型来确定调用淮。
重载函数必须具有不同参数列表,理解为:不同的参数个数,或不同的参数类型。
形参缺省值与重载有些类似,注意区分
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
2
3
4
5
- 定义时不可以靠左边缺省
```c++
int func(int x1 = 5, int x2, int x3) //错误
赋值时不可以中间缺省
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)
如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
int func(A a); A a(1,2); func(a);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化—个临时无名对象,传递给主调函数,此时发生复制构造。
- ```cpp
Box f() //函数f的类型为Box类类型
{
Box box1 (12, 15, 18) ;
return box1; / /返回值是Box类的对象
}
int main()
{
Box box2; //定义Box类的对象box2
box2=f() ; //返回Box类的临时对象,并将它赋值给box2
}
复制构造函数被自动调用的三种情况
定义—个对象时,以本类另—个对象作为初始值,发生复制构造;
Aa2 = a1;//Aa2(a1)
1
2
3
4
5
- -如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
- ```cpp
int func(A a); A a(1,2); func(a);
如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化—个临时无名对象,传递给主调函数,此时发生复制构造。
Box f(){//函数f的类型为Box类类型
Box box1(12,15,18);
return box1;//返回值是Box类的对象
}
int main(){
Box box2;//定义Box类的对象box2
box2=f();//返回Box类的临时对象,并将它赋值给box2
}
1
2
3
4
5
6
7
8
9
10
### 返回对象引用和返回对象的区别
```c++
Box funl ()//返回一个克隆对象
Box & fun(const Box & b)//返回引用(对象的名字)
返回引用,调用和被调函数对同一个对象进行操作,节省时间和内存。
函数不能返回在函数中创建的局部变量对象的引用, 因为当函数结束时,局部变量对象将消失,这种引用是非法的。(在这种情况下,通过new动态创建)
静态成员被该类的所有对象所共有、共享。用关键字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修饰的成员数据称为常数据成员
只能通过构造函数的参数初始化表对常数据成员进行初始化
const int hour; //声明hour为常数据成员
Time::Time (int h):hour(h){ hour = h; //错误}
1
2
3
4
5
6
7
8
9
- const修饰的成员函数称为常函数
- 常成员函数可以引用非const的数据成员,但不能修改其数值
- ```cpp
void get_ time() canst; //在声明和定义函数时者都要有const关键字,在调用时不必加const
const声明的对象为常对象。
常对象必须进行初始化,不能被更新(修改)。
对象的数据成员的值不被改变
const Time tl(l2,34,46); //tl是常对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- 对象的数据成员的值不被改变
Time canst tl (12, 34, 46); / /tl是常对象
常对象必须进行初始化,不能被更新(修改)。
## 运算符重载
运算符重载:为运算符(+_*/><...)定义函数(运算符重载函数一个(若干个)
遇到运算符根据不同的运算对象调用对应的函数
<返回类型> operator <运算符>(<参数表>)
operator是运算符重载函数关键字,operator<运算符>是函数名
运算符的操作数映射为重载函数的参数,运算符重载函数通常是类的成员困数或者是友元函数。
**重载运算符+**
```cpp
Class A{
int i;
public:
A(int a=0){i=a;}
A operator+(const A &a){//重载运算符+
A t;
t.i=i+a.i;
return t;
};
重载运算符+=
+=重载函数,其返回值类型为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 { |
二义性冲突
当派生类中新增加的数据或函数与基类中成员同名时,缺省优先调用派生类中的成员。
多个基类数据或函数同名时,利用类作用域符号来指明数据或函数的来源。
同一个公共的基类在派生类中产生多个拷贝,多占用空间,容易混淆。
ctass A{
public:
int x;
A(int a) { x=a;}};
class B: public A{
public:
int y;
B(int a=0,int b=0):A(a) { y=b;}};
class C: public A{
public:
int z;
C(int a,int c):A(a){ z=c; }};
class D: public B, public C{
public:
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
### 虚基类
在多重派生的过程中,若使公共基类在派生类中只有一个拷贝,可将公共基类说明为虚基类
由虚基类派生出的对象初始化时, **直接调用**虚基类的构造函数。
规则:
1) —个类可以在—个类族中既被用作虚基类,也被用作非虚基类。
2) 在派生类的对象中,同名的虚基类只产生—个虚基类了对象,而某个非虚基类产生各自的子对象。
## 多态
调用同—个函数名,根据引用类型实现不同的功能。
与java相同,基类的指针可以指向子类(派生类)对象。
C++中,若要访问派生类中相同名字的函数,必须将基类中的同名函数定义为虚函数, 只有用virtual修饰的函数才是可以被覆盖的
```cpp
class Point{
float x,y;
public:
Point() {}
float area(void){
returm 0.0:
}
const float Pi=3.14159.
}
class Circle: public Point{//类Point的派生类
float radius;
public:
Circle(float r): radius(r){}
float area(void){
return Piradius radius.
}
void main(void){
Point *pp;
//基类指针
Circle c(5.4321):
PP-&c;//,可以将派生类对象的地址赋给基类指针
cout<<pp->area()<<endl;//点的面积,结果是0
cout<<c.area<<endl;//圆面积
}
1 | class A{ |
父类(基类)的析构方法一般为virtual, 保证派生类的析构方法得到执行
在基类中不对虚函数给出实现, 在派生类中进行实现。这时基类中的虚函数只是一个入口,具体的操作由派生类中的对象实现。这种虚函数称为纯虚函数(对应Java抽象方法) 。
至少包含一个纯虚函数的类,称为抽象类(abstract c l asses) 。没有特殊关键字声明抽象类。l由象类只能作为基类,不能用来创建对象。C++ 支持多重继承,没有interface
在定义纯虚函数时,不能定义虚函数的实现部分。把函数名赋0, 本质上是将函数代码段为0, 即没有代码段实现。在没有通过派生类实现纯虚函数前,不能调用虚函数。
1 | class A{//抽象类 |
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:\StudentOnline\BlessingChess\即该项目的根路径,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就可以来接收到上传的文件
问题:如果表单项的名字和方法中形参名不一致,该怎么办?
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。
在上面我们提到了两个词:一个是持久层,另一个是框架。
持久层:指的是就是**数据访问层(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、注释:
那在讲解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)的概念,通过一小段描述信息来管理项目的构建、报告和文档。
Apache 软件基金会,成立于1999年7月,是目前世界上最大的最受欢迎的开源软件基金会,也是一个专门为支持开源项目而生的非盈利性组织。
依赖管理:
方便快捷的管理项目依赖的资源(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 |
缺失模块。
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