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

一元夺宝网站建设wordpress作者认证

一元夺宝网站建设,wordpress作者认证,软件开发流程文档模板,wordpress 获取优酷目录 一. 什么是引用 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 %dy %d\n, x, y); //10,20swap(x, y);printf(交换后x %dy %d\n, x, y); //20,10return 0; } 图2.4  演示代码2.4的运行结果至此可以总结出函数的三种调用方法  传值调用。传地址调用。传引用调用。问题void swap(int x, int y)和void swap(int x, int y)能否构成函数重载 答案是可以的。因为其满足构成函数重载的条件之一 参数的类型不同。 但是在调用这两个swap函数时会存在歧义。通过语句swap(x,y)调用无法确定是调用swap(int x, int y)还是swap(intx, int y)。 2、引用做函数的返回值 在演示代码2.5中定义函数int Add(int x, int y)函数返回z的别名。我们希望这个函数能够对xy进行计算。但是显然这段代码是有潜在问题的因为在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次传值调用耗时71ms100000次传引用调用耗时2ms传引用调用的效率远高于传值调用。这是因为传引用调用不用再为形参开辟一块内存空间而为形参开辟空间存在一定的时间消耗。 演示代码3.1 #includeiostream #includetime.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 #includeiostream #includetime.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的运行结果五. 引用和指针的区别 引用是定义一个变量的别名而指针存储一个地址。引用不占用额外的内存空间而指针要占用4bytes或8bytes的内存空间。引用在定义时必须初始化而指针可以不初始化。建议指针在定义时避免不初始化。引用一旦引用了某个实体便不能更改被引用实体而指针可以更改指向。对引用自加即对被引用的实体1指针自加向后偏移一个指针类型的大小bytes。没有多级引用有多级指针。访问实体时引用直接由编译器处理即可指针需要解应用。没有空引用但有空指针NULL。引用相对于指针更加安全。 因为指针存在野指针、空指针等问题造成指针过于灵活所以指针的安全性不如引用。 引用的底层是通过指针来实现的。 引用最大的局限性在于不能更改引用实体因此虽然引用的底层是通过指针实现的但引用不能替代指针来实现链表数据结构。因为链表的操作需要更改指针的指向。
http://www.dnsts.com.cn/news/83583.html

相关文章:

  • 外贸汽车网站制作购物网站的设计
  • 营销型网站托管莱州网站设计
  • 域名注册哪个平台比较好东莞关键词优化排名
  • 国外网站设计 网址成都装修公司前十名
  • 电子商务网站建设内涵东台做网站哪家便宜
  • 济源做网站怎么收费免费app开发工具
  • 郑州企业建站免费咨询广州知名网站建设后台管理便捷
  • 苏州高端网站建设机构福建省建设工程信息网
  • 网站建设费是无形资产吗重庆排名前十的互联网公司
  • 上海专业网站建设网wordpress忘记后台账号
  • 正确建设企业网站福田服务商app软件安装
  • 企业的网站建设公司wordpress后台主题
  • 徐州网站优化推广团购网站建立
  • 汝城县网站建设公司南京外贸网站建设报价
  • 网站样式模板下载丽江网站开发找千素网
  • 创建网站要钱吗石家庄网站建设推广公司电话
  • 公司主页网站开发开发公司副总求职简历
  • 成功的营销网站怎样修改手机网站首页
  • 好网站建设公司选择哪家好360下载
  • 清远市网站建设宝山做手机网站建设
  • 宽屏大气企业网站源码河南网站建设运营域名注册公司
  • 视频网站怎么建设鄂州网站开发
  • 中国行业网站联盟秦皇岛网站制作代理商
  • asp.net mvc5网站开发绵阳网站搜索排名
  • wordpress网站换字体颜色国际形势最新消息
  • 郑州做网站哪家专业建站的步骤有哪些
  • 网站制作新手教程建婚恋网站需要多少钱
  • 常用网站开发技术网站提交链接入口
  • 深圳高端网站定制设计wordpress 守望轩
  • 长沙网站建设+个人广东免费网络推广软件