C++面向对象..

1.面向对象的常见知识点

类、
对象、
成员变量(属性)、成员函数(方法)、
封装、继承、多态

2.类

在C++中可以通过struct、class定义一个类
struct和class的区别:
struct的默认权限是public(在C语言中struct内部是不可以定义函数的) 而class的默认权限是private(该权限限制了用class定义的类中的成员只可以在当前类中进行访问 不可以在当前源文件中除了类以外的其他地方访问)
虽说C语言的struct中不可以直接定义函数 但是可以间接定义 通过函数指针的方式指向一个函数 从而达到间接定义函数的目的
以下是C语言中模拟的通过struct定义的类

void test() {
	printf("hello world\n");
}
struct Person {
	int age;
	void(*run)();
};
int main() {
	struct Person p;
	p.age = 10;
	p.run = test;
	p.run();
	getchar();
	return 0;
}

但是在C++中 其实struct也可以用来定义结构体 那么struct到底是叫做结构体还是类 其实并不需要去纠结这个问题 因为这属于语法糖层面的问题
以下是C++中通过struct定义的类 而且在C++中 Person p就是在定义一个对象 只不过这个对象是存放在栈空间中 有别于Java中对象存放在堆空间的事实

struct Person {
	int m_age;
	void run() {
		cout << "Person::run() - " << m_age << endl;
	}
};
int main() {
	Person p;
	p.m_age = 10;
	getchar();
	return 0;
}

以下则是C++中通过class定义的类

class Person {
public:
	int m_age;
	void test() {
		cout << "Person::test() - " << m_age << endl;
	}
};
int main() {
	Person p;
	p.m_age = 30;
	p.test();
	getchar();
	return 0;
}

以上案例都是通过类变量直接访问类对象中的成员 我们也可以通过指针来间接访问类对象中的成员 以下是详细案例

class Person {
public:
	int m_age;
	void test() {
		cout << "Person::test() - " << m_age << endl;
	}
};
int main() {
	Person person;
	Person* p = &person;
	p->m_age = 11;
	p->test();
	getchar();
	return 0;
}

上述的person对象和p指针这些局部变量都是在函数的栈空间中自动分配和回收的 其中对于person对象来说 他的大小等价于m_age的大小
在这里插入图片描述

struct和class之间除了上述说的这个访问权限的区别之外 我们还可以利用反汇编去看一下是否存在其他的区别
以下是通过class定义类的代码以及汇编

class Car {
public:
	int m_price;
};
int main() {
	Car car;
	car.m_price = 10;
	getchar();
	return 0;
}

在这里插入图片描述
以下是通过struct定义的代码和汇编

struct Car {
	int m_price;
};
int main() {
	Car car;
	car.m_price = 10;
	getchar();
	return 0;
}

在这里插入图片描述
对比一下两者的汇编 可以发现 其实两者的底层是一模一样的 他们的唯一差别在于:class的默认权限是private 而struct的默认权限是public

对于以上的分析 可能会有这样的疑问 为什么对象的内存中只储存了成员变量的内存 而没有储存成员函数的内存呢
首先对于成员变量来说 肯定是每一个对象都有一块分配给成员变量的内存 因为每一个对象的属性都各不相同
但是对于成员函数来说 每一个对象共用一块成员函数的内存就够了(每一个对象调用函数所执行的代码相似甚至一样) 而且这块分配给成员函数的内存并不储存在对象中 而是储存在其他地方
至于更深层的原因 等到后面就会逐一解释的
以下是对于每个对象中都有一块分配给成员变量的内存 而每个对象都共用一块分配给成员函数的内存说法的检验

struct Car {
	int m_price;
	void run() {
		cout << "Car::run() - " << m_price << endl;
	}
};
int main() {
	Car car1;
	car1.m_price = 10;
	car1.run();
	Car car2;
	car2.m_price = 20;
	car2.run();
	Car car3;
	car3.m_price = 30;
	car3.run();
	getchar();
	return 0;
}

在这里插入图片描述
我们可以看到 三个对象中的三个成员变量的地址值是不一样的 说明每个对象中都有各自独立的成员变量 而三个对象所调用的成员函数的地址值确实一样的 也正说明了这三个对象共用同一个成员函数
在这里插入图片描述
而且有个细节 就是他将10赋值给了car1所在内存块中 说明了car1的地址值等于car1当中的m_price的地址值

在实际开发中 用class定义类是比较常见的

3.对象中的内存布局

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id" << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person p;
	p.m_id = 1;
	p.m_age = 10;
	p.m_height = 170;
	cout << "&p = " << &p << endl;// &p = 00F3F978
	cout << "&p.m_id = " << &p.m_id << endl;// &p.m_id = 00F3F978
	cout << "&p.m_age = " << &p.m_age << endl;// &p.m_age = 00F3F97C
	cout << "&p.m_height = " << &p.m_height << endl;// &p.m_height = 00F3F980
	getchar();
	return 0;
}

从打印结果来看 可以发现在对象中定义的多个成员变量的内存是连续分布的 并且是按照成员变量的定义顺序依次排布的

从内存这一方面我们也可以验证

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id" << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person p;
	p.m_id = 1;
	p.m_age = 10;
	p.m_height = 170;
	cout << "&p = " << &p << endl;// &p = 00F9FD24
	cout << "&p.m_id = " << &p.m_id << endl;
	cout << "&p.m_age = " << &p.m_age << endl;
	cout << "&p.m_height = " << &p.m_height << endl;
	getchar();
	return 0;
}

在这里插入图片描述
从内存中 我们可以发现 定义在对象中的多个成员变量是连续排布并且是按顺序排布的 也进一步证明了对象中只储存了成员变量的内存 并没有储存成员函数的内存

在上述例子中 person对象的内存是储存在函数的栈空间中 因此他可以自动的创建和销毁的 但是在Java中 我们知道对象的创建和销毁都是发生在堆空间中的

但是对于对象中存在不同类型的成员变量 可能会发生内存对齐现象 也就是类对齐 即一个类的大小也为其内部最大成员变量大小的倍数
对于下面这段代码来说 他就发生了内存对齐

class Person {
public:
	int m_age;
	long m_height;
	short m_weight;
};
int main() {
	Person p;
	p.m_age = 10;
	p.m_height = 20;
	p.m_weight = 30;
	cout << "&p = " << &p << endl;
	cout << sizeof(p) << endl;// 12
	getchar();
	return 0;
}

本来sizeof§的结果应该为10 可实际打印的结果却是12 原因在于他触发了类对齐 10显然不是最大成员字节数4的倍数 所以必须得凑到12 方可为4的倍数 所以实际上往原本的10个字节内存中又填充了2个字节 形成了最终的12个字节

4.this

既然我们之前提到了每一个对象中都有一块各自独有的成员变量内存 每一个对象共用一块独立的成员函数内存 那么对于以下这段代码来说 是如何做到不同的对象通过同一份内存去访问不同的成员的呢

struct Person {
	int m_age;
	void run() {
		cout << "Person::run() -- " << m_age << endl;
	}
};
int main() {
	Person p1;
	p1.m_age = 10;
	p1.run();// Person::run() -- 10	
	Person p2;
	p2.m_age = 20;
	p2.run();// Person::run() -- 20
	getchar();
	return 0;
}

我们从打印结果可以看到 这两个不同的对象调用同一份函数内存竟然可以做到打印不同的结果 其实这取决于this指针的存在
在代码区中的成员函数只有获取了位于栈空间中的成员变量的地址值才能够访问其内存 从而取出里面的值 而this指针正好提供了栈空间中成员变量的地址值
this指针本质上是一个指向当前函数调用者的指针 而他会作为对象中成员函数的隐式参数存在 所以在成员函数中 是可以通过this指针间接访问当前调用者对象中的成员的

1.反汇编窥探this

struct Person {
	int m_age;
	void run() {
		m_age = 20;
	}
};
int main() {
	Person p1;
	p1.m_age = 10;
	p1.run();// Person::run() -- 10	
	getchar();
	return 0;
}

我们可以借助excel辅助我们完整的走一趟这段代码背后的汇编语句
在这里插入图片描述
在这里插入图片描述
执行call命令
在这里插入图片描述
执行push ebp 将ebp的指向压栈
在这里插入图片描述
执行mov ebp, esp 将ebp的指向改成esp的指向
在这里插入图片描述
执行sub esp, xxx 将esp往低地址方向移动 移动的量为分配给run函数的栈空间
在这里插入图片描述
执行push ebx、push esi、push edi 将这三个寄存器依次压栈 这是为了防止等会的操作修改了这三个寄存器的值而无法还原
在这里插入图片描述
执行push ecx 将p1 即将函数调用者的地址值压栈
在这里插入图片描述
执行pop ecx 弹栈 并且将栈顶元素赋值给ecx
在这里插入图片描述
执行mov dword ptr [ebp - 8], ecx、mov eax, dword ptr [ebp - 8] 将p1的地址值传递给eax
执行mov dword ptr [eax], 20 将20传递给eax寄存器中的地址值所引导的内存
执行pop edi、pop esi、pop ebx 将栈顶的三个值弹出 并且依次赋值给三个寄存器
在这里插入图片描述
执行add esp, xxx 增量和刚才sub的减量是一致的 相当于回收刚才分配给run函数的栈空间
在这里插入图片描述
执行mov esp, ebp 将esp的指向改成ebp的指向
执行pop ebp 弹栈 并且将栈顶元素赋值给ebp
在这里插入图片描述
执行ret语句 首先会弹栈 其次会跳转到刚才弹出的栈顶元素
在这里插入图片描述
最后调用完毕 也维持了栈平衡

其中有关于this指针的几句核心代码为:

mov         dword ptr [ebp-0Ch],0Ah
lea         ecx,[ebp-0Ch]
mov         dword ptr [ebp-8],ecx
mov         eax,dword ptr [ebp-8]
mov         dword ptr [eax],14h

分析一下上述几句汇编代码
首先将10赋值给了ebp - 0ch 很显然这个ebp - 0ch是p1/p1.m_age的地址值
接着将ebp - 0ch直接赋值给ecx 这样ecx中储存的便是p1的地址值
接着将p1的地址值赋值给ebp - 8 显然ebp - 8是this指针的地址值
接着将ebp - 8中的内容赋值给了eax eax中储存的便是p1的地址值
最后将20赋值给了eax中地址值所引导的内存 也就是p1.m_age = 20

总之 this是一个指向当前函数调用者的指针

5.指针访问成员的本质

我们先来看一看通过对象直接访问成员的本质

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person p;
	p.m_id = 10;
	p.m_age = 20;
	p.m_height = 30;
	p.display();
	getchar();
	return 0;
}

其本质就是

 mov         dword ptr [ebp-14h],0Ah  
 mov         dword ptr [ebp-10h],14h  
 mov         dword ptr [ebp-0Ch],1Eh

我们大致也可以知道ebp - 14h是p的地址值 也是p.m_id的地址值
第一句话中ebp - 14h是p.m_id的地址值 相当于往p.m_id的空间中存放10
第二句话中的ebp - 10h和ebp - 14h相差4个字节 是p.m_age的地址值 他和p.m_id是相邻的 相当于往里面存放20
第三句话的ebp - 0ch和ebp - 10h也是相差4个字节 是p.m_height的地址值 相当于往其中存放了30

再来看一下通过指针间接访问成员的本质究竟是怎样的

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person person;
	Person* p = &person;
	p->m_id = 10;
	p->m_age = 20;
	p->m_height = 30;
	p->display();
	getchar();
	return 0;
}

他的核心汇编如下所示

// Person* p = &person;
// 首先将person对象的地址值赋值给了eax
lea         eax,[ebp-14h]  
// 其次将person对象的地址值通过eax赋值给了ebp - 20h这块内存 ebp - 20h就是p的地址值
mov         dword ptr [ebp-20h],eax  
// p->m_id = 10;
// 然后将person对象的地址值通过ebp - 20h赋值给了eax
mov         eax,dword ptr [ebp-20h]  
// 然后将10赋值给了eax中的person对象所引导的4个字节的内存 即m_id这块内存
mov         dword ptr [eax],0Ah  
// p->m_age = 20;
// 然后将person对象的地址值通过ebp - 20h赋值给了eax
mov         eax,dword ptr [ebp-20h]  
// 首先通过m_age和m_id之间的偏移量获取m_age的地址值 接着将20赋值给了结果地址值所引导的4个字节的内存 即m_age这块内存
mov         dword ptr [eax+4],14h  
// p->m_height = 30;
// 然后将person对象的地址值通过ebp - 20h赋值给了eax
mov         eax,dword ptr [ebp-20h]  
// 首先通过m_height和m_id之间的偏移量获取m_height的地址值 接着将30赋值给了结果地址值所引导的4个字节的内存 即m_height这块内存
mov         dword ptr [eax+8],1Eh  
mov         ecx,dword ptr [ebp-20h]  
call        00A914C4

总结一下指针间接访问对象的成员的汇编代码:
1.通过指针获取对象的地址值
2.通过偏移量(当前成员和对象地址之间的差)获取当前成员变量的地址值
3.根据成员变量的地址值访问成员变量所在的内存空间

但是对比了一下通过对象访问成员和通过指针访问成员的效率 从汇编指令的条数上来看 的确是通过对象访问成员的效率高
但是要知道一点 指针的出现并不是为了比对象访问效率高而诞生的 而是为了针对某些必须用到指针的情景而应运而生的

1.指针间接访问对象成员的有关思考题

以下代码中 实际结果会不会符合预期呢

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person person;
	person.m_id = 10;
	person.m_age = 20;
	person.m_height = 30;
	// 这边之所以需要进行强制转换的操作 原因在于&person.m_age取出来的是int类型数据的地址值 所以返回值应该是int* 和Person*不匹配
	Person* p = (Person*) & person.m_age;
	p->m_id = 40;
	p->m_age = 50;
	p->display();
	getchar();
	return 0;
}

对于这道思考题的结果 我们的预期可能是40 50 30 可实际上的结果是10 40 50
这就和我们刚才所讲到的通过指针间接访问成员中的偏移量有着莫大的关系了
我们刚才说过 通过指针间接访问对象成员的本质就是:通过指针获取对象地址 然后通过偏移量获取当前成员的地址值 最后通过这个地址值访问当前成员所在的储存空间
套在这道题上就是
我们通过指针获取到的地址值是person.m_age的地址值 是&person + 4
然后通过偏移量获取当前成员的地址值 也就是要获取person.m_id的地址值 也就是&person + 4 + 0 其实就是person.m_age的地址值 这和我们所想获取的person.m_id的地址值大相径庭 当然赋值的时候也就出现了差错
之后的p->m_age也是同理

以下代码中 通过对象访问的成员函数和通过指针访问的成员函数所打印的结果是否一致

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person person;
	person.m_id = 10;
	person.m_age = 20;
	person.m_height = 30;
	Person* p = (Person*) & person.m_age;
	p->m_id = 40;
	p->m_age = 50;
	// 以下两种写法访问成员函数时打印的结果是否一致
	person.display();
	p->display();
	getchar();
	return 0;
}

答案是不一致
我们其实得知道成员函数中访问成员变量的本质 就是通过this指针间接访问对象中的成员变量 因此他在访问某一成员的时候 也遵循"先通过指针获取对象地址 在通过偏移量获取成员地址 最后通过成员地址访问成员所在的储存空间"的原则
按照上述这个原则 对于对象直接访问的结果是肯定符合预期的 其中this指针是指向person对象的
但是对于指针间接访问的话 那么this指针储存的就是p指针中储存的地址值 即person.m_age的地址值 那么到时候在访问的过程中 比如访问person.m_id的时候 偏移量就是0 那么获取到的值是person.m_age的值 而不是person.m_id的值 同理打印person.m_age的时候 结果为person.m_height的值(&person + 4 + 4) 再者 打印person.m_height的时候访问到的内存是一块未知的内存(&person + 4 + 4 + 4) 从而打印出乱码来

我们可以看到 这段乱码是0xcccccccc
在这里插入图片描述
那么为什么要用一段cc来填充栈空间呢?原因在于当我们为一个函数分配一个新的栈空间时 这段空间中可能会有之前残留下来的垃圾数据 所以用cccc这种数据去填充整段分配的内存
那么为什么一定要选择cc作为填充的数据呢?原因在于之前残留的数据中可能存在某些危险的指令 万一我通过指针指向跳转指令 跳转到函数的栈空间中的某个危险代码 那么后果将不堪设想
而cc是int3的意思 int是interrupt的简称 即中断的意思 3是中断码 表示为断点的意思 我们都清楚 代码只要执行到断点位置处的话 那么就会停止执行

我们可以在汇编和内存中分析一下0xcccccccc

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person person;
	person.m_id = 10;
	person.m_age = 20;
	person.m_height = 30;
	Person* p = (Person*) & person.m_age;
	p->m_id = 40;
	p->m_age = 50;
	// 以下两种写法访问成员函数时打印的结果是否一致
	person.display();
	p->display();
	getchar();
	return 0;
}

还是刚才这段代码 但是我们进去display函数中分析分析
在这里插入图片描述
可以看到 在使用cc填充栈空间之前 栈空间还是一堆垃圾数据
当我们执行了mov eax, 0xcccc 以及 rep stos指令以后 再看一下内存中的效果
可以看到整个栈空间都被0xcccc所填满

6.函数执行时涉及的内存

我们在调用函数、执行函数代码的过程中 肯定会涉及到内存 一方面是用于储存函数代码的代码区 一方面是用于储存函数内部局部变量的栈空间 这两个究竟怎么区分呢?
其实不难 在调用函数的过程中 cpu会执行代码区中的函数代码 在执行过程中 遇到局部变量 就会在栈空间中为其开辟空间
有人说 为什么不直接在代码区中为局部变量开辟内存空间呢?因为代码区是只读的 而局部变量是可修改的 显然将局部变量放在代码区不合适 所以将其放置在栈空间中

7.C++的编程规范

我们之前学习Java的过程中 是有学习过Java的一套编程规范 也就是对于一些标识符来说 是需要有一些规范的 比如对于类、接口等这些类型的标识符采用大驼峰 对于方法、变量这些非类型的标识符采用小驼峰
而在C++中 也有着自己的一套规范:
变量名命名规范:
全局变量:g_
成员变量:m_
静态变量:s_
常量:c_
也可以使用和Java一样的驼峰标识去表示一个标识符 比如对于全局变量age来说 我们可以命名为gAge

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/448441.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Windows虚拟机的安装

Windows系统 总结知识点记忆级别&#xff1a; 1级&#xff1a;熟练记忆讲过的所有知识点(按照授课顺序&#xff0c;笔记顺序来提问)2级&#xff1a;灵活应用所学知识点(不按照顺序提问&#xff0c;面临陷阱提问)3级&#xff1a;应用所学知识解决实际问题4级&#xff1a;扩展应…

24 深度卷积神经网络 AlexNet【李沐动手学深度学习v2课程笔记】(备注:含AlexNet和LeNet对比)

目录 1. 深度学习机器学习的发展 1.1 核方法 1.2 几何学 1.3 特征工程 opencv 1.4 Hardware 2. AlexNet 3. 代码 1. 深度学习机器学习的发展 1.1 核方法 2001 Learning with Kernels 核方法 &#xff08;机器学习&#xff09; 特征提取、选择核函数来计算相似性、凸优…

陈景东:集中与分布式拾音与声信号处理 | 演讲嘉宾公布

一、声音与音乐技术专题论坛 声音与音乐技术专题论坛将于3月28日同期举办&#xff01; 声音的应用领域广泛而深远&#xff0c;从场所识别到乐器音响质量评估&#xff0c;从机械故障检测到心肺疾病诊断&#xff0c;声音都发挥着重要作用。在互联网、大数据、人工智能的时代浪潮中…

【python】random库函数使用简要整理

前言 简要快速清晰整理random库 函数 函数作用random()返回0-1间的浮点小数randint(1,10)返回1到10间的整数uniform(1,10)返回1-10间的小数randrange(1,10,2)从1每隔2取一个数到10&#xff0c;在这些数中返回一个choice(列表)从列表中随机返回一个 shuffle(列表) 对列表内容…

高等数学常用公式

高等数学常用公式 文章目录 内容大纲 内容 大纲 感谢观看 期待关注 有问题的小伙伴请在下方留言&#xff0c;喜欢就点个赞吧

群晖NAS使用Docker安装WPS Office并结合内网穿透实现公网远程办公

文章目录 推荐1. 拉取WPS Office镜像2. 运行WPS Office镜像容器3. 本地访问WPS Office4. 群晖安装Cpolar5. 配置WPS Office远程地址6. 远程访问WPS Office小结 7. 固定公网地址 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff…

C语言中的UTF-8编码转换处理

C语言UTF-8编码的转换 1.C语言简介2.什么是UTF-8编码&#xff1f;2.1 UTF-8编码特点&#xff1a; 3.C语言中的UTF-8编码转换处理步骤1&#xff1a;获取UTF-8编码的字节流步骤2&#xff1a;解析UTF-8编码步骤3&#xff1a;Unicode码点转换为汉字 4.总结 1.C语言简介 C语言是一门…

怎么做加密文件二维码?分享文件更安全

怎么做一个加密文件二维码&#xff1f;在日常的工作和生活中&#xff0c;通过扫描二维码来查看或者下载文件的方式&#xff0c;被越来越多的人所使用&#xff0c;一方面是二维码的成本低&#xff0c;另一方面有利于提升便捷性和用户体验。 为了保证内容的隐私性和安全性&#…

数据库期末速成100分训练,附练手数据库原件及教程

本文提供下面数据库代码的数据库原件&#xff0c;下载后可使用 教程如下&#xff1a; 1.打开sql sever 2.找到数据库 3.右键数据库点击“附加”&#xff0c;然后点击“添加” 4.导入数据库原件&#xff0c;点击确定 ps&#xff1a;如果没有sqlsever 或者页面编辑器&#x…

uniapp引入jQuery

安装 npm install jquery --saveoryarn add jquery引入 import Vue from vue import jquery from "jquery"; Vue.prototype.$ jquery;<template><view>abc</view> </template><script>export default {data() {return {}}} </scr…

tcp服务器客户端通信(socket编程)

目录 1.编程流程 2.代码演示 2.1 服务器代码 2.2 客户端代码 3.注意 3.1 ping命令 3.2 netstat命令 3.3 为什么memset? 3.4 哪个会阻塞? 3.5 显示连接信息 1.概念 1.1 编程流程 1.2 connect与listen connect方法执行后&#xff0c;会进行三次握手&#xff0c;建立连…

【python 】----Pytest基础知识与进阶知识

定义 用于编写和执行Python测试全功能测试框架(工具),是一个第三方库 安装 pip insatll pytest 安装pytest --version 校验 pytest的组成构成 不写调用语句也可以执行函数内容 在用例运行语句里面: -s:指的是开启与终端的交互,如果没有-s(程序不会输入与打印),一条用…

Java高级编程—注解

文章目录 1.注解的概述2.常见的Annotation示例2.1 生成文档相关的注解2.2 在编译时进行格式检查的注解2.3 跟踪代码依赖性&#xff0c;实现替代配置文件功能的注解 3.自定义Annotation4.JDK中的元注解4.1 Retention4.2 Target4.3 Documented & Inherited 5. JDK8中注解的新…

Vue2里,利用原生js input的 type=“file“时,获取上传成功后的文件名及文件内容。下载文件到本地

功能场景:现在有个上传下载文件的功能,不需要调后端接口,因为需求是不需要将文件存到数据库里。如下图,是上传成功的场景: 这里限制上传accept类型为pem, 这里主要用到了input的change事件,如果没上传文件则提醒上传文件后再下载,下载功能主要是运用创建a元素,传入blo…

数据结构小记【Python/C++版】——散列表篇

一&#xff0c;基础概念 散列表&#xff0c;英文名是hash table&#xff0c;又叫哈希表。 散列表通常使用顺序表来存储集合元素&#xff0c;集合元素以一种很分散的分布方式存储在顺序表中。 散列表是一个键值对(key-item)的组合&#xff0c;由键(key)和元素值(item)组成。键…

【2023最全kafka面试和答案】

2023最全kafka面试和答案 ​ 1.Kafka中的ISR(InSyncReplicate)、OSR(OutSyncReplicate)、AR(AllReplicate)代表什么&#xff1f; ISR : 速率和leader相差低于10秒的follower的集合OSR : 速率和leader相差大于10秒的followerAR : 所有分区的followerARISROSR 2.Kafka中的HW、L…

防爆气象传感器的技术原理

TH-WFB5在科技日新月异的今天&#xff0c;防爆气象传感器以其独特的魅力和广泛的应用前景&#xff0c;正逐渐走进人们的视野。这种高科技产品不仅为工业安全、环境保护等领域提供了有力保障&#xff0c;更在预测未来气象变化、防范自然灾害等方面发挥着不可替代的作用。 一、防…

ON1 Portrait AI 2023:智能美颜,打造完美人像 mac版

在数字化时代&#xff0c;人像摄影的需求和追求愈发高涨。为了满足摄影师对于完美人像的追求&#xff0c;ON1推出了全新的ON1 Portrait AI 2023。这款软件结合了先进的人工智能技术与人像处理的专业知识&#xff0c;为人像摄影带来了前所未有的智能体验。 ON1 Portrait AI 202…

104. Go单测系列4---编写可测试的代码

文章目录 一、剔除干扰因素二、接口抽象进行解耦三、依赖注入代替隐式依赖四、SOLID原则 本文是Go单测系列的最后一篇&#xff0c;在这一篇中我们不再介绍编写单元测试的工具而是专注于如何编写可测试的代码。 编写可测试的代码可能比编写单元测试本身更加重要&#xff0c;可测…

03-自媒体文章发布

自媒体文章发布 1)自媒体前后端搭建 1.1)后台搭建 ①&#xff1a;资料中找到heima-leadnews-wemedia.zip解压 拷贝到heima-leadnews-service工程下&#xff0c;并指定子模块 执行leadnews-wemedia.sql脚本 添加对应的nacos配置 spring:datasource:driver-class-name: com…