> 文档中心 > [C++入门](1) 命名空间|缺省参数|函数重载|extern “C“

[C++入门](1) 命名空间|缺省参数|函数重载|extern “C“


C++关键字(C++98)

C++总计63个关键字,C语言32个关键字

下表大致浏览一下。

asm do if return try continue
auto double inline short typedef for
bool dynamic_cast int signed typeid public
break else long sizeof typename throw
case enum mutable static union wchar_t
catch explicit namespace static_cast unsigned default
char export new struct using friend
class extern operator switch virtual register
const false private template void true
const_cast float protected this volatile while
delete goto reinterpret_cast

命名空间

我们见过C++代码开头通常会写这两个东西。

#include using namespace std;

第一行是包含头文件,iostream意思是输入(in)输出(out)流(stream),之前学C语言包含的是stdio.h,以后学C++主要就包iostream这个头文件。

第二行的namespace将是我们C++学习的第一个关键字,意思是命名空间

在学习C语言的时候我们发现,C语言存在命名冲突的问题。比如变量名与关键字重名,同一个域里面,变量名和函数名重名,变量名和变量名重名,都会导致冲突。在大型项目中这种冲突尤为常见。C语言解决不了这个问题,C++引入了namespace来解决。

定义命名空间

🌰例子:

void f(){return;}int f;
  • 全局域变量名和函数名冲突,会报错。
namespace mySpace{int f;}void f(){return;}
  • 定义一个命名空间mySpace,把int f放进去,不会报错。

像这样namespace后跟命名空间名字,然后接{}{}内的即为命名空间的成员,里面既可以定义变量,也可以定义函数和类型{}内也形成了一个新的域:命名空间域

注意:命名空间不影响变量的生命周期,它本质上还是全局变量。

  • 命名空间也可以嵌套

  • 同一个域里,两个同名的命名空间会在编译时合并。

namespace mySpace{int a;namespace n1{struct Node{int val;struct Node* next;};}}namespace mySpace{namespace n2{struct Node{int val;struct Node* naxt;};}}

使用命名空间

这里要引入一个新的运算符:::

::作用域限定符

🌰例1:

namespace n1{int a = 3;}int a = 6;int main(){int a = 8;printf("%d\n", a);printf("%d\n", ::a);printf("%d\n", n1::a);return 0;}
  • 熟悉C语言的应该知道,根据局部优先,第一个应该打印8。如果要访问全局变量怎么办呢?

  • 在前面加::::前面什么都不加,表示访问全局域,所以第二个打印6

  • 第三个指定了n1作用域,所以打印3。

🌰例2:

我们定义这样一个命名空间:

namespace N{int a = 10;int b = 9;int Add(int a, int b){return a + b;}int Sub(int a, int b){return a - b;}}

除了例1空间名称加作用域限定符的方式,还有两种使用方式。

1. 使用using将成员引入

using N::a;int main(){printf("%d\n", a);printf("%d\n", N::b);return 0;}
  • 把a放出来,下面使用的时候就可以不加::了,而b没有放出来,使用b仍然需要::

2. 使用using namespace将命名空间引入

using namespace N;int main(){printf("%d\n", a);printf("%d\n", b);printf("%d\n", Add(a, b));printf("%d\n", Sub(a, b));return 0;}
  • 这样就都可以直接用了。

  • 这两种方式都相当于把原来命名空间里的成员放到全局域里了。

对于嵌套的命名空间:

namespace mySpace{int a = 8;namespace n1{struct Node1{int val;struct Node1* next;};}namespace n2{struct Node2{int val;struct Node2* naxt;};}}

先把mySpace里的都放出来,然后把n1里的放出来:

using namespace mySpace;using namespace n1;int main(){printf("%d", a);struct Node1 node1;struct n2::Node2 node2;return 0;}

using namespace只会从全局域中找要释放的命名空间,所以只写一行using namespace n1;是不行的。

但也可以这样:

只有n1释放出来。

using namespace mySpace::n1;int main(){printf("%d", mySpace::a);struct Node1 node1;struct mySpace::n2::Node2 node2;return 0;}

现在我们知道,using namespace std;是释放了名为std的命名空间,std是C++标准库的命名空间。

平时练习会不在乎命名冲突的风险,所以全部展开,比较方便。项目中可以只展开部分常用的。


小问题

平时我们查cplusplus会发现头文件有两种,一个是带c不带.h的,一个是带.h不带c的。

[C++入门](1) 命名空间|缺省参数|函数重载|extern “C“

其实在C++中这两种头文件都是可以用的,区别在于,不带.h的具有命名空间。

C++输入输出

C语言中的scanfprintf在C++中依然可以使用。

C++我们还可以使用cincout

int main(){cout << "hello world" << endl;int a, b;cin >> a >> b;cout << a << b << endl;return 0;}

这里的双箭头不是移位运算符

  • >>叫做流提取运算符

  • <<叫做流插入运算符

  • endl表示换行

cincout可以一行输入(输出)多个,而且不用进行格式控制。但是当需要格式控制的时候用它们会很麻烦,此时依然可以选择用scanfprintf

缺省参数

也叫默认参数。

缺省参数的概念

缺省参数就是在声明或定义函数时为函数参数指定一个默认值,在调用函数时如未指定实参,则采用该默认值作为实参。

🌰例子

void f(int a = 1){cout << a << endl;}int main(){f();f(2);return 0;}
  • 没有传参时,使用缺省参数1
  • 传入参数2,则使用参数2

注意:

  • 缺省值必须是常量或者全局变量
  • 如果有函数声明,则缺省参数必须写在函数声明中。

缺省参数的分类

1. 全缺省参数

就是所有参数都给缺省参数:

void f(int a = 1, int b = 2, int c = 3){cout << a << endl;cout << b << endl;cout << c << endl;}int main(){f();f(10);f(10, 20);f(10, 20, 30);return 0;}
  • 注意:
    • 调用方式只有这4种。
    • 传参只能从左往右传,像f(, 20);这种是不被允许的。

2. 半缺省参数

部分参数给缺省参数

void f(int a, int b = 2, int c = 3){cout << a << endl;cout << b << endl;cout << c << endl;}int main(){f(10);f(10, 20);f(10, 20, 30);return 0;}
  • 注意
    • 调用方式只有这3种,因为第一个形参没有缺省参数,不能不传参。
    • 设定缺省参数必须从右往左,像void f(int a = 1, int b, int c)是不被允许的。

实际使用

typedef struct Stack{int* a;int top;int capacity;}ST;void StackInit(ST* ps, int n = 4){assert(ps);ps->a = (int*)malloc(n * sizeof(int));ps->top = 0;ps->capacity = n;}

在对栈进行初始化的时候,我们原先的代码是将其都初始化为0,后续插入数据的时候再去进行扩容。

使用缺省参数,即可在已经知道数据量的情况下传入适当的参数,直接开辟相应大小的空间,免去扩容带来的消耗。也可以像原来那样不传参,函数会自动使用缺省参数。

函数重载

重载英文名为overload

C++允许在同一作用域中声明多个同名的函数,这些同名函数的形参在个数、类型以及类型的顺序方面至少有一方面不同。

🌰例子

void swap(int* a, int* b){int tmp;tmp = *a;*a = *b;*b = tmp;}void swap(double* a, double* b){double tmp;tmp = *a;*a = *b;*b = tmp;}int main(){int a = 1, b = 2;double c = 1.1, d = 2.2;swap(&a, &b);swap(&c, &d);return 0;}
  • 这两个swap函数虽然同名,但是可以同时存在。

通过这个例子可以很明显地感受到,要使用多个函数实现一个类似的功能,不同的函数不需要起别的名字。

调用的时候函数名只要写swap,之后具体是调用哪个函数,编译器会通过传入的参数自动选择。

注意:形参必须满足上述荧光笔标记的条件,和形参名、返回类型没有关系,仅仅是这两者不同不可以重载,因为在调用时会产生歧义。

extern "C"

C语言不支持函数重载,是因为它和C++的编译方式不同,后面会专门写一篇文章来细讲。

正因为有这种不同,所以C和C++不能直接互相调用对方的库。

而有时候在C++工程中可能需要将某些函数按照C的风格来编译**,**在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。

  • 比如我们有一个静态库Stack,是我们之前用C语言写的,这边C++项目通过相对路径去调用它,配置好附加库目录和附加依赖项,发现不行。

[C++入门](1) 命名空间|缺省参数|函数重载|extern “C“

  • 加上extern "C"就可以了。这里用{}是因为包含的头文件会展开,内含多个函数声明。

[C++入门](1) 命名空间|缺省参数|函数重载|extern “C“


那么C能不能调C++呢?

当然也可以,但是不能对C项目动手脚,应该对C++静态库动手脚。因为C++知道C的规则,而C不知道C++的。

并且C语言中也没有extern "C"

[C++入门](1) 命名空间|缺省参数|函数重载|extern “C“

  • 这边用到了条件编译+宏,其中的__cplusplus是C++特有的
  • 这样C++静态库就会用extern "C"去替换EXTERN_C,将其按照C的规则编译。当C项目调用时,则会用空格去替换EXTERN_C,避免C语言编译器去识别extern "C"

在每个函数声明前面加EXTERN_C有点麻烦,也可以这样:

#ifdef __cplusplusextern "C"{#endifvoid StackInit(ST* ps);void StackDestory(ST* ps);void StackPush(ST* ps, STDataType x);void StackPop(ST* ps);bool StackEmpty(ST* ps);int StackSize(ST* ps);STDataType StackTop(ST* ps);#ifdef __cplusplus}#endif

这样的代码可以移植性就提高了,C语言项目是可以通过的:

[C++入门](1) 命名空间|缺省参数|函数重载|extern “C“

总结:无论是C调C++,还是C++调C,都是C++去包容C的规则。在C调C++库的时候,C++库的函数声明还是要按照C的写,不能出现重名函数。