1.函数重载
C++支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调用就表现出了多态行为,使用更灵活。C语言是不支持同一作用域中出现同名函数的。
1、参数类型不同
2、参数个数不同
3、参数类型顺序不同
注意:返回值不同不能作为重载条件,因为调用时也无法区分
只是返回类型不同的话,下面编译就会报错
这种情况也是不行的,下面两个函数构成重载,但第二个f1函数使用了缺省参数,f1()调用时会报错,存在歧义,编译器不知道调用谁。
2.引用
2.1引用的概念和定义
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。比如:水浒传中李逵,江湖上人称"黑旋风";林冲,外号豹子头;类型& 引用别名 = 引用对象;C++中为了避免引入太多的运算符,会复用C语言的一些符号,比如前面的<< 和 >>,这里引用也和取地址使用了同一个符号&,大家注意使用方法角度区分就可以。
如下图:引用其实就是取别名,本质上还是a,只不过给他起了个外号,同样,在对a引用时,改变引用也会改变它本身。
2.2引用的特性
• 引用在定义时必须初始化• 一个 变量可以有多个引用• 引用 一 旦引用一个实体,再不能引用其他实体
注意:引用必须初始化
2.3引用的使用
• 引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。• 引用传参跟指针传参功能是类似的,引用传参相对更方便一些。• 引用返回值的场景相对比较复杂,我们在这里简单讲了一下场景,还有一些内容后续类和对象中会继续深入讲解。• 引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很大的区别的,除了用法,最大的点,C++引用定义后不能改变指向,Java的引用可以改变指向。• 一 些主要用C代码实现版本数据结构教材中,使用C++引用替代指针传参,目的是简化程序,避开复杂的指针。
先来看一下引用传参
就比如栈,其他接口就不去一一实现了,如果我们要修改栈顶的话,可以直接对STTop()函数的返回值进行操作,因为它返回的是栈顶值的引用,我们改变它的引用就是改变栈顶本身,不然还要去实现修改栈顶数据的接口,就显得繁琐了
int& STTop(ST& rs)
{
assert(rs.top > 0);
return rs.a[rs.top];
}
STTop(st1) += 10;
cout << STTop(st1) << endl;
2.4const引用
• 可以引用一 个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。• 不过需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样一些场景下a*3的结果保存在一个临时对象中, int& rd = d 也是类似,在类型转换中会产生临时对象存储中间值,也就是,rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。• 所谓临时对象就是编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象,C++中把这个未命名对象叫做临时对象。
我们用const修饰变量a,那么a只能读不能写,如果我们对a引用ra,如果不加const修饰,我们就可以对ra进行读写操作,权限就放大了,这样就矛盾了,因为ra是a的引用(别名),a只能读不能写,那么ra也应该只能读不能写,所以编译就会报错。权限不能放大但是可以缩小,就像b,对b引用rb,并用const修饰,那么b可读可写,rb只能读不能写。
const引用可以直接引用一个数字常量,然后就是上面提到的第二点。
2.5指针和引用的关系
总结一下指针和引用:
C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有自己的特点,互相不可替代。• 语法概念上引用是一个变量的取别名不开空间,指针是存储一个变量地址,要开空间。• 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。• 引用在初始化时引用一个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。• 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。• sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8字节)• 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全一些。
3.inline
• 用“ inline”关键字修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就需要建立栈帧了,就可以提高效率。• inline对于编译器而言只是一个建议,也就是说,你加了inline编译器也可以选择在调用的地⽅不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁调用的短小函数,对于递归函数,代码相对多一些的函数,加上inline也会被编译器忽略。• C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试,C++设计了inline目的就是替代C的宏函数。• vs编译器 debug版本下面默认是不展开inline的,这样方便调试。• inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
内联函数能够提高程序效率,主要归因于以下几个方面:
1. 减少函数调用开销:当普通函数被调用时,会产生一系列额外的操作,比如将参数压入栈、保存当前指令地址、跳转至函数起始地址等。而内联函数在编译阶段,会将函数体的代码直接替换到调用处,避免了这些额外的操作,从而节省了时间。
2. 避免上下文切换:函数调用可能涉及到不同的指令地址和上下文切换。使用内联函数可以消除这种切换,使得程序的执行更加流畅,减少了 CPU 因切换上下文而产生的延迟。
3. 更好的优化机会:由于内联函数的代码直接嵌入到调用处,编译器能够更好地对整个代码段进行优化。例如,可以更有效地利用寄存器、进行指令重排等,以提高程序的执行速度。
4. 减少缓存缺失:函数调用可能导致跨越不同的缓存行,增加缓存缺失的概率。内联函数减少了这种可能性,因为相关的代码在物理上更加接近,提高了缓存的命中率,进而提高了程序的性能。
综上所述,内联函数通过消除函数调用的额外开销、优化代码布局和利用缓存等方式,有效地提高了程序的执行效率。但需要注意的是,内联函数并非适用于所有情况,对于复杂的大型函数,内联可能会导致代码膨胀等问题。
1. 减少函数调用开销:当普通函数被调用时,会产生一系列额外的操作,比如将参数压入栈、保存当前指令地址、跳转至函数起始地址等。而内联函数在编译阶段,会将函数体的代码直接替换到调用处,避免了这些额外的操作,从而节省了时间。
2. 避免上下文切换:函数调用可能涉及到不同的指令地址和上下文切换。使用内联函数可以消除这种切换,使得程序的执行更加流畅,减少了 CPU 因切换上下文而产生的延迟。
3. 更好的优化机会:由于内联函数的代码直接嵌入到调用处,编译器能够更好地对整个代码段进行优化。例如,可以更有效地利用寄存器、进行指令重排等,以提高程序的执行速度。
4. 减少缓存缺失:函数调用可能导致跨越不同的缓存行,增加缓存缺失的概率。内联函数减少了这种可能性,因为相关的代码在物理上更加接近,提高了缓存的命中率,进而提高了程序的性能。
综上所述,内联函数通过消除函数调用的额外开销、优化代码布局和利用缓存等方式,有效地提高了程序的执行效率。但需要注意的是,内联函数并非适用于所有情况,对于复杂的大型函数,内联可能会导致代码膨胀等问题。
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(int x),因此与程序的初衷相悖。f((void*)NULL)调用会报错。• C++11中引入nullptr,nullptr是一个特殊的关键字,nullptr是一种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。
在C++中NULL被定义为字面常量0,我们可以显式转换为(int*),如果强制转换为(void*),则会报错。
这是为什么呢?
在 C++中, null 实际上被定义为 0 。C++是一种强类型语言,不允许将 void* 类型的指针隐式转换为其他类型的指针。
虽然在 C 语言中可以进行这样的隐式转换,但 C++对类型要求更严格。任何指针类型可以隐式转换为 void* ,但反过来则必须进行强制类型转换。
例如,以下代码在 C++中会报错:
虽然在 C 语言中可以进行这样的隐式转换,但 C++对类型要求更严格。任何指针类型可以隐式转换为 void* ,但反过来则必须进行强制类型转换。
例如,以下代码在 C++中会报错:
//cpp
int* a = null;
void* b = null;
char* c = null;
c = a; // 出错
b = a; // 通过
c = b; // 出错
如果要进行赋值,必须进行显式转换:
//cpp
int* a = null;
void* b = null;
char* c = null;
c = (char*)a; // 通过
b = a; // 通过
c = (char*)b; // 通过
这样的设计是为了增强类型安全性,避免潜在的类型错误和歧义。在 C++11 中,引入了 nullptr 关键字来更明确地表示空指针。 nullptr 并非整型类别,也不是指针类型,但能转换成任意指针类型。使用 nullptr 可以更清晰地表达空指针的意图,并且避免了 null 可能导致的一些二义性问题。例如,如果有重载的函数:
//cpp
void func(int x);
void func(char* y);
调用 func(nullptr) 会明确地调用 func(char* y) ,而使用 func(null) 则可能会因为 null 被视为 0 而产生歧义。
所以我们可以用nullptr来解决这一问题,可以更清晰的表达空指针的意图。