欢迎来到博主的专栏——c++编程
博主ID: 代码小豪
文章目录
- 前言
- 重载函数
- 函数重载的规则
- 函数重载的原理
- 引用
- 引用变量的权限问题
前言
c语言对于编写大型项目有所缺陷,比如最常出现的标识符不能重复的问题(软件的代码量通常是数以万计的,而且由多人编写,因此命名的变量、函数重名是一个非常常见的问题)。因此当时就有人对c的功能进行扩展,增加了类的功能。再经过了多年的继续扩展,就发展处了c++这个编程语言。
由于c++是由c扩展而来的,因此c/c++之间互通的特性非常之多。想要学习c++,是不能绕开c语言的特性的。但是由于博客的篇幅原因,且博主在之前的专栏也有对c语言语法进行过讲解。所以在这个专栏中出现的C语言语法不会进行讲解。本专栏重点讲解一些c++有、而C语言没有的特性。以及一些c++与c语言有所差异的特性。
重载函数
前面提到了c语言存在的缺陷之一就是标识符不能重复,为了解决这个问题,c++提供了重载函数这个特性。
大家在平时的生活中有没有见过那种,词一样但是意思完全不同的句式。比如:“豆腐一块两块”,这句话有多种解读方式,具体是什么意思,还是要根据实际情况来解读。
重载函数也是同理,相同的函数名,如果函数类型不同,执行的方式也不同。如果是c语言则不允许这种形式,但是在实际运用当中是可能发生的。
比如:
int add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
前者的add函数用来计算整型数据相加、后者计算浮点类型数据相加。这在数学当中是不是允许的情况?但是c语言并不允许出现同名函数。因此很多人的解决方式都是加上前缀。
int int_add(int a, int b)
{
return a + b;
}
double double_add(double a, double b)
{
return a + b;
}
c++的重载函数的特性则可以忽略这个细节,重载函数可以让拥有相同函数名、但是不同参数的函数共存。而且会根据实际上传的参数来决定调用哪个函数。
int main()
{
int x, y;
x = 10, y = 20;
double a, b;
a = 3.1415, b = 1.4444;
printf("x+y=%d\n", add(x, y));
printf("a+b=%lf\n", add(a, b));
return 0;
}
x、y都是整型元素,因此调用的add为整型相加的add。a,b是浮点型数据,因此调用的add是浮点型相加的add。
结果如下:
函数重载的规则
重载函数的规则如下:
(1)函数的参数类型不同
(2)函数的参数数量不同
(3)函数的参数顺序不同
相同函数名的函数符合以上一条条件即可构成重载函数。
比如:
int add(int a, int b)//(1)
{
return a + b;
}
double add(double a, double b)//(2)
{
return a + b;
}
double add(int a, double b)//(3)
{
return a + b;
}
double add(double a, int b)//(4)
{
return a + b;
}
int add(int a, int b, int c)//(5)
{
return a + b + c;
}
(1)与(2)的重载符和第一个条件,(3)和(4)的重载符合第二个条件。(5)则符合第三个条件
函数重载的原理
为什么c++可以支持函数重载,而C语言不行呢?这得从函数调用的角度解释。
函数调用大家都清楚,但是函数调用是具体如何调用的呢?这次我们尝试从汇编角度来理解。
注意红色部分的call就是调用。call后面紧接的是函数名,将两者结合起来就是调用add函数这个操作。而函数名后面的16进制数是什么呢?没错就是函数的地址。
这时可能有人就有疑问了,我知道变量有地址,指针有地址,函数也有地址吗?是的,函数名就是函数的地址。在c语言中就有函数指针这个指向函数的指针。而函数名代表的就是函数的地址。
int(*pf)(int, int) = add;//这个pf就是函数指针,
//add则是函数的地址值,pf是指向add函数地址的函数指针
我们在汇编语言中可以看到,这五个重载函数的函数名代表的地址都是不同的。
因此我们可以来猜想一下:c++之所以可以支持重载函数,是因为c++可以为相同函数名,不同函数参数的函数设置不同的地址,而c语言则不会分辨相同函数名的函数是否具有相同的参数
那么c++为什么可以做到根据不同的函数参数进行不同的地址分配,而C语言不可以。难道说c++内置了一个人工智能?
这显然是不可能的。c++采用了另外一种命名方式:
由编译器设置一个符号表,根据函数的返回类型、参数类型、函数名。翻译成一个新的函数名。
相信使用过c++的人对这个报错应该很熟悉。但这里不讲报错的原因,而是注意报错中的重要信息
可以发现这五个乱码非常相似,而且这些乱码对应的函数也是我们的老熟人(add五兄弟)。在仔细观察乱码,我们可以发现一个规律。
除了这几个符号以外,其余乱码都是一致的,根据这五个函数的函数原型,我们可以大胆做一个猜测:在vs中(不同编译器的符号表不同),H对应参数类型int,N对应参数类型double
这些乱码就是编译器根据符号表将函数翻译过后的函数名。也就是说c++中,经过编译后的函数名不再是add,而是根据翻译过后乱码才是这个函数真正的函数名,函数名不同,调用的函数地址也不同,这是c++实现函数重载的原理。
引用
我写了一个函数,用来交换两个变量的值。请大家来判断以下这个函数有什么问题?
void Swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
不知道大家再刚入门C语言的时候有没有出现过这种错误,将函数的形式参数认为是实际参数。有了经验以后才明白,形式参数是实际参数的临时拷贝,对形式参数的修改不会影响到实际的参数。为了解决这个问题,可以将函数参数修改成指针类型。
void Swap(int* pa, int* pb)
{
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
这是利用指针的特性来实现对实际参数修改的功能,但是在面对多级指针的情况下,很容易忽略掉实参与形参的关系。
c++支持的引用类型可以避免这个问题。引用的操作符是(&),在C语言中这个操作符是取地址操作符,在c++这个符号依然可以作为取地址操作符,但是它还有新增的一个作用:
将&用于变量的声明时,可以将这个变量的类型改为引用类型
好比C语言中的解引用操作符(*),当这个符号用于两个变量或常量之间时,是相乘操作符。(&)在c++中也具备了两用的功能。
引用符号只能使用在变量的类型声明之后。比如:
int a = 0;
int& b = a;
此时变量b就是a的引用。对b进行的任何赋值操作都会对a进行修改。
b = 20;
b++;
此时a会先赋值20,在自加1,因此a的值被修改为21。
对一个变量,可以有多个引用,对其中任意一个引用变量进行操作都会对其余变量造成影响
int main()
{
int a = 0;
int& b = a;
int& c = a;
c++;
b++;
printf("a=%d\n", a);
printf("b=%d\n", b);
printf("c=%d\n", c);
return 0;
}
b、c同为a的引用变量,对c进行自加操作、a,b,c都会自加1,b也是同理,因此输出结果a,b,c都是2.
引用变量也可以用来引用引用变量,这句话有点绕,但是看代码就能一下子理解。
int a = 0;
int& b = a;
int& c = a;
int& d = b;
此时d就成了b的引用变量,对a,b,c,d的任何操作,都会对其余变量产生影响。
这种操作在C语言也可以用指针来实现。比如:
int a = 0;
int* b = &a;
int* c = &a;
int* d = b;
(*c)++;
(*b)++;
(*d)++;
难道引用就是C语言指针的变种吗?当然不是,引用和指针的功能类似,但是原理不一样。C语言是用指针指向变量的地址空间,通过解引用来操控变量。而c++的引用更像是为同一个地址空间同时赋予了多个变量名而已。
int a = 0;
int& b = a;
rintf("%p\n", &a);
printf("%p\n", &b);
运行这段代码也可以发现,a与b取地址后显示的地址空间是一致的。
引用类型变量也可以用来引用指针,只是位置要在(*)之后
int* pa = &a;
int*& pb = pa;
int&* pc = pb;//error 不允许指向引用的指针
引用变量也可以用来作为函数的参数,此时对形参的修改就会影响到实参。
void Swap(int&a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
引用变量的权限问题
引用需要遵守以下规则
(1)声明引用变量时必须赋值,且后续不能修改
(2)声明的引用变量不能扩大原变量的权限。
第一个很好理解,因为引用变量可以视为原变量的副本,既然是副本那就一定要有原本才能复制。
第二个条件就有点复杂了,什么是放大原变量权限,如果缩小原变量的权限又可不可行呢?
我们先回到c语言
int main()
{
const int a = 0;
int* pa = &a;
(*pa) = 10;
return 0;
}
在C语言中会出现这种情况:变量a明明是被限定成const类型的了,按理来说,这个a是绝对不能修改的,但是通过指针,我们可以绕过这个const,将a进行修改,这是不是违背了当初变量a被设计出来的初心了。因此C++新增了这个特性。原变量的权限不能被放大。
比如:
const int a = 0;
int& b = a;//error
const int& b = a;//true
c++想要绕开这个权限被禁用了。如果想要通过引用const类型的变量来修改值,会出现这种报错:
a本来是被限定了的,而b并没有被限定,这就是权限的放大,而const int类型与a的权限是平行的,因此不会报错。
如果是将权限进行缩小呢?
int a = 0;
const int& b = a;//true
int& c = a;//true
答案是允许的。那么b的权限被缩小了,会不会对a和c有所影响呢?
int a = 0;
const int& b = a;//true
int& c = a;//true
a++;//true
b++;//error
c++//true;
可以发现被限制权限的b不能进行修改操作,而a和c可以,所以对a和c的操作仍然能影响b,但是这不违背原变量的设计初衷,毕竟创建a的时候并没有限定a的操作权限。