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

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

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

下表大致浏览一下。

asmdoifreturntrycontinue
autodoubleinlineshorttypedeffor
booldynamic_castintsignedtypeidpublic
breakelselongsizeoftypenamethrow
caseenummutablestaticunionwchar_t
catchexplicitnamespacestatic_castunsigneddefault
charexportnewstructusingfriend
classexternoperatorswitchvirtualregister
constfalseprivatetemplatevoidtrue
const_castfloatprotectedthisvolatilewhile
deletegotoreinterpret_cast

命名空间

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

#include <iostream>
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的。

img

其实在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++项目通过相对路径去调用它,配置好附加库目录和附加依赖项,发现不行。

img

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

img


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

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

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

img

  • 这边用到了条件编译+宏,其中的__cplusplus是C++特有的
  • 这样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语言项目是可以通过的:

img

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

世真

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值