在
C/C++
中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或者名字的污染。namespace
关键字的出现就是针对这种问题的。
格式
namespace+命名空间的名字
{
括号里填写命名空间想要设置的内容
}
如:
namespace hp(命名空间的名字)
{
int a = 10;(命名空间的内容)
}
注意:命名空间只能对变量进行初始化,如int a=10,但是不能进行a=10这种赋值操作。
并且命名空间里定义的内容都是属于全局的
注意:当一个项目里出现多个同名的命名空间时,在编译的时候这几个命名空间里的内容会被合并。
第一种:
namespace hp {
int a = 10;
}
//1.在变量前直接声明作用域
int main() {
cout << hp::a << endl;
}
第二种:
//2.使用using关键字使用命名空间里的变量
using hp::a;
int main() {
cout << a << endl;
}
第三种
//3.通过using namespace std导入整个命名空间
using namespace hp;
int main() {
cout << a << endl;
}
注意:这种导入方式需要慎用,因为这样会导致命名空间里的内容都可以随意访问了,可能会使命名空间遭到污染。
编译器在查找数据的时候,会优先在局部进行查找,如果没找到就会去全局进行查找,如果没有指明某个命名空间,编译器不会去命名空间里查找数据。
这里编译器在局部和全局都没有找到a这个变量,因此在编译阶段出现报错。
缺省参数是声明或者定义函数时为函数的参数指定一个默认值,在调用该函数的时候,如果没有指定传递参数则采用该默认值,否则使用传递的参数。
缺省参数又划分为两种
一.全缺省
顾名思义全缺省就是函数的所有参数都设置了默认值
void func(int a = 10, int b = 20, int c = 30) {
cout << a << b << c << endl;
}
int main() {
func();
}
二.半缺省
半缺省也就是一个以上的参数都设置了默认值。
void func(int a , int b = 20, int c = 30) {
cout << a << b << c << endl;
}
int main() {
func(10);
}
这里有一个需要非常注意的点:半缺省必须从左到右,也就是如果左边有一个参数设置了默认值,其后面的参数都必须设置默认值。
如上图的参数b设置了默认值,其后面的参数c也必须设置默认值。
试想如果没有这个规则会发生什么,假设这里的c我们没有设置默认值,这时我想给b传参,但是系统无法判断这个参数是传给b还是传给c的。
所以说半缺省必须遵循从左到右。
什么是函数重载?
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的 形参列表(参数个数 或 类型 或 顺序) 必须不同,常用来处理实现功能类似数据类型不同的问题
共三种情况:
1.函数参数的个数不同
void func(int a,char c) {
cout << "int ,char" << endl;
}
void func(int a) {
cout << "int" << endl;
}
int main() {
int a=10;
char c = 'b';
func(a,c);
}
2.函数参数的顺序不同
void func(int a,char c) {
cout << "int ,char" << endl;
}
void func(char c,int a) {
cout << "char int" << endl;
}
int main() {
int a=10;
char c = 'b';
func(c,a);
}
3.函数参数的类型不同
void func(char c) {
cout << "char" << endl;
}
void func(int a) {
cout << " int" << endl;
}
int main() {
int a=10;
char c = 'b';
func(c);
}
在了解重载的底层原理之前,我们需要知道一些程序运行的原理与概念。
使用
c
语言写了三个文件,分别是func.h
,func.c
,test.c
.
func.h
装两个同名函数的声明,f()
和f(int a)
.
func.c
装了两个函数的实现
test.c
对这两个函数进行调用
回顾一下编译器编译这个程序的过程:
func.h func .c test.c
1.预处理:-> 头文件展开,宏替换,去掉注释,条件编译(有时希望只对代码其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。)
func.i test.i
2.编译: -> 检查语法,生成汇编代码
func.s test.s
3.汇编:-> 汇编代码转换成二进制机器码
func.o test.o
4.链接: -> 将要运行的二进制文件的符号表…内容链接在一起
可执行文件a.out
并且我们还需要知道符号表的概念:
符号表:在计算机科学中,符号表是一种用于语言翻译器(例如编译器和解释器)中的数据结构。在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及函数地址…
在
main
函数中,f()
函数的调用在汇编代码中其实是一句call
指令,call指令会根据地址跳到函数的跳转表,再根据函数的跳转表跳转到函数,然后创建函数栈帧。
函数的地址其实就是函数栈帧中的第一条指令的地址
接着我们返回看test.c
里的代码
在编译的运行过程中,func.h
头文件被展开,但是因为func.h
里只有函数的声明,没有函数的定义,所以main
函数这时候是不知道两个f()
重载函数的地址的,需要在链接阶段去func.c
的符号表里进行寻找。
fun.c的函数表里就存储着两个重载函数的地址。
这时在链接阶段,main
函数就会去fun.c
的符号表里去进行查找f()
函数的地址,问题也就是出在这了,在c
语言中,f()
函数在符号表里的存储是非常简单的,就是直接使用名字进行存储。而main
函数在查找这个函数的地址的时候,也只是使用不加修饰的函数名字进行查找,这时main
函数去符号表查找f()
函数的地址,这时就会发现两个地址,编译器就不知道该选择那个地址了,因此就会发生歧义,这也就是为什么c语言不支持函数重载。
关于c
语言查找函数地址的时候使用的查找方式非常简单,我们可以在下面的操作中进行验证。
结论:C语言编译器直接通过不加修饰的函数名字在符号表里查找函数的地址。
这里有三个文件,分别是
stack.h, stack.c,test.cpp
。
在stack.h
声明了stackinit()
这个函数,但是在stack.c
中没有对其进行定义,随后在test.c
中,我们运行这个函数,这时出现报错。是在编译链接的时候出现了错误,这里标红的地方可以看出,c
语言编译器在查找stackinit
这个函数的地址的时候用了很简单的名称去寻找。
接着我们列出 C++ 在查找函数地址时使用的查找方式进行对比
结论:C++通过修饰以后的函数名字在符号表里进行查找函数的地址。
可以看出也是在编译链接的时候出了错误,但是c++
编译器在查找stackinit
这个函数的地址的时候用了很复杂的名称去寻找
C++
的函数名修饰规则在不同的编译器下不同,我们看下在Linux
下C++
编译器是如何修饰函数名
两个函数分别是
void f();
void f(int a);
Linux下C++编译器的函数名修饰规则
-Z:前缀
1:函数名的长度
f:函数名
i:参数类型的首字母
看到这里大家应该大致能知道为什么C++
支持函数重载了。
func.c
的符号表
所以C++
编译器在链接时查找函数地址时不会产生歧义,因此C++
可以发生函数重载。
1.为何C语言不能发生函数重载
C
语言对于函数名的修饰规则非常简单,重载函数符号表的存储及链接阶段查找函数地址时会产生歧义。
2.为何C++能发生函数重载
C++
对于函数名的修饰规则较复杂,符号表不会产生歧义,并且在查找地址的时候也不会有歧义。