网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
namespace stu
{
int a = 0;
}
namespace stu
{
int b = 0;
}
//上面的两段命名空间定义和下面的一样
namespace stu
{
int a = 0;
int b = 0;
}
- 嵌套定义的命名空间内部的变量或者函数名和外部的变量或者函数名是可以相同的,这也是它出现要解决的问题所在。例如下面的操作是合法的:
namespace stu
{
int a = 0;
void fun()
{
return;
}
struct n
{
int c;
char d;
};
namespace N1
{
int a = 1;//使用:stu::N1::a
void fun()//使用:stu::N1::fun()
{
return;
}
struct n//使用:stu::N1::n
{
int m;
char n;
};
}
}
💘3.1.3 命名空间的引入
💖命名空间引入的使用
格式:
using namespace 命名空间名;
使用举例:
假设上面的命名空间的定义已经出现在了另一个.h头文件中,那么我们可以在.c文件中这样引入:
using namespace stu;//引入stu命名空间
using namespace N1;//引入命名空间N1
引入命名空间后我们可以直接使用stu中定义的变量、函数和类型,但是有许多需要注意的地方:
💖注意点
-
当我们如上面的例子中,引入了stu命名空间后,此时如果直接使用a的话使用的就是0,而不是1,如果我们想要使用N1中的a,我们就必须这样进行使用:
stu::N1::a
。 -
如果我们同时展开了上面的stu和N1,此时就不能直接使用变量a了,因为指向不明确,编译器不知道是stu里面的还是N1里面的,此时如果想要使用a的话,必须明确前面的域,比如
stu::a
和stu::N1::a
。 -
命名空间的引入也是有顺序的。如果我们想要引入N1,我们有两种写法
using namespace stu;
using namespace N1;//此时必须要有前面的那一行代码,不然会找不到N12. ``` using namespace stu::N1;
-
-
可以用什么引入什么,只引入某个命名空间的变量或者函数或者类型。例如:
using stu::a;
using stu::fun();
using stu::n;
💖3.2 命名空间的使用
namespace N
{
int a = 10;
int b = 20;
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
}
int main()
{
printf("%d\n", a); // 该语句编译出错,无法识别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 std::cout;
这样引入一些常用的,因为直接引入一个命名空间会造成命名污染,容易出现重定义的现象。
- 使用using namespace 命名空间名称引入
using namespce N;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
💖3.3 对std的解释
==std是封C++库的命名空间。==比如cout和cin就是封在标准命名空间中的。
如果我们不进行引入,那么我们只能像下面这样进行使用:
int main()
{
int a = 0;
std::cout << a << std::endl;
}
在封装到标准命名空间的名字前加上std::。
🧡3. C++输入&输出
我们来看一个程序:
#include<iostream>
using namespace std;
int main()
{
int a = 0;
cin >> a;
cout << a;
return 0;
}
运行结果:
说明:
- 使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间。
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件 即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持格式,后续编译器已不支持,因此推荐使用 +std的方式。 - 使用C++输入输出更方便,不需增加数据格式控制,编译器能够自动识别类型,比如:整形–%d,字符–%c
#include <iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
cin >> a;//一次输入一个数据
cin >> b >> c;//一次可以输入多个数据,默认以空格或者换行进行分割
cout << a << endl;//输出变量a中存储的值和endl(换行符)
cout << b << " " << c << endl;//输出变量b的值和空格和变量c的值还有换行符,说明一次可以输出多个数据
return 0;
}
和C语言不同的是:
无论是输入还是输出,我们都不需要指定相应的类型,编译器会自动进行类型识别和转换。
3. 在C++中,>>是流提取运算符,<<是流插入运算符。
cin >> a;//从cin(键盘)输入数据,然后数据被提取到了变量a中,这就是C++中变量的输入
cout << a << endl;//将a插入到标准输出控制台(一般是显示器)中去了
🧡4. 缺省参数
C++中函数的参数也可以配备胎。
💖4.1 缺省参数概念
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
void TestFunc(int a = 0)
{
cout << a << endl;
}
int main()
{
TestFunc(); // 没有传参时,使用参数的默认值,输出结果为0
TestFunc(10); // 传参时,使用指定的实参,输出结果为10
}
💖4.2 缺省参数分类
- 全缺省参数
void TestFunc(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
TestFunc();//输出结果为10 20 30
TestFunc(5);//输出结果为5 20 30
TestFunc(5,6);//输出结果为5 6 30
TestFunc(5,6,7);//输出结果为5 6 7
return 0;
}
注意:C++中不支持下面的语法:
TestFunc(,5,6);
- 半缺省参数(在使用函数时至少传一个)
void TestFunc(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
注意:
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在函数声明和定义中同时出现(即使值相同也不行,编译器会显示重定义默认参数)
//a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20)
{}
// 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
注意:此时又会出现两种情况:
1、声明给默认参数,定义不给默认参数。如下所示:
void fun(int a = 20);
void fun(int a)
{
cout << a << endl;
}
int main()
{
fun();//输出结果为20
fun(10);//输出结果为10
return 0;
}
2、声明不给默认参数,定义给默认参数。如下所示:
void fun(int a);
void fun(int a = 20)
{
cout << a << endl;
}
int main()
{
fun();//程序无法正常运行,函数不接受0个参数
//原因:在链接之前,各个cpp文件会生成.obj文件,如果在声明不给,.h就会在源文件中展开,程序在编译时程序无法找到它的默认参数,程序只有在链接的时候才会找对应函数的地址,才能知道它的默认参数,即编译阶段只能拿到声明,无法拿到定义,自然无法知道定义中的默认参数
fun(10);//输出结果为10
return 0;
}
- 缺省值必须是常量或者全局变量
- C语言不支持(编译器不支持)
🧡5. 函数重载
💖5.1 函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或 顺序(不同类型的形参))必须不同,常用来处理实现功能类似数据类型不同的问题。
函数重载的意义:
函数的名字仅仅是让编译器知道它调用的是哪个函数,而函数重载可以在一定程度上减轻程序员起名字,记名字的负担。
注意:main函数不能重载!
注意:C语言不支持同名函数,只要名字相同就属于重定义。
int Add(int left, int right)//函数1
{
return left + right;
}
double Add(double left, double right)//函数2
{
return left + right;
}
int Add(int a,int b,int c)//函数3
{
return a + b + c;
}
void Print(int a,double c)//函数4
{
cout << a << endl;
cout << c << endl;
}
void Print(double c,int a)//函数5
{
cout << a << endl;
cout << c << endl;
}
int main()
{
Add(10, 20);
Add(10.0, 20.0);
Add(10, 20, 30);
return 0;
}
(1)函数1和函数2属于参数类型不同
(2)函数1和函数3属于参数个数不同
(3)函数4和函数5属于参数顺序不同
问:为什么返回值无法作为函数重载的条件?
答:我们在调用函数时使用的仅仅是函数名和实参,并不涉及到返回值,所以返回值不同无法作为函数重载的条件。
💖5.2 名字修饰
为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
C++支持重载而C语言不支持重载的原因发生在它们的链接上,如下例所示:(linux下)
(1)C++情况下:
下面有三个源文件
f.cpp
#include"f.h"
void f(int a, double b)
{
printf("%d %lf", a, b);
}
void f(double a, int b)
{
printf("%lf %d", a, b);
}
f.h
#include<stdio.h>
void f(int a,double b);
void f(double a, int b);
test.cpp
#include"f.h"
int main()
{
f(1,3.14);
f(3.14,1);
return 0;
}
汇编的时候会生成符号表(.obj文件)(函数名和函数地址形成的映射)
f.cpp生成的符号表:
函数符号标识 | 地址 |
---|---|
_Z1fid | 0xffffff11 |
_Z1fdi | 0xffffff22 |
test.cpp生成的符号表:
函数符号标识 | 地址 |
---|---|
主函数(main函数) | 0x11223344 |
_Z1fid | ? |
_Z1fdi | ? |
在链接的时候test.cpp形成的符号表会和f.cpp形成的符号表进行链接,链接之后两个?都会成功被填充,所以此时可以成功链接。
注意:上面的i说明第一个参数类型是int,d说明第二个参数类型是double,1说明函数名只有1个字符,即f。
(2)C情况下:
下面有三个源文件
f.c
#include"f.h"
void f(int a, double b)
{
printf("%d %lf", a, b);
}
void f(double a, int b)
{
printf("%lf %d", a, b);
}
f.h
#include<stdio.h>
void f(int a,double b);
void f(double a, int b);
test.c
#include"f.h"
int main()
{
f(1,3.14);
f(3.14,1);
return 0;
}
汇编的时候会生成符号表(函数名和函数地址形成的映射)
f.c生成的符号表:
函数符号标识 | 地址 |
---|---|
f | 0xffffff11 |
f | 0xffffff22 |
test.c生成的符号表:
函数符号标识 | 地址 |
---|---|
主函数(main函数) | 0x11223344 |
f | ? |
f | ? |
在链接的时候test.c形成的符号表会和f.c形成的符号表进行链接,此时两个同名的f无法正常链接,这就是C语言不支持函数重载的原因。
当然,在链接的时候不止有这些,像链接的过程中还有静态库/动态库的调用,此时我们讨论下面的问题:
C语言能够调用C语言的静态库/动态库,C++能够调用C++的静态库/动态库,那么C语言能够调用C++的静态库/动态库?C++能够调用C语言的静态库/动态库?下一节就是!
💖5.3 extern “C”
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器, 将该函数按照C语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree 两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern “C”来解决。
下面将一步步带大家来在VS2019中模拟实现:
首先创建一个项目,和平时的一样,此处起名叫AddC.lib,然后创建Add.c源文件和Add.h头文件。创建好如下图所示:
此时我们准备生成静态库:
右击项目,点击属性。
改变配置类型,将应用程序改为静态库,然后点击确定。
此时点击生成解决方案。
此时可以看到已经生成.lib文件。
来到项目文件目录,点击Debug文件夹。
此时能够看到对应生成的.lib文件。
此时我们有一个C++的项目,我们想要使用上面的AddC.lib静态库。
首先在新建项目中包含刚才静态库的头文件,此处是采用#include路径的方式,当然,我们也可以直接将.h头文件复制到当前项目的文件夹下然后直接将其添加到左侧的头文件选项中,然后在我们当前项目源文件中用#include指令直接进行包含。
==注意:此时我们仍然是无法运行的,在编译的时候可以正常通过,但是链接的时候会出现问题,因为程序在链接的时候出现了问题,无法找到对应的Add函数。==此时我们还需要进行配置。
右击项目,点击进入属性选项。在左侧的目录中点击链接器,点击常规。然后点击附加目录,点击向下的箭头,点击编辑。
先点击黄色文件夹图标,然后点击三个点标志。
接下来将建立静态库项目的Debug文件夹选择。
选择之后点击确定。
点击左侧的输入,在附加依赖选项中添加AddC.lib,用分号和其它文件进行分割,然后点击确定。
此时我们已经成功配置好了,运行,发现无法正常运行,会出现链接错误,如下图所示:
此时我们再将静态库Add.c文件改为Add.cpp文件,发现程序能够正常运行输出2,为什么会发生这种情况呢?
因为我们.c文件在生成符号表的时候,和cpp文件是不一样的,.c文件不会带函数参数的类型,但是.cpp文件会带函数参数的类型,而我们的测试项目是.cpp文件,如果静态库文件是.c文件当然是不可以的,此时属于.cpp调用.c的静态库,而我们改变后缀后就是.cpp项目调用.cpp的静态库,此时是正常的,这就是上述情况出现的原因。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
(https://img-blog.csdnimg.cn/img_convert/d8ab7fc5d6075b38ac5a6e7065f7376a.png)
此时我们已经成功配置好了,运行,发现无法正常运行,会出现链接错误,如下图所示:
此时我们再将静态库Add.c文件改为Add.cpp文件,发现程序能够正常运行输出2,为什么会发生这种情况呢?
因为我们.c文件在生成符号表的时候,和cpp文件是不一样的,.c文件不会带函数参数的类型,但是.cpp文件会带函数参数的类型,而我们的测试项目是.cpp文件,如果静态库文件是.c文件当然是不可以的,此时属于.cpp调用.c的静态库,而我们改变后缀后就是.cpp项目调用.cpp的静态库,此时是正常的,这就是上述情况出现的原因。
[外链图片转存中…(img-soMWsL2t-1715536281442)]
[外链图片转存中…(img-r5P0Dz3r-1715536281443)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!