目录
ps:Pexels 上的 Creative Vix 拍摄的图片
🌏命名空间
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
🌙命名空间定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{ }即可,{ }中即为命名空间的成员。
一个命名空间就定义了一个新的作用域(从而区分内外即使是相同的成员名字),命名空间中的所有内容都局限于该命名空间中
举个例子🌰
namespace A
{
int a; //可以是变量,变量可以赋值
int b = 10;
int Add(int a, int b);//可以函数声明或定义
int Add(int a, int b)
{
return a + b;
}
}
同时命名空间的使用还有很多规则❗❗❗
(1)嵌套使用
namespace A { int a; int b = 10; int Add(int a, int b); int Add(int a, int b) { return a + b; } namespace B //命名空间还可以嵌套使用,甚至嵌套的名字也可以相同(但这样无法区分) { int c; //同名嵌套无法合并 int d; int Sub(int left, int right) { return left - right; } } }//namespace A
(2)同名命名空间会被合并
namespace N1 { int Mul(int left, int right) { return left * right; } } namespace N1 //同一层的命名空间会被合并,在同一工程的不同文件中也可 { int a; char b; }
注意:命名空间隔离的是命名冲突,是在不同作用域里面,不影响变量的生命周期(生命周期该是全局还是全局)
🌙命名空间的使用
(1)使用空间名称和作用域限定符 : :
int main() { printf("%d\n", N1::a); //其中::左边为空间作用域,右边为成员 return 0; //左边为空时表全局域,如 ::a }
(2)使用using将命名空间中成员引入
using N1::b; //使用指定区域的指定成员,因不是直接使用命名空间 int main() //故不需要加namespace { printf("%d\n", N::a); printf("%d\n", b); return 0; }
(3)使用using namespace 命名空间名称引入
using namespce N1; int main() { printf("%d\n", N::a); //引入命名空间也可单独指定成员 printf("%d\n", b); Add(10, 20); return 0; }
说明❗❗❗
< iostream >头文件以及std标准命名空间。std标准命名空间包含在头文件中
早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用<iostream>+std的方式。
🌏缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
举个例子🌰
void TestFunc(int a = 0)//函数调用是默认的形参 { cout<<a<<endl; } int main() { TestFunc(); // 没有传参时,使用参数的默认值 TestFunc(10); // 传参时,使用指定的实参 }
分类:全缺省 / 半缺省
全缺省:所有参数都缺省👇
半缺省:部分参数缺省👇
半缺省注意事项❗❗:半缺省参数必须从右往左依次来给出(如上图),不能间隔着给,不然指派不明。
其他注意事项
(1)缺省参数不能在函数声明和定义中同时出现。即缺省参数不能重定义。
举个例子🌰
//a.h 两个不同文件若是有歧义,编译器无法识别 void TestFunc(int a = 10); // a.c(定义) void TestFunc(int a = 20) {}
需要注意的是:一般缺省参数在不同文件中是写在声明中的。这样方可保证其他文件编译期间不出问题。同一文件除外,但也还是不能同时出现在声明和定义中
(2)缺省值必须是常量或者全局变量。
(3)C语言不支持(编译器不支持)
🌏函数重载
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。不同作用域不会构成重载。
举个例子🌰
很好❗❗
其实在某种程度上函数重载也就解释完了
那我们来看看下面两个问题👇
这个函数构成函数重载吗❓
short Add(short left, short right) { return left+right; } int Add(short left, short right) { return left+right; }
这个呢❓
void TestFunc(int a = 10) { cout<<"void TestFunc(int)"<<endl; } void TestFunc(int a) { cout<<"void TestFunc(int)"<<endl; }
答案:不是的
所以对于我们来说,只知道什么是重载是完全不够的,作为一名未来的C++大佬,我们还要知道C++为什么支持重载,C语言为什么不支持重载,以及重载的原理是什么,C和C++能否相互编译?
🌙函数重载底层原理
知识回顾
C和C++会经过编译i链接最终转换称为机器语言,重载主要是链接问题,但是也离不开编译预处理。
对详细编译链接内容可以通过下面文章了解👇(188条消息) C语言《翻译环境和执行环境(预处理文件的生成)》_苏某的橡皮擦的博客-CSDN博客
预处理:头文件的引用,条件编译,宏替换,删除注释等等。
编译:检查语法,转换成汇编代码。
汇编:形成符号表(包含函数名和函数符号地址的映射),转为机器代码。
链接:合并段表,符号表的重定位(得出函数地址)
正文
在Linux下利用objdump -S命令即可查看符号标识👇
这是C++的(指针就用P+类型)
规则:_Z+函数名长度+函数名+类型
这是C语言的
结论❗❗
由此我们可以得知C和C++编译期间的函数符号命名规则是不一样的,C语言符号表中函数名单纯是存储了函数的名称,而C++连同类型及顺序都会存储,所以就回答了我们前面的几个问题,以及为什么是同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同了。
需要注意的是:
C++函数名的标识没有包括返回值的,故单单是返回值不同也是无法构成函数重载的。
🌙C和C++的相互调用
C和C++编译期间的函数符号名的规则不同 (重载)也就导致C和C++之间的文件无法直接调用。
你说直接改后缀名编译不久可以了吗?——但是并不是所有文件你都能直接操作,可能为静态库或者动态库等。
extern "C"(C++调用C)
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern “C”来解决。
//extern "C" int Add(int left, int right);//把需要的函数转换为C语言 extern "C" //或者直接包文件 { #include "../../" //相对路径头文件,..表示返回上一层 } int main() { Add(1,2); return 0; } //或者直接括起来
注意❗❗:
extern "C"是C++的语法,C语言没有extern引用C++的说法
C调用C++
由于C++是包含C的,故改变也是在C++文件中改;
实现步骤
以c++编写的栈静态库去实现C语言文件中的题目
以下以两个文件进行操作test.c stackCPP.lib(c++静态库)
⭐利用条件编译区分C和C++,改动依然在C++文件中
⭐或者这样(两个方式是一样的)
然后链接静态库即可完成调用(只需引用相对路径的头文件,以及链接器附加依赖项和路径即可),详细的库连接就不在这列举说明了。、
说明:extern "C"的底层原因是因为c++的函数重载,因为C++函数重载的底层是C++的函数符号表的名称由函数名长度+函数名+类型,和C是不一样的。于是为了C++和C之间的相互调用,引入了extern "C"
即使前方的路依旧艰险,看不清方向也不要停止你的脚步~
文章到这里就结束了(~ ̄▽ ̄)~,如果喜欢本文的话不妨三连。有问题的话可以评论区留言哦
点赞✔ 收藏✔ 关注✔