| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C++的命名空间、缺省参数、函数重载 及引用 -> 正文阅读 |
|
[C++知识库]C++的命名空间、缺省参数、函数重载 及引用 |
目录 引子??? 自C语言诞生数年后,C++也同样于贝尔实验室问世。不同于C语言面向过程的编程特性,C++同时还可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计。 可以认为,C++是比C语言更高级一些的语言,它解决了很多C语言的不足,使用起来更方便,同时它也兼容C。 命名空间我们知道,C语言中在一个域里不能有重复的变量名、函数名,但是在不同域里面可以。 比如这里全局域里和局部域里都定义了变量a,在main函数里打印a的值,默认先访问的是离得近的局部域里的a,如果要访问全局域里的a,需要在前面加域作用限定符 : :?? 因为 : : 前是空的,默认访问全局。 ?可以得知:编译查找规则是默认先访问局部,再访问全局。 ?这个例子也可以看出,一个域里不能有重名。 C语言中在一个域里不能有重复的变量名、函数名,但是在C++中是支持的。 为什么C++中支持,就是因为命名空间的存在。 当我们在全局域里建立了一个命名空间,并在里面创建了变量、类型、函数等,那么即使命名空间外有重名的也不会造成影响(前提是使用时需要指定是哪里的)。 ?我们定义一个命名空间sak,在里面创建一个变量rand,此时就算和头文件里包含的函数rand重名,也不会有影响,因为namespace里的rand就像被围墙围起来的一样,从外面看不到了。 此外,这里的rand也还是全局变量,别看它在{ }内就觉得它是局部变量,它任然是全局的。 命名空间namespace只是改变了编译查找规则,并没有改变变量的生命周期。 原来编译默认是先找局部再找全局,如果加上指定,指定要找namespace里的变量,那么就改变了编译查找方式,直接去namespace里找,找不到就报错。 像上面,rand前不加任何指定修饰,默认访问的是全局的rand,而非命名空间的rand,也就是头文件stdlib.h 里 函数rand的地址。 要指定访问namespace里的rand,这么写就可以: ?上面就是编译器的两种查找规则。 命名空间里除了变量,还可以定义类型和函数。
并且命名空间还可以嵌套命名空间:
这里打印的时候先访问sak,找sak1,再找sak2,再找变量c,如果有一环找不到就报错。 当然,上面的变量a, b, c都是全局变量。 C++标准库中的函数或对象都是是在命名空间std中定义的 ,所以我们要使用标准函数库中的函数或对象都要使用std来限定。 等于说C++有一个超大的命名空间,里面放的是C++标准库中的函数和对象,而这个命名空间叫std,我们从最简单的打印来看窥探一下。 ?这里std:: 就是指定C++命名空间std,cout其实是个类,我们到后面学到了类和对象、重载、继承再说。 现在简单的记一下这是干什么的就可以了。可以将cout看作是控制台,输出a,b的值,endl则类似换行的意思。下面这样写一样可以证明。 ?其实从取名方式也可以看出,cout 是 console out (控制台)的缩写,endl则是end line(换行) <<在C语言中是左移操作符,C++也代表流输出运算符。 对应的,>>在C语言中是右移操作符,C++也代表流提取运算符。与cin配对使用,从控制台提取数据拿到变量中。 ?cout 和 cin都是自动识别类型的。 如果浮点数需要保留一位小数等,我们尽量还是用C语言的方法,C++ cout也可以做到,但是比较麻烦,因为C++兼容C,因此还是直接写成 %.1f? 比较方便。 如果是字符需要显示ASCLL值,强转即可。 ?有些打印C++比较麻烦,可以用C的方式打印。 我们知道std这个命名空间非常大,里面的东西也非常多,那么std是在一个文件内吗? 显然不是的,可以看到C++是分文件装到std里面的,那不同文件的可以合并吗? 由此得出,不同文件,namespace名字相同,则默认合并。 C++的命名空间std 也是如此合并的。头文件在编译的时候会展开,此时不同文件的std也就合并了。 并且同一个文件中同名的namespace也会合并。 至于嵌套,嵌套的命名空间不是同一级的,不可能合并也不需要考虑,同一级的才有可能合并。 C++的头文件没有规定要加 .h??? 但是#include<iostream>是不加.h的 除非是一些很老的编译器(如VC6.0)是#include<iostream.h>
缺省参数?传参时,只能从左往右依次传参,不能跳过某一个参数。 ? 缺省参数分为全缺省和半缺省,全缺省就像上面的代码一样,所有参数都缺省。 半缺省则是部分参数缺省,并且只能从右往左连续缺省,同样不能跳。 ? ?缺省参数不能在定义和声明中同时出现。如: ?像这里,如果.h?? 和.cpp里面缺省参数不一致,就会报重定义的错误,应该在.h里面(声明)缺省,而不是在.cpp(定义)中。 缺省参数有很多应用,比如上面代码,创建顺序表时,如果事先知道需要多大空间,就可以不扩容而是提起用缺省参数代替,不知道的情况下也直接用缺省参数,会方便很多。 函数重载函数重载就是允许使用同名函数,但是函数的参数不能一样,可以参数个数不同、参数类型不同,也可以参数类型顺序不同。 一、参数个数不同 ?二、参数类型不同 三、参数类型顺序不同 ?那么下面这种是不是参数类型不同的函数重载呢? 显然不是,类型顺序不同是多个不同的类型的顺序不同,int a,double b 和 int b,double a 它们本质是一样的,不属于函数重载 再来看一种: ? ?这种为什么会报错呢? 因为编译器不知道func调用的是哪一个函数,究竟是func( ),还是缺省参数的func(int b = 0,int a = 1)? ,这就存在二义性的问题。 C++如何支持函数重载在了解C++是如何支持函数重载之前,先来看一下C语言为什么不支持函数重载。 我们知道,C语言调用函数时,是去找它的地址的,从汇编角度来看,也就是call函数的地址 ?我们是如何找到函数的地址的?是借助符号表,符号表里面存的是函数名以及它的地址。 C语言不允许函数重载就是因为符号表存的函数名是唯一的,比如存swap函数,符号表里存的函数名就是swap,跟地址。 而C++里面不仅仅存的是函数名,还有参数名的首字母 ?Add(int a,int b)?? 符号表里:_...Addii func(int a,double b,int* c)?? 符号表里:_...funcidpi 这样就区分了同名函数。 返回值不同的函数能不能构成函数重载? 不能!不是因为上面讲的函数名修饰规则,可以在符号表里函数名带上返回值的首字母,这样理论上是可以区分的。但是关键是调用时的二义性。 ?如何区分要调用哪个函数呢?这就造成了二义性。 因此,这才是返回值不同不能构成函数重载的原因。 引用
简单来说,引用可以减少拷贝,提高效率,并且可以操作函数返回值,在很多地方取代了C的繁复指针使用,但是C++没有摒弃指针,而是将两者结合并用。 Java里引用完全取代了指针,但是C++里面并没有完全取代,但也方便了不少。 引用其实就是给变量取别名,这个变量可以是整型变量,也可以是指针变量....... ?经过引用,a现在有了4个名字:a,ra,x,y,它们都代表a,地址也相同,修改其中一个值,其他也都跟着改变。 那引用具体有什么好处呢? 以swap函数为例,C语言写法如下: 我们要将a,b的地址传参给swap,swap以指针形式接收,通过*解引用来改变外部实参a,b 如果不传地址只传值,那么形参不会改变实参。 这里只是简单的一级指针使用,比较好理解,如果是二级、乃至多级指针就很麻烦而且难以理解了,如果用引用,相当于直接改变本身,就比较好理解而且方便了。 引用,可以这么写: ?有的C++书上(乃至教材)在链表那一块为了简化会用到引用,避免使用二级指针。 ?本来是需要二级指针的,为了避免使用二级指针,改成引用的方式。 有的书上还会这么写: ?将结构类型重命名,并将结构指针也重命名为PLTNode 这里其实是typedef struct ListNode? ListNode;??? typedef struct ListNode*? PListNode;两句话 然后下面用引用简化代码。
?这里ra = b,是赋值,不是改变ra的指向。从地址就可以看出。 这就决定了C++引用无法替代指针! 因为不能改引用指向。而JAVA就可以改变指向,所以Java引用可以替代指针。 引用的使用场景一、做参数 平时我们写代码,比如排序里面 void Sort(int* a,int n) 这里的a和n做的是输入型参数,也就是这里的参数是传进来给我们用的。 而引用做参数,可以做输出型参数,比如swap函数里 void Swap(int& x,int& y) x,y 是输出型参数, 在swap函数里面改变以后传到外面使用的。 平时刷力扣,做OJ经常会碰到returnsize,那也是输出型参数,外面需要这个参数。 ?像这里,C里面都是用指针,需要解引用来访问,不太好理解,C++里面可以直接用引用,直接改变值,比较好理解。 二、做返回值 这里就涉及到两种返回方式了。一种是传值返回;一种是引用返回。 传值返回:
传值返回类似传参,都是需要拷贝的,我们从操作系统和建立栈帧的角度来看一下真个过程。 ?如图所示:main函数先建立栈帧,里面有一个ret变量,调用Count函数,建立Count函数的栈帧,如果n是static修饰的话,n就在静态区,n++,此时要返回n,先是创建一个临时变量,将n拷贝给临时变量然后再将临时变量给ret,调用函数结束Count函数栈帧随之销毁,但是n在静态区所以没有跟着销毁。 如果n没有static修饰的话,就在Count栈帧里,随栈帧销毁而销毁。因为是创建了临时变量拷贝数据,所以n销毁了ret也可以拿到n的值(实际上是拿到临时变量拷贝的值)。临时变量如果比较小,就在寄存器里,大的话是提前在main函数栈帧里开辟好空间给它了。 引用返回
?引用返回的不同之处就在于n相当于是ret的别名,直接返回给ret了,不需要拷贝,但是这样有一个问题。 如果n是在静态区还好,不影响返回。但如果n是在Count栈帧里的,那随着栈帧销毁n也就销毁了,ret拿到的就不一定是n的值了。 ?所以我们一定要清楚空间的申请和释放,空间原本就在那里,申请是获得了空间的使用权,释放销毁不是把空间丢掉了,而是还给操作系统使用权。 ?为什么第二三次打印会出现随机值呢? 结合上面所说的,Count栈帧销毁后,n也销毁了,ret是n的别名,会去n所在的地址拿值。 如果原本Count所在的位置没有被覆盖,那么原本n所在的位置也还是它原来的值1. 第二、三次打印,实际上cout也调用了函数,覆盖了原本Count栈帧的位置,所以ret此时取到的就是随机值。 结论:出了函数作用域,返回变量销毁了,不能引用返回,因为引用返回结果是未定义的。 ?? ??????? 出了函数作用域,返回变量存在,才能引用返回。 那么引用返回比传值返回有什么优势呢? 1、引用返回可以减少拷贝,提高效率(小的数据返回可能看不出,但是返回大的结构体等就很明显了) 2、引用返回可以改变返回值。 常引用常引用是指用const修饰的引用。 ?经const修饰的变量b不能直接引用,需要在前面加const。 这是权限大小问题。权限可以平移、可以缩小,但是不能放大。 ?const int& rra = a;? rra加了限制不能++,但是a还是可以++的。 权限缩小 缩小的是自己得到的权限,而不是原本变量的权限。 ?那这里const修饰引用有什么作用呢?
大家看个问题:a = b,改变a对b有没有影响?——没有,因为b是拷贝给a的; int& ra = a; 改变ra对a有影响吗?——有,因为ra是a的别名,不是拷贝。 既然如此:
这里调用func,都可以成功调用,因为x是a, rra, b的拷贝,修改x对实参没有影响。 但如果:???????????????
如果x是引用,那么rra 和 b就不能调用了 。 ?因为修改X就相当于修改了rra 和 b,将权限放大了,所以要在X前面加const修饰。 而我们使用引用作为参数时,一般都会加const修饰,不能修改参数,权限平移或权限缩小都是被接收的,总之不能权限放大。 有人会说,加const修饰就不能修改参数了,那要这个干什么。 在设计这个的时候,就是考虑到实际应用才设计的。const修饰的变量,正是不需要修改所以才const+引用避免误改,我们说引用是为了减少拷贝,提高效率的。 如果需要修改参数,那不加const就行了,比如swap函数。 再来看一个:
引用是可以引用常量的,但需要加const修饰,同理函数中缺省参数也是可以引用常量的。 ?int& rb = b;是不可以的,但是int a = b;? int a = (int)b; 是可以的。 ?实际上,相当于将double类型的b给临时变量,再将临时变量给a,强制类型转化也是一样。 临时变量具有常性,rb引用的是临时变量(double类型),所以不能引用,只能加const才可以。 同理:函数返回也是一样。
引用是否开辟空间,从语法的角度来说是不开辟空间的,而指针是存变量的地址,要开辟空间的。 但是从底层来说,两者都是开辟空间的。 ?观察引用和指针的反汇编代码,可以发现,两者的代码几乎一样,都要开辟空间。 从语法上说,ra是a的别名,不开辟空间;从底层来说,引用是指针来实现的。 引用和指针的区别: 其实引用还有很多小细节,篇幅原因就写到这里了,后续我会继续更新,将所有细节呈现给大家。 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 9:42:25- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |