博客主页:阿博历练记
文章专栏:c++
代码仓库:阿博编程日记
欢迎关注:欢迎友友们点赞收藏+关注哦
函数重载:是函数的一种特殊情况,C语言不允许同名函数,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(
参数个数
或类型
或类型顺序
)不同,友友们注意这里不比较返回值,常用来处理实现功能类似数据类型不同的问题.
1.参数类型不同
#include
using namespace std;
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(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
return 0;
}
2.参数个数不同
#include
using namespace std;
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();
f(10);
return 0;
}
#include
using namespace std;
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(10, 'a');
f('a', 10);
return 0;
}
❌误区一:
#include
using namespace std;
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(int b, char a)
{
cout << "f(int b, char a)" << endl;
}
int main()
{
f(10, 'a');
f('a', 10);
return 0;
}
友友们注意,函数重载是类型顺序不同,而不是形参的名字顺序不同.
❌误区二:
①
#include
using namespace std;
namespace N1
{
void func(int x)
{
}
}
namespace N2
{
void func(double x)
{
}
}
int main()
{
return 0;
}
#include
using namespace std;
namespace N1
{
void func(int x)
{
}
}
namespace N2
{
void func(int x)
{
}
}
int main()
{
return 0;
}
可能友友们会认为第①种构成函数重载,但是如果第一种构成重载的话,现在第②种很显然已经不是函数重载了,为什么程序还是可以编译通过呢,所以我们这里就可以从反面验证这里不构成函数重载,这里主要就是这两个函数不在同一个作用域里面,而函数重载要求在同一个作用域里面.
❌误区三:
#include
using namespace std;
void Func(int a)
{
cout << "void Func(int a)" << endl;
}
void Func(int a,int b=10)
{
cout << "void Func(int a,int b)" << endl;
}
int main()
{
/*Func(1);
Func(1, 2);*/
return 0;
}
友友们注意,这里构成函数重载,因为这两个函数参数的个数不同,与缺省参数没有关系.
#include
using namespace std;
void Func(int a)
{
cout << "void Func(int a)" << endl;
}
void Func(int a,int b=10)
{
cout << "void Func(int a,int b)" << endl;
}
int main()
{
Func(1);
Func(1, 2);
return 0;
}
虽然它们构成重载函数,但是在调用的时候会存在歧义,比如当实参只有1时,这两个函数都可以调用,编译器就不知道该调用谁了,所以就会产生调用不明确的报错.
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间,引用类型必须和引用实体是同种类型的.
一.引用做参数
引用示例1:
1.传址交换
#include
using namespace std;
void swap(int*x1,int*x2)
{
int tmp = *x1;
*x1 = *x2;
*x2 = tmp;
}
int main()
{
int a = 3;
int b = 5;
swap(&a, &b);
cout << a << endl;
cout << b << endl;
return 0;
}
2.引用交换
#include
using namespace std;
void swap(int&x1,int&x2)
{
int tmp = x1;
x1 = x2;
x2 = tmp;
}
int main()
{
int a = 3;
int b = 5;
swap(a, b);
cout << a << endl;
cout << b << endl;
return 0;
}
所以友友们当我们使用引用之后,就可以不用指针了,因为x1就是a的别名,x2就是b的别名,所以它们两个就是a和b,我们只需要直接交换就可以了.
引用示例2:
1.二级指针
#include
#include
using namespace std;
typedef struct
{
int val;
struct ListNode* next;
}ListNode;
void PushBack(ListNode**pphead,int x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->val = x;
newnode->next = NULL;
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//...
}
}
int main()
{
ListNode* plist = NULL;
PushBack(&plist, 1);
PushBack(&plist, 2);
PushBack(&plist, 3);
PushBack(&plist, 4);
return 0;
}
友友们对于这种情况,在我们没有使用引用之前,我们必须使用传址的方式,用二级指针接收,然后解引用才能改变plist,如果用一级指针接收的话,那么形参就是实参的一份临时拷贝,形参的改变不会影响实参,所以友友们这种方法是不是比较麻烦.
2.引用交换
#include
#include
using namespace std;
typedef struct
{
int val;
struct ListNode* next;
}ListNode;
void PushBack(ListNode*&phead,int x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->val = x;
newnode->next = NULL;
if (phead == NULL)
{
phead = newnode;
}
else
{
//
}
}
int main()
{
ListNode* plist = NULL;
PushBack(plist, 1);
PushBack(plist, 2);
PushBack(plist, 3);
PushBack(plist, 4);
return 0;
}
友友们这里phead就是plist的别名,所以它就是plist,所以改变phead就是改变plist,学完引用之后我们这里就可以不再用二级指针了.
二.引用做返回值
1.传值返回
#include
#include
using namespace std;
int count()
{
int n = 0;
n++;
// ...
return n;
}
int main()
{
int ret = count();
return 0;
}
2.传引用返回
#include
#include
using namespace std;
int& count()
{
int n = 0;
n++;
// ...
return n;
}
int main()
{
int ret = count();
//这里打印可能是1,也可能是随机值
cout << ret << endl;
return 0;
}
#include
#include
using namespace std;
int& count()
{
int n = 0;
n++;
// ...
return n;
}
int main()
{
int &ret = count();
//这里返回值可能是1,也可能是随机值
cout << ret << endl;
cout << ret << endl;
return 0;
}
知识小结:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
1.引用必须在定义的时候初始化.
2.一个变量可以有多个引用,比如这里b
,c
,d
都是a
的引用.
3.引用一旦引用一个实体,再不能引用其它实体.
#include
#include
using namespace std;
int main()
{
const int a = 0;
int x = 1;
int& b = a; //权限的放大
const int& c = a; //权限的平移
const int& d = x; //权限的缩小
return 0;
}
友友们在引用过程中,权限可以平移,缩小,但是不能放大.
误区①
#include
#include
using namespace std;
int main()
{
const int a = 0;
int b = a;
return 0;
}
这里b是a的赋值,把a的值拷贝给b,b的改变不会影响a,它和a根本不是一个变量,所以不涉及权限的放大.
误区②
#include
#include
using namespace std;
int main()
{
int i = 0;
double& d = i;
return 0;
}
友友们这里注意,i是int类型,d是double类型,当i赋值给d时,这里会有隐式类型转换,这里会产生一个double类型的临时变量,临时变量具有常性不能修改,所以这里实质上是一种权限的放大,所以我们应该加一个const保持权限的平移才可以.
误区③
#include
#include
using namespace std;
int func()
{
int a = 0;
return a;
}
int main()
{
int& ret = func();
return 0;
}
友友们注意这里是传值返回,这里不是用变量a返回,这里会生成一个临时变量,用临时变量返回然后再拷贝给ret,临时变量具有常性,不能修改,所以我们这里在引用的时候实质上也是权限的放大,所以我们要加上const保持权限的平移就可以了.
指针和引用的区别:
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4.没有NULL引用,但有NULL指针
5.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7.有多级指针,但是没有多级引用
8.访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
以
inline
修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
查看方式
1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
2.在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2013的设置方式)
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,导致代码膨胀。优势:少了调用开销,提高程序运行效率。
2 inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性.
3.inline不建议声明和定义分离,分离会导致链接错误。因为内联函数在调用的时候就展开了,所以编译器就没有生成函数的地址,所以在链接的时候就会找不到。
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
#include
#include
#include
#include
using namespace std;
int main()
{
int a = 0;
auto b = a;
auto c = &a; //普通场景没有价值,类型很长时很有价值,可以简化我们的代码
auto& d = a;
std::vector<std::string> v;
std::vector<std::string>::iterator it = v.begin();
auto it = v.begin();
return 0;
}
友友们,这里我们也可以通过
typeid
看它们的类型
int main()
{
int a = 0;
auto b = a;
auto c = &a; //普通场景没有价值,类型很长时很有价值,可以简化我们的代码
auto& d = a;
std::vector<std::string> v;
//std::vector::iterator it = v.begin();
auto it = v.begin();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(it).name() << endl;
return 0;
}
1. auto不能作为函数的参数
void TestAuto(auto a)
{} ,此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导,比如我们实参传了一个3,编译器无法判断是整形3还是字符3.
2.auto不能直接用来声明数组
auto b[] = {4,5,6}; 这种情况也是不允许的,编译器也无法判断类型.
#include
#include
using namespace std;
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
cout << array[i] << " ";
cout << endl;
for (auto e: array)
{
cout << e << " ";
}
cout << endl;
return 0;
}
范围for循环会依次取数组中的数据赋值给e,自动判断结束,自动迭代.
友友们,这里我们对x乘以2,为什么打印出来的数据还是没有变化呢,这里一定要注意,x是数组数据的拷贝,x的改变不会影响数组中的数据,所以我们需要加一个引用就可以了,此时x就是数组中数据的别名,就是数组中的数据,所以此时x的改变就会影响数组中的元素了.
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
友友们,这种是不可以的,当使用范围for循环的时候,array那里必须是数组名,C++实际上只把形参数组名作为一个指针变量来处理,用来接收从 实参传过来的地址.
using namespace std;
void f(int)
{
cout << "f(int)" << endl;
}
void f(int*)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
友友们注意,在C++中,NULL的类型就不是指针类型了,我们如果要当成指针使用的话,需要强转类型
注意:
1.在C++中引入了一个关键字nullptr,在使用nullptr表示指针空值时,不需要包含头文件
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3.为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
✨✨好了友友们,这期的内容到这里就告一段落了,下期不见不散.