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;
}
::
了,而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语言中的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. 全缺省参数
就是所有参数都给缺省参数:
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;
}
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;
}
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,之后具体是调用哪个函数,编译器会通过传入的参数自动选择。
注意:形参必须满足上述荧光笔标记的条件,和形参名、返回类型没有关系,仅仅是这两者不同不可以重载,因为在调用时会产生歧义。
extern "C"
C语言不支持函数重载,是因为它和C++的编译方式不同,后面会专门写一篇文章来细讲。
正因为有这种不同,所以C和C++不能直接互相调用对方的库。
而有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern "C"
,意思是告诉编译器,将该函数按照C语言规则来编译。
extern "C"
就可以了。这里用{}
是因为包含的头文件会展开,内含多个函数声明。那么C能不能调C++呢?
当然也可以,但是不能对C项目动手脚,应该对C++静态库动手脚。因为C++知道C的规则,而C不知道C++的。
并且C语言中也没有extern "C"
__cplusplus
是C++特有的extern "C"
去替换EXTERN_C
,将其按照C的规则编译。当C项目调用时,则会用空格去替换EXTERN_C
,避免C语言编译器去识别extern "C"
在每个函数声明前面加EXTERN_C
有点麻烦,也可以这样:
#ifdef __cplusplus
extern "C"
{
#endif
void 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的写,不能出现重名函数。