的C++奇迹之旅:值和引用的本质效率与性能比较
文章目录
- 请添加图片描述 @[TOC](文章目录)
- 📝引用# 🌠引用概念**引用**不是新定义一个变量,而是给**已存在变量取了一个别名**,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。>定义:**类型&引用变量名(对象名) = 引用实体;**例如以下代码,在变量名前加一个`&`,意思是一个引用类型,`b`是`a`的别名,也就是`a`有了一个外号,但还是`a`本身:```cppint a = 70;int& b = a; //引用:b是a的别名```我们接下来看看引用后的地址是否会发生改变:例如以下例子:```cppint main(){ int a = 70; int& b = a; //引用:b是a的别名 int& c = a; //引用:c是a的别名 c = 80; cout << a << endl; cout << &a << endl; cout << &b << endl; cout << &c << endl; return 0;}```代码运行图:在这个代码中,定义了一个变量`a`为`70`,`int& b = a`; 这里`b`是`a`的引用,给`a`取了一个外号`b`,`int& c = a`; 这里`c`是`a`的引用,又给`a`取了一个外号是`c`,因此我们对`c`还是对`b`进行修改,`a`都会发生改变,这是因为**编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间**。![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/039ecfab596d41869ff8a7c90e52e714.png)>注意:**引用类型必须和引用实体是同种类型的**## 🌉引用特性1. 引用**必须在定义时初始化**:==引用必须在定义时初始化,不能在之后单独为它赋值==。```cppint a = 10; int& ra = a; // 正确,ra初始化为aint& ra; // 错误,引用必须在定义时初始化```2. 一个变量可以有多个引用```cppint a = 10;int& ref1 = a; int& ref2 = a;ref1++; // a的值变为11cout << a << endl; // 输出11ref2--; // a的值变为10cout << a << endl; // 输出10```3. 引用一旦引用一个实体,再不能引用其他实体引用本质上**就是给原变量添加一个别名,它的内存地址就是原变量的地址**。所以对引用赋值或修改,实际上就是修改原变量。而指针不同,指针可以改变指向的对象:**一级指针**可以改变指向,如p可以从指向a改为指向其他变量,**二级指针**可以改变一级指针指向的地址,如`pp`可以改变`p`指向的地址而引用更像一个`const`指针:定义后不能改变指向的对象,就像`const`指针定义后不能改变指向但可以通过这个“`const`指针”来修改原对象,就像通过`const`指针可以修改原对象```cppint a = 10;int b = 20;int& ref = a; ref = b; // 错误!引用ref已经引用a,不能再引用bcout << ref << endl; // 输出10,ref依然引用a```如图:ref引用了a,这里的值发生改变是因为b赋值给了ref![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/09c2a3f2dc534036b45f7eef60d8744d.png)# 🌠使用场景## 🌉做参数(传值与传地址)当引用用来做参数时将会对代码进行大大的优化,并且具有可读性,如:当我们看了很多遍的交换了两个数的函数:```cppvoid Swap(int* a, int* b){ int tmp = *a; *a = *b; *b = tmp;}int main(){ int ra = 88; int rb = 99; Swap(&ra, &rb); return 0;}```形参是实参的一份临时拷贝,所以如果想交换需要,传地址,不能传值。```cppvoid Swap(int& a, int& b){ int tmp = a; a = b; b = tmp;}int main(){ int ra = 88; int rb = 99; Swap(ra, rb); return 0;}````a`和`b`分别是`ra`和`rb`的别名,当你调换`a`和`b`的纸时,其实是修改了`ra`和`rb`的地址的值,这样的好处就是,当你看代码时,引用`a`和`b`给人一种感觉,就是操作`ra`和`rb`本身。这隐藏了底层是通过地址操作原变量`ra`和`rb`的实现细节。从使用者的角度看,代码读起来就像直接交换`ra`和`rb`,而不是通过**复杂的地址操作实现**。>这里使用了引用挺好的,不用担心指针的解引用,地址相关操作,但是,前面我们知道,引用一旦指向一个实体,就无法改变指向,例如,**有关链表操作**,当我们要删除一个节点,是不是要改变前面节点的指针,让他指向后面节点,而引用恰恰不能改变,因此,**引用也不是完全替代指针的**![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d4134e1aec26472892b5035f8f74c7fe.png)回归正题,这里还有一个小注意点:作用域的不同,因此,在Swap函数里,取别的名字都可以,任由发挥,结果都相同。```cppvoid Swap(int& x, int& x){ int tmp = x; x = y; y = tmp;}int main(){ int ra = 88; int rb = 99; Swap(ra, rb); return 0;}```## 🌉传值、传引用效率比较**以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。**```cpp#include <time.h>struct A { int a[10000]; };void TestFunc1(A a) {}void TestFunc2(A& a) {}void TestRefAndValue(){ A a; // 以值作为函数参数 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 以引用作为函数参数 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分别计算两个函数运行结束后的时间 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;}int main(){ TestRefAndValue(); return 0;}```**按值传递(TestFunc1):**调`用`TestFunc1(a)`时,会将`a`进行拷贝,生成一个临时对象**a_copy**。**a_copy**作为参数传递给TestFunc1`。`TestFunc1`内部操作的实际上是`a_copy`,对`a_copy`的修改不会影响实参`a`。`TestFunc1`返回时,临时对象`a_copy`会被销毁。`TestFunc1`以值方式传递结构体`A`作为参数。这会导致每次调用都会对A进行值拷贝,对于一个包含`10000`个`int`成员的大结构体,拷贝开销很大。**按引用传递(TestFunc2):**调用`TestFunc2(a)`时,不会进行值拷贝,直接传递`a`的引用。`TestFunc2`内部操作的仍然是实参`a`本身。`TestFunc2`返回时,不需要销毁任何对象。TestFunc2以引用方式传递A。这种方式下,函数内直接操作的就是实参a本身,不会有任何拷贝开销。>总结:>`TestFunc1`值传递,效率低是因为值拷贝开销大`TestFunc2`引用传递,效率高是因为避免了值拷贝,直接操作的就是实参`a`本身![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/cea37fd6819d40059d37965342ee3719.png)>通过上述代码的比较,**发现传值和指针在作为传参以及返回值类型上效率相差很大**。# 🌠引用做返回值```cppint& Count(){static int n = 0;n++;// ...return n;}```我们先看看下面代码会输出什么结果?```cppint& Add(int a, int b){int c = a + b;return c;}int main(){int& ret = Add(1, 2);Add(3, 4);cout << "Add(1, 2) is :"<< ret <<endl;return 0;}```在Vs编译运行图:结果是**7**,真的是正确吗?![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/fe90c7c1ead24f92b1fd7df0a2186064.png)>问题分析:>**如果函数返回时,返回的对象已经超出了函数作用域(即已经被销毁),那么不能返回该对象的引用,必须返回值。**>在第一个示例中:```cppint& Add(int a, int b){int c = a + b;return c;}```>这里函数返回了局部变量`c`的引用,但`c`在函数返回后就已经被销毁了,所以这是一个**未定义行为**,输出结果是**不确定**的。>而在第二个示例中:```cppint& Add(int a, int b){int c = a + b;return c;}int main(){int& ret = Add(1, 2);Add(3, 4);cout << "Add(1, 2) is :"<< ret <<endl;return 0;}```这里同样是返回了局部变量`c`的引用,但是在`main`函数中又调用了一次`Add`函数,这时第一次调用返回的引用`ret`已经指向了一个==不存在的对象==,所以输出结果==也是未定义==的。>函数返回引用时必须确保返回的对象在调用者作用域内仍然存在,否则就会产生未定义行为。这是`C++`中函数返回引用需要特别注意的地方。![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/921cb60c97c3413da342531e41f3a314.png)答案思考:在`Visual Studio`上运行这段代码,输出结果是:```Add(1, 2) is :7```这个结果确实是**未定义**行为,但在某些情况下可能会输出`7`。之所以会出现这种情况,是因为**Visual Studio的编译器在处理这种未定义行为时可能会做一些特殊的优化或处理**,导致在某些环境下能够得到一个**看似合理**的结果。但这种行为是**不可靠**的,因为它依赖于具体的编译器实现细节。在不同的编译器或环境下,输出可能会完全不同。**正确的做法**:是要么返回值,要么返回一个在调用者作用域内仍然存在的对象的引用。这样可以确保代码的行为是可预测和可移植的。## 🌉引用和指针的区别1. **语法概念**:- **引用**是变量的别名,没有独立的存储空间,而是和其引用的实体共用同一块内存空间。- **指针**是一个独立的变量,存储了另一个变量的内存地址。2. **声明语法**:- 引用使用`&`符号声明,如`int& ref = x;`- 指针使用`*`符号声明,如`int* ptr = &x;`3. **操作方式**:- 引用直接访问和操作其引用的实体,如`ref = 10;`- 指针需要先解引用(`*`)才能访问其指向的实体,如`*ptr = 10;`4. **Null值**:- 引用不能为空(Null),必须在声明时初始化为一个有效的实体。- 指针可以为空(Null),指向空地址`0x0`。让我们看看例子来说明引用和指针的区别:假设我们有一个整型变量`x`,值为10。**使用引用:**```cppint x = 10;int& ref = x; // 声明引用ref,它是x的别名ref = 20; // 通过ref修改x的值cout << "x = " << x << endl; // 输出 x = 20````ref`是`x`的引用,它们共享同一块内存空间。通过`ref`修改值,实际上是在修改`x`的值。 输出`x`的值为20,因为`x`的值已经被修改了。**使用指针:**```cppint x = 10;int* ptr = &x; // 声明指针ptr,存储x的地址*ptr = 20; // 通过ptr解引用并修改x的值cout << "x = " << x << endl; // 输出 x = 20````ptr`是一个指向`x`的指针,存储了`x`的内存地址。通过`*ptr`解引用并修改值,实际上是在修改`x`的值。输出`x`的值为20,因为`x`的值已经被修改了。>在**底层实现**上实际是有空间的,因为**引用是按照指针方式来实现的**。```cppint main(){ int a = 10; int& ra = a; ra = 20; int* pa = &a; *pa = 20; return 0;}```我们来看下引用和指针的汇编代码对比得出:==在汇编中**引用**的底层逻辑还是**指针**,经过编译转换成汇编,还是进行指针的操作==![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/be0e447bf42344f2a653f8ca2108e349.png)引用和指针的不同点:1. **引用概念上定义一个变量的别名,指针存储一个变量地址**。2. **引用在定义时必须初始化,指针没有要求**3. **引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体**4. **没有NULL引用,但有NULL指针**5. 在`sizeof`中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(`32`位平台下占`4`个字节)6. **引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小**7. **有多级指针,但是没有多级引用**8. **访问实体方式不同,指针需要显式解引用,引用编译器自己处理**9. **引用比指针使用起来相对更安全**# 🌠常引用从上述代码中,我们可以得出以下关于常引用的结论:1. **常量引用**:```cppconst int a = 10;//int& ra = a; // 该语句编译时会出错,a为常量const int& ra = a;```对于常量对象`a`,我们可以使用常引用`const int& ra = a;`来引用它。这样做可以避免对常量进行修改,直接使用非常引用`int& ra = a;`会在编译时报错,因为不允许对常量进行非常引用。2. **字面量常引用**:```cpp// int& b = 10; // 该语句编译时会出错,b为常量const int& b = 10;```我们可以使用常引用`const int& b = 10;`来引用字面量常量。这样做可以避免创建临时变量, 直接使用非常引用`int& b = 10;`会在编译时报错,因为字面量不能被非常引用。3. **类型不匹配的常引用**:```cppdouble d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不同const int& rd = d;```根据类型不同的变量,如`double d = 12.34;`,我们可以使用常引用`const int& rd = d;`来引用它,直接使用非常引用`int& rd = d;`会在编译时报错,因为类型不匹配。---# 🚩总结![请添加图片描述](https://img-blog.csdnimg.cn/9d50bf7913d741e98c8220ede56599d1.gif)
文章目录
- 请添加图片描述 @[TOC](文章目录)
- 📝引用# 🌠引用概念**引用**不是新定义一个变量,而是给**已存在变量取了一个别名**,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。>定义:**类型&引用变量名(对象名) = 引用实体;**例如以下代码,在变量名前加一个`&`,意思是一个引用类型,`b`是`a`的别名,也就是`a`有了一个外号,但还是`a`本身:```cppint a = 70;int& b = a; //引用:b是a的别名```我们接下来看看引用后的地址是否会发生改变:例如以下例子:```cppint main(){ int a = 70; int& b = a; //引用:b是a的别名 int& c = a; //引用:c是a的别名 c = 80; cout << a << endl; cout << &a << endl; cout << &b << endl; cout << &c << endl; return 0;}```代码运行图:在这个代码中,定义了一个变量`a`为`70`,`int& b = a`; 这里`b`是`a`的引用,给`a`取了一个外号`b`,`int& c = a`; 这里`c`是`a`的引用,又给`a`取了一个外号是`c`,因此我们对`c`还是对`b`进行修改,`a`都会发生改变,这是因为**编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间**。![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/039ecfab596d41869ff8a7c90e52e714.png)>注意:**引用类型必须和引用实体是同种类型的**## 🌉引用特性1. 引用**必须在定义时初始化**:==引用必须在定义时初始化,不能在之后单独为它赋值==。```cppint a = 10; int& ra = a; // 正确,ra初始化为aint& ra; // 错误,引用必须在定义时初始化```2. 一个变量可以有多个引用```cppint a = 10;int& ref1 = a; int& ref2 = a;ref1++; // a的值变为11cout << a << endl; // 输出11ref2--; // a的值变为10cout << a << endl; // 输出10```3. 引用一旦引用一个实体,再不能引用其他实体引用本质上**就是给原变量添加一个别名,它的内存地址就是原变量的地址**。所以对引用赋值或修改,实际上就是修改原变量。而指针不同,指针可以改变指向的对象:**一级指针**可以改变指向,如p可以从指向a改为指向其他变量,**二级指针**可以改变一级指针指向的地址,如`pp`可以改变`p`指向的地址而引用更像一个`const`指针:定义后不能改变指向的对象,就像`const`指针定义后不能改变指向但可以通过这个“`const`指针”来修改原对象,就像通过`const`指针可以修改原对象```cppint a = 10;int b = 20;int& ref = a; ref = b; // 错误!引用ref已经引用a,不能再引用bcout << ref << endl; // 输出10,ref依然引用a```如图:ref引用了a,这里的值发生改变是因为b赋值给了ref![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/09c2a3f2dc534036b45f7eef60d8744d.png)# 🌠使用场景## 🌉做参数(传值与传地址)当引用用来做参数时将会对代码进行大大的优化,并且具有可读性,如:当我们看了很多遍的交换了两个数的函数:```cppvoid Swap(int* a, int* b){ int tmp = *a; *a = *b; *b = tmp;}int main(){ int ra = 88; int rb = 99; Swap(&ra, &rb); return 0;}```形参是实参的一份临时拷贝,所以如果想交换需要,传地址,不能传值。```cppvoid Swap(int& a, int& b){ int tmp = a; a = b; b = tmp;}int main(){ int ra = 88; int rb = 99; Swap(ra, rb); return 0;}````a`和`b`分别是`ra`和`rb`的别名,当你调换`a`和`b`的纸时,其实是修改了`ra`和`rb`的地址的值,这样的好处就是,当你看代码时,引用`a`和`b`给人一种感觉,就是操作`ra`和`rb`本身。这隐藏了底层是通过地址操作原变量`ra`和`rb`的实现细节。从使用者的角度看,代码读起来就像直接交换`ra`和`rb`,而不是通过**复杂的地址操作实现**。>这里使用了引用挺好的,不用担心指针的解引用,地址相关操作,但是,前面我们知道,引用一旦指向一个实体,就无法改变指向,例如,**有关链表操作**,当我们要删除一个节点,是不是要改变前面节点的指针,让他指向后面节点,而引用恰恰不能改变,因此,**引用也不是完全替代指针的**![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d4134e1aec26472892b5035f8f74c7fe.png)回归正题,这里还有一个小注意点:作用域的不同,因此,在Swap函数里,取别的名字都可以,任由发挥,结果都相同。```cppvoid Swap(int& x, int& x){ int tmp = x; x = y; y = tmp;}int main(){ int ra = 88; int rb = 99; Swap(ra, rb); return 0;}```## 🌉传值、传引用效率比较**以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。**```cpp#include <time.h>struct A { int a[10000]; };void TestFunc1(A a) {}void TestFunc2(A& a) {}void TestRefAndValue(){ A a; // 以值作为函数参数 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 以引用作为函数参数 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分别计算两个函数运行结束后的时间 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;}int main(){ TestRefAndValue(); return 0;}```**按值传递(TestFunc1):**调`用`TestFunc1(a)`时,会将`a`进行拷贝,生成一个临时对象**a_copy**。**a_copy**作为参数传递给TestFunc1`。`TestFunc1`内部操作的实际上是`a_copy`,对`a_copy`的修改不会影响实参`a`。`TestFunc1`返回时,临时对象`a_copy`会被销毁。`TestFunc1`以值方式传递结构体`A`作为参数。这会导致每次调用都会对A进行值拷贝,对于一个包含`10000`个`int`成员的大结构体,拷贝开销很大。**按引用传递(TestFunc2):**调用`TestFunc2(a)`时,不会进行值拷贝,直接传递`a`的引用。`TestFunc2`内部操作的仍然是实参`a`本身。`TestFunc2`返回时,不需要销毁任何对象。TestFunc2以引用方式传递A。这种方式下,函数内直接操作的就是实参a本身,不会有任何拷贝开销。>总结:>`TestFunc1`值传递,效率低是因为值拷贝开销大`TestFunc2`引用传递,效率高是因为避免了值拷贝,直接操作的就是实参`a`本身![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/cea37fd6819d40059d37965342ee3719.png)>通过上述代码的比较,**发现传值和指针在作为传参以及返回值类型上效率相差很大**。# 🌠引用做返回值```cppint& Count(){static int n = 0;n++;// ...return n;}```我们先看看下面代码会输出什么结果?```cppint& Add(int a, int b){int c = a + b;return c;}int main(){int& ret = Add(1, 2);Add(3, 4);cout << "Add(1, 2) is :"<< ret <<endl;return 0;}```在Vs编译运行图:结果是**7**,真的是正确吗?![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/fe90c7c1ead24f92b1fd7df0a2186064.png)>问题分析:>**如果函数返回时,返回的对象已经超出了函数作用域(即已经被销毁),那么不能返回该对象的引用,必须返回值。**>在第一个示例中:```cppint& Add(int a, int b){int c = a + b;return c;}```>这里函数返回了局部变量`c`的引用,但`c`在函数返回后就已经被销毁了,所以这是一个**未定义行为**,输出结果是**不确定**的。>而在第二个示例中:```cppint& Add(int a, int b){int c = a + b;return c;}int main(){int& ret = Add(1, 2);Add(3, 4);cout << "Add(1, 2) is :"<< ret <<endl;return 0;}```这里同样是返回了局部变量`c`的引用,但是在`main`函数中又调用了一次`Add`函数,这时第一次调用返回的引用`ret`已经指向了一个==不存在的对象==,所以输出结果==也是未定义==的。>函数返回引用时必须确保返回的对象在调用者作用域内仍然存在,否则就会产生未定义行为。这是`C++`中函数返回引用需要特别注意的地方。![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/921cb60c97c3413da342531e41f3a314.png)答案思考:在`Visual Studio`上运行这段代码,输出结果是:```Add(1, 2) is :7```这个结果确实是**未定义**行为,但在某些情况下可能会输出`7`。之所以会出现这种情况,是因为**Visual Studio的编译器在处理这种未定义行为时可能会做一些特殊的优化或处理**,导致在某些环境下能够得到一个**看似合理**的结果。但这种行为是**不可靠**的,因为它依赖于具体的编译器实现细节。在不同的编译器或环境下,输出可能会完全不同。**正确的做法**:是要么返回值,要么返回一个在调用者作用域内仍然存在的对象的引用。这样可以确保代码的行为是可预测和可移植的。## 🌉引用和指针的区别1. **语法概念**:- **引用**是变量的别名,没有独立的存储空间,而是和其引用的实体共用同一块内存空间。- **指针**是一个独立的变量,存储了另一个变量的内存地址。2. **声明语法**:- 引用使用`&`符号声明,如`int& ref = x;`- 指针使用`*`符号声明,如`int* ptr = &x;`3. **操作方式**:- 引用直接访问和操作其引用的实体,如`ref = 10;`- 指针需要先解引用(`*`)才能访问其指向的实体,如`*ptr = 10;`4. **Null值**:- 引用不能为空(Null),必须在声明时初始化为一个有效的实体。- 指针可以为空(Null),指向空地址`0x0`。让我们看看例子来说明引用和指针的区别:假设我们有一个整型变量`x`,值为10。**使用引用:**```cppint x = 10;int& ref = x; // 声明引用ref,它是x的别名ref = 20; // 通过ref修改x的值cout << "x = " << x << endl; // 输出 x = 20````ref`是`x`的引用,它们共享同一块内存空间。通过`ref`修改值,实际上是在修改`x`的值。 输出`x`的值为20,因为`x`的值已经被修改了。**使用指针:**```cppint x = 10;int* ptr = &x; // 声明指针ptr,存储x的地址*ptr = 20; // 通过ptr解引用并修改x的值cout << "x = " << x << endl; // 输出 x = 20````ptr`是一个指向`x`的指针,存储了`x`的内存地址。通过`*ptr`解引用并修改值,实际上是在修改`x`的值。输出`x`的值为20,因为`x`的值已经被修改了。>在**底层实现**上实际是有空间的,因为**引用是按照指针方式来实现的**。```cppint main(){ int a = 10; int& ra = a; ra = 20; int* pa = &a; *pa = 20; return 0;}```我们来看下引用和指针的汇编代码对比得出:==在汇编中**引用**的底层逻辑还是**指针**,经过编译转换成汇编,还是进行指针的操作==![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/be0e447bf42344f2a653f8ca2108e349.png)引用和指针的不同点:1. **引用概念上定义一个变量的别名,指针存储一个变量地址**。2. **引用在定义时必须初始化,指针没有要求**3. **引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体**4. **没有NULL引用,但有NULL指针**5. 在`sizeof`中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(`32`位平台下占`4`个字节)6. **引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小**7. **有多级指针,但是没有多级引用**8. **访问实体方式不同,指针需要显式解引用,引用编译器自己处理**9. **引用比指针使用起来相对更安全**# 🌠常引用从上述代码中,我们可以得出以下关于常引用的结论:1. **常量引用**:```cppconst int a = 10;//int& ra = a; // 该语句编译时会出错,a为常量const int& ra = a;```对于常量对象`a`,我们可以使用常引用`const int& ra = a;`来引用它。这样做可以避免对常量进行修改,直接使用非常引用`int& ra = a;`会在编译时报错,因为不允许对常量进行非常引用。2. **字面量常引用**:```cpp// int& b = 10; // 该语句编译时会出错,b为常量const int& b = 10;```我们可以使用常引用`const int& b = 10;`来引用字面量常量。这样做可以避免创建临时变量, 直接使用非常引用`int& b = 10;`会在编译时报错,因为字面量不能被非常引用。3. **类型不匹配的常引用**:```cppdouble d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不同const int& rd = d;```根据类型不同的变量,如`double d = 12.34;`,我们可以使用常引用`const int& rd = d;`来引用它,直接使用非常引用`int& rd = d;`会在编译时报错,因为类型不匹配。---# 🚩总结![请添加图片描述](https://img-blog.csdnimg.cn/9d50bf7913d741e98c8220ede56599d1.gif)
📝引用# 🌠引用概念引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。>定义:类型&引用变量名(对象名) = 引用实体;例如以下代码,在变量名前加一个&
,意思是一个引用类型,b
是a
的别名,也就是a
有了一个外号,但还是a
本身:cppint a = 70;int& b = a; //引用:b是a的别名
我们接下来看看引用后的地址是否会发生改变:例如以下例子:cppint main(){ int a = 70; int& b = a; //引用:b是a的别名 int& c = a; //引用:c是a的别名 c = 80; cout << a << endl; cout << &a << endl; cout << &b << endl; cout << &c << endl; return 0;}
代码运行图:在这个代码中,定义了一个变量a
为70
,int& b = a
; 这里b
是a
的引用,给a
取了一个外号b
,int& c = a
; 这里c
是a
的引用,又给a
取了一个外号是c
,因此我们对c
还是对b
进行修改,a
都会发生改变,这是因为编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。>注意:引用类型必须和引用实体是同种类型的## 🌉引用特性1. 引用必须在定义时初始化:引用必须在定义时初始化,不能在之后单独为它赋值。cppint a = 10; int& ra = a; // 正确,ra初始化为aint& ra; // 错误,引用必须在定义时初始化
2. 一个变量可以有多个引用cppint a = 10;int& ref1 = a; int& ref2 = a;ref1++; // a的值变为11cout << a << endl; // 输出11ref2--; // a的值变为10cout << a << endl; // 输出10
3. 引用一旦引用一个实体,再不能引用其他实体引用本质上就是给原变量添加一个别名,它的内存地址就是原变量的地址。所以对引用赋值或修改,实际上就是修改原变量。而指针不同,指针可以改变指向的对象:一级指针可以改变指向,如p可以从指向a改为指向其他变量,二级指针可以改变一级指针指向的地址,如pp
可以改变p
指向的地址而引用更像一个const
指针:定义后不能改变指向的对象,就像const
指针定义后不能改变指向但可以通过这个“const
指针”来修改原对象,就像通过const
指针可以修改原对象cppint a = 10;int b = 20;int& ref = a; ref = b; // 错误!引用ref已经引用a,不能再引用bcout << ref << endl; // 输出10,ref依然引用a
如图:ref引用了a,这里的值发生改变是因为b赋值给了ref# 🌠使用场景## 🌉做参数(传值与传地址)当引用用来做参数时将会对代码进行大大的优化,并且具有可读性,如:当我们看了很多遍的交换了两个数的函数:cppvoid Swap(int* a, int* b){ int tmp = *a; *a = *b; *b = tmp;}int main(){ int ra = 88; int rb = 99; Swap(&ra, &rb); return 0;}
形参是实参的一份临时拷贝,所以如果想交换需要,传地址,不能传值。cppvoid Swap(int& a, int& b){ int tmp = a; a = b; b = tmp;}int main(){ int ra = 88; int rb = 99; Swap(ra, rb); return 0;}````a`和`b`分别是`ra`和`rb`的别名,当你调换`a`和`b`的纸时,其实是修改了`ra`和`rb`的地址的值,这样的好处就是,当你看代码时,引用`a`和`b`给人一种感觉,就是操作`ra`和`rb`本身。这隐藏了底层是通过地址操作原变量`ra`和`rb`的实现细节。从使用者的角度看,代码读起来就像直接交换`ra`和`rb`,而不是通过**复杂的地址操作实现**。>这里使用了引用挺好的,不用担心指针的解引用,地址相关操作,但是,前面我们知道,引用一旦指向一个实体,就无法改变指向,例如,**有关链表操作**,当我们要删除一个节点,是不是要改变前面节点的指针,让他指向后面节点,而引用恰恰不能改变,因此,**引用也不是完全替代指针的**![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d4134e1aec26472892b5035f8f74c7fe.png)回归正题,这里还有一个小注意点:作用域的不同,因此,在Swap函数里,取别的名字都可以,任由发挥,结果都相同。
cppvoid Swap(int& x, int& x){ int tmp = x; x = y; y = tmp;}int main(){ int ra = 88; int rb = 99; Swap(ra, rb); return 0;}## 🌉传值、传引用效率比较**以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。**
cpp#include <time.h>struct A { int a[10000]; };void TestFunc1(A a) {}void TestFunc2(A& a) {}void TestRefAndValue(){ A a; // 以值作为函数参数 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 以引用作为函数参数 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分别计算两个函数运行结束后的时间 cout << “TestFunc1(A)-time:” << end1 - begin1 << endl; cout << “TestFunc2(A&)-time:” << end2 - begin2 << endl;}int main(){ TestRefAndValue(); return 0;}**按值传递(TestFunc1):**调`用`TestFunc1(a)`时,会将`a`进行拷贝,生成一个临时对象**a_copy**。**a_copy**作为参数传递给TestFunc1`。`TestFunc1`内部操作的实际上是`a_copy`,对`a_copy`的修改不会影响实参`a`。`TestFunc1`返回时,临时对象`a_copy`会被销毁。`TestFunc1`以值方式传递结构体`A`作为参数。这会导致每次调用都会对A进行值拷贝,对于一个包含`10000`个`int`成员的大结构体,拷贝开销很大。**按引用传递(TestFunc2):**调用`TestFunc2(a)`时,不会进行值拷贝,直接传递`a`的引用。`TestFunc2`内部操作的仍然是实参`a`本身。`TestFunc2`返回时,不需要销毁任何对象。TestFunc2以引用方式传递A。这种方式下,函数内直接操作的就是实参a本身,不会有任何拷贝开销。>总结:>`TestFunc1`值传递,效率低是因为值拷贝开销大`TestFunc2`引用传递,效率高是因为避免了值拷贝,直接操作的就是实参`a`本身![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/cea37fd6819d40059d37965342ee3719.png)>通过上述代码的比较,**发现传值和指针在作为传参以及返回值类型上效率相差很大**。# 🌠引用做返回值
cppint& Count(){static int n = 0;n++;// …return n;}我们先看看下面代码会输出什么结果?
cppint& Add(int a, int b){int c = a + b;return c;}int main(){int& ret = Add(1, 2);Add(3, 4);cout << “Add(1, 2) is :”<< ret <<endl;return 0;}在Vs编译运行图:结果是**7**,真的是正确吗?![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/fe90c7c1ead24f92b1fd7df0a2186064.png)>问题分析:>**如果函数返回时,返回的对象已经超出了函数作用域(即已经被销毁),那么不能返回该对象的引用,必须返回值。**>在第一个示例中:
cppint& Add(int a, int b){int c = a + b;return c;}>这里函数返回了局部变量`c`的引用,但`c`在函数返回后就已经被销毁了,所以这是一个**未定义行为**,输出结果是**不确定**的。>而在第二个示例中:
cppint& Add(int a, int b){int c = a + b;return c;}int main(){int& ret = Add(1, 2);Add(3, 4);cout << “Add(1, 2) is :”<< ret <<endl;return 0;}这里同样是返回了局部变量`c`的引用,但是在`main`函数中又调用了一次`Add`函数,这时第一次调用返回的引用`ret`已经指向了一个==不存在的对象==,所以输出结果==也是未定义==的。>函数返回引用时必须确保返回的对象在调用者作用域内仍然存在,否则就会产生未定义行为。这是`C++`中函数返回引用需要特别注意的地方。![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/921cb60c97c3413da342531e41f3a314.png)答案思考:在`Visual Studio`上运行这段代码,输出结果是:
Add(1, 2) is :7这个结果确实是**未定义**行为,但在某些情况下可能会输出`7`。之所以会出现这种情况,是因为**Visual Studio的编译器在处理这种未定义行为时可能会做一些特殊的优化或处理**,导致在某些环境下能够得到一个**看似合理**的结果。但这种行为是**不可靠**的,因为它依赖于具体的编译器实现细节。在不同的编译器或环境下,输出可能会完全不同。**正确的做法**:是要么返回值,要么返回一个在调用者作用域内仍然存在的对象的引用。这样可以确保代码的行为是可预测和可移植的。## 🌉引用和指针的区别1. **语法概念**:- **引用**是变量的别名,没有独立的存储空间,而是和其引用的实体共用同一块内存空间。- **指针**是一个独立的变量,存储了另一个变量的内存地址。2. **声明语法**:- 引用使用`&`符号声明,如`int& ref = x;`- 指针使用`*`符号声明,如`int* ptr = &x;`3. **操作方式**:- 引用直接访问和操作其引用的实体,如`ref = 10;`- 指针需要先解引用(`*`)才能访问其指向的实体,如`*ptr = 10;`4. **Null值**:- 引用不能为空(Null),必须在声明时初始化为一个有效的实体。- 指针可以为空(Null),指向空地址`0x0`。让我们看看例子来说明引用和指针的区别:假设我们有一个整型变量`x`,值为10。**使用引用:**
cppint x = 10;int& ref = x; // 声明引用ref,它是x的别名ref = 20; // 通过ref修改x的值cout << "x = " << x << endl; // 输出 x = 20ref`是`x`的引用,它们共享同一块内存空间。通过`ref`修改值,实际上是在修改`x`的值。 输出`x`的值为20,因为`x`的值已经被修改了。**使用指针:**```cppint x = 10;int* ptr = &x; // 声明指针ptr,存储x的地址*ptr = 20; // 通过ptr解引用并修改x的值cout << "x = " << x << endl; // 输出 x = 20
ptr是一个指向
x的指针,存储了
x的内存地址。通过
*ptr解引用并修改值,实际上是在修改
x的值。输出
x的值为20,因为
x的值已经被修改了。>在**底层实现**上实际是有空间的,因为**引用是按照指针方式来实现的**。```cppint main(){ int a = 10; int& ra = a; ra = 20; int* pa = &a; *pa = 20; return 0;}```我们来看下引用和指针的汇编代码对比得出:==在汇编中**引用**的底层逻辑还是**指针**,经过编译转换成汇编,还是进行指针的操作==![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/be0e447bf42344f2a653f8ca2108e349.png)引用和指针的不同点:1. **引用概念上定义一个变量的别名,指针存储一个变量地址**。2. **引用在定义时必须初始化,指针没有要求**3. **引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体**4. **没有NULL引用,但有NULL指针**5. 在
sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(
32位平台下占
4个字节)6. **引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小**7. **有多级指针,但是没有多级引用**8. **访问实体方式不同,指针需要显式解引用,引用编译器自己处理**9. **引用比指针使用起来相对更安全**# 🌠常引用从上述代码中,我们可以得出以下关于常引用的结论:1. **常量引用**:```cppconst int a = 10;//int& ra = a; // 该语句编译时会出错,a为常量const int& ra = a;```对于常量对象
a,我们可以使用常引用
const int& ra = a;来引用它。这样做可以避免对常量进行修改,直接使用非常引用
int& ra = a;会在编译时报错,因为不允许对常量进行非常引用。2. **字面量常引用**:```cpp// int& b = 10; // 该语句编译时会出错,b为常量const int& b = 10;```我们可以使用常引用
const int& b = 10;来引用字面量常量。这样做可以避免创建临时变量, 直接使用非常引用
int& b = 10;会在编译时报错,因为字面量不能被非常引用。3. **类型不匹配的常引用**:```cppdouble d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不同const int& rd = d;```根据类型不同的变量,如
double d = 12.34;,我们可以使用常引用
const int& rd = d;来引用它,直接使用非常引用
int& rd = d;`会在编译时报错,因为类型不匹配。—# 🚩总结
相关文章:
的C++奇迹之旅:值和引用的本质效率与性能比较
文章目录 请添加图片描述 [TOC](文章目录) 📝引用# 🌠引用概念**引用**不是新定义一个变量,而是给**已存在变量取了一个别名**,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。>定义&#…...
【C++】vector问题解决(非法的间接寻址,迭代器失效 , memcpy拷贝问题)
送给大家一句话: 世界在旋转,我们跌跌撞撞前进,这就够了 —— 阿贝尔 加缪 vector问题解决 1 前言2 迭代器区间拷贝3 迭代器失效问题4 memcpy拷贝问题 1 前言 我们之前实现了手搓vector,但是当时依然有些问题没有解决ÿ…...
风控系统之普通规则条件,使用LiteFlow实现
个人博客:无奈何杨(wnhyang) 个人语雀:wnhyang 共享语雀:在线知识共享 Github:wnhyang - Overview 提要 参考:智能风控筑基手册:全面了解风控决策引擎 前面有可配置输入参数的接…...
在一套Dockerfile中完成编译和运行环境部署
大纲 解释型语言编译环境解释环境编译型语言编译环境运行环境 方法编译环境安装系统安装编译依赖下载代码特殊处理(可以忽略)编译准备(可以忽略)编译打包依赖(编译结果) 运行环境安装操作系统安装运行时依赖…...
ubuntu系统里克隆github代码到本地,提示fatal: unable to connect to github.com的解决方案
打开命令行终端生成一个新的SSH密钥对。如果你还没有SSH密钥或者想创建一个新的,可以使用以下命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com"当系统提示你“Enter a file in which to save the key”,时,…...
常见docker使用命令
#搭建镜像 “”" sudo docker build -t es_refresh:V1.20230303 . “”" #启动容器 “”" docker run -d --namepara_classify -v /etc/localtime:/etc/localtime -v /data/chenhw/multi_label_classification:/edb2vec -p 8066:8066 --gpus ‘“device0”’…...
Ubuntu系统中设置中文输入法的教程
1、Ubuntu介绍: (https://cn.ubuntu.com/) (Ubuntu | 全球领先的用于个人电脑、平板及手机的操作系统) Ubuntu是一款基于Debian的开源Linux操作系统,由英国Canonical公司赞助支持的全球性社区共同开发。U…...
练习14 Web [极客大挑战 2019]Upload
phtml格式绕过,burp修改content-type绕过,常见的文件上传存放目录名 题目就叫upload,打开靶机 直接上传一个图片格式的一句话木马,返回如下: 提交练习5和9中的两种可以执行图片格式php代码的文件,修改con…...
3.6k star, 免费开源跨平台的数据库管理工具 dbgate
3.6k star, 免费开源跨平台的数据库管理工具 dbgate 分类 开源分享 项目名: dbgate -- 免费开源跨平台的数据库管理工具 Github 开源地址: GitHub - dbgate/dbgate: Database manager for MySQL, PostgreSQL, SQL Server, MongoDB, SQLite and others. Runs under…...
2024.3.2力扣每日一题——受限条件下可到达节点的数目
2024.3.2 题目来源我的题解方法一 深度优先搜索方法二 并查集 题目来源 力扣每日一题;题序:2368 我的题解 方法一 深度优先搜索 使用深度优先搜索实现,在搜索过程中根据restricted进行截停。 时间复杂度:O(n) 空间复杂度&#…...
在云端遇见雨云:一位服务器寻觅者的指南
引言:寻觅一座云端归宿 当我踏入数字世界的边缘,带着对网络的探索与期待,我迫切需要一座安全可靠的数字栖息地。云计算技术正如一场魔法般的变革,而在这片广袤的云端中,雨云就像是一位友善的向导,引领我穿越…...
Pygame基础10-物理模拟
PyMunk PyMunk是一个模拟物理的库。 注意,PyMunk只是进行物理模拟,不包含可视化的功能。如果需要可视化,可使用pygame等库。 可用pip安装pymunk pip install pymunk pymunk中的概念: space: 物理空间。 包含gravity 模…...
蓝桥杯 --- 日期问题模板
目录 1.如何判断闰年 2.如何遍历当前年份的每一天 3.如果想要输出某一年某一天到某一年某一天之间一共有多少天。 4.精确到具体周几到周几的问题分析 5.如何直接通过一层for循环枚举年月日 习题: 蓝桥杯竞赛特别喜欢考日期问题,今天给大家分享一下…...
Java 处理Mysql获取树形的数据
Mysql数据: 代码如下: Entity: Data Accessors(chain true) public class Region {private BigInteger id;//名称private String name;//父idprivate BigInteger parentId;private List<Region> children;private Integer createTim…...
前端三剑客 —— CSS ( 坐标问题 、定位问题和图片居中 )
前期内容回顾: 1.常见样式 text-shadow x轴 y轴 阴影的模糊程度 阴影的颜色 box-shadow border-radio 实现圆角 margin 内边距 padding 外边距 background 2.特殊样式 媒体查询:media 自定义字体:font-face { font-family:自定义名称&#…...
向量数据库 | AI时代的航道灯塔
向量数据库 | AI时代的航道灯塔 什么是向量检索服务拍照搜商品 你使用过向量数据库吗?使用体验?为什么向量数据库能借由大模型引起众多关注向量数据库在当前AI热潮中是昙花一现,还是未来AI时代的航道灯塔? 今天的话题主要是讨论向…...
Linux中的conntrack命令深入解析
在Linux网络管理和监控领域,conntrack命令是一个强大的工具,它提供了对netfilter连接跟踪系统的直接访问🔍。这篇文章将深入探讨conntrack的由来、底层原理、参数意义,以及其常见用法,并对返回结果的每个字段进行详细解…...
反截屏控制技术如何防止信息通过手机拍照泄漏?
反截屏控制技术为企业数据安全提供了重要的防护措施。通过以下几点,有效阻止了信息通过拍照等方式的泄漏: 反截屏控制开启,用户启动截屏操作时,允许非涉密内容截屏操作,但所有涉密内容窗口会自动隐藏,防止涉…...
0.k8s简介
目录 k8s是什么 k8s不是什么 云原生 微服务 整体式架构与微服务架构 微服务的特性 微服务的优势 k8s是什么 Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快…...
VScode 集成终端设置默认打开当前文件夹 mac系统
一.快捷键设置 搜索 openInIntegratedTerminal 如图: 二.设置cmd 默认打开位置 点击设置 搜索 ntegrated:cwd 如下图: 三.查看ip 快捷指令: ipconfig getifaddr en0...
HDLbits 刷题 -- Alwaysblock2
学习: For hardware synthesis, there are two types of always blocks that are relevant: Combinational: always (*)Clocked: always (posedge clk) Clocked always blocks create a blob of combinational logic just like combinational always blocks, but…...
一、Docker部署GitLab(详细步骤)
Docker部署GitLab(详细步骤) 一、拉取镜像二、启动容器三、修改配置四、修改密码五、浏览器访问 一、拉取镜像 docker安装教程:https://qingsi.blog.csdn.net/article/details/131270071 docker pull gitlab/gitlab-ce:latest二、启动容器 …...
Vue3 Ajax(axios)
Vue 版本推荐使用 axios 来完成 ajax 请求。 安装方法 使用 cdn: <script src"https://unpkg.com/axios/dist/axios.min.js"></script> 使用 npm: $ npm install axios GET 方法 我们可以简单的读取 JSON 数据: const app {data() {r…...
正则表达式引擎库汇合
1.总览表格 一些正则表达式库的对比 index库名编程语言说明代码示例编译指令1Posix正则C语言是C标准库中用于编译POSIX风格的正则表达式库 posix-re.cgcc posix-re.c 2PCRE库C语言提供类似Perl语言的一个正则表达式引擎库。 一般系统上对应/usr/lib64/libpcre.so这个库文件&am…...
eBay买家号注册下单容易死号?是什么原因导致?
随着电子商务的迅猛发展,跨境电商平台eBay日益成为众多消费者和商家的首选。然而,自去年下半年以来,eBay推出的新规则给买家号的注册带来了前所未有的挑战。许多新用户反映,在注册eBay买家号后,往往遭遇刚注册就被冻结…...
【Linux】-进程知识铺垫①计算机硬件的组织:冯诺依曼体系结构详细解读②关于操作系统对软硬件及用户的意义
目录 编辑 1.关于计算机的体系结构 1.1 冯诺依曼体系结构的诞生 2.冯诺依曼体系结构 2.1 cpu:运算器:更多的是让cpu具有特殊的数据计算功能: 2.2 控制器 2.3输入设备 2.4输出设备 3.计算机各个硬件设备之间的关系 4.内存与计算机效率 5.关于为什么总说…...
让ECC升级S/4HANA一步到位的“全面升级方案包”
SAP最新一代商务套件S/4HANA比ECC系统拥有更高性能的存储数据库HANA、更个性化的Fiori用户界面设计系统,能够大大提升系统效率,带来便捷、高效、良好的用户体验。但企业原先使用的ECC系统里面保存了积累多年的关键流程和数据,让企业面对系统升…...
AutoGluon
官网 amazon (base) PS C:\Users\gg葱> conda env list # conda environments: # pytorch1 C:\Users\gg葱\.conda\envs\pytorch1 base * D:\ANCDD:\Documents\LMm\env\pytorch2(base) PS C:\Users\gg葱> conda create --prefixD:/Doc…...
【网站项目】少儿编程管理系统
🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板ÿ…...
基于C语言的数据结构--顺序表讲解及代码函数实现展示
本篇文章是数据结构的开篇,所以我们先来了解一下什么是数据结构。 什么是数据结构 数据结构是由“数据”和“结构”两个词组合而来,自然要以两个词分别去阐述。 首先,什么是数据?数据(data)是事实或观察的结果,是对客…...
wordpress漏洞webshell/登封网站建设公司
网友解答:大家好我是大明 今天就WIN7开机慢的这一问题给大家做一分享 小编总结了两种处理这问题的方法。方法一:开始-运行输入msconfig命令选择启动关闭无用的加载项如下图所示:之后确定重启就OK啦!方法二:通过开始-运行 输入&…...
我的世界做皮肤壁纸网站/厦门百度关键词seo收费
转自: http://rfyiamcool.blog.51cto.com/1030776/1335700/ 简单的说就是,每个硬件设备(如:硬盘、网卡等)都需要和 CPU 有某种形式的通信以便 CPU 及时知道发生了什么事情,这样 CPU 可能就会放下手中的事情去处理应急…...
html是建网站导航栏怎么做/已备案域名购买平台
小编的本本前段时间经常连接不上网络,后来小编打开了远程桌面服务,然后让朋友通过远程操作来帮我看看是不是哪里出现了问题。最后经过小编朋友高超的电脑技术,小编的电脑终于可以连上网络了。那么今天小编就来教你们怎么打开远程桌面服务。远…...
成都网站建设制作公司/广州最新新闻事件
ACL配置IP地址搭建ospf网络配置基本ALC控制访问企业网络中的设备进行通信时,需要保障数据传输的安全可靠和网络的性能稳定,网络安全很重要。 访问控制列表ACL(Access Control List)可以定义一系列不同的规则,设备根据这些规则对数据包进行分类…...
住房和城乡建设委员会官方网站/seo优化是什么
本文档的Copyleft归rosetta所有,使用GPL发布,可以自由拷贝、转载,转载时请保持文档的完整性。参考资料:《Linux设备驱动程序》第3版 LDD3e, LKD3e, 《 Linux per-CPU实现分析 》,linux-2.6.27,irq_balance要…...
做钓鱼网站论坛/长沙网站外包公司
1.爬楼梯 假设你正在爬楼梯,需要n步你才能到达顶部。但每次你只能爬一步或者两步,你能有多少种不同的方法爬到楼顶部? 思考: 登上第1层:1种 登上第2层:2种 登上第3层:123种 登上第4层&…...