C++深入学习part_1

Linux下编译C++程序

安装g++命令:sudo apt install g++

编译命令:$ g++ *.cc 或者 *.cpp -o fileName;

hellworld

在这里插入图片描述

编译程序可以看到:
在这里插入图片描述

namespace命名空间

首先,命名空间的提出是为了防止变量重名冲突而设置的。
浅浅试一下:
在这里插入图片描述
现在我们进行编译的时候会发现报错:
在这里插入图片描述
从报错提示可以看到,他希望我们使用wd::display()的方式来调用该函数。(::称为作用域限定符)
这是因为display函数是定义在namespace命名空间里的,所以想要使用其内置成员的时候我们需要加上其对应的空间名:
在这里插入图片描述
再来编译:
在这里插入图片描述
完美运行通过。

命名空间还可以嵌套使用:
在这里插入图片描述
完美运行:
在这里插入图片描述
这是命名空间的一种使用方式,还有一种则是如下:
在这里插入图片描述
使用了using编译指令之后就可以不用再带对应的命名空间名了,因为上图中using编译指令会将std该空间的所有实体全部引入。

注意:第二种方式使用时必须知道该空间中有哪些实体,如果不知道,这样的写法就依然存在可能造成冲突的风险。

什么意思?
我们来试一下,这里使用std标准命名空间测试:
在这里插入图片描述
可以看见我们的cout函数具有二义性,因为std中也有一个该函数,编译会报错:
在这里插入图片描述
ambiguous:二义性。

所以初学时大型项目里面最好不要使用using编译指令,因为有可能造成冲突(你并不知道std中有多少函数)。

推荐使用using声明机制,即:using std::cout; 它只引入这一个实体。
另外在命名空间中直接定义的实体,不要直接缩进。

匿名命名空间

其实就是不带空间名就是匿名的命名空间,匿名命名空间可以直接使用其内部定义的实体。

#include <iostream>
using namespace std;
//匿名命名空间
namespace {int number = 4;}int main(void){cout << number << endl;return 0;
}

我们看存在的一种情况:

#include <iostream>
using namespace std;
//匿名命名空间
namespace {int number = 4;}int number = 5;int main(void){cout << number << endl;return 0;
}

此时不出意料肯定会报错,因为具有二义性:
在这里插入图片描述
所以我们为了强调我们用的是哪个number,就需要使用匿名空间的作用域操作符:

#include <iostream>
using namespace std;
//匿名命名空间
namespace {int number = 4;//而这个变量只能在本模块内部使用}int number = 5; //该全局变量可以跨模块使用,即可以在另一个.cpp文件中使用
//所谓模块:一个*.c/*.cc/*.cpp的文件就可以叫做一个模块//同理:
static int s_number = 5; //也只能在本模块内部使用int main(void){//使用作用域操作符来使用匿名命名空间cout << ::number << endl;return 0;
}

运行就没有问题了,因为我们强调了使用命名空间中的number:
在这里插入图片描述

跨模块调用extern关键字

当我们要跨模块调用另一个cpp文件中的变量或者函数时,需要使用extern关键字。
我们在hello.cpp文件中写上g_number = 100;

#include <iostream>
using namespace std;//等待被namespace1.cpp调用的变量
int g_number = 100;//这里要注意嗷,我们上面的所谓跨模块调用意思是这些模块本身就属于同一个项目
//而一个项目只能有一个main函数,所以这里我们注释掉hello.cpp中的main函数
//int main() {//      cout << "hello world" << endl;//    return 0;
//}

然后我们在namespace1.cpp文件中通过extern关键字来引用它:

#include <iostream>
using namespace std;
//匿名命名空间
namespace {int number = 4;//而这个变量只能在本模块内部使用}int number = 5; //全局变量可以跨模块使用,即可以在另一个.cpp文件中使用//使用hello.cpp文件中的g_number变量
extern int g_number;int main(void){//使用作用域操作符来使用匿名命名空间cout << ::number << endl;//打印g_numbercout << g_number << endl;return 0;
}

编译运行:
在这里插入图片描述

另外,在同一个模块中可以定义多次命名空间;在不同的模块中也可以定义多次命名空间:

#include <iostream>using namespace std;//第一次定义命名空间wd
namespace wd{void show(); //这里是第一次声明实体show()
}int main(){//调用wd中的show()wd::show();return 0;
}//这里我们第二次定义命名空间wd
namespace wd{//第二次定义实体showvoid show();
}//这里我们第三次定义命名空间wd
namespace wd{//第三次声明并且定义实体showvoid show(){cout << "我是第三次被声明啦" << endl;}
}

注意虽然命名空间可以随便声明,但是它里面的函数声明可是只能有一次定义嗷(就和正常的函数一样)。
编译运行:
在这里插入图片描述
不光是本文件可以重复声明命名空间,跨文件(或者说跨模块)也一样可以,这里我们在namespace3文件中定义一个同名wd:

#include<iostream>
using namespace std;//在namespace3.cpp文件中定义重名namespace wd
namespace wd
{void print(){cout << " 我是跨模块的命名空间嗷  " << endl;}
}

然后我们返回到刚刚的测试文件中去调用它:


#include <iostream>using namespace std;//在这里调用跨文件的namespace wd
namespace wd{void print();
}//第一次定义命名空间wd
namespace wd{void show(); //这里是第一次声明实体show()
}int main(){//调用wd中的show()//wd::show();//调用跨文件的namespace3.cpp中的wdwd::print();return 0;
}//这里我们第二次定义命名空间wd
namespace wd{//第二次定义实体showvoid show();
}//这里我们第三次定义命名空间wd
namespace wd{//第三次声明并且定义实体showvoid show(){cout << "我是第三次被声明啦" << endl;}
}

编译运行:
在这里插入图片描述

总结:
1、命名空间的提出是为了防止变量重名冲突而设置的,可以嵌套使用
2、去除了命名空间名就是所谓的匿名命名空间,匿名命名空间的实体无法跨模块调用。
3、在同一个模块中可以定义多次命名空间,在不同的模块中也可以定义多次命名空间。

const修饰类型和对象

const:修饰类型或对象成为常量值的关键字,常量值不可以改变且必须初始化。

#include <iostream>using namespace std;#define MAX 1000void test(){int a;//const int b; 必须要继续初始化,否则报错const int c = 1;//c = 2; error 常量是不能进行修改的//有同样效果的还有宏定义#definecout << MAX << endl;
}int main(){test();return 0;}

宏定义与const常量的区别(面试常考)

1、发生的时机不一样:
宏定义是在预处理时,而const常量是在编译时
2、类型检查不一样:
宏定义是没有类型检查的,只是简单做了字符串的替换,虽然也有编译阶段,但在编译阶段没有报错,将出错的时机推迟到了运行时,但运行时阶段的错误是更难发现的;而const是由类型检查的,这样更加安全一些

那么什么叫宏定义只是作了字符串的简单替换呢?
这里我们举例说明:

#include <iostream>using namespace std;//举例说明为什么宏定义只是进行了简单的字符串替换
#define kBase 3+4void test(){int a = 10;int d = a * kBase;cout << "d: " << d << endl;
}int main(){test();return 0;}

上面代码理想的值应该是得到10 * (3 + 4) = 70,但编译运行结果为:
在这里插入图片描述
我们可以用如下命令去查看预处理阶段的代码长什么样:
在这里插入图片描述
上面的constL.cpp和constL.i都是文件名,.i文件就是我们的预处理文件,打开它可以看见:
在这里插入图片描述

3+4被当作字符串一样直接替换了kBase,所以最后的结果就成了10*3+4-34。

总结:要定义常量时最好使用const或者枚举enum类型。

const修饰指针

# include <iostream>using namespace std;int main(){int a = 10;//这种形式是常量指针,表示p1所指向的a对象的值不可以改变//即p1也可以指向别人,如p1 = &b;//但(*p1) = 20; 企图修改a的值就是错误的,该值不可改变const int* p1 = &a;//int const* p2 = &a; 这种格式和上面p1指针是一个意思,且不怎么用//这种形式是指针常量,表示p3所指的地址值不可以改变//即p3不可以指向别人了,如p3 = &b; 就是错误的,指向不可改变//而(*p3) = 30; 这是可以的,其所指对象的值可以改变int* const p3 = &a;
}

C++堆空间申请方式以及内存泄露

C语言中申请堆内存空间使用的是malloc和free函数。
在C++中也有类似的方式:new表达式与delete表达式。

int * pint = new int(10); //new表达式申请空间的同时,也进行了初始化delete pint; //释放申请的堆空间

简单尝试:

#include <iostream>using namespace std;int main(){//new表达式执行完毕之后,返回的是相应类型的指针int* pint = new int(1);cout << "*pint  = " << *pint << endl;
}

运行编译:
在这里插入图片描述
但此时我们的代码是有问题的,因为没有释放掉我们的pint空间,即发生了内存泄漏。
那我们怎么检测我们的程序是否发生了内存泄露呢?
答案是使用一些内存检测工具,比较常用的如:valgrind
执行下面的命令下载它:
在这里插入图片描述

内存泄露检测工具-valgrind(面试高频考点)

下载完毕后我们执行以下操作:
在这里插入图片描述

上面的操作结束后现在我们就可以直接使用别名命令memcheck来检测内存泄露了,我们来检测一下我们刚刚的内存是否存在泄露:
在这里插入图片描述
从in use at exit:4bytes in 1 blocks等信息中可以看出,存在内存泄露问题,着就是内存检测工具valgrind的简单使用。

所以要记得回收内存啊!

关于new还有一种使用方式:

#include <iostream>using namespace std;int main(){//1、第一种new的使用方式//new表达式执行完毕之后,返回的是相应类型的指针int* pint = new int(1);cout << "*pint  = " << *pint << endl;delete pint;//2、第二种new的使用方式//new表达式要申请的空间为数组int* parr = new int[10];//注意数组的堆空间申请和释放的语法嗷,中括号别掉delete[] parr;}

引用

在这里插入图片描述

引用作为函数参数传递

首先回忆一下之前的几种参数传递方式:

1、值传递

还是使用经典的交换两个变量值的内容来作例子:
在这里插入图片描述
由上图可知,在使用值传递时,其实传递的仅仅是变量值的拷贝,而我们建立的在swap函数中定义的tmp值也不过是个临时变量,当swap函数执行结束后tmp变量也就随即消失了。所以值传递并不能实现交换两个值的内容,这是由于两个函数并不共享一块内存空间决定的,虽然它们都存在在栈空间内。
注意图中的箭头仅仅意味着进行了一个参数的拷贝而已,即a1的值拷贝给了变量x。

2、地址传递

在这里插入图片描述
地址传递就不一样了,上图明显可以看见指针px指向了a的地址,指针py指向了b的地址。
所以对两个指针解引用可以得到:*px = 1; *py = 2
在swap函数中,第20行代码tmp暂存了 *px的值,然后第二十一行中,px所指地址空间上的值被修改成了 *py的值,即 *px = 2;
第22行代码则将 *py的值变成了 1。
所以达到了我们想要交换两个变量值的目的:
在这里插入图片描述

3、引用传递

在这里插入图片描述
这就没啥好说的了,因为引用其实就是别名,所以操作x和y其实就是在直接操作a和b罢了。

引用的出现就是为了替代指针,尽量让程序员减少犯错的概率。
其底层实现依然是指针,而且是一个受限制的指针,即引用一旦被绑定到某个对象上后就不能够解绑去绑定到别的对象上。

引用作为函数的返回值

在这里插入图片描述
在这里插入图片描述

强制转换与函数重载

C风格强制转换:

TYPE a = (TYPE) EXPRESSION;

但这种风格存在缺陷,就是安全性不足,无法保证转换之后类型的合法性。

而C++风格的强制转换就不一样了

有四种:
1static_cast(最常用,比如常见的指针转换:把void*转换成其它类型的指针)2const_cast(去除常量属性)3dynamic_cast(动态类型转换,只用在多态时基类与派生类之间的转换)4reinterpret_cast(在任意类型之间轻易转换,但是不要轻易使用,用的最少)

上面四种转换方式只是含义不一样,但写法是通用的,形式都如下:
在这里插入图片描述

static_cast

这个没什么好讲的,就是正常用就型:

int* p = static_cast<int*> (malloc(sizeof(int)));

上面这一句代码就是一个很典型的应用,在C风格中malloc函数返回的是void*,如果要使该行代码不报错的话,就必须进行类型转换,那么此时用static_cast是非常合适的。

const_cast


#include <iostream>using namespace std;void display(int* p){ //明显要求传入一个非const指针*p = 10;cout << "*p = " << *p << endl;}int main(){const int a = 1;display(&a);//在实参传递时,只有const变量,如果传递成功的话就有修改a的值的风险}

如上面注释所说,这肯定是报错的:
在这里插入图片描述
那如果我们一定要传这个const常量参数呢?
那就可以用上const_cast了:

#include <iostream>using namespace std;void display(int* p){ //明显要求传入一个非const指针*p = 10;cout << "*p = " << *p << endl;}int main(){const int a = 1;//进行了去除const的强制类型转换display(const_cast<int*> (&a));}

现在再运行就没有问题了:
在这里插入图片描述
虽然我们使用了const_cast进行转换,但是我们并没有真正改变const变量的值,这一点要注意,即上面代码中的a的值是没有变化的。

而且更有意思的是,当我们打印指针p的值(即变量a的地址)的时候会发现,它的地址居然和常量a是一模一样的:

#include <iostream>using namespace std;void display(int* p){ //明显要求传入一个非const指针*p = 10;cout << "*p = " << *p << endl;cout << "p所指地址为: " << p << endl;
}int main(){const int a = 1;//进行了去除const的强制类型转换display(const_cast<int*> (&a));cout << "a的地址为: " << &a << endl;}

运行结果:
在这里插入图片描述
这就很扯:地址值是一样的,但是值不一样。

所以迷惑性很强,一般情况下最好不要用const_cast。(这里很多资料里面都没有一个明确的说法,据说是*p的值存在了所谓的寄存器中,并没有真正写入内存啥的,反正知道有这么回事就行了)

dynamic_cast和reinterpret_cast两个就不讲了,基本用不到

函数重载

在这里插入图片描述

C语言不支持函数重载!

在这里插入图片描述
在这里插入图片描述
由上图可以发现,确实对于不同的重载函数其实就是改变一下对应的名字来调用而已。add是函数名,然后add后面的ii就是参数列表中各个参数的缩写。

C++与C的混合编程

上一节我们知道了C++在内部是使用了名字改编的原理来支持函数重载的,但是C语言不支持函数重载自然也就没有所谓名字改编的操作,这就导致了C和C++在进行混合编程的时候会出现一些兼容问题:

在这里插入图片描述

为什么需要进行混合编程:很明显,C比C++早十二年出来,很多库都是C写的,C++只能去兼容和适应C的法则;

为了解决这样的问题,我们引入了下面的方式来解决兼容性问题:

在这里插入图片描述
上图右侧就是混合编程的编译结果,示例代码如下:

#include <iostream>using namespace std;//用C语言的方式来调用该函数
extern "C"{//只要放在该区域的代码,就会按照C的方式进行调用//不会进行名字改编int add(int x, int y){return x + y;}
}// end of extern "C"//下面这些重载函数都是按C++方式来进行调用的
long add(long x , long y){return x + y;
}int add (int x,int y,int z){return x + y + z;
}int add(int x,long y){return x + y;
}int add(long x, int y){return x + y;
}int main(){return 0;
}

上述这是 extern "C"声明在实现文件.cpp文件中的情况,但是如果是在头文件中情况又当如何呢?

在头文件中,文件是有可能被C编译器编译的,也有可能是被C++编译器编译的,自然的说C编译器肯定是不需要上面那段extern ''C"就能编译,会节省时间,而C++编译器则需要这段代码,如何做才能得到这样的效果?

答案是使用C++中的条件编译,宏定义:
在这里插入图片描述
所以在头文件中加上上述内容:

//宏_cplusplus只有C++的编译器才会定义
//C的编译器没有该宏
//意思就是只有该被包围起来的代码是被C++编译器编译时才会出现
//若是被C编译器编译的话就不会出现
#ifdef _cplusplus
extern "C"
{
#endifint add(int x,int y){return x + y;}
#ifdef _cplusplus
}
#endif

通过上述方法就可以完美解决C与C++的混合编程问题。

默认参数

在这里插入图片描述
这其实没啥好说的,就注意一下上面说的一个点:
默认参数的设置要求必须从右到左进行;
另外设置默认参数的时候要注意是否有其它的重载函数与其设置了默认参数的参数列表产生调用时的二义性就行。

inline函数

首先在C语言中,其实有类似的语法,函数宏定义:

#include <iostream>using namespace std;//C语言中的函数宏定义
#define multiply(x,y) x * yint main(){int a = 3, b = 4;int c = 5, d = 6;cout << multiply(a,b) << endl;//输出为12//但还是之前的问题,宏定义只是简单替换成了字符串//所以下面的语句其实是:a+b*c+d = 29cout << multiply(a+b,c+d) << endl;
}

编译运行:
在这里插入图片描述
接下来我们看在C++中有同样功能的inline函数:

#include <iostream>using namespace std;//C语言中的函数宏定义
#define multiply(x,y) x * y//C++中的inline函数
//为什么有inline函数:就是因为每次函数的调用都是绝对有开销的(比如栈空间的消耗)
//那么加上inline关键字的话,在编译时编译器会将该函数进行语句的替换
//下面的函数调用就会被替换成语句 x / y,极大的提升了效率
//它的效率与宏函数保持一致,还更加安全inline int divide(int x,int y){return x / y;
}int main(){int a = 3, b = 4;int c = 5, d = 6;cout << multiply(a,b) << endl;//输出为12//但还是之前的问题,宏定义只是简单替换成了字符串//所以下面的语句其实是:a+b*c+d = 29cout << multiply(a+b,c+d) << endl;//调用inline内联函数cout << divide(d,a) << endl;
}

为了降低犯错误的概率,尽量使用inline内联函数。

内联函数的使用要求

在这里插入图片描述

C++内存布局(面试常考)

在这里插入图片描述
上图是每一个进程被装载到内存中运行时的内存空间分布示意图,每个进程被装载到内存中运行时都会有如上几个区。
32位操作系统意思就是每一次读写数据的话只能读写32个位也就是四个字节,因为只有32根地址线来传送数据(所以32根地址线最大传送的数据就是当这32个位全为1的时候,最小就是当这三十二个位全为0的时候,这就决定了该类型操作系统的内存地址空间范围),那么2的32次方也就是4G大小的内存空间,其中一部分用来作OS的系统空间,即上图中的内核态,用来运行一些内核程序,而剩下的部分就是用户去区,也就是上图的用户态,用户进程(也就是我们所编写的程序)都会运行在用户态中。我们的C++程序也一样会运行在用户态里,只不过完整的程序根据其代码的不同会被分到不同的内存区域中,其中栈区总是位于虚拟地址的高位部分,向低地址方向进行生长,而堆区则在其下面由低地址向高地址生长,全局/静态区(或者说读写段)和只读段(或者说文字常量区和程序代码区)则依次往下存放。

接下来我们来一一验证,通过本次学习以后必须清楚自己写下的每一句代码中的数据是存储在哪个空间里的。

#include <iostream>using namespace std;int gNumber = 1;static long sNumber = 2;const int kNumber = 3;void test(){//对于使用指针声明的字符串,该字符串位于文字常量区,声明时应该加上const否则会有警告//所以正确的声明应该是:const char* pstr = "hello,world";char* pstr = "hello,world";//*ptr = 'H'; 错误,因为文字常量区是只读区域,所以从侧面反映了其确实位于文字常量区//该字符串位于栈上,相当于用"hello,world"字符串去初始化了这个字符数组char pstr2[] = "hello,world";int number = 1;const int number2 = 1;const int* const p = &number2;static int sLocalNumber = 10;//pint本身位于栈上int* pint = new int(1); //堆区delete pint;printf("pstr: %p\n",pstr);printf("&pstr: %p\n",&pstr);/*这里要注意辨析一下pstr2和&pstr2的区别,虽然它们俩打印出来的地址是一样的* pstr2是指该字符数组的首个元素的地址,即&pstr2[0]的地址,我们通过对其+1可以拿到&pstr2[1]的地址,也可以访问其元素* 而&pstr2的意思则是取整个字符数组的地址,也就是首个元素的地址* 但此时对&pstr2+1的话我们不会拿到第二个元素的地址,反而是会把整个字符数组当作第一个元素,然后去访问下一个字符数组* 的元素,也就是偏移的是整个数组的长度而不是偏移一个元素的长度*/printf("pstr2: %p\n",pstr2);printf("&pstr2: %p\n",&pstr2);//pstr2 = 0x11; 错误 数组名是一个常量,不能修改它的值printf("&gNumber: %p\n",&gNumber);//全局静态区printf("&sNumber: %p\n",&sNumber);//全局静态区printf("&number: %p\n",&number);//栈区printf("&number2: %p\n",&number2);//依然是放在栈上,因为该常量是定义在本函数内的,声明周期在本函数内printf("&kNumber: %p\n",&kNumber); //放在文字常量区,所谓文字常量意思是“字面常量“//包括数值常量、字符常量和符号常量printf("&sLocalNumber: %p\n",&sLocalNumber);//放在全局静态区//查看函数的地址//函数的名称即函数的入口地址存在于全局静态区,即程序存在它就存在//所以查看其地址时会发现函数地址和全局变量的地址相近//但是通过函数名去调用具体函数时就会在栈空间里了//所以函数内部的局部变量都是存放在栈空间上printf("&test: %p\n",&test);
}int main(){test();//查看main函数的地址printf("&main: %p\n",&main);}

编译运行:
在这里插入图片描述

自己可以对照着看看,加深一下理解,然后下面是对上面代码中提到的pstr2和*pstr2的区别图示:在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

漏洞复现-易思无人值守智能物流文件上传

免责声明&#xff1a; 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…

STC89C51基础及项目第10天:LCD显示字符(非标协议外设)

1. 初识LCD1602&#xff08;233.79&#xff09; 非标协议外设 LCD1602显示 LCD1602&#xff08;Liquid Crystal Display&#xff09;是一种工业字符型液晶&#xff0c;能够同时显示 1602 即 32 字符(16列两行) 引脚说明 第 1 脚&#xff1a; VSS 为电源地第 2 脚&#xff1…

Unity AI Sentis 基础教程

Unity AI Sentis基础教程 Unity AI Sentis基础教程Unity AI 内测资格申请Unity 项目Package Manager开始尝试模型下载识别图片完整代码代码搭载运行 射线绘画 URP&#xff08;扩展&#xff09;射线绘画脚本脚本搭载效果 Sentis 是 AI 模型的本地推理引擎&#xff0c;它利用最终…

3D孪生场景搭建:模拟仿真

前面几期文章介绍如何使用NSDT 编辑器 搭建3D应用场景&#xff0c;本期介绍下孪生场景中一个一个非常重要的功能&#xff1a;模拟仿真。 1、什么是模拟仿真 模拟仿真是一种用于描述、分析和模拟现实世界中系统、过程或事件的计算机模型和程序。仿真通过输入各种参数和条件&am…

【iOS】——仿写计算器

文章目录 一、实现思路二、实现方法三、判错处理 一、实现思路 先搭建好MVC框架&#xff0c;接着在各个模块中实现各自的任务。首先要创建好UI界面&#xff0c;接着根据UI界面的元素来与数据进行互动&#xff0c;其中创建UI界面需要用到Masonry布局。 二、实现方法 在calcu…

Maven(4)-利用intellij idea创建maven 多模块项目

本文通过一个例子来介绍利用maven来构建一个多模块的jave项目。开发工具&#xff1a;intellij idea。 一、项目结构 multi-module-project是主工程&#xff0c;里面包含两个模块&#xff08;Module&#xff09;&#xff1a; web-app是应用层&#xff0c;用于界面展示&#xff…

AdaBoost(上):数据分析 | 数据挖掘 | 十大算法之一

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

特殊笔记_10/7

安装node到第4.1就行&#xff08;安装npm的淘宝镜像&#xff09; Node.js安装与配置&#xff08;详细步骤&#xff09;_nodejs安装及环境配置_LI4836的博客-CSDN博客 安装vscode 下载组件&#xff1a; 点击第五个 Auto Close Tag&#xff1a;自动闭合标签 Chinese (Simpli…

MQ - 36 云原生:业界MQ的计算存储分离的设计与实现

文章目录 导图概述什么是存算分离架构必须是存算分离架构吗实现存算分离架构的技术思考如何选择合适的存储层引擎存储层:分区存储模型的设计计算层:弹性无状态的写入业界主流存算分离架构分析RocketMQ 5.0 架构分析Pulsar 存算架构分析总结导图 概述 结合云原生、Serverless…

JetBrains ToolBox修改应用安装位置

TooBox修改应用安装位置 1.关闭ToolBox 2.修改配置文件 找到配置文件所在位置 C:\Users\用户名\AppData\Local\JetBrains\Toolbox\.settings.json增加install_location字段 "install_location": "E:\\DevTool\\IDE",E:\DevTool\IDE可以改成自己想要的…

Springboot项目log4j与logback的Jar包冲突问题

异常信息关键词&#xff1a; SLF4J: Class path contains multiple SLF4J bindings. ERROR in ch.qos.logback.core.joran.spi.Interpreter24:14 - no applicable action for [properties], current ElementPath is [[configuration][properties]] 详细异常信息&#xff1a…

常见排序算法详解

目录 排序的相关概念 排序&#xff1a; 稳定性&#xff1a; 内部排序&#xff1a; 外部排序&#xff1a; 常见的排序&#xff1a; 常见排序算法的实现 插入排序&#xff1a; 基本思想&#xff1a; 直…

自学接口测试系列 —— 自动化测试用例设计基础!

一、接口测试思路总结 ❓首先我们在进行接口测试设计前思考一个问题&#xff1a;接口测试&#xff0c;测试的是什么&#xff1f; ❗我们必须要知道&#xff0c;接口测试的本质&#xff1a;是根据接口的参数&#xff0c;设计输入数据&#xff0c;验证接口的返回值。 那么接口…

day24-JS进阶(构造函数,new实例化,原型对象,对象原型,原型继承,原型链)

目录 构造函数 深入对象 创建对象三种方式 构造函数 new实例化执行过程(important!) 实例成员&静态成员 实例对象&实例成员 静态成员 内置构造函数 基本包装类型 Object Object.keys(obj)返回所有键组成的字符串数组 Object.values(obj)返回所有值组成的字…

Nginx支持SNI证书,已经ssl_server_name的使用

整理了一些网上的资料&#xff0c;这里记录一下&#xff0c;供大家参考 什么是SNI&#xff1f; 传统的应用场景中&#xff0c;一台服务器对应一个IP地址&#xff0c;一个域名&#xff0c;使用一张包含了域名信息的证书。随着云计算技术的普及&#xff0c;在云中的虚拟机有了一…

RPC分布式网络通信框架项目

文章目录 对比单机聊天服务器、集群聊天服务器以及分布式聊天服务器RPC通信原理使用Protobuf做数据的序列化&#xff0c;相比较于json&#xff0c;有哪些优点&#xff1f;环境配置使用项目代码工程目录vscode远程开发Linux项目muduo网络库编程示例CMake构建项目集成编译环境Lin…

在Android中实现动态应用图标

在Android中实现动态应用图标 你可能已经遇到过那些能够完成一个神奇的技巧的应用程序——在你的生日时改变他们的应用图标&#xff0c;然后无缝切换回常规图标。这是一种引发你好奇心的功能&#xff0c;让你想知道&#xff0c;“他们到底是如何做到的&#xff1f;”。嗯&…

HTML 笔记 表格

1 表格基本语法 tr&#xff1a;table row th&#xff1a;table head 2 表格属性 2.1 基本属性 表格的基本属性是指表格的行、列和单元格但并不是每个表格的单元格大小都是统一的&#xff0c;所以需要设计者通过一些属性参数来修改表格的样子&#xff0c;让它们可以更更多样…

VR全景展示带来旅游新体验,助力旅游业发展!

引言&#xff1a; VR&#xff08;虚拟现实&#xff09;技术正以惊人的速度改变着各行各业&#xff0c;在旅游业中&#xff0c;VR全景展示也展现了其惊人的影响力&#xff0c;为景区带来了全新的宣传机会和游客体验。 一&#xff0e;什么是VR全景展示&#xff1f; VR全景展示是…

华硕平板k013me176cx线刷方法

1.下载adb刷机工具, 或者刷机精灵 2.下载刷机rom包 华硕asus k013 me176cx rom固件刷机包-CSDN博客 3.平板进入刷机界面 进入方法参考&#xff1a; ASUS (k013) ME176CX不进入系统恢复出厂设置的方法-CSDN博客 4.解压ME176C-CN-3_2_23_182.zip&#xff0c;把UL-K013-CN-3.2.…