[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++中这两种头文件都是可以用的,区别在于,不带.h的具有命名空间。
C++输入输出
C语言中的scanf
和printf
在C++中依然可以使用。
C++我们还可以使用cin
和cout
int main(){cout << "hello world" << endl;int a, b;cin >> a >> b;cout << a << b << endl;return 0;}
这里的双箭头不是移位运算符
-
>>
叫做流提取运算符 -
<<
叫做流插入运算符 -
endl
表示换行
cin
和cout
可以一行输入(输出)多个,而且不用进行格式控制。但是当需要格式控制的时候用它们会很麻烦,此时依然可以选择用scanf
和printf
缺省参数
也叫默认参数。
缺省参数的概念
缺省参数就是在声明或定义函数时为函数参数指定一个默认值,在调用函数时如未指定实参,则采用该默认值作为实参。
🌰例子
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++项目通过相对路径去调用它,配置好附加库目录和附加依赖项,发现不行。
- 加上
extern "C"
就可以了。这里用{}
是因为包含的头文件会展开,内含多个函数声明。
那么C能不能调C++呢?
当然也可以,但是不能对C项目动手脚,应该对C++静态库动手脚。因为C++知道C的规则,而C不知道C++的。
并且C语言中也没有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调C++,还是C++调C,都是C++去包容C的规则。在C调C++库的时候,C++库的函数声明还是要按照C的写,不能出现重名函数。