您现在的位置是:首页 >技术教程 >带领你打开C++的神秘之门--完结篇网站首页技术教程

带领你打开C++的神秘之门--完结篇

初阶牛 2024-08-31 00:01:02
简介带领你打开C++的神秘之门--完结篇

在这里插入图片描述

?个人主页:? :✨✨✨初阶牛✨✨✨
?推荐专栏1: ???C语言初阶
?推荐专栏2: ???C语言进阶
?个人信条: ?知行合一
?本篇简介:>:讲解C++中的函数重载、引用、auto关键字、内联函数等.
金句分享:
✨生活本就沉默,但是跑起来有风!✨

前言

本篇文章内容很多,讲解c++入门的一些语法,最好是有C语言的基础,这样学起来更加轻松.内容丰富需要慢慢消化,花费时间也很长,总计约1.5w字,希望能对友友们有所帮助.

一、函数重载

  函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

理论看一遍就行啦,还是直接上栗子好理解吧!
?栗子

1.1 函数参数类型不同

//函数参数类型不同
#include <iostream>
#include <stdio.h>

//展开部分常用的
using std::cout;
using std::cin;
using std::endl;

namespace cjn
{
	//函数1
	int add(int e1, int e2)
	{
		cout << "整形:";
		return e1 + e2;
	}
	//函数2
	double add(double e1, double e2)
	{
		cout << "浮点形:";
		return e1 + e2;
	}

}
int main()
{
	int a = 0, b = 0;
	double c = 0, d = 0;
	cin >> a >> b;
	//a和b是int型,会调用int add(int e1, int e2)函数
	cout << cjn::add(a, b) << endl;

	cin >> c >> d;
	//c和d是double型,会调用double add(double e1, double e2)
	cout << cjn::add(c, d) << endl;
	return 0;
}

输入:

2 3
3.4 5.2

输出

整形:5
浮点形:8.6

函数1和函数2虽然函数名相同,但是函数的参数不同,构成函数重载.

1.2 函数参数的个数不同

?栗子

//函数参数个数不同时
#include <iostream>
#include <stdio.h>

using std::cout;
using std::cin;
using std::endl;

namespace cjn
{
	void fun()				//函数1
	{
		cout << "fun()" << endl;
	}
	void fun(int a)			//函数2
	{
		cout << "fun(int a)" << endl;
	}
}
int main()
{
	cjn::fun();		//会调用函数1
	cjn::fun(0);	//会调用函数2

	return 0;
}

运行结果:

fun()
fun(int a)

此时还有一个特殊情况:

namespace cjn
{
	void fun()				//函数1
	{
		cout << "fun()" << endl;
	}
	void fun(int a=4)			//函数2
	{
		cout << "fun(int a)" << endl;
	}
}
int main()
{
	cjn::fun();//此时编译器不知道应该调用哪一个函数
	return 0;
}

分析:

由于函数2设置了缺省值,所以在不传参时,会产生混乱.

1.3 函数参数顺序不同

?栗子

//函数参数顺序不同

#include <iostream>
#include <stdio.h>

using std::cout;
using std::cin;
using std::endl;

namespace cjn
{
	void fun(int a,char b)				//函数1
	{
		cout << "fun(int a,char b)" << endl;
	}
	void fun(char a,int b)				//函数2
	{
		cout << "fun(char a,int b)" << endl;
	}
}
int main()
{

	cjn::fun(1,'c');		//会调用函数1
	cjn::fun('a',0);		//会调用函数2

	return 0;
}

运行结果:

fun(int a,char b)
fun(char a,int b)

1.4 不构成函数重载

函数的返回值不同 :不构成函数重载

//不支持函数重载
namespace cjn
{
	int fun(char a,int b)				//函数1
	{
		cout << "fun(int a,char b)" << endl;
	}
	double fun(char a,int b)				//函数2
	{
		cout << "fun(char a,int b)" << endl;
	}
}

为什么C不支持函数重载,而C++支持?(重点)

上面也说了,返回值不同也不支持函数重载,让我们从底层来揭秘吧!

C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。这些在C语言中的程序环境阶段有细讲.

我们知道,如果我们只有函数的声明,相当于只拿到了承诺,具体的函数定义并没有拿到,要在最后的链接阶段去通过符号表(很重要)拿到函数地址(兑现承诺).

而符号表中对于函数名的修饰规则在C和C++中是不同的.

例如:

//以下列函数为例

int Add(int a, double b)
{
	......
}

void Swap(double* e1, double* e2)
{
	double tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下图是g++修饰后的名字规则。
C语言和C++的函数名修饰的不同

在这里插入图片描述

在C语言中,只是简单的将函数名直接存入符号表,而C++则考虑到函数的参数类型,函数名长度等因素.

而g++环境下的C++中的函数修饰后变成【_Z+函数长度+函数名+类型首字母】

疑问:如果两个函数函数名和参数是一样的,返回值不同构成函数重载吗?

在这里插入图片描述

示例:

int add(int a, int b)
{
	;
}
double add(int a, int b)
{
	;
}

int main()
{
	add(2, 3);//调用哪个函数?返回值没有体现也无法体现在调用处
	return 0;
}

答案是:不构成函数重载,即使修改了底层函数名修饰规则也不行,因为在调用函数时,返回值无法体现,无法区分该调用哪个函数.

1.5 “extern C”

由于C和C++编译器对函数名字修饰规则的不同,在有些场景下可能就会出问题,比如:

情况1:

C++中调用C语言实现的静态库或者动态库,反之亦然

情况2:

多人协同开发时,有些人擅长用C语言,有些人擅长用C++

在这种混合模式下开发,由于C和C++编译器对函数名字修饰规则不同,可能就会导致链接失败,在该种场景下,就需要使用extern “C”。在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。
?栗子:

为了能在C/C++工程中都能使用,函数声明时需加上extern “C”

#ifdef __cplusplus//如果是c++项目(这里要求用c语言实现)
extern "C"//此时这条语句会执行
{
#endif
int Add(int left, int right);
int Sub(int left, int right);
#ifdef __cplusplus
}
#endif

解释:

#ifdef _cplusplus
}
#endif
作用:

(1):c++工程中保证是以C的方式实现

_如果是C++工程,编译器已经定义_cplusplus宏,编译时该宏是可以被识别的,被声明的函数就被extern "C"修饰了,此时C++编译就知道,该函数是按照C的方式编译的,这样在链接时就会按照C的方式找函数名字.

(2):C工程中不受影响

如果是C工程,编译器未定义_cplusplus宏,编译时该宏无法被是被,则条件编译就无效,函数就不会被extern "C"修饰 .

二、引用

引入:

C指针玩法:

在C语言阶段,我们可以通过一个指针去找变量,指针就好比是老板的秘书(一级指针),老板太忙了,只给秘书(一级指针)留了联系方式,我们可以通过秘书(一级指针)去找变量,如果秘书(一级指针)也比较忙,就会给秘书也会自己的秘书留下联系方式,即秘书的秘书(二级指针).

C++引用玩法:

例如:有个小女孩真名叫“涂山苏苏”,我们也可以叫她“小蠢货”,”蠢货苏”等别名,或者“苏苏”等其它小名.这些名词虽然不一样,但是代表的内容都是“涂山苏苏”本人,这便是C++引用的做法,取别名.

图解:

在这里插入图片描述

示例代码:

# include <iostream>

using std::cout;
using std::cin;
using std::endl;

int main()
{
	int a = 5;
	//下面都是对a的引用,即a的别名.
	int& b = a;
	int& c = a;
	int& d = a;
	int& e = a;

	cout << a << " " << b << " " << c << " " << d << " " << e << endl;
	return 0;
}

运行结果:

5 5 5 5 5

2.1 引用特点:

  1. 引用必须初始化.
int main()
{
	int a = 5;
	//如果我们引用不初始化
	int& b;
	return 0;
}

改代码会显示错误信息:

在这里插入图片描述

这也很好理解,引用就类似与取别名,如果连对象都没有,那别名还有啥意义.

  1. 引用一旦确定了引用实体后,就不能像指针一样改变指向了.
int main()
{
	int a = 5;
	int c = 1;
	
	int& b=a;
	b = c;//这样b就是c的别名了吗?
	cout << a << " " << b << " " << c << endl;
	return 0;
}

运行结果:

1 1 1

b=c并不是将b改为c的别名,而是赋值,将b的值给改了,b改了,那也就等于a也改了.

2.2 使用场景:

做参数:

引用指针在简单传参(后续有复杂的自定义对象传参,效果更加明显)是的对比.

在这里插入图片描述

做返回值:

(1)我们之前的一般返回是这样的.

//普通返回
int test()
{
	static int a = 0;
	a += 5;
	return a;
}

int main()
{
	int& c = test();
	cout << c << endl;
	return 0;
}

(2)改用引用作为返回值后:

//引用返回
int& test()//注意看返回值的类型
{
	static int a = 0;
	a += 5;
	return a;//返回的是a的别名
}
int main()
{
	int& c = test();//此时c是作为别名接收返回值
	cout << c << endl;
	return 0;
}

图解:

在这里插入图片描述

引用作为返回值的写法减少了拷贝,所以明显效率更高.

那以后我们都用引用作为返回值吗?(友情提示引用虽好,可不要贪杯哦!);

我们看一下下面的情况:

//引用返回
int& test()
{
	int a = 0;
	a += 5;
	return a;
}
int add(int a, int b)
{
	int sum = a + b;
	return sum;
}
int main()
{

	int& c = test();
	add(22, 44);
	cout << c << endl;
	return 0;
}

在这里插入图片描述

结果:

66

c 是函数test()的返回值,我们打印的是c,为啥结果确实add(22,44)的返回值,是巧合吗?

这就要考虑所在环境了,还是画图比较好理解,上图解!

在这里插入图片描述

如果引用做返回值时,返回的空间是被系统收回的,那就很危险.

引用作为返回值的时候,可以修改返回值,或者获取返回值,而不是获取返回值的拷贝(临时变量).

(1)普通返回:

//普通返回
int test()
{
	static int a = 0;
	a += 5;
	return a;
}

int main()
{

	int c = test();
	c =  95;//对c不会影响a,因为它只是a的拷贝返回
	cout << test() << endl;//调用了两次,所以结果为10
	return 0;
}

运行结果:

10

(2)引用返回:

//引用返回
int& test()
{
	static int a = 0;
	a += 5;
	return a;
}
int add(int a, int b)
{
	int sum = a + b;
	return a + b;
}
int main()
{

	int& c = test();
	c =  95;//对c修改,就是对a修改.
	cout << test() << endl;
	return 0;
}

运行结果:100

可以通过调试发现,test返回的是a的别名,所以c是a的别名的别名.

在这里插入图片描述

小结:

1.引用做参数基本上所有场景都可以.

  (1)方便做输出型参数(例如:swap()函数),可以用传引用改变实参.

  (2)传参是以别名的形式,中间没有拷贝,可以提高效率.

2.引用做返回值时需要特别注意,如果出了作用域,对象空间被系统收回了,就不能用引用返回.其它情况建议用引用返回,可以减少拷贝,提高效率.

  (1)同样,可以减少拷贝,提高效率.

  (2)可以修改返回值,或者是获取返回值.(后续会遇到这种情况).

2.3 常引用?

(1)权限放大:

  从只读–>可读,可写,权限放大会报错.因为不安全.

	//情况1
	const int a = 6;
	//错误写法
	int& ra = a; //该语句编译时会报错,因为a变量具有常性,而ra是可读可写,权限不能放大.
	//正确写法:
	const int& ra = a;//权限的平移


	//情况2
	//错误写法
	int& b = 5; // 该语句编译时会出错,b为常量
	//正确写法
	const int& b = 5;//权限的平移


	//情况3
	double d = 13.14;
	//错误写法
	int& rd = d; // 这里会发生隐式类型转换,而产生的临时变量具有常性.
	//正确写法
	const int& rd = d;

	//情况4
int Test_Const()
{
    int x = 2, y = 3;
	int sum = x + y;
	return sum;
}

int main()
{
	//错误写法
	int& ret=Test_Const();//返回值是临时变量(因为函数栈帧被销毁了,需要借助寄存器,或者别的产生临时变量返回)的拷贝,临时变量具有常性.
	//正确写法
	const int& ret = Test_Const();
	return 0;
}

情况3的特别说明:隐式转换(操作数两边数据类型不同时,要保证数据两边的类型不变,需要借助临时变量).

在这里插入图片描述

(2)权限缩小:

  从可读可写–>只读,权限缩小,更加安全.

	int& Test_Const()
{
	int x = 2, y = 3;
	int sum = x + y;
	return sum;
}

int main()
{
	//权限平移
	int& ret = Test_Const();//注意Test_Const函数的返回值是sum的别名,并不是具有常性的临时变量,所以这里不会报错
	//缩小
	const int& ret = Test_Const();//从可读可写-->只读,权限缩小,更加安全,不会报错.
	return 0;
}

小结:

 权限可以平移和缩小,但是不可以放大,使用时需要注意.

2.4 从底层探究引用和指针.

示例代码:

int main()
{
	int a = 4;

	//从语法上看,引用不开空间,而是用别名直接对a操作
	int& b = a;
	b = 5;

	//从语法看,指针需要开空间存储a的地址,然后通过地址去找a
	int* p = &a;
	*p=20;
	return 0;
}

  我们通过调试窗口,打开反汇编窗口,观察汇编代码:

在这里插入图片描述

我们发现,在底层引用指针的实现逻辑是一样的.都需要开空间,但是在语法上,我们依旧认为它是没有开空间的.

就好比:

  老婆饼里面没有老婆,红烧狮子头里面没有狮子,娃娃菜里面没有娃娃.

  1. 引用语法概念上定义一个变量的别名,而指针是存储一个变量地址。
  2. 引用在定义时必须初始化,否则编译器不知道是谁的“别名”,而指针没有规定,只不过我们习惯性初始化为NULL而已.
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体(上面有讲到),而指针可以在任何时候指向任何一个同类型实体,并不受限制.
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
    4个字节)
  6. 引用的赋值,就是对引用实体进行修改,而指针+1或者赋值则是对实体的地址编号操作,并不会影响实体.
  7. 不存在多级引用,但是可以多个引用可以引用一个实体,即一个实体有多个别名.指针可以有多级指针.
  8. 访问实体方式不同,指针需要显式解引用,引用编译器会自己转换处理.
  9. 安全性:引用比指针使用起来相对更安全 ,好歹不存在空指针吧!???

三、重新认识一下auto关键字

3.1 auto关键字的介绍

在c语言中:

  auto是C语言的一个关键字,关键字主要用于声明变量的生存期为自动,这个关键字不怎么多写,因为所有的局部变量默认就是auto的。

int a=0;//默认就是自动生存期
//等价于下面的
auto int a=0;//写成这样也太麻烦了,我们一般直接省略不写.

  C语言中提供了存储auto,register,extern,static说明的四种存储类别。四种存储类别说明符有两种存储期:自动存储期和静态存储期。

自动存储期:

auto和register。自动变量指在局部创建该变量,然后出了局部的作用域,该变量的声明周期就结束了,还给操作系统了.

静态存储周期:

extern,static

C++赋予了auto新的“生命”:

  C++11标准中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型
指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得.

?栗子

#include <stdlib.h>
# include <iostream>

using std::cout;
using std::cin;
using std::endl;


int main()
{
	int a = 3;
	double b = 4.1;
	auto c = a + b;//会自动推导出c的类型
	cout << c << endl;

	cout << typeid(c).name() << endl;//typeid(c).name()会打印变量c的类型,后续会介绍,这里了解一下就行.
	return 0;
}

  这样看似乎auto关键字的作用不是很大,我们可以直接写double c,那是因为没有遇到复杂的场景,试着看一下下面这段代码(我们暂时不需要看懂代码的作用,只需要关注类型名即可).

#include <string>
#include <map>
int main()
{
	std::map<std::string, std::string> m{ 
    { "name", "名字" },
	{ "age", "年龄" },
	{"sex","性别"} };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

由于在工程中很多情况下,我们不能展开头文件,难免会遇到这样的类型:(上面代码的it的类型)

std::map<std::string, std::string>::iterator

使用宏替换可以吗?

可以是可以,但是宏替换的缺点你是否能接受?

  1. 没有安全检查,直接进行替换.
  2. 可读性很差,也不方便调试.

那是否有小伙伴想到使用typedef进行类型重定义呢?

其实typedef也是有缺点的.

例如:

typedef char* pchar;
int main()
{
	char arr[] = "cjn";
	
	//const pchar p1;//此语句报错,这里是指向不可改变的常量指针,定义时需要初始化
	const pchar p1=arr;
	*p1 = 'x';//指向不可以改变,但指向的内容可以.
	printf("%s
", arr);


	char* parr =arr;//定义一个字符指针
	const pchar* p2=&parr;//指向字符指针的地址
	**p2 ='a';//解引用后的指针与p1类型一样,指向的内容可以改
	//*p2 = NULL;//但是指针指向不能改
	printf("%s
", arr);

	return 0;
}

const pchar等价于char* const表示指针的指向 不能改变.

因为此处类型pchar相当于是类型char* 的别名,const pchar表示const修饰的是char* ,即char*指针的的指向不能被修改,但是其指向的内容可以修改.

const pchar*的类型等价于 char * const* .

typedef有的场景很容易让我们误解类型.

3.2 使用细节:

  1. 使用时必须初始化:
int main()
{
	auto a;
	return 0;
}

在这里插入图片描述

原因:

  在编译阶段编译器需要根据初始化表达式来推导auto的实际类型,而未初始化变量,则无法进行推导。注意auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型

  1. 不能直接推导出引用类型.

    int main()
    {
    	int a = 3;
    	auto b = a;//如果想b是a的引用,这里并不能达到我们想要的效果.
    	//正确写法
    	auto& c = a;//此时c就表示是一个引用类型,auto可以推导出是int类型
    	return 0;
    }
    
  2. 如果是推导指针类型:

    此时,auto和auto*没有区别(记住,只有在推导指针类型时没有区别,试着理解一下,因为指针类型可以直接推导出来)

    int main()
    {
    	int a = 3;
    	auto b = &a;//这里auto会自动推导成int*,表示b是一个整形指针int*指向a
    	
    	auto* c = &a;//此处auto会推导称为int与*结合为int*
    	//所以说此处auto和auto*声明变量没区别.
    	return 0;
    }
    
  3. 同一行定义多个变量的情况:

    默认将第一个推导出来的类型作为整条语句其他变量的类型.

    int main()
    {
    	auto x = 3, y = 5;				//同类型不会报错
    	auto a = 3, b = 4.5, c = 3;		//不同类型会报错,因为会将推导出来的第一个类型作为这条语句所有变量的类型
    	return 0;
    }
    
  4. 不能用来声明数组:

int main()
{
	auto x[5] = { 1,2,3,4,5 };//报错
	return 0;
}

6.不能做形参推导:

void test(auto a)//报错
{
    ......
}

在这里插入图片描述

四、内联函数

概念:

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧
的开销,内联函数提升程序运行的效率 .

//默认Debug下,内联不会起作用.
inline int add(int a, int b)
{
	return a + b;
}
int main()
{
	int c = add(2, 3);
	return 0;
}

需要修改默认属性,更加方便我们在debug版本下观察内联函数.

视频教程:

在这里插入图片描述

4.1 观察内联函数的实现:

在这里插入图片描述

在这里插入图片描述

4.2 内联函数的特点

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替
    换函数调用
    .

    缺陷:由于是在调用处展开,则代码量将会扩大,也就导致目标文件的增大.

    优势:少了调用开辟函数栈帧的开销,提高程序运行效率。

  2. 你说内联就内联?编译器是不会信任你的,你将编译器搞崩溃了(代码膨胀咋办?

    所以inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同**,一般建议:将函数规
    模较小
    (即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、**不是递归频繁调用的函数
    采用inline修饰,否则编译器将不会采用=内联方式.

在这里插入图片描述

所以不要以外任何情况下内联都是好的,要视情况而定,对于短小的函数,且大量频繁调用的采用内联比较合适.

  1. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会
    找不到 .
//test.c(主函数区)

#include <iostream>

using std::cout;
using std::cin;
using std::endl;


#include "add.h"
int main()
{
	int a = 2, b = 3;
	cout << add(a, b) <<endl;
}



//add.c


int add(int a, int b)
{
	return a + b;
}



//add.h

inline int add(int a, int b);


  编译器可以编译通过,但是链接不上,因为声明时显示采用内联方式,内联函数的函数名并不会进符号表(因为会展开),而在主函数区中执行add(a, b)时,就找不到函数的地址,因为符号表中找不到.

4.3 相关面试考点:

(1) 宏函数的优缺点?
优点:
1.增强代码的复用性。(在预处理阶段直接进行宏替换)
2.提高性能。(没call指令,不需要为函数开辟栈帧)
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。(标识符不具备指向性)
3.没有类型安全的检查 。(编译器不会报错)

(2) **C++**有哪些技术替代宏?

  1. 常量定义 换用const enum
  2. 短小函数定义 换用内联函数

五、C++的一颗“语法糖”?

5.1 基于范围的for循环

在c++11中,有一种写法是基于范围的for循环,被称为“语法糖”,让我们尝一尝这颗“糖”甜不甜吧????

以前我们打印数组中的每个元素,我们是这样的.

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//对数组的每个元素*2
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		arr[i] *= 2;
	}
	//打印每个元素
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		cout << arr[i] << " ";
	}
	return 0;
}

使用“语法糖”后:

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//对数组的每个元素*2
	
	错误写法
	//for (auto x:arr)
	//{
	//	x *= 2;
	//}
	
	//正确写法
	for (auto& x:arr)
	{
		x *= 2;
	}


	//打印每个元素
	for (auto x : arr)
	{
		cout << x << " ";
	}
	return 0;
}

解释:

  对于一个有范围的集合而言,程序员来手动再写一遍范围是没有必要的,多余之举,有时候还会写错(例如:忘记下标从0开始)。此事交给任劳任怨的编译器完成比较好,因此C++11标准中引入了基于范围的for循环。

格式:

for循环后的括号由冒号(? 分为两部分:

第一部分:范围内用于迭代的变量(取名随意,与给变量名起名一样,有意义即可),
第二部分:表示被迭代的范围,数组名表示范围是整个数组.

5.2 “糖虽好,注意粘牙哦!”

for循环迭代的范围必须是确定的:
  对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定

void print_for(int array[])//数组传参过来之后就是首元素的地址,而不是整个数组,所以范围不确定
{
	for (auto& e : array)
		cout << e << endl;
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	print_for(arr);
	return 0;
}

六、认识nullptr

  在C语言阶段,我们提倡创建一个变量之后,要给定一个初始值,减少一些可能出现的未知错误(例如:野指针,随机值等),对于指针我们经常使用下面这段代码进行初始化.也就是NULL.

#include <stdio.h>

int main()
{
	int* p = NULL;
	return 0;
}

NULL究竟是什么呢?我们右击NULL,在弹出的快捷菜单中,选择“转到定义”命令,可以查看到下面这段解释.

在这里插入图片描述

  很明显,NULL就是个宏定义,而这里NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。

不论采取上面何种定义,在使用空值(NULL)的指针时,都不可避免的会遇到一些麻烦 .

栗子:

void f(int)
{
	cout << "f(int)" << endl;
}
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(0);
	f(NULL);//这里想调用f(int*)函数
	f((int*)NULL);
	return 0;
}

运行结果:

f(int)
f(int)
f(int*)

  这里因为NULL被定义为0,所以并没有调用f(int*)函数成功.因为语言必须向下兼容,所以不能直接修改NULL的定义,为了解决这一问题,C++11中引入了新的的nullptr为void*类型.

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下
将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的**。**
  2. 在**C++11中,sizeof(nullptr) **与sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

七、结语:

C++的入门知识就分享到这里了,下次会分享"类和对象的知识",那时候应该要等到暑假啦,码文不易,如果觉得文章有帮助的话,可以三连支持一波吗????

欢迎友友们私信与牛牛讨论问题.,只是牛牛的认知范围有限,目前只关注c语言,数据结构,C++等部分领域.
在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。