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

这篇具有很好参考价值的文章主要介绍了带领你打开C++的神秘之门--完结篇。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

带领你打开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语言中,只是简单的将函数名直接存入符号表,而C++则考虑到函数的参数类型,函数名长度等因素.

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

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

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

示例:

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++引用的做法,取别名.

图解:

带领你打开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;
}

改代码会显示错误信息:

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

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

  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 使用场景:

做参数:

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

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

做返回值:

(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;
}

图解:

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

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

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

我们看一下下面的情况:

//引用返回
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;
}

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

结果:

66

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

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

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

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

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

(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的别名的别名.

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

小结:

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的特别说明:隐式转换(操作数两边数据类型不同时,要保证数据两边的类型不变,需要借助临时变量).

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

(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;
}

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

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

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

就好比:

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

  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\n", arr);


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

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

原因:

  在编译阶段编译器需要根据初始化表达式来推导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)//报错
{
    ......
}

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

四、内联函数

概念:

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

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

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

视频教程:

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

4.1 观察内联函数的实现:

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

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

4.2 内联函数的特点

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

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

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

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

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

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

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

  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,在弹出的快捷菜单中,选择“转到定义”命令,可以查看到下面这段解释.

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

  很明显,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++等部分领域.
带领你打开C++的神秘之门--完结篇文章来源地址https://www.toymoban.com/news/detail-476280.html

到了这里,关于带领你打开C++的神秘之门--完结篇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • OpenCV:打开计算机视觉的魔法之门

    嗨,亲爱的读者们!欢迎来到这场计算机视觉的奇妙之旅!今天,我们将一同揭开计算机视觉的神秘面纱,而我们的向导就是一款强大的工具——OpenCV。别担心,我们将从零开始,一步步地领略计算机视觉的魅力。 首先,让我们聊一聊OpenCV。OpenCV,全称为Open Source Computer Vi

    2024年01月25日
    浏览(41)
  • 深入理解 Web3(二)—— 问题解决之门已打开

    正如我在《深入理解 Web3(一)——— 解决什么问题?》 所阐述的,目前 Web3 核心需要解决的问题是数据应该与平台分离,平台即使不存在了,数据依旧可用。 那么,如何实现这个目标呢?下面我将以 Mirror 为例,一窥 Web3 的解决之道。 Mirror 是什么? 你在网上搜索 Mirror.

    2024年02月02日
    浏览(39)
  • Hadoop分布式计算与资源调度:打开专业江湖的魔幻之门

    本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明,所有版权属于黑马程序员或相关权利人所有。本博客的目的仅为个人学习和交流之用,并非商业用途。 我在整理学习笔记的过程中尽力确保准确性,但无法保证内容的完整性和时效性。本博客的

    2024年02月11日
    浏览(35)
  • 解释性AI:打开AI决策过程之门的金钥匙

    可解释性AI(XAI)旨在提高人工智能系统的透明度和可理解性,使人们更好地理解AI的决策过程和原理。随着AI技术的广泛应用,XAI成为了一个备受关注的重要领域。它不仅有助于建立人们对AI的信任,还可以帮助解决AI伦理和偏见等问题。XAI的研究和应用涵盖了从算法改进、可

    2024年02月19日
    浏览(51)
  • 【Python百宝箱】数据的第三维:Python打开的3D时空之门

    在计算机科学和工程领域,3D图形和可视化是强大的工具,可以帮助我们更好地理解和呈现复杂的数据。本文将深入探讨Python中几个重要的3D图形和可视化库,包括MayaVi、VTK、Plotly、PyOpenGL、Three.js、Holoviews和PyVista。通过学习这些库,读者将能够在科学、工程和数据分析中更灵

    2024年02月01日
    浏览(48)
  • 【“C++ 精妙之道:解锁模板奇谭与STL精粹之门“】

    1. 泛型编程 2. 函数模板 3. 类模板 4. 什么是STL 5. STL的版本 6. STL的六大组件 7. STL的重要性 8. 如何学习STL 9.STL的缺陷 如何实现一个通用的交换函数呢? 使用函数重载虽然可以实现,但是有一下几个不好的地方: 1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型

    2024年02月04日
    浏览(53)
  • 初遇C++之语法篇(完结)

    🧃博客主页:阿博历练记 📖文章专栏:c++ 🚍代码仓库:阿博编程日记 🍡欢迎关注:欢迎友友们点赞收藏+关注哦🌹 📜1.1函数重载的概念 函数重载:是函数的一种特殊情况, C语言不允许同名函数 ,C++允许在 同一作用域 中声明几个功能类似的 同名函数 ,这些同名函数的形参

    2024年02月14日
    浏览(53)
  • 【C++ leetcode】双指针(专题完结)

    给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请 你返回所有和为 0 且不重复的三元组。 注意: 答案中不可以包含重复的三元组。 . - 力扣(LeetCode) 画图 和 文字 分析 这道题和 两数之

    2024年04月13日
    浏览(37)
  • 【C++系列Pn】引用——背刺指针的神秘刺客“吃我一棍”

    前言 大家好吖,欢迎来到 YY 滴 C++系列 ,热烈欢迎! 如标题所示,本章主要内容主要来侃侃“ 引用 ”这个刺客!如下就是大纲啦~ 引用,即 取别名 。它的最大特点是编译器 不会为引用变量而开辟空间 ,他们 共用同一块 空间。   1. 引用使用时必须要 初始化 。 2. 引用在初

    2024年02月06日
    浏览(44)
  • C++内存管理机制(侯捷)笔记4(完结)

    本文是学习笔记,仅供个人学习使用。如有侵权,请联系删除。 参考链接 Youtube: 侯捷-C++内存管理机制 Github课程视频、PPT和源代码: https://github.com/ZachL1/Bilibili-plus 下面是第四讲和第五讲的笔记。 第四讲:loki库的allocator 第五讲:other issues,主要介绍GNU C++提供的其他分配器的

    2024年02月02日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包