函数重载
C++支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调用就表现出了多态行为,使用更灵活。C语言是不支持同一作用域中出现同名函数的
cpp
//参数类型不同
int clic(int a , int b)
{
return a * b;
}
double clic(double a, double b)
{
return a + b;
}
cpp
//参数个数不同
void f()
{
}
void f(int a)
{
}
cpp
//参数类型顺序不同
int clic(double a, int b)
{
return a * b;
}
double clic(int b, double a)
{
return a + b;
}
cpp
//数据类新顺序不同
void f1(int a, char b)
{
}
void f1(char b, int a)
{
}
还要注意两点:
1.不能将返回值作为重载条件,因为调用时无法区分


2.这个构成函数重载,但是在编译时会报错,存在歧义,编译器不知道要调用谁


引用
引用的概念和定义
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间, 它和它引用的变量共用同一块内存空间
类型& 引用别名 = 引用对象
C++中为了避免引入太多的运算符,会复用C语言的一些符号, 比如前面的<< 和 >>,这里引用也和取地址使用了同一个符号&,注意使用方法角度区分就可以
cpp
int main()
{
int a = 10;
//引用:b 和 c 是 a 的别名
int& b = a;
int& c = a;
b++;
//也可以给b取别名,因为b还是a的别名
int& d = b;
//在输出里"&"是取地址符号
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
引用的特性
引用在定义时必须初始化


一个变量可以有多个引用

引用一旦引用一个实体,再不能引用其他实体
这里我们透过地址查看,如果地址相同就是引用,不同就是赋值
cpp
int main()
{
int a = 15;
int& b = a;
int& c = a;
cout << &b << endl;
cout << &c << endl;
return 0;
}

cpp
int main()
{
int a = 15;
int& b = a;
int& c = a;
int d = 30;
c = d;
cout << &c << endl;
cout << &d << endl;
return 0;
}

引用的使用
引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被 引用对象
引用传参跟指针传参功能是类似的,引用传参相对更方便一些
平常我们在调用函数时用的都是传址调用
cpp
void swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
int main()
{
int x = 0, y = 3;
cout << x << "" << y << endl;
swap(&x, &y);
cout << x << "" << y << endl;
return 0;
}
我们使用引用就可以不需要使用指针
cpp
void swap(int& x, int& y)
{
int t = x;
x = y;
y = t;
}
int main()
{
int x = 0, y = 3;
cout << x << " " << y << endl;
swap(x, y);
cout << x << " " << y << endl;
return 0;
}
这就是它的第一个功能:做函数形参,修改形参影响实参
当然结构体也可以使用
cpp
struct A
{
//..
};
void func(struct A& aa)
{}
int main()
{
struct A a;
func(a);
return 0;
}
这是它的第二个功能:做函数形参,减少拷贝,提高效率
这里我们再写一个顺序表:
cpp
typedef struct SeqList
{
int* a;
int size;
int capacity;
}SL;
void SLInit(SL& sl, int n = 4)
{
sl.a = (int*)malloc(n * sizeof(int));
// ...
sl.size = 0;
sl.capacity = n;
}
void SLPushBack(SL& sl, int x)
{
// ...扩容
sl.a[sl.size] = x;
sl.size++;
}
int SLAt(SL& sl, int i)
{
assert(i < sl.size);
return sl.a[i];
}
我们让他返回之后加1会发生什么事?
cpp
int main()
{
for (size_t i = 0; i < s.size; i++)
{
SLAt(s, i) += 1;
}
return 0;
}


我们发现它报错了,这是为啥?
因为返回的是临时变量,临时变量不能被修改

当我们在返回类型上使用引用会怎么样
cpp
int& SLAt(SL& sl, int i)
{
assert(i < sl.size);
return sl.a[i];
}

我们发现不报错了!
因为返回的是别名,别名可以直接影响实参

这时候就要说引用的第三个功能:引用作为返回值类型,修改返回对象,也能减少拷贝,提高效率
const引用
可以引用一个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访 问权限在引用过程中可以缩小,但是不能放大
const引用必须是另一个const的引用
cpp
int main()
{
int x = 15;
//权限不能放大
const int a = x;
const int& b = a;
return 0;
}
如果不是则会报错


但const可以引用非const的对象
cpp
int main()
{
int y = 0;
int& c = y;
//权限可以缩小
const int r = y;
return 0;
}
指针同理

当一个const引用时会将数据保存在临时对象中,例如

此时y会将数据保存到临时对象中

那为什么会报错呢?
因为C++规定临时对象具有常性,因此触发了权限放大,必须要进行常引用
const引用也可以引用常数,也可以引用不同类型的数据

所谓临时对象就是编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象, C++中把这个未命名对象叫做临时对象
指针和引用的关系
C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功 能有重叠性,但是各有自己的特点,互相不可替代
语法概念上引用是一个变量的取别名不开空间,指针是存储一个变量地址,要开空间
引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的
引用在初始化时引用一个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象
引用可以直接访问指向对象,指针需要解引用才是访问指向对象
sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下 占4个字节,64位下是8byte)
指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全一些
inline
用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧了,就可以提高效率
我们怎么看inline是否有展开呢?可以透过反汇编进行查看

这样并没有被展开,因为vs的debug版本默认不展开
inline对于编译器而言只是一个建议,也就是说,你加了inline编译器也可以选择在调用的地方不展 开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁 调用的短小函数,对于递归函数,代码相对多一些的函数,加上inline也会被编译器忽略
C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调 试,C++设计了inline目的就是替代C的宏函数
vs编译器debug版本下面默认是不展开inline的,这样方便调试,debug版本想展开需要设置一下 以下两个地方



inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地 址,链接时会出现报错
cpp
//F.h
#include <iostream>
using namespace std;
inline void f(int i);
cpp
//F.cpp
void f(int i)
{
cout << i << endl;
}
cpp
//test.cpp
int main()
{
/*int ret = Add(3, 5);
cout << ret << endl;*/
f(10);
return 0;
}

nullptr
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
cpp
#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); 调用会报错
cpp
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
//本想透过NULL调用指针版本f(int*)函数,结果NULL = 0,调用了f(int)函数,与程序的初衷相悖
f((int*)NULL);
return 0;
}

如果使用((void*)NULL)会报错
cpp
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
f((void*)NULL);
return 0;
}

C++11中引入nullptr,nullptr是一个特殊的关键字,nullptr是一种特殊类型的字面量,它可以转换 成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被 隐式地转换为指针类型,而不能被转换为整数类型
cpp
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(nullptr);
return 0;
}