C++(函数重载,引用,nullptr)
1.函数重载
C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。传参时会自动匹配传入的参数,对应该函数的形参类型,进行函数调用,这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。C语⾔是不⽀持同⼀作⽤域中出现同名函数的。
这里介绍三种不同情况:
a)参数类型不同:
//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;
}
int main()
{
int ret1=Add(10, 20);
cout << ret1 << endl;
double ret2=Add(10.1, 20.2);
cout << ret2 << endl;
return 0;
}
b)参数个数不同
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();
f(10);
return 0;
}
c)参数类型顺序不同
// 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()
{
f(10, 'a');
f('a', 10);
return 0;
}
注意:这里有一种情况会报错,存在歧义,编译器不知道用谁(和前面讲的缺省函数有关)
例如:
void f1()
{
cout << "f1()" << endl;
}
void f1(int a = 10)
{
cout << "f1(int a=10)" << endl;
}
int main()
{
f1();
return 0;
}
当不传入参数时,编译器不知道是调用f1(),还是用缺省定义的int a=10的f1();
编译器报错:
2.引⽤
2.1 引⽤的概念和定义
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间(这里所说的是语法层面), 它和它引⽤的变量共⽤同⼀块内存空间。(这里可以理解为,一个小伙伴可以有乳名,外号,和大名但都是同一个人,好比:“你们抓鲁迅管我周树人什么事”)
类型& 引用·别名=引用对象;
C++中为了避免引⼊太多的运算符,会复⽤C语⾔的⼀些符号,⽐如前⾯的>,这⾥引⽤也和取地址使⽤了同⼀个符号&,⼤家注意使⽤⽅法⻆度区分就可以。
int main()
{
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = a;
// 也可以给别名b取别名,d相当于还是a的别名
int& d = b;
++d;
// 这⾥取地址我们看到是⼀样的
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
这里a,b,c,d都是同一块空间。
2.2 引⽤的特性
a)引⽤在定义时必须初始化;
b)⼀个变量可以有多个引⽤;
c)引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体;
int main()
{
int a = 10;
// 编译报错:“ra” :必须初始化引⽤
//int& ra;
int& b = a;
int c = 20;
b = c;
// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
// 这⾥是⼀个赋值
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
3.3 引⽤的使⽤
a)引⽤在实践中主要是于引⽤传参和引⽤做返回值中减少拷⻉提⾼效率和改变引⽤对象时同时改变被引⽤对象。
b)引⽤传参跟指针传参功能是类似的,引⽤传参相对更⽅便⼀些。
c)引⽤返回值的场景相对⽐较复杂,在这⾥简单讲了⼀下场景,后面会有深入讲解。
d)引⽤和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引⽤跟其他 语⾔的引⽤(如Java)是有很⼤的区别的,除了⽤法,最⼤的点,C++引⽤定义后不能改变指向, Java的引⽤可以改变指向。
例如:
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0, y = 1;
cout << x << " " << y << endl;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}
比如之前用C语言实现的栈的数据结构(小编之前的文章里有详细讲解),传的参数是地址,这里也可以用引用:
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST& rs, int n = 4)
{
rs.a = (STDataType*)malloc(n * sizeof(STDataType));
rs.top = 0;
rs.capacity = n;
}
// 栈顶
void STPush(ST& rs, STDataType x)
{
//assert(ps);
// 满了,扩容
if (rs.top == rs.capacity)
{
printf("扩容\n");
int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
STDataType * tmp = (STDataType*)realloc(rs.a, newcapacity *
sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
rs.a = tmp;
rs.capacity = newcapacity;
}
rs.a[rs.top] = x;
rs.top++;
}
// int STTop(ST& rs)
int& STTop(ST& rs)
{
assert(rs.top > 0);
return rs.a[rs.top];
}
int main()
{
//调⽤全局的
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);
cout << STTop(st1) << endl;
STTop(st1) += 10;
cout << STTop(st1) << endl;
return 0;
}
还有之前取栈顶这个函数,返回值为int类型,这里可以通过int&这样的返回类型实现,栈顶元素的++:
int& STTop(ST& rs)
{
assert(rs.top > 0);
return rs.a[rs.top-1];
}
int main()
{
//调⽤全局的
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);
cout << STTop(st1) << endl;
STTop(st1) += 10;
cout << STTop(st1) << endl;
return 0;
}
3.4 const引⽤
a)可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象,因为对象的访 问权限在引⽤过程中可以缩⼩,但是不能放⼤。
int main()
{
const int a = 10;
//错误:int& ra = a;属于是权限放大了//这里必须为const int& ra = a;
const int& ra = a;
return 0;
}
b)需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场 景下a*3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产⽣临时对 象存储中间值,也就是时,rb和rd引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥ 就触发了权限放⼤,必须要⽤常引⽤才可以。
(所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象)。
#include<iostream>
using namespace std;
int main()
{
int a = 10;
const int& ra = 30;
// 编译报错: “初始化” ⽆法从“int”转换为“int& ”
// int& rb = a * 3;
const int& rb = a * 3;
double d = 12.34;
// 编译报错:“初始化” :⽆法从“double”转换为“int& ”
// int& rd = d;
const double& rd = d;
return 0;
}
3.5 指针和引⽤的关系
a)C++中指针和引⽤就像两个性格迥异的亲兄弟,指针是哥哥,引⽤是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有⾃⼰的特点,互相不可替代。
b)语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
c)引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
d)引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
e)引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
f)sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下 占4个字节,64位下是8字节)
g)指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。
4.nullptr
NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif
C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种 定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本f(int*)函数,但是由于NULL被定义成0,调⽤了f(intx),因此与程序的初衷相悖。C语言中f((void*)NULL); 调⽤会报错。
C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换 成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被 隐式地转换为指针类型,⽽不能被转换为整数类型。
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
// 本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。
f(NULL);
f((int*)NULL);
// 编译报错:error C2665 : “f”: 2个重载中没有⼀个可以转换所有参数类型
// f((void*)NULL);
f(nullptr);
return 0;
}