目录
- 1 命名空间 -- namespace
- 2 CPP的输入与输出(io)
- 2.1 输入输出流的一些规定
- 2.2 实操一下
- 2.3 关于endl
- 2.4 关于精度控制
- 2.5 效率提高
- 3 缺省参数(默认参数)
- 3.1 样例
- 3.2 全缺省与半缺省
- 3.3 缺省参数的意义
- 4 函数重载
- 4.1 函数重载的基本使用
- 4.2 函数重载调用歧义
这里是oldking呐呐,感谢阅读口牙!先赞后看,养成习惯!
个人主页:oldking呐呐
专栏主页:深入CPP语法口牙
1 命名空间 – namespace
namespace
是CPP中的一个关键字,namespace
的出现是为了改善C语言命名冲突的问题,我们来举个例子
#include <stdio.h>
//#include <stdlib.h>
int rand = 0;
int main()
{
printf("%d\n", rand);
return 0;
}
-
以上代码中我们没有包含头文件
stdlib.h
,编译顺利通过 -
但一旦我们包含头文件
stdlib.h
,编译会因为stdlib.h
中包含有rand
的定义而报错,也就是说C语言会有命名冲突的问题()
-
于是在CPP中我们引入了
namespace
的概念,尽可能地防止出现命名冲突的问题 -
定义上,我们可以创建一个自己的命名空间,然后同事啥的又可以创建一个他自己的命名空间
-
使用上,比方说我要使用自己的变量,就需要在变量名前加命名空间的名字和
::
(namespace's name::rand
) -
::
:域作用限定符,
#include <stdio.h>
#include <stdlib.h>
namespace oldkingnana
{
int rand = 0;
}
int main()
{
printf("%d\n", oldkingnana::rand);
return 0;
}
-
命名空间的这个操作就像是厕所一样,厕所有我家厕所,也有同事家厕所,都叫厕所你说你要用厕所,我咋知道你要用谁家的厕所?于是就加一个前缀说明一下,要用谁家的厕所
-
除了命名空间外,CPP其实还包括很多个不同的域
- 局部域(小区的公共厕所):里面的变量只能在局部用
- 全局域(市区的公共厕所):里面的变量哪儿都能用
- 命名空间域(某个人家里的公共厕所):里面的变量申请了才能用
- 类域(同样是某个人家里的公共厕所):里面的变量申请了都不一定能用
-
各家各户都可以拥有自己的厕所这个操作说明不同域里头可以有同名字的变量,同一个域里面不能有同名字的变量
-
不同的域之间会产生隔离,如果你想用其他域的变量,你得先写个前缀啥的
-
举个例子(类域我们后面再考虑):
#include <stdio.h>
#include <stdlib.h>
namespace oldkingnana
{
int rand = 0;
}
void fun()
{
int rand = 1;
printf("%d\n", rand);//局部域
}
int main()
{
printf("%d\n", oldkingnana::rand);//命名空间域
printf("%p\n", rand);//全局域
fun();
return 0;
}
- 我们知道访问变量,会优先查找局部的变量,再查找全局的变量,如果我们一定要在
fun()
函数里面,去访问全局的变量该怎么操作呢??
void fun()
{
int rand = 1;
printf("%d\n", rand);//局部域
printf("%p\n", ::rand);//全局域
}
-
如果
::
前面没有写任何类或者命名空间的名字,那就默认访问全局的变量 -
域影响编译的时候查找变量的/函数/类型出处(声明/定义)的逻辑,有了域隔离之后,就能避免名字冲突的问题,局部域和全局域除了会影响编译查找逻辑之外,还会影响变量的生命周期,命名空间域和类域不影响变量的生命周期
-
注意:命名空间域只能定义在全局,不能是局部
-
域里面可以包含函数,也可以包含结构,或者是类,使用方法也是同理即可,某某域里的某某变量/函数/结构/类
namespace oldkingnana
{
int rand = 0;
int Add(int x, int y)
{
return x + y;
}
typedef struct my_struct
{
int x;
int y;
}my_struct;
}
int main()
{
printf("%d\n", oldkingnana::Add(1, 2));
oldkingnana::my_struct My_Struct;
// (变量类型) | (变量名)
//命名空间的前缀是包括在变量类型里的
return 0;
}
- 其他要求:
- namespace只能定义在全局,但它可以嵌套定义
- 定义了多个命名空间会被认为是同一个命名空间
- CPP标准库都放在一个叫做
std
(standard
)的命名空间中
#include <iostream>
namespace ok
{
int WC = 0;
namespace ok_person
{
int WC = 1;
}
}
namespace ok
{
int WC1 = 3;
}
int main()
{
std::cout << ok::WC << ok::ok_person::WC << ok::WC1 << std::endl;
return 0;
}
-
cout
就放在<iostream>
的std
的域中,在<iostream>
我们能找到std
开始的位置
-
我们甚至能看到它引入了另一个文件
<ostream>
中的cout
-
所以说如果我们想用
cout
,就需要说明一下用std
标准库里头的cout
(开发者自己都怕命名冲突的问题) -
输出结果
-
同名字的命名空间会合并(逻辑上),包括不同文件下同名字的命名空间一样会合并
-
Add.h
#pragma once
namespace ok
{
typedef int AddType;
int Add(AddType x, AddType y);
}
- Add.cpp
#include "Add.h"
namespace ok
{
int Add(AddType x, AddType y)
{
return (int)(x + y);
}
}
- test.cpp
int main()
{
std::cout << ok::Add(1, 2) << std::endl;
return 0;
}
-
比方说我自己写了个栈,放在
Stack.h
和Stack.cpp
下,并且我不想有别人因为命名冲突影响代码运行,我把栈的所有代码都用oldkingnana
这个命名空间封装了,如果后面我想添加队列到我的命名空间里,就不需要找到命名空间再往里头写,直接开一个新文件,叫Queue.h
和Queue.cpp
即可,重新定义一个一模一样名字的命名空间即可 -
编译查找一个变量的时候,默认情况下,会优先查找局部域,然后是全局域,但不会去查找命名空间域,所以一般对于命名空间的访问,有以下几种
- 指定命名空间访问(推荐)(上面我们说过了用
::
,就不再提了) - 用
using
将命名空间的某个成员展开,项目中经常访问的成员且不存在冲突的成员比较推荐这种方式 - 展开命名空间所有的成员(不推荐,风险较大,自己用在小工程倒是不错)
- 指定命名空间访问(推荐)(上面我们说过了用
-
using
展开单独的某个成员
using std::cout;
using std::endl;
int main()
{
cout << 1 << endl;
return 0;
}
using
展开整个命名空间(相当于是把命名空间的变量变成全局的了)
using namespace std;
int main()
{
cout << 1 << endl;
return 0;
}
2 CPP的输入与输出(io)
2.1 输入输出流的一些规定
-
CPP引入了一个新的头文件
<iostream>
,我们在前文也见识过了,全称是"Input Output Stream",定义了标准输入输出对象 -
std::cin
是istream
类对象,面向窄字符的标准输入流 -
std::cout
是ostream
类对象,面向窄字符的标准输出流 -
std::endl
是一个函数,当他输入进流中的时候,相当于给流输入进一个换行符,并且刷新缓冲区 -
<<
用于把内容输入进流中,>>
用于把内容从流中输出 -
CPP输入输出不需要手动指定格式,只需要把内容扔到流里头就能输出出来,CPP会自动识别变量的类型(函数重载),甚至自定义对象也可以很好地输入输出,本质上
-
注意:
<<
和>>
只能有两个操作数,意味着你不能这样写
-
以上的情况就是用了三个操作数,当然是不可以的了
2.2 实操一下
using std::cout;
using std::endl;
using std::cin;
int main()
{
int i = 0;
int j = 0;
cin >> i >> j;
cout << i << j << endl;
return 0;
}
- 输入是同样的默认以空格和或者换行符间隔,和C同样的味道
2.3 关于endl
endl
并非一个字符或者变量什么的,endl本质是一个函数,这个函数的调用过程非常复杂,过程相当于是put了一个"\n"
,涉及函数重载的一些内容
2.4 关于精度控制
- CPP自己是带一套自己的精度控制逻辑的,目前来讲如果一定要控制精度,用原来C语言那套逻辑就行,CPP自己的需要用各种函数设置还是很没必要的
2.5 效率提高
- 加这三句代码可以在某些竞赛中提高输出效率,复杂的咱后面的文章再提
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
3 缺省参数(默认参数)
3.1 样例
#include <iostream>
void fun(int i = 0)
{
std::cout << i << std::endl;
}
int main()
{
fun();
fun(10);
return 0;
}
-
其实本质上就是定义函数的时候为形参设置一个默认参数,当形参没有接收到实参的时候就使用默认值
-
一般我们称这个默认参数为"缺省参数",当然直接叫默认参数也没问题,理解意思就行
-
了解过一些python就很能理解了
3.2 全缺省与半缺省
- 全缺省 – 每个参数都有缺省值
- 半缺省 – 一部分参数有缺省值
//半缺省
void fun1(int a, int b = 2, int c = 3)
{
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;
}
//全缺省
void fun2(int a = 1, int b = 2, int c = 3)
{
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;
}
int main()
{
fun1(1);
fun1(1, 11);
fun1(1, 11, 111);
fun2();
fun2(1);
fun2(1, 11, 111);
return 0;
}
- CPP规定:半缺省必须从右向左连续缺省,不能间隔跳跃给缺省值
int fun1(int a, int b = 1, int c, int d = 1)
{
return 0;
}
-
这么干的话就过不了编译
-
当然,半缺省也不能不传值,毕竟叫半缺省嘛
-
CPP规定:调用函数的时候,只能从左到右连续地传实参,不能跳越给实参
-
CPP规定:函数声明和定义分离时,函数声明和定义不能同时给缺省参数,只能在声明时给缺省参数
-
如果声明有缺省参数,定义也有缺省参数,那到底应该用哪个??所以进行了这样的规定
//正确写法
int fun1(int a, int b = 0, int c = 0, int d = 0);
int fun1(int a, int b, int c, int d)
{
return 0;
}
int main()
{
//fun1(1, , 3);
//fun1();
return 0;
}
3.3 缺省参数的意义
- 有时候我们写一个函数的时候,并不清楚以后需要传多大参数,这时候我们就可以用缺省参数
- 比方说,我们初始化栈的时候,开辟空间的大小,写函数的时候其实是不知道的,我们此时加一个缺省参数来规定开辟空间的大小,一般情况下我们不需要传大小进去,默认给个16字节,除非数据量一开始就很大很大,此时就可以手动设置参数,就很方便,灵活,还能尽可能少的进行后期扩容
4 函数重载
4.1 函数重载的基本使用
- CPP允许在同一个作用域中出现同名的函数,只需要这俩函数形参不同就行,类型不同或者个数不同,这表现出了函数的多态,使用上会更加灵活一些
- 同样啊,了解过python之后恍然大悟啊
- 调用的时候会根据实参自动匹配相对应的函数进行调用,老智能了!
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
double Add(double x, int y)
{
return x + y;
}
double Add(int x, double y)
{
return 0;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
cout << Add(1.1, 2) << endl;
cout << Add(1, 2.2) << endl;
return 0;
}
-
同样实现一个加法函数,这样就不用担心参数类型对不上的问题了,还是非常方便的
-
PS:仅返回值不同不能构成函数重载,因为调用的时候编译器依旧不知道你想调用哪个函数
4.2 函数重载调用歧义
- 以下情况下构成函数重载,但调用会出现错误,因为编译器认为你这俩函数调用起来好像都没问题
void fun()
{
}
void fun(int x = 0)
{
}
int main()
{
fun();
return 0;
}