目录
1、命名空间
命名空间的出现是为了解决C语言中出现的命名冲突问题,关键字namespace
include<stdio.h>
include<stdlib.h>
int rand=0;
int main()
{
printf("%d",rand);
return 0;
}
//程序运行会报错:rand变量和头文件<stdlib.h>中的rand()函数的冲突问题
用namespace关键字解决上面的问题
include<stdio.h>
include<stdlib.h>
namespace name
{
int rand=0;
}
int main()
{
printf("%d",name::rand);
return 0;
}
1.1、域作用限定符 ::
域包括:全局域、局部域、命名空间域、类域等
域作用限定符的使用-> 命名空间域的名称 :: 该命名空间内的变量名 ----- 到该命名空间域中访问该变量
例子:
#include<stdio.h>
int x = 0;
namespace example
{
int x = 1;
}
int main()
{
int x = 2;
printf("%d\n", x);//访问的局部变量
printf("%d\n", ::x);//访问的全局变量
printf("%d\n", example::x);//访问的命名空间example中的变量
return 0;
}
小结:
编译器的搜索原则
- 不指定域,先局部再全局
- 指定域,直接到指定域搜索
1.2、命名空间的定义
namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员
//命名空间域里面可以定义变量、结构体、函数等等
#include<iostream>
namespace name1
{
int x = 0;
int Add(int a, int b)
{
return a + b;
}
}
namespace name2
{
struct Node
{
int val;
struct Node* next;
};
}
int main()
{
printf("%d\n", name1::Add(1, 2));
printf("%d\n", name1::x);
struct name2::Node list;//使用name2域中的结构体声明变量list
return 0;
}
1.3、命名空间的使用
①使用域作用限定符
int main()
{
printf("%d",N::a);//访问N域里面的变量a
return 0;
}
②使用using将命名空间中某个成员引入
using N::b;
int main()
{
printf("%d",b);//访问N域里面的变量b
return 0;
}
③使用using namespace 命名空间名称 引入
using namespace N;//直接将N里面的所有內容展开到全局域中,可以随意访问
int main()
{
printf("%d",b);//访问N域里面的变量b
return 0;
}
2、缺省参数
2.1、 缺省参数概念:就是不给形参赋值时,会有默认值void Func(int a=0)
{
cout<<a<<endl;
}
int main()
{
Func();//打印0
Func(10);//打印10
return 0;
}
2.2、缺省参数分类
①全缺省
//全缺省:所以参数均有缺省值
void func(int a=0,int b=1,int c=2)
{
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
}
int main()
{
//全缺省在给参数时,不能跳跃着给,要按顺序
func();
func(10);
func(10,20);
func(2,3,4);
return 0;
}
②半缺省
//半缺省的缺省值要从右往左连续给
void func(int a,int b,int c=2)
{ //左 <- 右
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
}
int main()
{
func(1,2);
func(2,3,4);
return 0;
}
注意:
- 缺省值只能是常量或者全局变量
- 缺省参数不能在函数定义和声明中同时出现
- 缺省函数在声明和定义分离的情况下,缺省参数在函数声明中给
3、函数重载
3.1、函数重载概念:在同一作用域内,定义了函数名相同,但参数类型不同、参数个数不同、参数类型顺序不同的同名函数,构成重载函数
int add(int x,int y)
{
return x+y;
}
//参数类型不同
double add(double x,double y)
{
return x+y;
}
//参数个数不同
int add(int x,int y,int z)
{
return x+y+z;
}
//参数类型顺序不同
int add(double x,int y)
{
return x+y;
}
4、引用
4.1、引用概念:引用就是给已经存在的变量取一个别名,编译器不会给别名单独开辟内存空间,它和它的引用对象共用同一块内存空间比如:李逵,在家叫铁牛,在江湖上叫黑旋风,不论是铁牛还是黑旋风,他们指的都是李逵
//类型& 引用变量名=引用实体;
int main()
{
int a=10;
int& b=a;//定义引用类型
cout<<a<<endl;//10
cout<<b<<endl;//10
printf("%p",&a);
printf("%p",&b);
//两个变量的地址是一样的
return 0;
}
注意:引用类型和引用实体类型要是同种类型(或者是可以隐式转换为引用类型的对象)
4.2、引用特性:
①引用在定义时必须初始化
②一个变量可以有多个引用
③引用一旦引用一个实体,就不可以再引用其他实体
int main()
{
int a=10;
//int& r;不初始化编译器会报错
int& ra=a;
int& raa=a;
return 0;
}
4.3、常引用
int main()
{
const int a=10;
//int& r=a;a是常量,不能这样引用,会报错,这样引用相当于扩大权限,本来a不能改的,现在可以通过变量r来改变,这样显然是不合理的
const int& ra=a;//权限要平移才可以,这样引用常量就没问题
const int&b=10;//直接引用常量10也是同理
double c=13.14;
//int& rc=c;这句会报错,因为编译器不允许非const引用绑定到不同类型的对象上,引用类型和引用实体类型要是同种类型(或者是可以隐式转换为引用类型的对象),double不能隐式转化为int类型,并且会丢失精度,所以会报错
const int& ret=c;
//这里不会报错是因为C++允许const引用绑定到临时对象上,即使这些临时对象类型和引用对象类型不完全匹配,这种情况下,编译器会创建一个临时的int对象,该对象通过double类型的值c转化为int类型得到的,const引用ret引用的就是这个临时对象
return 0;
}
总结:
- const引用可以绑定到临时对象上,因为编译器知道你不会通过这个引用来修改它所引用的值
- 绑定过程中会进行类型转换,这里是double到int的转换,但只有当引用是const时,这种转换才是允许的。
- 尽管引用ret在语法上看起来像是直接引用变量c,但实际上它是引用了一个临时的int值。每次通过ret访问值时,都会重新进行这个转换
4.4、引用的使用场景
①做参数
②做返回值
int& add(int x, int y)
{
int c = x + y;
return c;
}
int main()
{
int& ret = add(1, 2);
add(2, 3);
cout << ret << endl;
return 0;
}
//这里的ret的结果是5,add函数运行结束后,该函数对应的栈空间就会被回收,局部变量c也就没有意义了,ret实际上就是引用了一块已经被释放的空间,再次调用add函数时,ret引用的c位置就被修改成了5,因此ret的值也改变了
注意:
- 函数返回时,如果返回对象是临时变量(出了函数作用域不在的)必须用传值返回,反之,用引用返回
- 以值作为参数或者返回值类型时,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低
4.5、引用和指针的区别
引用在语法概念上没有独立的空间,就是一个别名,和引用对象共用一块空间,但是在底层是有空间的,因为引用是按照指针的方式来实现的
不同点:
- 引用概念上定义一个变量的别名,指针存储一个变量的地址
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能在引用其他实体,而指针可以随时指向同类型的实体
- 没有NULL引用,但有NULL指针
- 在sizeof中的含义不同,引用结果为引用类型的大小,但指针始终都是地址空间所占的字节数(32位平台:4个字节,64位平台:8个字节)
- 引用自加为引用实体加1,指针自加表示向后偏移一个类型的大小
5、内联函数
5.1、概念:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用
加了关键字inline,在编译阶段直接完成函数体内的操作,不需要建立栈帧
5.2、inline的特性
- inline是一种以空间换时间的做法,如果编译器将函数当作内联函数来处理,在编译阶段,会用函数体替换函数调用,这样可能会导致目标文件变大,但少了调用开销,提高了运行效率
- inline不建议声明和定义分离,分离会导致链接错误,因为inline被展开了,就没有函数地址了,链接就会找不到
- 适合函数规模较小的函数,不是递归且频繁调用的函数采用inline修饰,否则编译器会忽略掉inline的特性
内联只是向编译器发出请求,编译器可以忽略
6、nullptr
NULL其实是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量,显而易见,这样会出现一些分歧
void fun(int x){}
void fun(int* p){}
int main()
{
fun(0);
fun(NULL);
fun((int*)0);
return 0;
}
//fun(NULL)本身是想去调用传指针的函数,但是他会被理解为字面量0调用func(int)类型的函数,这和他想调用的完全不一样,只能去进行强制类型转换,才可以调用fun(int* p)
C++为了解决这一缺陷,衍生出了关键字nullptr,nullptr仅表示指针空指,在后续完全可以使用nullptr代替NULL避免出现不必要的问题,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同