文章目录
1、命名空间
1.1 为什么有命名空间
C++作为C语言的扩展,在C语言中,由于本身自带大量变量和函数名,在使用者进行命名的时候,为了避免出现命名冲突或命名污染,C++的开发者们创造了命名空间。
比如:
很明显命名冲突了
#include <stdio.h>
#include <math.h>
int abs = 1;
int main()
{
printf("%d\n", abs);
//编译报错:error c2365 "abs":重定义;以前的定义是"函数"
return 0;
}
如果我们一定要在引用math.h下定义abs为一个变量,并且使用它,C语言是实现不了的,这个时候就需要C++的命名空间。
1.2 定义命名空间
在C++中,有一个域作用限定符 : : ,它的作用是引用限定的域,默认情况下访问全局域。
比如:
#include <stdio.h>
int a = 0;
int main()
{
int a = 1;
printf("%d\n", a); //1
// :: 域作用限定符
// 默认访问全局
printf("%d\n", ::a); //0
return 0;
}
定义命名必须使用关键词 namespace,后面跟空间名,这个名可以自己取,之后再接{}。
#include <stdio.h>
#include <math.h>
//不影响 变量生命周期,只是改变限定域和编译查找规则
//定义在静态区
namespace N1
{
int abs = 10;
int x = 1;
}
void func()
{
//先在局部找,没有就从全局找
printf("%p\n", abs); //打印abs函数地址 地址随机
printf("%d\n", N1::abs); //通过域操作符打印N1命名空间中的abs。 10
printf("%d\n", N1::x); //通过域操作符打印N1命名空间中的x。 1
}
命名空间还可以嵌套使用,并且命名空间除了定义变量,还可以定义类型和函数
#include <stdio.h>
namespace N1
{
int abs = 10;
int x = 1;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
namespace N2
{
int a = 1;
struct Node
{
struct Node* next;
int val;
};
}
}
int main()
{
printf("%d\n", N1::Add(1, 2));
struct N1::Node node1;
// struct N2::Node node2; //err 由于 N2 在 N1 里 需要先调用N1
struct N1::N2::Node node3;
N1::x = 5;
N1::N2::a = 2;
return 0;
}
同一个项目中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
这也说明了如果在同一个文件如果有两个相同名称的命名空间,也会合并。
因为本质上,在预处理的时候,会将引用的文件展开,放在一个文件里,所以本质都是放在一个文件。
1.3使用命名空间
加命名空间名称以及作用域限定符
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 namespce N;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
在这里介绍一下C++ 的标准命名空间 std,并通过std完成三种hello world的写法。
第一种
using 将命名空间std展开,任意std里定义的都可以直接使用,这种叫做全部展开,但这有一个坏处,就是在复杂的std命名空间中,与我们自己定义的名称避免不了冲突,所以在大型项目避免全部展开std。
//1.
#include <iostream>
using namespace std;
int main()
{
cout << "Hello world!!" << endl;
return 0;
}
第二种
部分展开,指定std命名空间内展开,这是比较好的,因为能很好避免命名冲突。
//2.
#include <iostream>
using std::cout;
using std::endl;
int main()
{
cout << "Hello world!!" << endl;
return 0;
}
第三种
这种很规矩,不过第二种方法可以更加 便利。
//3.
#include <iostream>
int main()
{
std::cout << "Hello world!!" << std::endl;
return 0;
}
2、缺省参数
2.1 什么是缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值,在调用该函数时,如果没有指定实参,则采用该函数形参的缺省值,否则使用指定的实参。
void Func(int a = 0)
{
cout<<a<<endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
2.2 缺省参数分类
全缺省
void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
半缺省
出现形参和缺省值
void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
值得注意的是:
- 缺省参数只能从右往左连续给,不能间隔给。
- 函数定义和函数声明中不能同时出现缺省参数,缺省参数一般都设在声明中。(因为如果同时给,值又不同,编译器无法识别) 。
- 缺省参数只能是常量或全局变量。
2.3 缺省参数的应用场景
看代码:
//缺省参数的用法
namespace N1
{
//缺省参数不能在函数声明和定义中同时出现
//放在声明中
typedef struct stack
{
int top;
int* a;
int capacity;
}ST;
void StackInit(ST* st, int size)
{
st->a = (int*)malloc(sizeof(int) * size);
st->capacity = 0;
st->top = 0;
}
}
namespace N1
{
void StackInit(ST* st, int size = 4);
}
//缺省能让函数在多种场景下操作
//不能直接修改函数定义
int main()
{
//未知大小时,使用默认size = 4.
N1::ST st1;
StackInit(&st1);
//已知大小 只需在定义的时候改就行,如果是直接定义变量,在未知大小就无法传值了。
N1::ST st2;
StackInit(&st2, 100);
return 0;
}
3、函数重载与缺省参数
3.1 什么才是函数重载
函数重载的前提,一定是函数在同一作用域下才会有重载。
第一种:
函数重载 函数名相同,参数不同(类型)
//函数重载 函数名相同,参数不同(个数、类型、顺序)
int add(int x, int y)
{
return x + y;
}
double add(double x, double y)
{
return x + y;
}
void swap(int* px, int* py)
{
int tmp = *px;
*px = *py;
*py = tmp;
}
void swap(double* px, double* py)
{
double tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
cout << add(1, 2) << endl;
cout << add(1.1, 2.2) << endl;
int a = 0, b = 1;
swap(&a, &b);
double c = 1.1, d = 2.2;
swap(&c, &d);
cout << a << endl;
cout << c << endl;
return 0;
}
第二种:
函数重载 函数名相同,参数不同(顺序)
顺序不同是形参类型顺序不同
void f(int a, char b)
{
cout << "f(int a, char b)" << endl;
}
void f(char a, int b)
{
cout << "f(char a, int b)" << endl;
}
void f(int a, int b)
{
cout << "f(int a, int b)" << endl;
}
void f(char a, char b)
{
cout << "f(char a, char b)" << endl;
}
第三种:
函数重载 函数名相同,参数不同(个数)
//参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();
f(1);
f(0, 'A');
f('A', 1);
return 0;
}
3.2 构成函数重载,但编译不能通过
拥有缺省参数的f()和无参数的f(),拥有函数重载参数个数不同的要求,但是 f() 在调用时编译器不能识别,所以编译不能通过,也就报错了。
//构成函数重载 -- 但f()调用会报错,存在歧义
void f()
{
cout << " f() " << endl;
}
void f(int a = 0, char b = 1)
{
cout << "f(int a, char b)" << endl;
}
int main()
{
f(10);//可以编译
f(10, 20); //可以编译
/*f();*/ //err 歧义 二义性
return 0;
}
3.3 函数重载的原理–命名修饰
一个程序的运行,需要通过预处理、编译、汇编和链接,在汇编中会有一个符号表的形成,在C语言中,符号表主要包括函数名和函数地址,而在C++中,符号表通过对函数名进行了修改,加上了函数的参数类型,这就使得C++出现了函数重载。
如以下一串代码
int Add(int a, int b)
{
return a + b;
}
void func(int a, double b, int* p)
{}
int main()
{
Add(1, 2);
func(1, 2, 0);
return 0;
}
C程序正常都是单单函数名加地址,
而C++程序 在Linux环境下通过反汇编看到,函数Add和func的函数名都进行了改变_Z3代表长度,Addii分别代码函数名和参数类型。
所以为什么函数重载有参数类型、参数类型顺序和参数类型个数这几种情况。
下面讨论一个问题:
函数返回类型不同是否能作为函数重载条件?
如果按上述所说,我们将函数返回值也加入命名不就行了。
答案是不能,为什么呢?
看代码
如果这种方式能构成重载,那么在函数调用的时候,并没有什么能够显示返回值的类型,编译器无法区分,所以返回值不用加入命名规则,所以也就不构成重载。
int func(int a, double b, int* p)
{
return a;
}
double func(int a, double b, int* p)
{
return b;
}
int main()
{
func(1, 2, 0);
func(1, 2, 0);
return 0;
}
本章完~