一、命名空间
1.1、namespace的意义
在c/c++中可能有头文件里面包含的函数或者变量与自定义在全局域里面的函数或变量重名的,这样就会造成命名冲突,而namespace就是通过把自定义的函数、变量和自定义结构体隔离成到同的域中,避免冲突
比如下列代码:
cpp
#include<stdio.h>
#include<stdlib.h>
int rand = 0;
int main()
{
printf("%d ",rand);
return 0;
}
编译结果如下:

正确的方式是使用一个局部域把他们隔离开
cpp
#include<stdio.h>
#include<stdlib.h>
namespace wwm
{
int rand = 0;
}
int main()
{
printf("%d ",wwm::rand);
return 0;
}
" :: " 是作用域解析运算符:其作用是指定某作用域里面的成员进行操作
1.2、namespace的定义
1、namespace的用法:
namespace name(命名空间的名字) 最后再接一对{}
{}里面的都是命名空间的成员
**2、namespace的本质:**namespace实际上另外划分的一个域,这个域是与全局域相互独立的,不同域的函数和变量等可以同名同参
**3、c++中的域:**c++中有局部域、全局域命名空间域和类域。域影响的是编译时语法查找⼀个变量/函数/类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的⽣命周期,命名空间域和类域不影响变量⽣命周期。
所以namespace只能定义在全局域中
namespace还可以嵌套定义
比如:
cpp
#include<iostream>
using namespace std;
namespace wwm
{
int rand = 0;
namespace san
{
int rand = 10;
}
}
int main()
{
cout << wwm::rand << "\n" << wwm::san::rand << endl;
return 0;
}
而且在项目工程中多文件中定义的同名namespace会认为是同⼀个namespace,不会冲突。
1.3、命名空间的使用
当我们编译时,编译器就会到局部变量和全局变量中查找,不会进入命名空间中查找。所以当我们要去使用命名空间里面定义的成员时可以使用以下三种方式:
1、指定命名空间访问:(项目工程中推荐使用)
cpp
// 指定命名空间访问
#include<iostream>
namespace wwm
{
char a[] = "ymzwyjdnsxl";
}
int main()
{
printf("%s ",wwm::a);
return 0;
}
2、using将命名空间的某个成员展开,意思就是把该命名空间的成员暴露在当前作用域当中,并且展开之后就可以不用使用::(域解析运算符)指定命名空间访问了
cpp
#include<iostream>
namespace wwm
{
char a[] = "ymzwyjdnsxl";
int i = 731;
}
using wwm::i;//展开在全局域中
void test()
{
//printf("%s\n",a);//err
printf("%s\n",wwm::a);//因为在全局域中没有展开命名空间wwm中的成员a
//所以需要指定命名空间访问
}
int main()
{
using wwm::a;//展开在局部域中
printf("%s \n", a);
printf("%d \n", i);
return 0;
}
3、展开命名空间中的全部成员
也就是使改名吗空间的所有成员全部暴露在当前作用域中,展开之后该命名空间只在当前作用域有效
比如:
cpp
#include<iostream>
namespace wwm
{
char a[] = "ymzwyjdnsxl";
int i = 731;
}
using namespace wwm;//把命名空间wwm的的所以成员展开在全局域中
void test()
{
printf("%s\n",a);
}
int main()
{
//using namespace wwm;//把命名空间wwm的所有成员展开在局部域中
printf("%s \n", a);
printf("%d \n", i);
return 0;
}
二、C++的输入&输出
是InputOutputStream的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象。
其实很多编译器里面的都包含了c语言的标准库,所以我们也可以使用c语言的输入输出printf和scanf。
cin是C++库里面的标准输⼊流。
cin的用法:
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 10;
double b = 3.14;
char c = 'A';
//可以一次性输入一个或多个不同类型的数据
cin >> a >>;
cin >> b >> c; //自动识别数据类型并输入
return 0;
}
cout是C++库里面的标准输出流
cout的使用:
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 10;
double b = 3.14;
char c = 'A';
//可以一次输出一个或多个类型
cout << a <<endl;
cout << " " << b << " " << c << '\n' << "\n" << endl;
//自动识别数据类型并输出
return 0;
}
这里的'\n'、"\n"和endl都是换行
注意:
流插运算符和流提取运算符只能有一个右操作数
错误示范:
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 10;
double b = 3.14;
char c = 'A';
cin >> a b c; //错误示范,只能有一个右操作数
cout << a "" << b c << endl;//错误示范
return 0;
}
三、缺省参数
**1、缺省参数的定义:**缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数,缺省参数也可以叫(默认参数)。
2、缺省参数分为全缺省和半缺省
2.1、全缺省
全缺省就是全部形参都是给的缺省值
比如:
cpp
#include<iostream>
using namespace std;
int Add(int a = 10,int b = 20)
{
return a + b;
}
int main()
{
int a = Add();//当函数没有传参时,使用的是参数的默认值,也就是10,20
int b = Add(5);
//传参时使用的是实际的参数值,还有一个没有传参就同上,使用参数的默认值20
//只能从左到右传值
int c = Add(5, 15);//两个都传参了就都是用实际的参数值
//int d = Add(,10);//err
cout << a << " " << b << " " << c << endl;
return 0;
}
注意:
1、全缺省只能从左至右连续传值,且不能跳跃传值
2、我们在使用缺省参数时,函数的声明和函数的定义不能同时都给缺省参数,如果函数的声明里面给了缺省参数那么函数的定义里面就不能给缺省参数。
cpp
#include<iostream>
using namespace std;
int Func(int a = 2,int b = 1);//函数声明里面给了默认构造
void Print()
{
cout << Func() << endl;
}
int Func(int a,int b)//函数的定义里面就不给
{
return a + b;
}
int main()
{
Print();
return 0;
}
这是因为如果声明和定义给了不同的缺省值,编译器无法判断该用哪个(比如声明写func(int a=1),定义写func(int a=2),调用func()时默认缺省值是该用 1 还是 2?)
我们通常在函数的声明里面给缺省参数,这样调用者就能清楚地知道哪些参数可以省略
2.2、半缺省
半缺省就是部分形参给缺省值
cpp
#include<iostream>
using namespace std;
int Add(int a,int b = 20)
{
return a + b;
}
int main()
{
int b = Add(5);//没有给定缺省值的参数必须要传实参,给定了缺省值的参数就可以省略
int c = Add(5, 1);
cout << b << " " << c << endl;
return 0;
}
注意:
默认参数的设置必须遵循从右到左、连续指定 的原则 ------不能跳过右侧未设缺省值的参数,给左侧参数设缺省值。
四、函数重载
C++支持在同一域中出现同名的函数,但是这些函数的形参不能相同。
在C++中函数重载有三种方法:
1、参数类型不同
cpp
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()
{
cout << Add(10, 15) <<"\n" << Add(1.21, 12.12) << endl;
return 0;
}
2、参数个数不同
cpp
void f(int a)
{
cout << "f(int a)" << endl;
}
void f(int a,int b)
{
cout << "f(int a,int b)" << endl;
}
int main()
{
f(1);
f(1, 2);
return 0;
}
3、参数类型顺序不同
cpp
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(1,'a');
f('a', 1);
return 0;
}
注意:
1、返回值类型不能作为重载条件
2、有缺省参数的重载函数
cpp
void Pd(int a = 10)
{
}
void Pd()
{
}
int main()
{
Pd();
return 0;
}
结果:

五、引用
5.1、引用的概念和定义
5.1.1、引用的概念
引用其实就是给已经创建好的变量创建一个别名,编译器是不会给这个别名开空间的,也就是这个别名只是一个指向该变量的另一个名称,实际上这个别名就是这个变量。如果我么们对别名进行操作的话那也相当于对这个变量进行操作。
5.1.2、引用的定义
引用的语法
类型& 引用别名=引用对象;
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 5;
int& b = a;//b是a的别名
int& c = b;//c又是b的别名,我们可以对变量的别名取别名
int& d = c;
cout << a << " " << b << " " << c << " " << d << endl;
}
输出其实都是5,如果李白是a,一个人给李白取个外号叫李太白,另一个人给李白取外号叫诗仙。实际上这三个名字都是一个人,只是叫法不一样。
5.2、引用的特性
1、引用在定义时必须初始化
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 5;
//int& b;//错误,引用必须初始化
int& b = a;//正确
return 0;
}
2、一个变量可以有多个引用
所有层级的别名最终都指向最初的原变量,本质上都是同一个实体 ------ 操作任何一个别名,都会直接作用于原变量。
3、引用一旦引用一个实体,再不能引用其他实体(别名不能更改它的指向)
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 5;
int i = 0;
int& b = a;
int& c = b;//我们可以对变量的别名取别名
int& d = c;
//&b = i;//错误,引用一旦初始化后,就不能再指向别的变量
cout << a << " " << b << " " << c << " " << d << endl;
}
4、使用引用传参和使用引用做返回值
使用引用传参和使用引用做返回值能够减少拷贝带来的消耗使得程序更加高效并且还能通过改变引用对象从而实现对被引用对象的改变
5、引用传参其实和指针传参类似
6、引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。
5.3、const引用
1、我们可以引用⼀个const对象,但是必须对该对象使用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。
cpp
#include<iostream>
using namespace std;
int main()
{
const int a = 10;
//int& b = a;//err,因为对const对象进行引用必须使用const引用
//上面这条指令就是把权限放大了
const int& b = a;//对const对象进行引用必须使用const引用
int c = 0;
const int& d = c;//对普通对象可以使用const引用
//c被const引用之后,c的引用d就会权限缩小,也就是只能读,不能写了
//但是c的权限不会有任何改变
return 0;
}
2、在下类这一类场景下也需要使用const引用"int &b = a*c;double d = 1.234;const int &f = d;"等需要把运算结果或类型转换的结果保存在临时空间中这一类的情况下需要使用const引用
cpp
int a = 10;
const int& ra = 30;
// int& rb = a * 3;
//编译报错: "初始化" :⽆法从"int"转换为"int& "
const int& rb = a * 3;
double d = 12.34;
// int& rd = d;
// 编译报错:"初始化" :⽆法从"double"转换为"int& "
const int& rd = d;
其实临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象
5.4、指针和引用的关系
指针和引用各有各的用法,实际运用中也是相辅相成,各有特点,相互都不能代替的。
1、语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个地址的变量,需要开空间。
2、引用在定义时必须初始化,指针可以不强制初始化。
3、引用一旦初始化引用了一个对象之后,就不能在引用其他对象。而指针在初始化后,可以更改指向的对象
4、引用可以直接访问对象,而指针需要解引用之后才能访问对象
5、指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全一些。
6、 sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(因平台而异)
六、inline
inline是一个修饰函数的关键字,被inline修饰的函数叫内联函数。编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧了,就可以提高效率效率。
inline只是一个我们给编译器的一个建议,实际展开和调用还是得编译器做主。当我们对一个里面包含循环、递归、大量分支和代码的函数进行inline修饰,实际上编译器是不会展开在调用的地方展开内联函数,它会和对其他普通函数一样进行调用,也就是他会忽略我们的建议。
cpp
inline int Func(int i)
{
for (int a = 1;a<i;a++)
{
return a;
}
}
int main()
{
int Func(10);
return 0;
}
这样就不会展开内联函数
inline只使用于频繁调用的短小(可能是3~5行,看编译器)且无复杂结构的函数,被inline修饰的这类函数在进行编译时就会在调用的地方展开。
cpp
inline int Add(int a = 0,int b = 1)
{
return a + b;
}
int main()
{
int ret = Add(1, 2);
return 0;
}
注意:inline不能把声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
七、nullptr
在c语言中的NULL其实是一个宏定义:
cpp
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
在C++中使用NULL就会有问题,这是因为C++支持函数重载,这是因为NULL 被定义为整数 0,结合函数重载的 "参数类型匹配规则",会导致 NULL 无法区分 "整数 0" 和 "空指针"
比如我们使用函数重载来测试以下:
cpp
#include<iostream>
using namespace std;
void f(int x)
{
cout << "x = " << x << endl;
}
void f(int* x)
{
cout << "x is a null pointer" << endl;
}
int main()
{
f(NULL);
return 0;
}
结果:

当我们把NULL强转为void*之后:
cpp
#include<iostream>
using namespace std;
void f(int x)
{
cout << "x = " << x << endl;
}
void f(int* x)
{
cout << "x is a null pointer" << endl;
}
int main()
{
f(NULL);
f((void*)NULL);
// 报错原因:
// 1. C++中NULL本质是整数0,强制转void*后得到void*类型的空指针;
// 2. C++为了类型安全,禁止void*隐式转换为其他具体指针类型(如int*);
// 3. 函数f要求int*参数,void*无法隐式匹配,因此编译报错。
f((int*)NULL); // 编译通过原因:
// 1. 显式将整数0(NULL)转换为int*类型的空指针;
// 2. 转换后参数类型与f(int* x)完全匹配,无隐式转换需求,因此通过编译。
return 0;
}
所以c++中就使用nullptr来表示空指针。
nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换 成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被 隐式地转换为指针类型,⽽不能被转换为整数类型。