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++风格的强制转换就不一样了
有四种:
1、static_cast(最常用,比如常见的指针转换:把void*转换成其它类型的指针)2、const_cast(去除常量属性)3、dynamic_cast(动态类型转换,只用在多态时基类与派生类之间的转换)4、reinterpret_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的区别图示:
相关文章:

C++深入学习part_1
Linux下编译C程序 安装g命令:sudo apt install g 编译命令:$ g *.cc 或者 *.cpp -o fileName; hellworld 编译程序可以看到: namespace命名空间 首先,命名空间的提出是为了防止变量重名冲突而设置的。 浅浅试一下࿱…...
leetCode 300.最长递增子序列 (贪心 + 二分 ) + 图解 + 优化 + 拓展
300. 最长递增子序列 - 力扣(LeetCode) 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如ÿ…...
Spring加载后置处理器方式之模板方法
Spring加载后置处理器方式之模板方法 1. 未使用模板方法时2. 使用模板方法后 1. 未使用模板方法时 public static void main(String[] args) {MyBeanFactory myBeanFactory new MyBeanFactory();myBeanFactory.getBean();}static class MyBeanFactory {public Object getBean(…...
【高性能计算】CUDA编程之OpenCV的应用(教程与代码-4)//test error
imread命令将返回以蓝色、绿色和红色(BGR格式)开头的三个通道 处理视频的main函数中需要做的第一件事是创建VideoCapture对象。 GPU CUDA模块中的函数都定义在cv::cuda命名空间中,将设备上配置给图像数据用的显存块作为其参数。 gettickcount…...
高德地图行政区域四级级联数据拉取;省市区县乡镇级联数据
高德地图行政区域四级级联数据拉取 高德地图行政区域级联选择 高德地图行政区域级联选择 使用以下代码拉取高德官方省市区县乡镇四级级联数据 function p(name/* 行政区域名称 */){return $.ajax({"url": "https://lbs.amap.com/_AMapService/v3/config/dis…...

Qt_基础
目录 1概述1.1 什么是QT1.2 QT的发展史1.3 支持的平台1.4 QT版本1.5 下载与安装1.6 QT的优点1.7 成功案例 2 创建 Qt 项目2.1 使用向导创建2.2 .pro文件2.3 帮助文档(QTcreator自带的)2.4 QT应用程序介绍 3 创建第一个小程序3.1 按钮的创建3.1.1 设置主窗口标题的函数3.1.2 **固…...

最新AI创作系统源码ChatGPT网站源码V2.6.3/支持Midjourney绘画/支持OpenAI GPT全模型+国内AI全模型
一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统,支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Chat…...
UML建模语言分析和设计
UML(Unified Modeling Language,统一建模语言)是一种用于软件系统分析、设计和实现的标准化建模语言。UML提供了多种图形化工具,用于描述系统的不同方面,包括用例、类、对象、状态、活动和序列等。 在软件开发中&…...

SystemUI导航栏
SystemUI导航栏 1、系统中参数项1.1 相关开关属性2.2 属性设置代码 2、设置中设置“三按钮”导航更新流程2.1 属性资源覆盖叠加2.2 SystemUI导航栏接收改变广播2.3 SystemUI导航栏布局更新2.4 时序图 android13-release 1、系统中参数项 1.1 相关开关属性 设置->系统->…...
3d 贴图下载quixel
Quixel Megascans https://polyhaven.com/a/studio_small_03 Quixel Bridge:3D艺术家的宝库 在3D建模和渲染的世界中,找到高质量、适合项目的贴图素材至关重要。Quixel Bridge就是这样一个为3D艺术家提供大量免费贴图素材的资源库。在本文中ÿ…...

Linux权限维持
SSH 后门 软链接sshd 目标主机建立软连接: ln -sf /usr/sbin/sshd /tmp/su;/tmp/su -oport1189 #端口可以任意指定,最好伪装一下 查看端口: netstat -anlp|grep 1189 攻击机ssh登录: ssh rootx.x.x.x -p 1189 #如果root用户…...
互联网通信的核心协议HTTP和HTTPS
HTTP:超文本传输协议 HTTP,全称为超文本传输协议(Hypertext Transfer Protocol),是一种用于在Web上传输超文本文档的协议。它是Web通信的基础,允许浏览器与Web服务器之间的数据交换。HTTP使用了经典的客户…...

javaWeb网上购物系统的设计与实现
摘 要 随着计算机网络技术的飞速发展和人们生活节奏的不断加快,电子商务技术已经逐渐融入了人们的日常生活当中,网上商城作为电子商务最普遍的一种形式,已被大众逐渐接受。因此开发一个网上商城系统,适合当今形势,更加…...

MySQL 主从复制、读写分离
MySQL 主从复制、读写分离 1、MySQL 主从复制1.1什么是主从复制?1.2为什么要读写分离呢?1.3 什么时候要读写分离?1.4主从复制与读写分离1.5mysql支持的复制类型1.6主从复制的工作过程1.7MySQL 读写分离原理1.8目前较为常见的 MySQL 读写分离分…...

基于虚拟阻抗的下垂控制——孤岛双机并联Simulink仿真
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

windows内核编程(2021年出版)笔记
1. Windows内部概览 1.1 进程 进程包含以下内容: 可执行程序,代码和数据私有的虚拟地址空间,分配内存时从这里分配主令牌,保存进程默认安全上下文,进程中的线程执行代码时会用到它私有句柄表,保存进程运…...

时序预测 | MATLAB实现EMD-iCHOA+GRU基于经验模态分解-改进黑猩猩算法优化门控循环单元的时间序列预测
时序预测 | MATLAB实现EMD-iCHOAGRU基于经验模态分解-改进黑猩猩算法优化门控循环单元的时间序列预测 目录 时序预测 | MATLAB实现EMD-iCHOAGRU基于经验模态分解-改进黑猩猩算法优化门控循环单元的时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 EMD-iCHOAGR…...

FFmpeg 命令:从入门到精通 | FFmpeg 解码流程
FFmpeg 命令:从入门到精通 | FFmpeg 解码流程 FFmpeg 命令:从入门到精通 | FFmpeg 解码流程流程图FFmpeg 解码的函数FFmpeg 解码的数据结构补充小知识 FFmpeg 命令:从入门到精通 | FFmpeg 解码流程 本内容参考雷霄骅博士的 FFmpeg 教程。 流…...

连接虚拟机工具推荐
连接虚拟机工具推荐 连接虚拟机的工具有很多种,以下是一些常用的推荐: PuTTY:这是一个非常常用的SSH和telnet客户端,适用于Windows系统。它允许你在本地机器上通过命令行接口远程登录到虚拟机。 SecureCRT:这是一个支…...

万字详解HTTP协议面试必备技能
目录 一、HTTP 是什么 二、理解 "应用层协议" 2.1理解 HTTP 协议的工作过程 2.2HTTP 协议格式 2.3抓包工具的使用 2.4抓包工具的原理 2.5抓包结果 2.5.1HTTP请求 2.5.2HTTP响应 2.6协议格式总结 三、HTTP 请求 (Request) 3.1认识 URL 3.1.1URL 基本格式 …...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...