当前位置:   article > 正文

【C++】-- 命名空间、函数重载、内联函数_c++ 命名空间 函数

c++ 命名空间 函数

目录

一、命名空间

1.命名空间的定义

2.命名空间的3种使用方式

二、C++输入输出

 三、缺省参数

1.缺省参数定义

2.缺省参数分类

四、函数重载

1.函数重载概念

2.c++支持重载的原因

3.C++中将一个函数按照C的风格来编译 

 五、内联函数

1.定义

2. 特性

3.为什么不能每个函数都使用inline 

4.inline和宏

六、指针空值nullptr 


 到c++板块啦

一、命名空间

C++中存在大量的变量、函数和类,都存在于全局作用域中,会导致冲突。使用命名空间可以对标识符的名称进行本地化,来避免命名冲突或名字污染,达到名称隔离的目的。

1.命名空间的定义

C++使用namespace关键字定义命名空间,用来解决命名冲突问题,格式为:namespace后面跟命名空间名字,再接一对{ } ,{ }中是命名空间的成员。

(1)命名空间格式:

  1. namespace N1 //N1是命名空间的名称
  2. {
  3. //命名空间的内容,既可以定义变量,也可以定义函数
  4. int a = 0;
  5. int Add(int left, int right)
  6. {
  7. return left + right;
  8. }
  9. }

(2)命名空间可嵌套:

  1. namespace N2 //N2命名空间中嵌套了N3命名空间
  2. {
  3. int a = 0;
  4. int b = 0;
  5. int Add(int left, int right)
  6. {
  7. return left + right;
  8. }
  9. namespace N3
  10. {
  11. int c;
  12. int d;
  13. int sub(int left, int right)
  14. {
  15. return left - right;
  16. }
  17. }
  18. }

(3)同一个工程中允许存在多个同名命名空间,编译器会合成到同一个命名空间中,如:

001-test.cpp中存在N1命名空间

“::”操作符是域解析操作符

  1. #include<iostream>
  2. using namespace std;
  3. namespace N1 //N1是命名空间的名称
  4. {
  5. //命名空间的内容,既可以定义变量,也可以定义函数
  6. int e;
  7. int Add(int left, int right)
  8. {
  9. return left + right;
  10. }
  11. }
  12. int main()
  13. {
  14. cout <<"e="<< N1::e << endl;//endl是全局换行符
  15. return 0;
  16. }

 002-test.cpp中也存在N1命名空间

  1. #include<iostream>
  2. namespace N1 //N2命名空间中嵌套了N3命名空间
  3. {
  4. int b = 0;
  5. int c = 8;
  6. namespace N3
  7. {
  8. int c;
  9. int d;
  10. int sub(int left, int right)
  11. {
  12. return left - right;
  13. }
  14. }
  15. }

 

注意:

(1)同一命名空间不能定义相同的变量名和函数名。

(2)一个命名空间定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。

(3)C++为了防止命名冲突,把自己库里面的东西都定义在std命名空间中。

2.命名空间的3种使用方式

(1) 加命名空间名称及作用域限定符(N::a),比较麻烦,每个用到域中成员的地方都得指定成员的域

  1. #include<iostream>
  2. using namespace std;
  3. namespace N
  4. {
  5. int a = 0;
  6. int Add(int left, int right)
  7. {
  8. return left + right;
  9. }
  10. int Sub(int left, int right)
  11. {
  12. return left - right;
  13. }
  14. namespace N1 //定义嵌套命名空间
  15. {
  16. int max(int left, int right)
  17. {
  18. return left > right ? left : right;
  19. }
  20. }
  21. }
  22. int main()
  23. {
  24. cout << "a = " << N::a << endl;
  25. //嵌套命名空间的使用
  26. int b = N::N1::max(3, 4);
  27. cout << "b = " << b << endl;
  28. return 0;
  29. }

(2)是用using将命名空间成员或函数引入(using N::a,访问a时就不需要加访问限定符"::")

  1. #include<iostream>
  2. using namespace std;
  3. namespace N
  4. {
  5. int a = 0;
  6. int Add(int left, int right)
  7. {
  8. return left + right;
  9. }
  10. int Sub(int left, int right)
  11. {
  12. return left - right;
  13. }
  14. }
  15. using N::a;
  16. int main()
  17. {
  18. cout << "a = " << a << endl;
  19. return 0;
  20. }

(3)使用using namespace 命名空间名称引入(using namespace N,访问a时就不需要加访问限定符"::")

  1. #include<iostream>
  2. using namespace std;
  3. namespace N
  4. {
  5. int a = 0;
  6. int Add(int left, int right)
  7. {
  8. return left + right;
  9. }
  10. int Sub(int left, int right)
  11. {
  12. return left - right;
  13. }
  14. }
  15. using namespace N;
  16. int main()
  17. {
  18. cout << "a = " << a << endl;
  19. return 0;
  20. }

对于std命名空间,如果使用using namespace 命名空间名称将std整个引入,那么库里面的东西全都被展开包含到全局了,虽然看起来方便,但是如果自己定义的内容和库冲突了,就没法解决了。所以日常练习可以使用using namespace std的方式引入std命名空间,规范的工程项目中不推荐。

using namespace std;

 可以展开常用的命名空间,下面代码只用到打印,只展开cout和endl命名空间来代替展开std:

  1. #include<iostream>
  2. using std::cout;
  3. using std::endl;
  4. namespace N
  5. {
  6. int a = 0;
  7. int Add(int left, int right)
  8. {
  9. return left + right;
  10. }
  11. int Sub(int left, int right)
  12. {
  13. return left - right;
  14. }
  15. }
  16. using N::a;
  17. int main()
  18. {
  19. cout << "a = " << a << endl;
  20. return 0;
  21. }

 使用cout和endl时,也可以不使用using nampspace 命名空间,直接指定成员所在的域

  1. #include<iostream>
  2. namespace N
  3. {
  4. int a = 0;
  5. int Add(int left, int right)
  6. {
  7. return left + right;
  8. }
  9. int Sub(int left, int right)
  10. {
  11. return left - right;
  12. }
  13. }
  14. using N::a;
  15. int main()
  16. {
  17. std::cout << "a = " << a << std::endl;
  18. return 0;
  19. }

二、C++输入输出

 1.使用cout标准输出(控制台)和cin标准输入(键盘)时,必须要包含< iostream >头文件及std标准命名空间:

  1. #include<iostream>
  2. using namespace std;

2.c++可以自动识别类型,不需要增加数据格式控制。C语言必须要指定数据格式:整型--%d,字符--%c。

  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a;
  6. double b;
  7. char c;
  8. cin >> a;
  9. cin >> b >> c;
  10. cout << a << endl;
  11. cout << b << " " << c << endl;
  12. return 0;
  13. }

 三、缺省参数

1.缺省参数定义

缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用给定的实参。

  1. #include<iostream>
  2. using namespace std;
  3. void TestFunc(int a = 0)
  4. {
  5. cout << a << endl;
  6. }
  7. int main()
  8. {
  9. TestFunc(); //未给定实参
  10. TestFunc(10); //给定实参
  11. return 0;
  12. }

2.缺省参数分类

(1)全缺省参数:在函数声明或定义时为所有参数都指定一个默认值,调用函数时只给传了实参的参数赋值为实参,否则为默认值:

  1. #include<iostream>
  2. using namespace std;
  3. void TestFunc(int a = 0, int b = 10, int c = 20)
  4. {
  5. cout << a << endl;
  6. cout << b << endl;
  7. cout << c << endl;
  8. cout << "\n" << endl;
  9. }
  10. int main()
  11. {
  12. TestFunc(3);
  13. TestFunc(3,4);
  14. TestFunc(3, 4, 5);
  15. return 0;
  16. }

(2)半缺省参数:在函数声明或定义时从右向左为部分参数指定一个默认值,且不能间隔给出

  1. #include<iostream>
  2. using namespace std;
  3. //void TestFunc(int a = 10, int b, int c = 20)间隔给出默认值,错误
  4. //void TestFunc(int a = 10, int b, int c)从左向右给出默认值,错误
  5. void TestFunc(int a, int b = 10, int c = 20)
  6. {
  7. cout << a << endl;
  8. cout << b << endl;
  9. cout << c << endl;
  10. cout << "\n" << endl;
  11. }
  12. int main()
  13. {
  14. TestFunc(3);
  15. TestFunc(3,4);
  16. TestFunc(3, 4, 5);
  17. return 0;
  18. }

注意:

(1)缺省参数不能在函数声明和定义中同时出现。

(2)缺省值必须是常量或者全局变量

(3)C语言不支持缺省(编译器不支持)

四、函数重载

c语言不允许定义同名函数,但是c++能够通过函数重载定义同名函数 

1.函数重载概念

C++中重载函数通常用来命名一组功能相似而数据类型不同的函数:函数重载是指在同一作用域内,一组函数名相同,不同参数列表(不同参数个数 或 不同类型 或 不同顺序)的函数,这组函数被称为重载函数。

函数重载作用:减少了函数名的数量,避免了名字空间的污染,增加了程序可读性。

如下3个Add函数构成重载,它们的参数类型都不相同,参数类型分别是int、double、long

  1. #include<iostream>
  2. int Add(int left, int right)
  3. {
  4. return left + right;
  5. }
  6. double Add(double left, double right)
  7. {
  8. return left + right;
  9. }
  10. long Add(long left, long right)
  11. {
  12. return left + right;
  13. }
  14. int main()
  15. {
  16. Add(10, 20);
  17. Add(10.3, 20.5);
  18. Add(10L, 20L);
  19. return 0;
  20. }

注意1:函数是否重载取决于同名函数参数列表是否相同,函数重载和函数返回值类型没有关系。

如下两个函数不是函数重载,编译会报错,因为参数列表相同,不构成重载,尽管返回值类型不同,但函数重载定义并不关注返回值类型。

  1. int Add(short left, short right)
  2. {
  3. return left + right;
  4. }
  5. short Add(short left, short right)
  6. {
  7. return left + right;
  8. }

注意2:带缺省参数的函数和原函数不算重载

 如下两个函数参数类型、个数、顺序都相同,不构成重载

  1. int Add(int left, int right)
  2. {
  3. return left + right;
  4. }
  5. int Add(int left = 2, int right = 3)
  6. {
  7. return left + right;
  8. }

 另外,虽然下面的两个f函数构成重载,但是调用时报错,因为16行对f函数进行调用时,编译器并不知道是该匹配两个参数的f函数还是该匹配带有缺省参数c的f函数,因此会报错。

  1. void f(int a, int b, int c=1)
  2. {
  3. }
  4. void f(int a, int b)
  5. {
  6. }
  7. int main()
  8. {
  9. f(1, 2);
  10. f(1, 2, 3);
  11. return 0;
  12. }

2.c++支持重载的原因

 c/c++编译过程:

在linux环境中,创建f.h文件,写如下代码:  

 创建f.cpp文件,写如下代码:

创建main.c文件,写如下代码:

 执行g++ -o cpp f.cpp main.cpp

 编译通过,生成cpp可执行文件

执行objdump -S cpp

 生成汇编代码:

 

 main.cpp的#include "f.h"包含了f.h文件,f.h文件只是包含了两个add函数的声明,在编译阶段,编译器会认为函数定义在其他地方,而让函数编译通过,等到链接时,再找函数的定义。

链接时,通过函数名即函数地址去其他目标文件中找add的地址,如果找到了就填上add的地址。

根据c++函数名修饰规则,只要参数不同,修饰出来的函数名就不同,这就支持了重载:把参数类型首字母带进函数名中,参数不同,函数签名就不同。

_Z3addii函数名中,_Z是前缀,3是函数的字符长度(add长度为3),ii是两个参数类型int的首字母

同理,_Z3adddd函数名中,_Z是前缀,3是函数的字符长度(add长度为3),dd是两个参数类型double的首字母。

以上也能看出c++函数签名和返回值类型无关。

那么,对于c语言直接拿函数名做名称,无法支持定义两个同名函数

f.h

 f.c

main.c

执行 执行g++ -o test f.cpp main.cpp

编译通过,生成test可执行文件 

 执行objdump -S test

 生成汇编代码:

C语言无法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,这就支持了重载。

3.C++中将一个函数按照C的风格来编译 

使用extern "C"可以在C++文件中引用c文件的函数

f.h文件

  1. #pragma once
  2. #define _CRT_SECURE_NO_WARNINGS 1
  3. #include<stdio.h>
  4. int add(int a, int b);

f.c文件

  1. #include "f.h"
  2. int add(int a, int b)
  3. {
  4. return a + b;
  5. }

 main.cpp

  1. #include "f.h"
  2. int main()
  3. {
  4. int ret = add(2, 3);
  5. printf("%d",ret);
  6. return 0;
  7. }

程序报错,这是因为当c++调用add函数时,add函数被编译器编译后,函数签名变成了"?add@@YAHHH@Z"链接时,main函数却找不到这个符号。  

为了能够让c++调用c文件中实现的函数,需要给该函数加上extern "C",修改f.h文件如下:

  1. #pragma once
  2. #define _CRT_SECURE_NO_WARNINGS 1
  3. #include<stdio.h>
  4. //写法一
  5. extern "C" int add(int a, int b);
  6. //写法二,能针对多行声明:可将要按照c风格进行编译的所有函数声明全部放在{ }中
  7. //#ifdef __cplusplus
  8. //extern "C"{
  9. //#endif
  10. //int add(int a, int b);
  11. //#ifdef __cplusplus
  12. //};
  13. //#endif

 编译成功

为什么加上extern "C"后,就能执行成功呢?

这是由于windows下的编译器会在c语言符号(变量和函数)前加上“_”,即add函数被编译后的符号为_add。因为c语言如果只有函数声明而缺少函数定义,会报缺少"_add"符号:

f.h

  1. #pragma once
  2. #define _CRT_SECURE_NO_WARNINGS 1
  3. #include<stdio.h>
  4. //extern "C" int add(int a, int b);
  5. #ifdef __cplusplus
  6. extern "C"
  7. #endif
  8. int add(int a, int b);

f.c

  1. #include "f.h"
  2. //int add(int a, int b)
  3. //{
  4. // return a + b;
  5. //}

 main.c

  1. #include "f.h"
  2. int main()
  3. {
  4. printf("%d", add(2, 3));
  5. return 0;
  6. }

在VS下,c++文件调用c实现的函数时,编译时,c函数add编译后的符号为"_add"。而在c++的main中,add编译后的符号为"?add@@YAHHH@Z"。两个符号不一致,导致链接失败。而当add在extern "C"中声明后,c++的main中就会将add编译成符号"_add",符号一致,链接成功。

 五、内联函数

1.定义

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,因此,内联函数能够提升程序运行的效率。 

  1. #include<iostream>
  2. int Add(int a, int b)
  3. {
  4. int c = a + b;
  5. return c;
  6. }
  7. int main()
  8. {
  9. int ret = 0;
  10. ret = Add(1, 2);
  11. return 0;
  12. }

VS环境下,F10-调试-窗口-反汇编,发现会call Add

加上inline关键字后,在release模式和debug模式下查看是否存在call Add语句

(1)release模式下,直接查看编译器生成的汇编代码中是否存在call Add

(2)debug模式下,编译器默认不会对代码进行优化,需要对编译器进行设置,否则不会展开:调试-调试属性-配置属性-C/C++-常规-调试信息格式-程序数据库(/Zi)

调试-调试属性-配置属性-C/C++-常规-内联函数扩展-只适用于_inline(/Ob1)

查看反汇编结果,没有call Add,直接使用了add指令:

2. 特性

(1)inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。 

(2)inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。

(3)inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到

f.c 

  1. #pragma once
  2. #include <iostream>
  3. using namespace std;
  4. inline void f(int i);

 f.cpp

  1. #include "f.h"
  2. void f(int i)
  3. {
  4. cout << i << endl;
  5. }

main.cpp

  1. #include "f.h"
  2. int main()
  3. {
  4. f(10);
  5. return 0;
  6. }

 编译报错,链接错误,如下所示。将inline修饰的函数声明和定义放在.h或源文件就可以了。

3.为什么不能每个函数都使用inline 

假如有一个函数,进行编译汇编后有100条指令,如果有10个地方调用

(1)该函数不加inline,建立栈帧,总计100 + 10 = 110条指令

(2)该函数加inline,不建立栈帧,每个地方都展开,总计100 * 10 = 1000条指令

从110条指令变成1000条指令,虽然建立栈帧不一定比不建立栈帧慢,但是编译出来的可执行程序变大,安装软件的人体验变差,执行程序内存消耗变多。

4.inline和宏

C语言为了避免小函数建帧的消耗,提供宏函数支持,在预处理阶段展开。既然C语言已经解决了,为什么C++还要提供inline函数?

因为宏有以下缺点:

(1)不支持调试(编译阶段进行了替换)

(2)宏函数语法复杂,容易出错

(3)没有类型安全检查

c++使用三种法师代替宏:枚举、const常量定义、inline内联函数。

六、指针空值nullptr 

NULL实际是一个宏,NULL的定义: 

NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。但对于如下代码:

  1. void f(int)
  2. {
  3. cout<<"f(int)"<<endl;
  4. }
  5. void f(int*)
  6. {
  7. cout<<"f(int*)"<<endl;
  8. }
  9. int main()
  10. {
  11. f(NULL);
  12. f((int*)NULL);
  13. return 0;
  14. }

在c++中,NULL被定义为0,想用f(NULL)用空指针NULL作为参数,但是根据打印结果,发现 f(NULL)调用的是参数为int的f函数:

这是因为C++中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。 为此,C++11新增了关键字nullptr,用于表示空指针。为向后兼容,C++11仍然可以使用0来表示空指针,因此表达式nullptr=0为true,但使用nullptr提供了更高的类型安全。

注意:

1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。

2.nullptr是指针类型,不能转化为整形类型,可以隐式转换为任意类型的指针,也可以隐式转换为bool类型代表false。

3. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。 

4. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/439652
推荐阅读
相关标签
  

闽ICP备14008679号