目录
编辑
一、C++关键字(C++98)
二、命名空间
2.1 域
2.2 命名空间域
2.1 命名空间定义
2.2 命名空间使用
三、C++输入&输出
四、缺省参数
4.1 缺省参数概念
4.2 缺省参数分类
五、函数重载
5.1 函数重载概念
5.2 C++支持函数重载的原理--名字修饰(name Mangling)
一、C++关键字(C++98)
C++兼容C语言,所以在C语言的基础上又增添了许多的关键字。C语言有32个关键字,而C++总计63个关键字,差不多是C语言的2倍。
二、命名空间
2.1 域
在C++中,域是指变量、函数、类等标识符的可见性和生存期,主要有以下几种类型的域:
- 全局域:全局域是指在全局范围内声明的标识符,可以在整个程序中访问。全局域中的标识符在程序开始时创建,在程序结束时销毁。
- 局部域:局部域是指在函数或代码块内部声明的变量的作用域。局部域中的变量只能在声明它们的函数或代码块内部访问,超出该范围就会被销毁。
- 命名空间域:命名空间是一种将标识符组织在一起以避免命名冲突的机制。在命名空间中声明的标识符具有命名空间域,只能在该命名空间内访问。
- 类域:类域是指在类定义中声明的成员变量和成员函数的作用域。类域中的成员变量和成员函数只能在类的成员函数内部或通过类的对象访问。
域作用限定符:
在C++中,域作用限定符用于指定变量、函数或类成员所属的域或命名空间。域作用限定符使用双冒号符号 “::” ,其语法格式为:
//指定变量所属的命名空间 namespace_name::variable_name; //指定函数所属的命名空间 namespace_name::function_name(); //指定类成员变量所属的类 class_name::member_variable; //指定类成员函数所属的类 class_name::member_function();
示例:
#include <iostream> namespace A { int num = 10; } namespace B { int num = 20; } //编译器使用变量或函数时的搜索原则: //1.如果不指定域,先在局部域搜索 //2.再去全局域搜索 //3.如果指定域,就直接去指定域搜索 int main() { //输出10,指定变量num属于命名空间A std::cout << A::num << std::endl; //输出20,指定变量num属于命名空间B std::cout << B::num << std::endl; return 0; }
我们再来看看下面这段代码:
int x = 1;
int main()
{
int x = 0;
printf("%d\n", x);
return 0;
}
毫无疑问,程序的输出结果为 0 ,那怎么样才能让程序输出 1 呢?
其实非常简单,我们只需要在x的前面加上域作用限定符,:: 左边不指定,就默认访问全局域中的变量。
int x = 1;
int main()
{
int x = 0;
printf("%d\n", x);
printf("%d\n", ::x);
return 0;
}
2.2 命名空间域
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
//因为rand(用来生成随机数)是函数名,现在将它定义为全局变量
//会和库里边的函数产生命名冲突
//C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
int main()
{
printf("%d\n", rand);
return 0;
}
2.1 命名空间定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{ }即可,{ }中即为命名空间的成员。
// Lv是命名空间的名字,一般开发中是用项目名字做命名空间名。
// 1. 正常的命名空间定义
namespace Lv
{
//命名空间中可以定义变量/函数/类型
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
//2. 命名空间可以嵌套
// test.cpp
namespace N1
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N2
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
// ps:一个工程中的test.h和上面test.cpp中两个N1会被合并成一个
// test.h
namespace N1
{
int Mul(int left, int right)
{
return left * right;
}
}
🌴注意:
一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
2.2 命名空间使用
🍂命名空间中成员该如何使用呢?比如:
#include <iostream>
namespace N
{
//命名空间中可以定义变量/函数/类型
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
// 编译报错:error C2065: “a”: 未声明的标识符
printf("%d\n", a);
return 0;
}
🍂命名空间的使用有三种方式:
🌴加命名空间名称及作用域限定符
int main()
{
printf("%d\n", N::a);
return 0;
}
🌴使用using将命名空间中某个成员引入
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
🌴使用using namespace 命名空间名称引入
using namespace N;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
三、C++输入&输出
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
cout << "Hello world!!!" << endl;
return 0;
}
说明:
- 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
- cout和cin是全局的流对象,endl(endline)是特殊的C++符号,表示换行输出,他们都包含在包含<iostream >头文件中。
- <<是流插入运算符,>>是流提取运算符。
- 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型。
注意:
早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用<iostream>+std的方式。
#include <iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
// 可以自动识别变量的类型
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
std命名空间的使用惯例:
std是C++标准库的命名空间,如何展开std使用更合理呢?
- 在日常练习中,建议直接using namespace std即可,这样就很方便。
- using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模较大,就很容易出现问题。所以建议在项目开发中使用像std::cout这样的格式,使用时指定命名空间 +using std::cout展开常用的库对象/类型等方式。
四、缺省参数
4.1 缺省参数概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
#include <iostream>
using namespace std;
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
4.2 缺省参数分类
- 🌴全缺省参数
#include <iostream>
using namespace std;
void Func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(100); // 传参时,使用指定的实参
return 0;
}
- 🌴半缺省参数
#include <iostream>
using namespace std;
void Func(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func(10, 20, 30);
Func(10, 20);
Func(100);
return 0;
}
注意:
- 半缺省参数必须从右往左依次来给出,不能间隔着给。
- 缺省参数不能在函数声明和定义中同时出现。通常出现在声明中,因为编译器在“编译”阶段会检查语法,如果出现在定义中,编译器就会报错。
//a.h void Func(int a = 10); // a.cpp void Func(int a = 20) {} // 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该 用那个缺省值。
- 缺省值必须是常量或者全局变量。
- C语言不支持(编译器不支持)。
五、函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
比如:
刚刚翻看儿子的作业,有一道题要求用“一边······一边······"造句。他写的是:一对男女在打架,我不知道该帮谁,因为一边是爸爸,一边是妈妈。这是《读者》杂志的一则笑话。”一边······一边“连起来用,表示一个动作与另一个动作同时进行,这里的”一边“是副词,但是,”一边“还可以做方位名词,表示某个地方。
5.1 函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似、数据类型不同的问题。
#include <iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
f(10, 'a');
f('a', 10);
return 0;
}
5.2 C++支持函数重载的原理--名字修饰(name Mangling)
为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
【PS:有忘记的小伙伴可以参考博主的另一篇文章:程序环境和预处理】
1. 实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?
2. 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。
3. 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。
4. 由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,所以下面我们使用g++演示这个修饰后的名字。
5. 通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。
- 🌴采用C语言编译器编译后结果
结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。
- 🌴采用C++编译器编译后结果
结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
- 🌴Windows下名字修饰规则
6. 通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
7. 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。