一、命名空间
1.什么是命名空间
命名空间(namespace)是C++中的一种机制,用来解决不同代码库之间的命名冲突问题。
先来看一个例子:
#include <iostream>
void print()
{
std::cout << "Hello from print()" << std::endl;
}
// 假设这里有另一个库也定义了print函数
void print()
{
std::cout << "Hello from another print()" << std::endl;
}
int main()
{
print(); // 编译错误,函数名冲突
return 0;
}
这是在写大型项目时经常容易遇到的事情,假如这两个库分别由两个不同的人编写,可能这两个库的代码分别能在各自的环境中正常运行,但当代码提交到一起时,就会出现命名冲突问题问题。
为了解决这种问题,C++提出了命名空间的概念,就是通过把全局范围内的变量、函数和类等放在一个逻辑命名空间内,避免名字重叠。 就像这样:
#include <iostream>
namespace FirstLibrary
{
void print()
{
std::cout << "Hello from FirstLibrary::print()" << std::endl;
}
}
namespace SecondLibrary
{
void print()
{
std::cout << "Hello from SecondLibrary::print()" << std::endl;
}
}
int main()
{
FirstLibrary::print(); // 正常调用
SecondLibrary::print(); // 正常调用
return 0;
}
命名空间包含的成员可以是变量、函数、类和结构体、枚举、类型定义....你还可以套娃在命名空间空间中嵌套命名空间(很少这样做),例如:
namespace MyLibrary
{
int a = 10;
double b = 1.2;
int Add(int left, int right)
{
return left + right;
}
class MyClass
{
public:
void display()
{
// 方法实现
}
};
struct Poiont
{
int x;
int y;
};
enum MyEnum
{
VALUE1,
VALUE2
};
typedef int MyInt;
namespace MyLibrary_2
{
int a = 0;
}
//... ...
}
2.如何访问命名空间的成员
2.1作用域展开符
即 直接在要访问的成员名称前加`::`。
`::`是作用域限定符,不作用域限定符则无法访问到该命名空间的成员,因为编译器默认只在全局范围中查找。
int main()
{
// 访问变量
std::cout << MyLibrary::a << std::endl;
// 调用函数
MyLibrary::Add(1,2);
// 使用类
MyLibrary::MyClass obj;
obj.display();
// 使用结构体
MyLibrary::Poiont s = { 1, 9 };
std::cout << "Point: x = " << s.x << ", y = " << s.y << std::endl;
// 使用枚举
MyLibrary::MyEnum e = MyLibrary::VALUE1;
std::cout << "MyEnum value: " << e << std::endl;
// 使用类型别名
MyLibrary::MyInt i = 100;
std::cout << "MyInt: " << i << std::endl;
// 调用嵌套命名空间的函数
std::cout << MyLibrary::MyLibrary_2::a << std::endl;
return 0;
}
2.2命名空间展开
即 使用`using namespace`指令,命名空间展开可以分为全展开和半展开。
全展开:使用using namespace
using namespace MyLibrary;
使用这条语句之后,MyLibrary中的所有成员均可直接访问,即不用在成员前加`::`
#include<iostream>
using namespace std;
int main()
{
cout << "using namespace std" << endl;
return 0;
}
如上面这段代码,使用 using namespace std 将C++标准命名空间展开后,就可以直接访问std里面的cout和endl了
半展开:使用using
using MyLibrary::a;
使用`using`引入特定的成员,这样在程序中即可直接访问该成员
2.3总结
这三种方式各有优劣,直接使用作用域限定符最为清晰明确,但较为冗长;
`using` 声明适合局部引入特定成员;
`using namespace` 指令则最简便,但可能引入命名冲突风险。
3.注意事项
- 命名空间会自动合并,这意味着可以定义多个同名的命名空间。
- 命名空间只能在全局定义!!
-
`using namespace` 不等于取消命名空间, 影响的是代码编译时候查找该变量的规则(即在指定命名空间和全局变量中寻找), 比如在使用`using namespace std`后, 依然可以使用 `std :: cout` 来使用 `cout`
二、缺省参数
C++ 的缺省参数(也叫默认参数)是指在函数声明或定义时,给某些参数提供一个默认值。这样在调用函数时,如果不传递这些参数,函数会自动使用默认值。比如下面这段代码:
#include<iostream>
using namespace std;
// a = 0 b = 1 c = 2即为默认值,函数传参时可以选择不传值
void Func(int a = 0, int b = 1, int c = 2)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
cout << "Func(1, 2)\n";
Func(1, 2);
cout << endl;
cout << "Func()\n";
Func();
return 0;
}
1.缺省参数分类
1.1全缺省参数
void Func(int a = 10, int b = 20, int c = 30)
1.2半缺省参数
void Func(int a, int b = 10, int c = 20)
2.使用缺省参数的注意事项
2.1申明缺省参数时从右到左
缺省参数必须从右到左依次出现,例如,不能先给 a 指定默认值而不给 b 默认值。
// 错误
void func(int a = 5, int b);
// 正确
void func(int a, int b = 10);
2.2传递参数时从左到右
不存在不连续的缺省值,即 例如在传参时不能省略第一个参数,而给出第二个参数
#include<iostream>
using namespace std;
void Func(int a = 0, int b = 1, int c = 2) // a = 0 是舔狗 没人的时候它就上
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
Func(, 2, 3); //这样是不正确的!
return 0;
}
2.3如果申明和定义分离,则只在声明中写缺省参数
如果函数在头文件中声明并在源文件中定义,则缺省参数不能在函数声明和定义中同时出现,所以在函数声明的时候给缺省参数, 定义的时候不给缺省参数。
因为如果声明与定义位置同时出现,恰巧两个位置提供的值不同,编译器就无法确定到底该用哪个缺省值。
2.4缺省值必须是常量或者全局变量
这一点显然,给出的缺省值一定是一个固定的值,而不是一个变化的值,否则这个缺省值将毫无意义
三、函数重载
1.什么是函数重载
C++ 的函数重载是指在同一个作用域中,可以定义多个同名但参数不同的函数。编译器会根据调用时的实参类型和数量来决定调用哪个函数。这使得程序更具灵活性和可读性。
或许你已经发现了,在C++中,`std::cout` 不需要像 `printf()` 一样给定参数类型,它能自动判断参数类型输出,这其实就是一种函数重载。
在C语言中,由于不存在函数重载,我们只能通过区分函数名称来处理不同类型的数据
void printInt(int i)
{
printf("%d", i);
}
void printDouble(double f)
{
printf("%f", f);
}
void printString(char s[])
{
printf("%s", s);
}
有了函数重载之后:
void print(int i)
{
cout << "整数: " << i << endl;
}
void print(double f)
{
cout << "浮点数: " << f << endl;
}
void print(string s)
{
cout << "字符串: " << s << endl;
}
int main() {
print(10); // 调用 print(int)
print(3.14); // 调用 print(double)
print("Hello"); // 调用 print(string)
return 0;
}
我们只需定义一个函数名 print,根据不同的参数类型调用不同的实现,代码更简洁易读。
2.构成函数重载的关键点
- 函数名相同:所有重载函数必须有相同的名字。
但注意,函数重载强调在同一个作用域中,所以两个不同命名空间的同名函数不构成函数重载
- 参数不同:重载函数的参数类型、个数或顺序至少有一个不同。
- 返回类型:返回类型可以不同,但仅靠返回类型不同不能构成重载。