深入计算机语言之C++:C到C++的过度-1
https://developer.aliyun.com/article/1624600
七、函数重载
函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这
些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型
不同的问题。
可以通过参数类型,参数个数,参数类型顺序来构成函数重载:
int Add(int a, int b) { return a + b; } double Add(double a, double b)//类型不同 { return a + b; } int Add(int a, int b, int c)//个数不同 { return a + b; } int Add(char a, int c) { return a + c; } int Add(int a, char c)//类型顺序不同 { return a + c; }
注意:
- 返回值类型不同无法构成函数重载
- 缺省值不同也不能构成函数重载
八、引用
8.1 引用的概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间 。其语法为:
引用对象类型& 引用变量名(对象名) = 引用实体;
引用类似于指针,因为指向同一块空间,所以改变引用变量引用实体也会改变。
#include<iostream> using namespace std; int main() { int a = 1; int& b = a;//引用 int& c = b; cout << &a << endl; cout << &b << endl; cout << &c << endl; c++; cout << a << endl; cout << b << endl; cout << c << endl; return 0; }
8.2 引用的特性
8.2.1 引用时必须初始化
int& b;//错误的,必须初始化 int& b = a;
8.2.2 一个变量可以有多个引用(给别名取别名)
int a = 1; int& b = a; int& c = a;//多个引用
8.2.3 引用一旦引用一个实体,再不能引用其他实体
int a = 1; int& b = a; b = 2;//这时是赋值,相当于a = b = 2;
8.3 引用的使用
8.3.1 作为函数的参数
#include<iostream> using namespace std; void swap(int& a, int& b) { int z = a; a = b; b = z; } int main() { int a = 1, b = 2; swap(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; }
8.3.2 做函数返回值
函数的返回值是存储在一个临时的变量里面。这个变量正常情况下是不可修改的,可以看作一个常量,我们不能对常量进行赋值。
但使用引用作为返回值相当于返回一个引用,没有中间拷贝过程和临时变量,进而同时改变了引用对象(func1(a))和被引用对象(a)。
#include<iostream> using namespace std; int& func1(int& a) { a++; return a; } int main() { int a = 1; func1(a) = 10; cout << a; return 0; }
8.3.3 错误示范
1. 引用指向的空间栈帧销毁
int& func() { int a = 0; return a; }
返回了a的引用,但当离开函数,函数的栈帧销毁,相当于返回了一个野指针
2. 引用指向的函数多次调用
int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); Add(3, 4); cout << ret <<endl; return 0; }//输出什么
输出结果为:7
那是因为在第二次调用函数Add(3,4)时,会在原来第一次调用Add(1,2)建立栈帧的空间上建立栈帧所以返回值c的值会被重新覆盖,ret是指向Add所在位置的栈帧的别名,所以ret值也会发生改变。
8.4 const引用
8.4.1 对常变量的引用(权限不可放大)
我们可以通过 const 修饰引用来让其变为常引用。这时引用变量是不能被修改的,并且只能将常变量复杂给常引用,不能将常变量赋值给引用,必须用const来引用。
const int a = 10; // 编译报错:error C2440: “初始化”: ⽆法从“const int”转换为“int &” // 这⾥的引⽤是对a访问权限的放⼤ //int& ra = a; //这样才可以 const int& a = 10; // 编译报错:error C3892: “ra”: 不能给常量赋值 //ra++;
8.4.2 const引用普通变量(权限可缩小)
const 引⽤也可以引⽤普通变量,因为对象的访问权限在引⽤过程中可以缩⼩,但是不能放⼤。
// 这⾥的引⽤是对b访问权限的缩⼩ int b = 20; const int& rb = b; // 编译报错:error C3892: “rb”: 不能给常量赋值 //rb++;
8.4.3 const可以引用含有常性的对象
含有常性的变量包括常数,函数返回值等。
const int& ra = 30;
int a = 1, b = 2; const int& ra = a * 3; const int& rb = a + b;
double d = 12.34; // 编译报错:“初始化”: ⽆法从“double”转换为“int &” // int& rd = d; int rc = d;//隐式类型转换 const int& rd = d;
不需要注意的是类似 int& ra = a*3; int& rb = a + b; int& rd = d; 这样⼀些场景下 a*3 的运算结果和 a 保存在⼀个临时对象中。 int& rd = d 也是类似,引用时进行类型转换被称为隐式类型转换,在类型转换中会产⽣临时对象存储中间值。也就是此时,ra 和 rd 引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥ 就触发了权限放⼤,必须要⽤常引⽤才可以。
所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。
注:
const int a = 10; int& ra = a;//权限放大 int rb = a;//权限没有放大
a处于一块临时对象,只拥有读取权限,没有写入权限,此时 int& ra 是指向a所在的空间(别名),要求读取和写入的权限,所以就产生了权限放大。第二个只是将 a 的值读取拷贝给 rb,并没有产生权限放大。
const 引用传参在未来学习模板类的时候会有进行运用。
8.5 引用与指针的区别
- 语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个地址的变量,要开空间。
- 引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
- 引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
- 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
- sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
- 指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。
九、内联函数
在C语言中,无论宏常量还是宏函数虽能提升程序运行效,但都有易出错,无法调试等缺陷。而C++为了弥补这一缺陷,引入了内联函数的概念代替宏函数。
以关键字inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
#include<iostream> using namespace std; inline int Add(int x, int y) { return x + y; } int main() { Add(1, 2); return 0; }
vs编译器 debug版本下⾯默认是不展开inline的,这样⽅便调试,debug版本想展开需要设置⼀下以下两个地⽅。
C/C++:常规——调试信息格式改成程序数据库,优化——内联函数扩展改成只适用于_inline
注意:
- 内联函数是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。内联函数的优势减少了调用开销,提高程序运行效率,缺陷就是可能会使目标文件变大。
- inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
- inline不能声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
因为内联函数会在调用时直接展开,编译器默认认为不需要地址,如果声明与定义分离内联函数的地址根本不会进入符号表,链接时就无法找到定义的函数,就会发生链接错误。
十、nullptr
在C语言中,定义了一个宏NULL,在传统的C头文件(stddef.h)中,可以看到如下代码 :
#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif
由此我们知道NULL既可以代表数字0,也可以代表空指针。这种模棱两可的定义就可能引出一些问题,比如下面这段代码:
#include<iostream> using namespace std; void func(int a) { cout << "func(int)" << endl; } void func(int*p) { cout << "func(int*)" << endl; } //函数重载 int main() { func(0); func(NULL); func((int*)NULL); return 0;//输出?? }
我们的本意可能是将NULL当成一个指针,但是在默认情况下NULL被编译器当做数字0。这种问题是我们并不想看见的,所以C++11引入了nullptr来代替NULL。
C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。