C++学习传送门
在编写项目时,我们会包含很多头文件,这会造成出现大量的变量、函数和类等,而这些类、函数、变量的名称都存在与全局作用域中会产生命名冲突和命名污染,也不便于我们进行自定义类和函数。
而namespace的作用就是:定义一个新的作用域,将一些类、函数、变量等写在其中。可以实现对标识符名称的本地化,防止命名冲突和污染。
写法如下:
namespace scope
{
int i;
int add(int a,int b)
{
return a + b;
}
strcut Node
{
struct Node* next;
int data;
}
}
命名空间也可以嵌套定义
namespace N1
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N2
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
同一个项目中,在不同的文件里可以定义多个相同名字的命名空间,链接器最终会将它们链接成同一个命名空间
以下以使用命名空间std为例:
第一,可以在每次使用std内的成员时,加上命名空间的名字std 和 作用域限定符::
int main()
{
int a=10;
std::cout<<a<<std::endl;
return 0;
}
第二,可以通过using使用所要访问的成员
using std::cout;
int main()
{
int a=10;
cout<<a<<std::endl;
return 0;
}
第三,通过using直接使用整个命名空间
using namesapce std;
int main()
{
int a=10;
cout<<a<<endl;
return 0;
}
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。
在函数定义与声明分离时,编译器要求只能在声明时指明缺省值,而定义时不可以。
void Func(int a = 0)
{
cout<<a<<endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
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;
}
1. 半缺省参数必须从右往左依次来给出,不能间隔着给
2. 缺省参数不能在函数声明和定义中同时出现(一般规定在声明中出现)
3. 缺省值必须是常量或者全局变量
4. C语言不支持(编译器不支持)
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这
些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型
不同的问题。
注意三种区分依据:参数个数、参数类型、参数类型的排列顺序
不能通过返回值类型定义函数重载
#include
using namespace std;
// 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;
}
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
// 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()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
f(10, 'a');
f('a', 10);
return 0;
}
c++中,当程序进行链接处理时,函数名字将会被修饰成【_Z+函数名字长度+函数名+类型首字母】。
如: int add(int a ,int b); 会被修饰为Z3addii
void test(char* arr,int i); 会被修饰为Z4testPci 大写P代表指针
由此也可知道返回值类型不同无法构成函数重载,因为只有返回值不同的函数在名字修饰后仍然相同。
不过如果两个函数通过参数类型、参数个数、参数类型顺序不同已经构成重载,此时这两个函数即使返回值类型不同也可以构成重载
简而言之:重载函数返回值类型可以不同,但不能只通过返回值类型不同来构成重载函数
全缺省函数和无参数函数重载时会产生二义性
如下代码中,编译器将无法决定调用哪一个Test函数。
void Test(int x = 0, int y = 0)
{
cout << x + y << endl;
}
void Test()
{
cout << "empty" << endl;
}
int main()
{
Test();
return 0;
}
引用不是创建一个新变量,而是为变量取别名。
引用变量和实体变量共用一块内存空间。
int main()
{
int a = 10;
int& b = a;
cout << a << ' ' << b << endl;
cout << &a << ' ' << &b << endl;
return 0;
}
(1)引用变量在定义时必须初始化,且引用不会为空
//不对引用初始化会报错
int& a;
//引用的类型要与实体数据类型保持一致
//初始化
int b=10;
int& ra=b;
(2)引用一旦引用一个实体变量,就不可以再引用其他实体变量
(3)一个实体变量可以有多个引用
int main()
{
int a = 10;
int& ra = a;
int& rra = a;
cout << a << ' ' << ra <<' ' <<rra<< endl;
cout << &a << ' ' << &ra <<' '<<&rra<< endl;
return 0;
}
(4)引用变量相较实体变量的权限不可放大
int main()
{
//权限不变
int a = 10;
int& ra = a;
//权限放大(错误)
const int b = 20;
int& rb = b;
//权限缩小
int c = 30;
const int& rc = c;
//对常量也要保证权限不放大
const int& r = 10;
return 0;
}
引用作为返回值的前提:引用的实体在函数调用结束后不会随着栈帧销毁。
int& test()
{
static int a=10;
return a;
}
如果我们对出了函数作用域就会销毁的对象使用引用返回,就会产生类似与c语言中使用野指针访问已释放的动态空间的结果:动态空间释放后,指向动态空间的指针未被赋值为NULL,而我们又再次使用该指针访问所指向的空间,将会得到随机数。因为在动态空间释放后,这片内存虽然未消失,但不再受保护,可能已被挪用,也可能保持不变。
例如:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;
return 0;
}
上述代码的最终结果是7
原因便是在第一次调用add函数时,系统在栈区开辟了空间作为函数栈帧,在函数栈帧内分配空间创建了变量c,然后c被赋值为3,c返回给引用接收。最后函数栈帧销毁,变量c也被销毁,ret所访问的空间不再受保护。
第二次调用add函数,系统在同样的栈区创建栈帧,然后在同样的位置再次创建变量c,c被赋值为7,返回c。这次没有引用接收返回值。但ret所访问的空间仍是c当初所在的空间。所以最后打印7。
此次所打印值是7,而不是随机值,完全属于碰巧现象,两次调用所创建的栈帧和局部变量位置重合。但我们绝不能对这种访问抱有侥幸心理。对于在栈桢销毁时,同时销毁的局部变量绝不能进行引用返回!
引用作为返回值的优势
第一,减少拷贝,提高效率。
第二,可以修改返回值。
内存空间的销毁意味着什么?
第一,内存仍然存在,只是该内存的所有不再是我们,我们所存入的数据不再受保护,可能会被系统覆盖。
第二,我们仍然能够访问该内存,只是读取的数据随机。
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
引用作为参数的优势
第一,减少拷贝,提高效率。
第二,作为输出型参数,可以在函数内改变外部变量。
在进行强制类型转换、传值调用、函数返回值时会创建临时变量,而临时变量具有常性,我们可以粗浅地理解为临时变量被const修饰。
因此当我们使用引用执行会产生临时变量的操作时,应当使用const 修饰引用,保持权限的一致。
当我们对double类型变量进行int类型引用,编译器会因为类型不一致而报错
double a=3.13;
int& b=a
而当我们对变量a进行强制类型转换后,编译器仍会报错
double a=3.14;
int& b=(int)a;
原因就是在进行强制类型转换时,编译器并不是直接将a转换再赋值给b,而是隐性地拷贝a生成了一个临时变量,然后对临时变量进行转换在赋值给b。完成工作的临时变量便随即被释放。
而错误的原因便在于:临时变量具有常性。引用绝不可以放大权限,应当使用const修饰引用!
我们使用const修饰后,就编译成功。
double a=3.14;
const int& b=(int)a;
在我们学习c语言时,就已经知道所谓的传值调用就是将实参的一份拷贝赋值给形参。而在拷贝过程中就会产生临时变量,而为了满足临时变量的常性,我们常常需要使用常引用作为形参。
函数结束前的返回值也是通过生成临时变量实现的。当我们使用引用接收返回值时应注意const的使用。
被inline修饰的函数在编译时就会直接展开,所以在被调用时会直接执行而不创建栈帧。
这是一种以空间换时间的做法,省去栈帧的创建与销毁的过程,可节省时间,提高效率。
其实在C语言中的宏函数也可实现类似的作用,而c++中补充内联函数的原因在于宏函数的三个劣势。
1.宏函数在预处理阶段会直接进行替换,无法进行调试工作
2.宏函数没有类型安全检测
3.宏函数极易写错
第一,内联函数只是向编译器的一个请求,编译器可自行决定是否执行内敛操作;
第二,递归函数,或函数体归于大(一般超过十行)的函数不适合使用内联,编译器也不会执行内联。因为对较大的函数执行内联,会因为展开函数而造成代码膨胀,而代码膨胀的后果就是可执行文件过大。
第三,在定义内联函数时不支持声明与定义的分离。因为在多文件编程中,一旦对声明与定义分离的函数加上inline进行内敛操作,编译器就不会在汇编阶段形成内联函数对于的符号表。这将导致多个目标文件中的内函数在进行链接时找不到对应的定义,最终产生链接错误。
这对于函数体过大的函数同样适用。即使我们错误的对不该使用inline的函数进行内联,并且编译器没有通过这个请求。但在汇编时,编译器还是会因为函数前的inline而不在生存其对应的符号表。
因此内联函数绝不可以声明与定义分离
第四,的不过版本中编译器无法实现内联操作。因为内联执行的函数展开与debug版本保存编译信息的目的相悖。