一、命名空间
1.1 命名空间的价值
c语言项目普遍存在着命名冲突的问题,C++引入namespace就是为了更好的解决这一问题。
我们来看下面的代码:
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
printf("%d\n", rand);
return 0;
}
如果我们编译上面的代码,编译器会给我们报一个错误:“rand”: 重定义;以前的定义是“函数”
为了解决这样的问题,在C++中引入了命名空间。
2.2 命名空间的定义
1.命名空间的普通定义:
namespace zj
{
int a;
double b;
int add(int x, int y)
{
return x + y;
}
}
这就是命名空间的普通定义,在命名空间内既可以定义变量也可以定义函数,zj是命名空间的名字。
2.命名空间的嵌套定义:
namespace zj
{
int a;
double b;
int add(int x, int y)
{
return x + y;
}
namespace zhangsan
{
int x = 0;
int add(int x, int y)
{
return (x + y) * 10;
}
}
}
命名空间只能定义在全局,但命名空间是可以嵌套的,在一个命名空间内输入另一个命名空间。
3.项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace,不会冲突。
namespace zj
{
int a;
double b;
}
namespace zj
{
int add(int x, int y)
{
return x + y;
}
}
2.3 命名空间的使用
#include<stdio.h>
namespace zj
{
int a = 0;
int b = 1;
}
int main()
{
printf("%d\n", a);
return 0;
}
我们在执行上述代码时,会发生报错,显示a是未声明的标识符。
这其中的原因在于编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间⾥⾯去查找,所以我们查找不到命名空间里的a变量,导致了这个报错。
下面我们来讲讲命名空间的三种使用方法:
1.指定命名空间访问
先看下列代码:
#include<stdio.h>
namespace zj
{
int a = 0;
int b = 1;
}
namespace zhangsan
{
int a = 1;
int b = 2;
}
int main()
{
printf("%d\n", zj::a);
printf("%d\n", zhangsan::a);
return 0;
}
在上述代码中我创建了两个命名空间,一个是zj一个是zhangsan,他们中都创建了两个变量,我们可以使用::来访问指定的命名空间,像zj::a就是访问zj命名空间的a变量。
结果:
#include<stdio.h>
namespace zj
{
int a = 0;
int b = 1;
}
namespace zhangsan
{
int a = 1;
int b = 2;
}
using zhangsan::a;
int main()
{
printf("zj::a=%d\n", zj::a);
printf("zhangsan::a=%d\n", a);
return 0;
}
与第一种方法不同的是,我们增加了using zhangsan::a,他的意思是将zhangsan命名空间中的a成员展开,这样我们便能在全局中查找到zhangsan命名空间的a变量,所以我们将第二个printf的输出修改为a,结果与第一种方法是一样的。
结果:
3.展开命名空间中全部成员
#include<stdio.h>
namespace zj
{
int a = 0;
int b = 1;
}
namespace zhangsan
{
int a = 1;
int b = 2;
}
using namespace zhangsan;
int main()
{
printf("zj::a=%d\n", zj::a);
printf("zhangsan::a=%d\n", a);
printf("zhangsan::b=%d\n", b);
return 0;
}
与第二种方法只将命名空间内的某一个成员展开不同,第三种方法是将命名空间全部展开,像上述代码便将命名空间zhangsan全部展开,所以我们在全局中能找到命名空间zhangsan的a、b变量,所以我们在printf上输出的a、b变量其实就是命名空间zhangsan内的变量a、b.
加入了printf变量后,便于输出函数printf重名了,导致编译器识别不出来printf是输出函数还是常量printf,就会报错,所以这种方法是是有很大弊端的,如果你将两个或以上的命名空间同时展开,而且这些命名空间里又有重名的变量,便会报错,所以不推荐使用这种方法。
二、C++的输入输出
C++的输入输出函数包含在头文件<iostream>中,<iostream> 是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象。
相比与C语言的输入输出函数,C++的输入输出函数要更加高级,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式,C++的输入输出可以⾃动识别变量类型,其实最重要的是C++的流能更好的⽀持⾃定义类型对象的输⼊输出。
在C++中,cout是输出函数,cin是输入函数,endl是换行,这三个函数都属于C++标准库,而C++的标准库都存放在命名空间std中,所以我们要通过命名空间的使用方式去使用他们,一般在日常生活的练习当中,为了寻求方便,我们一般会将整个命名空间std全部展开,当然也仅限于日常练习,在其他时候还是不要轻易的将命名空间全部展开,前面已经说过了他的弊端。
下面我们通过一段简单的代码来了解C++的输入输出。
int main()
{
int a;
double b;
cin >> a;
cin >> b;
cout << "" << a << " " << b << endl;
return 0;
}
结果:
三、缺省参数
void func(int x = 20)
{
cout << "" << x << endl;
}
int main()
{
func();
func(10);
return 0;
}
函数的参数int x=20就是缺省参数,在调用函数时,如果没有传递参数,那么就会采用缺省参数,传递了参数就要所传递的参数。
结果:
可以看到,在未传递参数时,输出的值为缺省参数20,传递参数时,输出的便是所传递的参数。
3.1 全缺省
void func(int x = 20,int y=30,int z=40)
{
cout << "" << x+y+z << endl;
}
int main()
{
func();
func(10);
func(10, 20);
func(10, 20, 30);
return 0;
}
如上图代码所示,函数func就是全缺省函数。
结果:
3.2 半缺省
void func(int x ,int y=30,int z=40)
{
cout << "" << x+y+z << endl;
}
int main()
{
func(10);
func(10, 20);
func(10, 20, 30);
return 0;
}
上图所示的函数func便是半缺省函数,因为半缺省函数不能间隔跳跃给缺省值,所以像这种形式void func(int x ,int y=30,int z)的半缺省便是错的。
注意:函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值
四、函数重载
4.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;
}
int main()
{
int ret1=Add(3, 5);
cout << "" << ret1 << endl;
double ret2=Add(3.4, 6.6);
cout << "" << ret2 << endl;
return 0;
}
这便是参数类型不同的函数重载。
结果:
4.2 参数个数不同
来看下面的代码:
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();
f(10);
return 0;
}
这便是参数个数不同的函数重载。
结果:
4.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()
{
f(3, 'a');
f('a', 3);
return 0;
}
这便是参数类型顺序不同的函数重载。
结果:
五、引用
类型& 引⽤别名 = 引⽤对象;
int main()
{
int b = 10;
int& c = b;
cout << "" << &b << endl;
cout << "" << &c << endl;
}
上述代码便是引用,相当于给b取了个别名叫c,输出的结果我们能看到b和c的地址应该是一样的。
结果:
5.1 引用的特性
不初始化的话编译器会报错。
2.⼀个变量可以有多个引⽤
可以看见,对于a的其他引用的地址都是a的地址。
3. 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体
b是a的引用,c的值为20,b=c其实是将c的值赋给b,而不是将b改为c的引用,所以最后a的值被改为了20。
5.2 引用的使用
1.引用做参数
引用做参数就和指针做参数一样,形参可以影响到实参,我们用最经典的swap函数来验证一下。
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0, y = 1;
cout << x << " " << y << endl;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}
这就代表着以后我们想要让形参影响实参,可以不再传指针,而是可以传引用。
5.3 const引用
下面我来距离几种const引用比较容易出错的地方:
1.
这时第一种可能出错的情况,这里的引用是对a访问权限的放大,在int前加个const就好。
2.
这时第二种可能出现的情况,因为const修饰的常量,所以我们不能对常量做任何修改。
int main()
{
const int a = 10;
const int& ra = a;
int b = 20;
const int& rb = b;
return 0;
}
这两种对const的引用才是正确的。
六、引用与指针的关系
七、内联函数
inline int Add(int x, int y)
{
int ret = x + y;
ret += 1;
ret += 1;
ret += 1;
return ret;
}
int main()
{
int ret = Add(1, 2);
cout << Add(1, 2) * 5 << endl;
return 0;
}
这时我们通过调试进到反汇编中,如果在反汇编中,发现有call Add语句就是没有展开,否则就是展开了
我们可以看到,里面的指令有call且有Add函数,所以内联函数Add是没有被展开的,大家也可以删减一下代码再去看看是否被展开。
八、nullptr
nullptr是C++中的空指针,与C语言的空指针NULL不同,在C++中,NULL代表的是0。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
所以在C++中,NULL会被定义为字面常量0,所以C++
#include<iostream>
using namespace std;
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
f(nullptr);
return 0;
}
结果:
由此可见,0和NULL都被当做是常量,将NULL强制转换为int*也变成了指针,所以(int*)NULL和nullptr都是指针。
九、总结
上面所讲的都是C++一些入门的基础知识,如果觉得有帮助的话,别忘了一键三连,谢谢各位。