C++基础入门(二)(函数重载,引用,内联函数,nullptr)

目录

一. 函数重载

1. 概念

2. 实现

(1). 参数类型不同

(2). 参数个数不同

(3). 参数类型顺序不同

3. 注意事项

(1). 返回值不能作为重载的条件

(2). 不能仅按函数返回类型重载

(3). 与缺省参数的问题

二. 引用

1. 概念和定义

2. 引用的特性

(1). 引用在定义时必须初始化

(2). 一个变量可以有多个引用

(3). 引用一旦引用一个实体,再不能引用别的实体

3. 引用的使用

4. 引用做函数返回值

5. const 引用

 6. 指针和引用的关系

三. 内联函数

概念

 使用

 注意

 四. nullptr


一. 函数重载

1. 概念

C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。C语⾔不⽀持同⼀作⽤域中出现同名函数的。

2. 实现

有三个使用情景,分别为参数类型不同,参数个数不同和参数类型顺序不同

(1). 参数类型不同
#include<iostream>

using namespace std;
int Add(int aaaa, int bbbb)
{
	cout << "调用的是整数相加的函数" << endl;
	return aaaa + bbbb;
}

double Add(double aaaa, double bbbb)
{
	cout << "调用的是双精度浮点数相加的函数" << endl;
	return aaaa + bbbb;
}

int main()
{
	int a = 10, b = 22;
	double aa = 0.221, bb = 3.025;
	cout << a << " " << b << endl;
	cout << aa << " " << bb << endl;
	return 0;
}
(2). 参数个数不同
#include<iostream>

using namespace std;
void n()
{
	cout << "调用的是无参数的函数" << endl;
}
void n(int a)
{
	cout << "调用的是有参数a的函数" << endl;
}

int main()
{
	n();
	n(2);
	return 0;
}
(3). 参数类型顺序不同

 

#include<iostream>
using namespace std;
void nn(int a,char b)
{
	cout << "调用的是先int型数据再char型数据的函数" << endl;
}
void nn(char a,int b)
{
	cout << "调用的是先char型数据再int型数据的函数" << endl;
}
int main()
{
	nn(1, 'x');
	nn('x', 1);
	return 0;
}
3. 注意事项
(1). 返回值不能作为重载的条件

例如一下两个函数

int nn(int a,char b)
{
	return 1;
}

int nn(int b,char a)
{
	return 0;
}

进行函数调用时并不能区分两个函数的差别,所以不可以

(2). 不能仅按函数返回类型重载

如下两个函数

void nn(int a,char b)
{
	printf("1111\n");
}

int nn(int b,char a)
{
	return 0;
}
(3). 与缺省参数的问题
#include<iostream>

using namespace std;

void ffff()
{
	cout << "111111111" << endl;
}
void ffff(int a = 10)
{
	cout << a << endl;
}

int main()
{	
	ffff(110);
	ffff();
	return 0;
}

以上两个代码构成重载

我们发现仅调用ffff(110);可以正常进行,但调用f();直接会报错,因为存在歧义,编译器会不知道调用谁

二. 引用

1. 概念和定义

引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间, 它和它引⽤的变量共⽤同⼀块内存空间。就像人会有小名一样,例如张三,他的家人可能平时叫他小三,虽然称呼不同,但都是叫的张三这一个人

定义方法为:

类型& 引用别名 = 引用对象

C++中为了避免引入太多运算符,会复用C语言的一些符号,比如前面的<<和>>,这里引用和取地址使用了同一个符号,注意使用方法区分即可。

#include<iostream>
using namespace std;
int main()
{
	int aa = 0;
    //bb和cc是aa的别名
	int& bb = aa;
	int& cc = aa;
    //给别名bb取别名,dd仍然是aa的别名
	int& dd = bb;
	++dd;

	cout << aa << "  " << &aa << endl;
	cout << bb << "  " << &bb << endl;
	cout << cc << "  " << &cc << endl;
	cout << dd << "  " << &dd << endl;
	return 0;
}

假设a的别名是b,那b就等同于a

别名的别名d依然等同于其本身

2. 引用的特性
(1). 引用在定义时必须初始化

错误代码如下

#include<iostream>
using namespace std;
int main()
{
	int a = 111;
	int& aa;
	return 0;
}

 修改为

#include<iostream>
using namespace std;
int main()
{
	int a = 111;
	int& aa=a;
	return 0;
}
(2). 一个变量可以有多个引用

即一个人可以有多个称呼

#include<iostream>
using namespace std;
int main()
{
	int a = 111;
	int& aa=a;
	int& aaa = a;
	int& aaaa = a;
	return 0;
}
(3). 引用一旦引用一个实体,再不能引用别的实体

如下所示

#include<iostream>
using namespace std;
int main()
{
	int a = 111;
	int& aa=a;
	int c = 20;
	aa = c;
	cout << a << " " << aa << endl;
	return 0;
}

其中的 aa = c ; 并非是让aa称为c的别名,因为C++不能改变指向,这里是一个赋值操作

3. 引用的使用

在平时的实践中引用主要是在引用传参和引用做返回值中减少拷贝,提高效率。并且在改变引用对象时同时改变被引用对象

引用传参跟指针传参功能是类似的,引用传参相对更方便一些

代码如下

#include<iostream>
using namespace std;

void Swap1(int* a, int* c)
{
	*a = 20;
	*c = 111;
}

void Swap2(int& a, int& c)
{
	a = 20;
	c = 111;
}

int main()
{
	int a1 = 111;
	int a2 = 111;
	int c1 = 20;
	int c2 = 20;
	Swap1(&a1, &c1);
	Swap2(a2, c2);
	cout << a1 << " " << c1 << endl;
	cout << a2 << " " << c2 << endl;
	return 0;
}

 我们发现两种方式均完成了改变

在之前数据结构的时间中我们都是传的指针但是也可以通过引用来替代

int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;//栈的大小
}

int StackSize(ST& ps)
{
	return ps.top;//栈的大小
}

 上述代码效果相同

指针变量也可以取别名

比如

void Push(pNode** pd,int x)
{
    //...........
}

void Push(pNode*& pd,int x)
{
    //...........
}

这里的pNode*& pd就是给指针变量取别名,这样就不需要用二级指针了,相对而言简化了程序

4. 引用做函数返回值

不要返回局部变量引用

由于函数执行完毕时,局部变量的内存空间会被系统自动回收。如果此时外部代码试图通过引用访问该局部变量,实际上访问的是已经被回收的内存空间,被称为野引用或空引用,造成严重后果

返回值可以用引用接收

如果函数的返回值是引用那么这个函数调用可以做左值

#include<iostream>
using namespace std;

int a = 100;
int& ppp()
{
	return a;
}
int main()
{
	int& c = ppp();
	ppp() = 10;
	cout << c << endl;
	return 0;
}

由于返回值是a的别名,所以可以直接通过赋值来改变他

5. const 引用

可以引用一个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。

#include<iostream>
#include"stack.h"
using namespace std;

int main()
{
	const int aa = 10;
	//自身不能改变,别名自然也不能改变
	//int& d = aa;
	const int& c = aa;
	
	int b = 20;
	//权限可以缩小,不能放大
	const int& f = b;
	//int& e = f;//不可以增大权限
	//int& p = 30;
	const int& p = 30;
	return 0;
} 

const引用还可以引用常数

而在下列代码中

#include<iostream>
#include"stack.h"
using namespace std;

int main()
{
	int a = 1;
	int b = 2;
	//int& t = (a + b);
	const int& t = (a + b);//引用的是临时对象,临时对象生命周期就跟着引用走

	//(临时对象在,两数加减乘除等,函数传值返回,类型转换 产生)
	double dd = 1.21;
	int i = dd;
	//int& pp = dd;
	const int& pp = dd;
	return 0;
} 

 上述引用,都是引用的临时对象,(a+b)结果保存在一个临时对象中,类型转换时也会产生临时对象来存储中间值,C++规定临时对象具有常性,所以想要引用就会触发权限放大,必须使用常引用

所谓的临时对象就是编译器需要一个空间暂存表达式的求值结果时创建的一个未命名的对象,C++中把这个未命名对象叫做临时对象。引用后临时对象生命周期就跟着引用走临时对象一般在,两数加减乘除等,函数传值返回,类型转换时产生

 6. 指针和引用的关系

在实践中,功能具有重叠性,但是各有自己的特点,互相不可代替。

在语法概念上,引用是一个变量取别名不额外开辟空间,指针是存储一个变量地址,要开空间。

引用在定义时必须初始化,指针建议初始化,但不是必须的

引用在初始化时引用一个对象后,就不能再引用其他对象;而指针可以再不断改变指向对象。

引用可以直接访问指向对象,指针需要解引用才能访问指向对象

sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下站4个字节,64位下占8字节)

指针经常出现空指针和野指针问题,引用很少出现,使用起来相对安全些

引用的底层实现与指针其实没有什么区别

三. 内联函数

概念

⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联函数就不需要建⽴栈帧了,就可以提⾼效率。用来替换C语言中的宏函数,因为宏函数也会在预处理时替换展开,但宏函数的实现很复杂很容易出错,且不方便调试2,C++设计inline的目的就是替代C的宏函数

 使用

 nline对于编译器⽽⾔只是⼀个建议,也就是说,你加了inline编译器也可以选择在调⽤的地⽅不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适⽤于频繁调⽤的短⼩函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略

 因为其本质上就是空间换时间,假设内联函数本身有1000条指令,有100个位置调用他,不展开的话是 1000+100条指令 ,展开的话是1000*100条指令

#include<iostream>
#include"stack.h"
using namespace std;

inline int Add(int a, int b)
{
	int s = a + b;
	return s;
}

int main()
{
	int s = Add(5, 2);
	cout << Add(5, 2) * 5 << endl;

	return 0;
}

 vs编译器debug版本下面默认是不展开inline的,这样方便调试,可以修改,步骤如下

 注意

inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。使用内联函数时声明处不能展开,所以只能找其地址(相当于内联属性没有了),但函数实现那里又认为自己是内联函数所以出现错误

 四. nullptr

NULL实际上是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

#ifndef NULL    
    #ifdef __cplusplus
        #define NULL    0    
    #else       
        #define NULL    ((void *)0) 
    #endif
#endif

C++中NULL可能被定义为字⾯常量0,或者C中被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到⼀些麻烦,如下代码

#include<iostream>
#include"stack.h"
using namespace std;

void fff(int x)
{
	cout << "整数" << endl;
}

void fff(int* x)
{
	cout << "指针" << endl;
}

int main()
{
	fff(1);
	fff(NULL);
	fff((int*)NULL);
	//fff((void*)NULL);
	return 0;
}

上述代码结果为

本想通过fff(NULL);调⽤指针版本的fff(int* x);函数,但是由于NULL被定义成0调⽤了fff(int x);,因此与程序的初衷相悖。fff((void*)NULL);调用则会报错

C++11中引⼊nullptr来代替NULL,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,值是空指针它的类型可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,不会被隐式转换为整数类型

 


这篇文章就到这里啦,如果帮到你可以点点赞

(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-21 11:22:01       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-21 11:22:01       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-21 11:22:01       45 阅读
  4. Python语言-面向对象

    2024-07-21 11:22:01       55 阅读

热门阅读

  1. 测试人员如何进行需求分析

    2024-07-21 11:22:01       18 阅读
  2. 设计模式--模板方法

    2024-07-21 11:22:01       17 阅读
  3. 使用winget安装git

    2024-07-21 11:22:01       20 阅读
  4. [C/C++入门][for]22、输出奇偶数之和

    2024-07-21 11:22:01       17 阅读
  5. 科普文:CodeReview小结

    2024-07-21 11:22:01       18 阅读
  6. c++第三课:类和对象

    2024-07-21 11:22:01       14 阅读
  7. 一种Android系统双屏异显的两路音频实现方法

    2024-07-21 11:22:01       12 阅读
  8. windows核心编程:第3章内核对象防止多开

    2024-07-21 11:22:01       17 阅读