当前位置: 首页 > news >正文

广州开发区建设和环境保护网站搜狗搜索引擎优化论文

广州开发区建设和环境保护网站,搜狗搜索引擎优化论文,动态网站设计论文1500字,google属于搜索引擎类网站.目录 一. 什么是引用 1.1 引用的概念 1.2 引用的定义 二. 引用的性质和用途 2.1 引用的三大主要性质 2.2 引用的主要应用 三. 引用的效率测试 3.1 传值调用和传引用调用的效率对比 3.2 值返回和引用返回的效率对比 四. 常引用 4.1 权限放大和权限缩小问题 4.2 跨…

目录

一. 什么是引用

1.1 引用的概念

1.2 引用的定义

二. 引用的性质和用途

2.1 引用的三大主要性质

2.2 引用的主要应用 

三. 引用的效率测试

3.1 传值调用和传引用调用的效率对比

3.2 值返回和引用返回的效率对比

四. 常引用 

4.1 权限放大和权限缩小问题

4.2 跨数据类型的引用问题

五. 引用和指针的区别


一. 什么是引用

1.1 引用的概念

引用,通俗的讲,就是给已经存在的变量取一个别名,而不是创建一个新的变量。引用和被引用对象共同使用一块内存空间。

引用就好比一个人的大名和小名,大名和小名都是一个人。再比如,李逵外号黑旋风,叫李逵和黑旋风表示同一个人。

1.2 引用的定义

引用定义的语法格式:类型& 引用的名称 = 被引用实体

如,定义int a = 10,希望再定义一个引用b,来表示整形变量a的别名,语法为:int& b = a。演示代码1.1展示了引用的定义过程,对原变量a和引用b的其中任意一个赋值,都会使a和b的值均发生改变,这是因为a和b共用一块内存空间。

演示代码1.1:

int main()
{int a = 10;int& b = a;  //b是a的引用(别名)printf("a = %d, b = %d\n", a, b);  //10,10a = 20;  //对a赋值,同时改变a和bprintf("a = %d, b = %d\n", a, b);  //20,20b = 30;  //对b赋值,同时改变a和bprintf("a = %d, b = %d\n", a, b);  //30,30return 0;
}
图1.1 演示代码1.1的运行结果

二. 引用的性质和用途

2.1 引用的三大主要性质

1、引用在定义时必须初始化

定义引用时必须给出这个引用的被引用实体是谁,如:int &b; -- 是非法的。

演示代码2.1:

int main()
{int a = 10;int& b;  //报错int& c = a;  //初始化引用return 0;
}
图2.1  演示代码2.1的报错信息

 2、一个变量可以有多个引用

我们可以为一个变量取多个别名。如演示代码2.2所示,给a变量取b、c、d三个别名是可行的。对a、b、c、d中的任意一个赋值,都会使a、b、c、d的值均发生改变。a、b、c、d共用一块内存空间。

演示代码2.2:

int main()
{int a = 10;int& b = a;int& c = a;int& d = a;  //为a取b、c、d三个别名printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d);  //10,10,10,10c = 20;printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d);  //20,20,20,20return 0;
}
图2.2  演示代码2.2的运行结果

 3、一个引用一旦引用了某个实体,就不能再引用其他实体

演示代码2.3中的b = c并不是将b变为变量c的引用,而是将变量c的值赋给b,通过打印b和c的地址,我们可以发现b和c并不共用一块内存空间,而赋值之后,a和b的值都变为了20。

演示代码2.3:

int main()
{int a = 10;int& b = a;int c = 20;b = c;   //将c的值赋给b,而不是让b变为c的引用printf("&b = %p, &c = %p\n", &b, &c);   //b和c的地址不一致printf("a = %d, b = %d\n", a, b);  //a、b都变为了c的值20return 0;
}
图2.3  演示代码2.3的运行结果

正是因为引用一旦引用了某个实体之后就不能再引用其他实体,所以引用无法替代指针来实现链表数据结构。否则就无法实现链表的增、删等操作,链表的增删操作需要改变指针的指向。

2.2 引用的主要应用 

1、引用做函数参数

要写一个swap函数,实现两个整形数据的交换,如果用C语言来写这个函数,就必须使用指针来作为函数的参数,即:void swap(int* px, int* py)。但是,如果使用C++来写,则可以用引用传参来替代指针传参,因为引用和被引用实体共用一块内存空间,引用传参使得函数内部可以控制实参所占用的内存空间,这是,swap可以声明为:void swap(int& rx, int& ry)。

演示代码2.4:

void swap(int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
}int main()
{int x = 10, y = 20;printf("交换前:x = %d,y = %d\n", x, y);  //10,20swap(x, y);printf("交换后:x = %d,y = %d\n", x, y);  //20,10return 0;
}
图2.4  演示代码2.4的运行结果

至此,可以总结出函数的三种调用方法: 

  1. 传值调用。
  2. 传地址调用。
  3. 传引用调用。

 问题:void swap(int x, int y)和void swap(int& x, int& y)能否构成函数重载?

答案是可以的。因为其满足构成函数重载的条件之一 :参数的类型不同。

但是,在调用这两个swap函数时,会存在歧义。通过语句swap(x,y)调用,无法确定是调用swap(int x, int y)还是swap(int&x, int& y)。

2、引用做函数的返回值

在演示代码2.5中,定义函数int& Add(int x, int y),函数返回z的别名。我们希望这个函数能够对x+y进行计算。但是显然,这段代码是有潜在问题的,因为在add函数调用结束后,为add函数创建的栈帧会被销毁,这块栈空间会还给操作系统。此时再使用add函数的返回值,就会造成对内存空间的非法访问,而大部分情况下,编译器不会对非法访问内存报错。

演示代码2.5:

int& add(int x, int y)
{int z = x + y;return z;
}int main()
{int& ret = add(1, 2);printf("ret = %d\n", ret);return 0;
}

对于演示代码2.5的运行结果,可以分为两种情况讨论:

  • 函数栈帧销毁后,编译器不对被销毁的栈空间进行清理,打印函数的返回值,结果依旧为x + y的值。
  • 函数栈帧销毁后,编译器对被销毁的栈空间进行清理,函数的返回值为随机值。

在VS2019 编译环境下,演示代码2.5的运行结果为3,说明VS编译器不会清理被销毁的函数栈帧空间中内容。

图2.5  演示代码2.5的运行结果

既然VS编译器不会对被销毁的函数栈帧进行清理,那么是否在VS编译环境下,可以正常使用演示代码2.5中的add函数呢?答案显然是否定的,这可以从以下两个方面解释:

  • 如果在其他编译环境下进行编译,则被销毁的函数空间可能会被清理,这样会降低代码的可移植性。
  • 即使函数栈帧空间不被清理,但这块空间已经换给了操作系统,如果调用完add函数后再调用其他函数,那么原本为z开辟的空间可能会被覆盖,从而改变ret的值。

如演示代码2.6所示,第一次调用add函数使用ret来接收返回值,第二次调用add函数不接收返回值。但是第二次调用add函数之后,ret的值却变为了30,这是因为第二次调用add函数覆盖了第一次调用时创建的函数栈帧,原来第一次调用存放变量z的内存空间的内容由3变为了30,因此,程序运行的结果为30。这段代码在运行过程中栈帧的创建和销毁情况见图2.7。

演示代码2.6:

int& add(int x, int y)
{int z = x + y;return z;
}int main()
{int& ret = add(1, 2);cout << ret << endl;add(10, 20);cout << ret << endl;return 0;
}
图2.6 演示代码2.6的运行结果
图2.7  两次调用add函数栈帧的开辟和被覆盖情况

总结(什么时候可以用引用返回,什么时候不可以):

  • 如果出了函数作用域,函数返回的对象被销毁了,则不能使用引用类型作为返回值。
  • 如果出了函数作用域,函数的返回对象还没有被销毁(存储返回对象的内存还没有还给操作系统),则可以使用引用作为返回值。

演示代码2.7给出了两种可以使用引用作为返回的情况,一种是以静态变量作为返回对象,另一种是返回对象为调用函数中开辟的一块内存空间中的内容(调用函数中开辟的数组)。

演示代码2.7:

int& func1()
{static int n = 0;++n;return n;
}char& func2(char* str, int i)
{return str[i];
}int main()
{cout << func1() << endl;  //1cout << func1() << endl;  //2char ch[] = "abcdef";for (int i = 0; i < strlen(ch); ++i){func2(ch, i) = '0' + i;}cout << ch << endl;  //012345return 0;
}
图2.8  演示代码2.7的运行结果

思考问题:既然函数完成调用时才会返回,而调用完成时函数栈帧又会被销毁。那么,以值作为函数返回类型时,时如何从函数中接收返回值的呢?

就比如演示代码2.8中的add函数,函数返回值时add函数中的临时变量z的值,在主函数中的ret如何从add函数中接收z值。

演示代码2.8: 

int add(int x, int y)
{int z = x + y;return z;
}int main()
{int ret = add(2, 3);return 0;
}

答案其实很简单,ret并不是直接从add函数栈帧的空间中接收返回值,而是在add函数完成调用、函数栈帧销毁之前,存储一个临时变量用于接收函数的返回值,然后在将临时变量的值赋给ret。

那么,这个临时变量存储在什么位置呢?分两种情况讨论:

  • 如果返回值比较小,则使用寄存器充当临时变量。
  • 如果返回值比较大,则将临时变量放在调用add函数的函数内部,在调用add函数之前在调用add的函数的栈帧中预先开辟一块空间用于存储临时变量。
图2.9  值返回情况下函数返回值被接收的过程

三. 引用的效率测试

3.1 传值调用和传引用调用的效率对比

演示代码3.1分别执行100000次传值调用和100000次传引用调用,每次传值调用传给函数的形参的大小为40000bytes,记录传值调用和传引用调用消耗的时间。

程序运行结果显示,10000次传值调用耗时71ms,100000次传引用调用耗时2ms,传引用调用的效率远高于传值调用。这是因为传引用调用不用再为形参开辟一块内存空间,而为形参开辟空间存在一定的时间消耗。

演示代码3.1:

#include<iostream>
#include<time.h>
using namespace std;//大小为40000bytes的结构体
typedef struct A
{int arr[10000];
}A;void Testvaluefunc(A a) { };   //传值调用测试函数
void TestReffunc(A& a) { };  //传引用调用测试函数void TestRefAndValue1()
{A a;int i = 0;size_t begin1 = clock();  //记录开始传值调用的时间(传值调用100000次)for (i = 0; i < 100000; ++i){Testvaluefunc(a);}size_t end1 = clock();  //记录结束传值调用的时间size_t begin2 = clock();  //记录开始传引用调用的时间(调用100000次)for (i = 0; i < 100000; ++i){TestReffunc(a);}size_t end2 = clock();cout << "传值调用10000次耗费时间:" << end1 - begin1 << endl;cout << "传引用调用10000次耗费时间:" << end2 - begin2 << endl;
}
图3.1  演示代码3.1的运行结果

3.2 值返回和引用返回的效率对比

演示代码3.2分别执行100000次值返回函数和100000次引用返回函数,记录调用值返回函数和调用引用返回函数消耗的时间。程序运行结果表明,调用100000次值返回函数耗时136ms,调用100000次引用返回函数耗时2ms,引用返回的效率远高于值返回。

演示代码3.2:

#include<iostream>
#include<time.h>
using namespace std;typedef struct A
{int arr[10000];
}A;A a;A TestValuefunc2()
{return a;
}A& TestReffunc2()
{return a;
}void TestRefAndValue2()
{int i = 0;size_t begin1 = clock();  //记录开始时间(调用100000次)for (i = 0; i < 100000; ++i){TestValuefunc2();}size_t end1 = clock();  //记录结束时间size_t begin2 = clock();  //记录开始的时间(调用100000次)for (i = 0; i < 100000; ++i){TestReffunc2();}size_t end2 = clock();  //记录结束时间cout << "以值作为返回:" << end1 - begin1 << "ms" << endl;cout << "以引用作为返回:" << end2 - begin2 << "ms" << endl;
}int main()
{TestRefAndValue2();  //引用作为返回和值作为返回的效率测试return 0;
}
图3.2  演示代码3.2的运行结果

四. 常引用 

4.1 权限放大和权限缩小问题

如果int& b = a,而a是整形常量,被const关键字修饰,那么b就不能作为a的别,因为a变量是只读的,而将b定义为int&类型,则表明b是可读可写的类型,b对a存在权限放大问题。

对于int a = 10,使用const int& b = a来表示a的别名是可以编译通过的。因为a为读写类型,而b为只读类型,b相对于a权限缩小,C++允许权限缩小。

总结:C++允许权限缩小,不允许权限放大。

演示代码4.1:

int main()
{//权限放大问题const int a = 10;//int& b = a;   //报错const int& b = a;  //编译通过//权限缩小int c = 10;const int& d = c;   //能够编译通过return 0;
}

4.2 跨数据类型的引用问题

看一个很诡异的问题。在演示代码4.2中,定义一个双精度浮点型数据double d = 1.1,编译程序,出现下面的现象:

  • 将d赋给int型数据i1,编译通过。
  • 用int& i2 = d来作为d的引用(别名),编译报错。
  • 但是,使用const int& i3 = d来作为d的引言,编译通过。

演示代码4.2:

int main()
{double d = 11.11;int i1 = d;  //强转,编译通过//int& i2 = d;   //编译报错const int& i3 = d;  //编译通过printf("&d = %p\n", &d);printf("&i3 = %p\n", &i3);return 0;
}

那么,为什么const int& i3类型的可以作为d的引用,而int& i2却不行?问题出在强制类型转换上。要理解这个问题,首先要清楚强制类型转换的过程,强制类型转换(int i1 = d),并不是将d强转后的数据直接赋给i1,而是先将d强转为int类型数据的值存储在一个临时变量中,然后再将临时变量的值传给i1,详见图4.1。

图4.1 数据强制类型转换的过程

临时变量具有常性,只可读不可改。因此,int& i2 = d就存在权限放大的问题,编译无法通过,而const int& i3 = d不会存在权限放大的问题,可以编译通过。但是,这里的i3就不再是d的别名,而是存储d强转为int类型数据值的临时变量的别名,因此i3和d的地址也就不同。演示代码4.2打印了i3和d的地址,表面他们不同,i3其实并不是d的别名。

提示:一定要弄清楚强转类型转换时临时变量做中间值的问题!

图4.2  演示代码4.2的运行结果

五. 引用和指针的区别

  1. 引用是定义一个变量的别名,而指针存储一个地址。
  2. 引用不占用额外的内存空间,而指针要占用4bytes或8bytes的内存空间。
  3. 引用在定义时必须初始化,而指针可以不初始化。(建议指针在定义时避免不初始化)。
  4. 引用一旦引用了某个实体,便不能更改被引用实体,而指针可以更改指向。
  5. 对引用自加,即对被引用的实体+1,指针自加,向后偏移一个指针类型的大小(bytes)。
  6. 没有多级引用,有多级指针。
  7. 访问实体时,引用直接由编译器处理即可,指针需要解应用。
  8. 没有空引用,但有空指针NULL。
  9. 引用相对于指针更加安全。

因为指针存在野指针、空指针等问题,造成指针过于灵活,所以指针的安全性不如引用。

引用的底层是通过指针来实现的。

引用最大的局限性在于不能更改引用实体,因此虽然引用的底层是通过指针实现的,但引用不能替代指针来实现链表数据结构。因为链表的操作需要更改指针的指向。

http://www.shuangfujiaoyu.com/news/28329.html

相关文章:

  • 兰州新区规划建设局网站如何创建一个app
  • 网站建设流程是这样的 里面有很建站优化公司
  • 郑州做网站优化运营商专业seo整站优化
  • 浙江高端建设网站百度官网下载安装
  • 红色文化网站建设百度推广手机app下载
  • 长春网站排名推广大数据营销系统怎么样
  • 线上企业订单管理系统网站网站搭建源码
  • 黄冈公司做网站建立网站需要多少钱
  • 江西省建设工程安全质量监督管理局网站站长之家查询的网址
  • php网站开发视频教程下载网络推广公司名字大全
  • 给wordpress上锁拼多多标题关键词优化方法
  • 南昌哪个公司做网站好微商软文范例
  • 传销教你做网站网站关键词排名优化
  • wordpress建站seo国际军事形势最新消息
  • 如何提升网站的权重怎么做网站?
  • 网站交易平台怎么注册成都seo达人
  • 贵阳网站开发公司永久免费wap自助建站
  • 广东网站制作多少钱seo关键词布局技巧
  • 做网站回答上海网站推广广告
  • 跨境经验分享保定seo博客
  • 东莞怎么制作网站“跨年”等关键词搜索达年内峰值
  • adobeXD做网站免费发布推广信息的软件
  • 石家庄建设厅网站百度一下你就知道了 官网
  • 网站上传不了图片是什么原因网络营销的分类
  • 怎么挂代理访问网站网站seo搜索引擎优化案例
  • 清丰网站建设费用seo优化包括哪些
  • 网站开发的时间流程西安百度推广开户多少钱
  • 湛江网站模软文素材库
  • wordpress免费淘宝客主题seo引擎搜索
  • 上海的建设网站北京seo外包 靠谱