目录
1.命名空间
1.1命名空间定义
1.2 命名空间使用
2.C++输入&输出
3.缺省参数
3.1 缺省参数概念
3.2 缺省参数分类
3.2.1全缺省参数
3.2.2半缺省参数
4.函数重载
4.1 函数重载概念
4.2名词修饰
4.2.1为什么C++支持函数重载,而C语言不支持函数重载呢?
4.2.2extern “C”
1.命名空间
? ? ? 在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作 用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字 污染,namespace 关键字的出现就是针对这种问题的。
1.1命名空间定义
? ? ? ?定义命名空间,需要使用到namespace 关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名 空间的成员。
[1] 普通的命名空间
namespace N1 // N1为命名空间的名称
{
? ?// 命名空间中的内容,既可以定义变量,也可以定义函数
? ?int a;
? ?int Add(int left, int right)
? {
? ?return left + right;
? }
}
[2]命名空间可以嵌套
namespace N2
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N3
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
[3] 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
namespace N1
{
? ?int Mul(int left, int right)
? {
? ?return left * right;
? }
}
<!--注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。-->
1.2 命名空间使用
命名空间中成员该如何使用呢?比如:
namespace N
{
? ?int a = 10;
? ?int b = 20;
? ?int Add(int left, int right)
? {
? ? ? ?return left + right;
? }
? ?int Sub(int left, int right)
? {
? ? ? ?return left - right;
? }
}
?
int main()
{
? ?printf("%d\n", a); // 该语句编译出错,无法识别a
? ?return 0;
}
命名空间的使用有三种方式:
[1]加命名空间名称及作用域限定符
int main()
{
? ?printf("%d\n", N::a);
? ?return 0;
}
[2]使用using 将命名空间中成员引入
using N::b;
int main()
{
? ?printf("%d\n", N::a);
? ?printf("%d\n", b);
? ?return 0;
}
[3]使用using namespace* 命名空间名称引入
using namespce N;
int main()
{
? ?printf("%d\n", N::a);
? ?printf("%d\n", b);
? ?Add(10, 20);
? ?return 0;
}
2.C++输入&输出
[1]使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空 间。 <!--注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件--> <!--即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文--> <!--件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用<iostream>+std的方式。-->
[2]用C++输入输出更方便,不需增加数据格式控制,比如:整形--%d,字符--%c
#include <iostream>
using namespace std;
int main()
{
? ?int a;
? ?double b;
? ?char c;
? ?cin>>a;
? ?cin>>b>>c;
? ?cout<<a<<endl;
? ?cout<<b<<" "<<c<<endl;
? ?return 0;
}
3.缺省参数
? ? ? C++中函数的参数也可以“配备胎”。
3.1 缺省参数概念
? ? ? ?缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该 默认值,否则使用指定的实参。
void TestFunc(int a = 0)
{
? ?cout<<a<<endl;
}
int main()
{
? ?TestFunc(); // 没有传参时,使用参数的默认值
? ?TestFunc(10); // 传参时,使用指定的实参
}
?
3.2 缺省参数分类
3.2.1全缺省参数
void TestFunc(int a = 10, int b = 20, int c = 30)
{
? ?cout<<"a = "<<a<<endl;
? ?cout<<"b = "<<b<<endl;
? ?cout<<"c = "<<c<<endl;
}
3.2.2半缺省参数
void TestFunc(int a, int b = 10, int c = 20)
{
? ?cout<<"a = "<<a<<endl;
? ?cout<<"b = "<<b<<endl;
? ?cout<<"c = "<<c<<endl;
}
<!--注意:-->
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在函数声明和定义中同时出现
-
//a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20)
{}
// 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
int Add(int left, int right) - 缺省值必须是常量或者全局变量?
- C语言不支持(编译器不支持?
4.函数重载
? ? ? ?自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。 比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前 者是“谁也赢不了!”,后者是“谁也赢不了!”
4.1 函数重载概念
? ? ? ?函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。
int Add(int left, int right)
{
? ?return left+right;
}
double Add(double left, double right)
{
return left+right;
}
long Add(long left, long right)
{
return left+right;
}
?
int main()
{
? ?Add(10, 20);
? ?Add(10.0, 20.0);
? ?Add(10L, 20L);
? ?
? ?return 0;
}
<!--如果函数名相同,只有函数的类型不同,而参数个数和参数类型相同,不能认为是重载函数。-->
例如:
Short Add(short left, short right)
{
return left+right;
}
int Add(short left, short right)
{
return left+right;
}
4.2名词修饰
4.2.1为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
| | | test.i | 预处理阶段 | 进行宏替换,条件编译,头文件展开,去掉注释 | test.s | 编译阶段 | 首先会进行语法语义检错,无误后要将C文件编译成汇编文件 | test.o | 汇编阶段 | 将汇编文件转换成可执行的机器指令 | test.c | 链接阶段 | 把的所有的目标文件以及所依赖的库文件链接到一起生成可执行程序 |
? ? ? ? ? 实际我们的项目通常是由多个头文件和多个源文件构成,而通过我们C语言阶段学习的编译链接,我们 可以知道,当前a.cpp 中调用了b.cpp 中定义的Add函数时,编译后链接前,a.o 的目标文件中没有 Add 的函数地址,因为Add 是在b.cpp 中定义的,所以Add 的地址在b.o 中。那么怎么办呢?
分析:
所以链接阶段就是专门处理这种问题,链接器看到a.o 调用Add ,但是没有Add 的地址,就会到b.o 的符 号表中找Add的地址,然后链接到一起。那么链接时,面对Add函数,连接器会使用哪个名字去找呢?所以每个编译器都有自己的函数名修饰规则。
[1]linux 下名词修饰规则
? ? ? ? 由于Windows下vs的修饰规则过于复杂,而Linux 下gcc 的修饰规则简单易懂,下面我使用了gcc 演示了这个修饰后的名字。通过下面我们可以看出gcc 的函数修饰后名字不变。而g++ 的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。
采用C语言编译器编译后结果
?结论:在linux 下,采用gcc 编译完成后,函数名字的修饰没有发生改变。
采用C++编译器编译后结果
?
结论:在linux 下,采用g++ 编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。而g++ 的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。
[2]windows下名词修饰规则
? ? ? 首先给出要进行测试的C++代码,如下:
using namespace std;
?
int Add(int a, int b)
{
cout << "test to overLoad (int Add)" << endl;
return a + b;
}
?
void Add(double a, double b)
{
cout << "test to overLoad (void Add)" << endl;
}
?
int main()
{
cout << "test start!" << endl;
int k = Add(1, 2);
Add(1.1, 2.2);
return 0;
}
? ? 进入调试,当程序走到 int k = Add(1, 2) 时,打开反汇编代码进行查看,查看当前Add函数的命名。C++编译器下:
由于VC++ 中显示的不是很清楚,我将该信息复制到txt 文件中,进行详细的分析:
? ? ? 对比一下发现修饰后的格式为:? + 函数名 + @@YA + 返回值 + 参数1 + 参数2 + @Z,int 类型对应的是字母H ,void 类型对应的是字母X ,double 类型对应的是字母N 。我们可以明显看到函数名、参数的类型都被加入了修饰,这样编译器和链接器就可以区分同名但不同类型参数的函数,就能及时的找到对应的函数。
还使用同样的代码,修改为C程序后对其进行调试,对其进行查看,很快就能发现,程序连编译都无法编译,但是为了查看C编译器的函数修饰规则,我们只对一个Add 函数进行测试。
:
? ? ? ? ?可以发现C编译器下修饰的格式为:_ + 函数名,因此,若遇到函数名相同的情况(即函数重载),C 编译器就无法判断到底调用哪个函数,就会发生错误,这才是为什么C++ 可以重载函数,而C不能重载函数的真正原因即是C语言对同名函数无法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。此时也就明白,为什么函数重载要求参数不同,而与返回值没关系。
4.2.2extern “C”
? ? ? ?有时候在C++ 工程中可能需要将某些函数按照C 的风格来编译,在函数前加extern "C" ,意思是告诉编译器,将该函数按照C 语言规则来编译。
extern "C" int Add(int left, int right);
?
int main()
{
? ?Add(1,2);
? ?return 0;
}
|